# Wot I dun: Map projections project

A Mercator projection map where you can click to change the centre of projection. Screenshot below:

The map is currently centred on northern Greenland, and I’m moving the cursor to recentre it on Madagascar. A live, working version is also available here. I’m pretty happy with it: some time I might try and extend it to make a scrollable version that reprojects as you go, but the current version is more or less what I wanted.

The final code is pretty short. But learning how to do it took AGES… mostly going round in circles figuring out what to do in the first place.

This project started with some idle scrolling around a very zoomed-out Google Maps view. I was looking at something or other in Northern Canada and obviously the map projection was pretty stupidly distorted at that latitude. It occurred to me that projections wouldn’t be so bad if you could recentre them on whatever you were interested in as you went along. So I decided to investigate a bit further…

I quickly found Bernhard Jenny’s paper on Adaptive Composite Map Projections (available here), with the Cartography and Geovisualization Group at Oregon State University. Exactly the kind of thing I was looking for!

I wasn’t aiming for anything nearly this fancy, but I thought it was a nice idea to play around with and learn some new things. I only later discovered that his scrollable map was also available online along with the source code on Github. Probably a good thing, as this way I just made things up as I went along, and learnt more.

There was a lot to learn. My programming background is pretty limited: I did some very numerics-based stuff in C and Mathematica during my Ph.D., but not a lot else. Since I finished I’ve spent some time playing around learning basic bits and pieces of Python, JavaScript, HTML and CSS, largely from Codeacademy tutorials and the physics-y Python tutorials on this website. I thought it was time to have my own project that would put some things together. I rarely spent a whole day at a time on it, just worked on it in dribs and drabs between other stuff.

On the way I learned a lot of new things:

• practice with JavaScript, Python and the plotting program Pyxplot
• use of the JSON file format
• a tiny bit of jQuery and use of the Flot plotting library for JavaScript
• use of the d3 data visualisation library for JavaScript
• use of Markdown to write this blog entry

The list of contents below gives an idea of the kind of scenic route I took to get the final map. At least I got there in the end…

## Contents:

• Thursday 19th June: Found some data: downloaded Natural Earth shapefiles and converted to GeoJSON with ogr2ogr.
• Friday 20th June: Tried to understand the JSON format and use JavaScript to extract data values. Also found data in a simple column format and plotted with Pyxplot, a plotting program I already know how to use.
• Wednesday 25th June: Basic file input in Python, for reading in data files.
• Monday 30th June: Maths time! Got out a pen and paper and worked out how to transform the coordinates.
• Tuesday 1st July: Put the maths into Pyxplot and produced a plot centred on Antarctica. Then started to learn a bit about Flot, a JavaScript plotting library, and made a scatter plot.
• Wednesday 2nd July: Used Python to get the column data into a Flot-readable JSON format.
• Thursday 3rd July: Writing-up day. Used Dillinger online editor to get practice writing in Markdown.
• Monday 7th July: Got a working (but very, very slow) map plotted in Flot.
• Tuesday 8th July: Abrupt change of direction. Ditched Flot and started learning the JavaScript data visualisation library d3. Learned how to use a local server and followed a map tutorial.
• Wednesday 9th July: Worked through another d3 map tutorial and made an abortive attempt at adding a ‘click to centre’ feature.
• Thursday 10th July and Friday 11th July: Time to learn the d3 syntax properly. Worked through a lot of tutorials on bar charts, scatter plots, axes and transitions.
• Tuesday 15th July: Success! Used new understanding of d3 to produce a map that worked. Also added a cursor and some other bits.

## Thursday 19th June

The first task was to get the data. Well, really the first task was working out what data I actually wanted. My first thought was to just find a big list of longitudes and latitudes of points on country boundaries which I could then transform.

This was more complicated than I expected (kind of a theme with the project). After googling around I found Natural Earth, which provides vast amounts of relevant data, but in a complicated shapefile format I knew nothing about. I then learned how to convert this to a GeoJSON file, another format I knew nothing about. On the plus side, it was human-readable and JSON is apparently widely used, so looked like it was worth the effort to learn to use.

I did this with ogr2ogr with help from some question like this (can’t find the exact one any more).

Finally, success of a kind! I got an enormous file I didn’t understand, which I called mapdata.json.

## Friday 20th June

I started off by trying to understand the JSON format, using the minimal example I found here to learn how to extract some data out of one.

I knew no Javascript other than what I learned in a few Codeacademy tutorials, so this was kind of slow. I did’t even know how to run Javascript in the browser, so first I used this tutorial editor and stuck in the minimal example.

OK, that worked. Getting somewhere!

Next up, I made an extremely cut-down version of the massive mapdata.json file, which I called mapdata_cut.json. It just had the details and coordinates for Aruba, a nice small blob on the map.

After some trial and error, I made an html page with a script that gets the first set of coordinates out (mapcoords_1.html).

<h1>Get map coordinates from file</h1>
First set of coordinates for Aruba:

<script>// <![CDATA[
var obj = [{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "properties": { "scalerank": 3, "featurecla": "Admin-0 country", "LABELRANK": 5.000000, "SOVEREIGNT": "Netherlands", "SOV_A3": "NL1", "ADM0_DIF": 1.000000, "LEVEL": 2.000000, "TYPE": "Country", "ADMIN": "Aruba", "ADM0_A3": "ABW", "GEOU_DIF": 0.000000, "GEOUNIT": "Aruba", "GU_A3": "ABW", "SU_DIF": 0.000000, "SUBUNIT": "Aruba", "SU_A3": "ABW", "BRK_DIFF": 0.000000, "NAME": "Aruba", "NAME_LONG": "Aruba", "BRK_A3": "ABW", "BRK_NAME": "Aruba", "BRK_GROUP": "", "ABBREV": "Aruba", "POSTAL": "AW", "FORMAL_EN": "Aruba", "FORMAL_FR": "", "NOTE_ADM0": "Neth.", "NOTE_BRK": "", "NAME_SORT": "Aruba", "NAME_ALT": "", "MAPCOLOR7": 4.000000, "MAPCOLOR8": 2.000000, "MAPCOLOR9": 2.000000, "MAPCOLOR13": 9.000000, "POP_EST": 103065.000000, "GDP_MD_EST": 2258.000000, "POP_YEAR": -99.000000, "LASTCENSUS": 2010.000000, "GDP_YEAR": -99.000000, "ECONOMY": "6. Developing region", "INCOME_GRP": "2. High income: nonOECD", "WIKIPEDIA": -99.000000, "FIPS_10_": "AA", "ISO_A2": "AW", "ISO_A3": "ABW", "ISO_N3": "533", "UN_A3": "533", "WB_A2": "AW", "WB_A3": "ABW", "WOE_ID": 23424736.000000, "WOE_ID_EH": 23424736.000000, "WOE_NOTE": "Exact WOE match as country", "ADM0_A3_IS": "ABW", "ADM0_A3_US": "ABW", "ADM0_A3_UN": -99.000000, "ADM0_A3_WB": -99.000000, "CONTINENT": "North America", "REGION_UN": "Americas", "SUBREGION": "Caribbean", "REGION_WB": "Latin America & Caribbean", "NAME_LEN": 5.000000, "LONG_LEN": 5.000000, "ABBREV_LEN": 5.000000, "TINY": 4.000000, "HOMEPART": -99.000000 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -69.996938, 12.577582 ], [ -69.936391, 12.531724 ], [ -69.924672, 12.519232 ], [ -69.915761, 12.497016 ], [ -69.880198, 12.453559 ], [ -69.876820, 12.427395 ], [ -69.888092, 12.417670 ], [ -69.908803, 12.417792 ], [ -69.930531, 12.425971 ], [ -69.945139, 12.440375 ], [ -69.924672, 12.440375 ], [ -69.924672, 12.447211 ], [ -69.958567, 12.463202 ], [ -70.027659, 12.522935 ], [ -70.048085, 12.531155 ], [ -70.058095, 12.537177 ], [ -70.062408, 12.546820 ], [ -70.060374, 12.556952 ], [ -70.051096, 12.574042 ], [ -70.048736, 12.583726 ], [ -70.052642, 12.600002 ], [ -70.059641, 12.614244 ], [ -70.061106, 12.625393 ], [ -70.048736, 12.632148 ], [ -70.007151, 12.585517 ], [ -69.996938, 12.577582 ] ] ] } }
]
}];

document.getElementById("demo").innerHTML = obj[0].features[0].geometry.coordinates[0][0];
// ]]></script>



That was enough fun for one morning. Later, I finally found data in the simple column format I was originally looking for, from the US National Geophysical Data Center. Saved it as worldmap.dat.

I plotted it with my favourite plotting program, Pyxplot (similar to gnuplot but much prettier defaults and extra features). My very basic plot file (worldmap_basic.ppl’) was

set terminal png
set output 'worldmap_basic.png'

set nokey
plot 'worldmap.dat' every ::1 with lines


which produced the output

Excellent, I now have a world map! I may want to try changing to the Mercator projection (or something else) later, but actually just plotting latitude vs. longitude isn’t bad. I discovered that this simple projection is called the equirectangular projection

Talking of Gnuplot, I then realised I could have just got the data from their demo files. Well, why make things simple for myself… anyway, the JSON stuff will hopefully come in handy later.

## Wednesday 25th June

Back to it after a couple of days off the project, which involved a lot of reinstalling my broken old version of Ubuntu and then scavenging free programs from my university before my password ran out. Interesting fact: if you forget to type ‘sudo’, Mathematica only tells you this twenty minutes into the installation process, and you have to start again.

This time I was learning something about file input and output in Python (I’m writing up these notes a week later and I no longer remember why that was my priority). I used this tutorial and started out with

for line in open('worldmap.dat', 'r'):
print line[0]


The first thing I learned was not to read in the first character from each file if you have a very long file. I made a cutdown version called shortmap.dat.

Next up was printing all the data:

for line in open('shortmap.dat', 'r'):
for i in range(0,len(line)):
print line[i]


This script is highly useful for anyone who wants their data spat out one character per line. Finally I tried,

for line in open('shortmap.dat', 'r'):
for i in range(0,len(line)):
print line[i]


That was enough for my first foray into the exciting world of Python input and output.

## Monday 30th June

This week I was back on I was hoping would be familiar ground, doing the maths to transform the coordinates. The idea being, recenter the map so that instead of latitude 0, longitude 0 being in the middle, you pick some arbitrary point as the centre.

My brain was not really cooperating though.

First I naively thought that just adding on to both coordinates and replotting would do the trick. So as a first test I tried adding 200 to the longitude (first column of the data file) and 100 to the latitude.

I did have enough wits make sure I still plotted the same coordinate range: -180 to 180 for longitude, -90 to 90 for latitude. So I included some modulo symbols and typed

plot 'worldmap.dat' using (($1+200)%360 - 180):(($2+100)%180 - 90) every ::1 with lines


and got a big mess (bigmess.png):

The coordinate range was right but not much else. Once I’d experimented a bit and figured out what was going on I realised there were two major problems:

1. The vertical and horizontal lines. These occur if a continent gets chopped in half at the boundaries of the map, and Pyxplot tries to join its coordinates together. I couldn’t be bothered to think about that one so just replotted with points instead:
plot 'worldmap.dat' ... with points pt 1 ps 0.02

1. More fundamentally, the map is completely borked. Transforming the longitude works as intended – you can easily move the map so that the Pacific is in the middle. However transforming the latitude is not so simple. Moving Antarctica higher up the map will not magically make it the right shape!

This should have been obvious but somehow wasn’t. So I had to think a bit harder, with a bit of help from this Stack Exchange question.

Time for pen and paper.

Call the latitude $\theta$ and the longitude $\phi$. Then in Cartesians we have:

$x = \cos\theta \cos\phi ,$
$y = \cos\theta \sin\phi ,$
$z = \sin\theta .$

Note the difference to usual spherical coordinates as we are using latitude not colatitude. Now I want to rotate the sphere by some fixed angles $\theta'$ and $\phi'$. The $\phi'$ (longitude) coordinate just shifts the centre of the map so I can add it on at the end – it’s only the $\theta'$ (latitude) coordinate that needs to be treated specially, so I’ll take $\phi' = 0$ for now. I’ll rotate by $\theta'$ in the $x$$z$ plane, giving new Cartesian coordinates $(x_1, y_1, z_1)$ defined by

$x_1 = \cos\theta' x + \sin\theta' z ,$
$y_1 = y ,$
$z_2 = -\sin\theta' x + \cos \theta' z .$

Inverting, we have that

$\theta' = \arcsin\left(z_1\right) ,$
$\phi' = \arcsin\left(\frac{y_1}{x_1}\right) .$

I could then do a bunch of substitutions to rewrite $(\theta', \phi')$ in terms of $(\theta, \phi)$, and it would probably simplify a bit… but what’s the point, if the computer will be doing it for me anyway?

That gave me something to stick into Pyxplot… but that required a bit more reading up so I left it for another day. Looking ahead, I also found Flot, a plotting library for JavaScript. Could be worth playing with.

## Tuesday 1st July

I’d only really used Pyxplot before to plot existing data, not transform it first. So first I needed to learn about doing numerical calculations with Pyxplot.

It was actually pretty intuitive, except for using degrees instead of radians which was a bit of a faff. Once I’d got it figured out, my final plot file (worldmap_recentre) was

set terminal png

set output 'worldmap_recentre.png'
set nokey

set unit of angle deg
set unit angle nodimensionless

set xlabel "$phi$"
set ylabel "$theta$"

# Translate to Cartesians:

x(theta, phi) = cos(theta)*cos(phi)
y(theta,phi) = cos(theta)*sin(phi)
z(theta,phi) = sin(theta)

# Pick the new latitude thetadash and longitude phidash you want the map centred on

phidash = 90*unit(deg)

# Rotate the Cartesian coordinates by thetadash to get new set (x1, y1, z1)
# (the phidash rotation can be added at the end)

y1(theta,phi) = y(theta,phi)

# Invert to get the new coordinate axes (theta1, phi1)
# - also need to add on phidash to phi1

theta1(theta,phi) = asin(z1(theta,phi))
phi1(theta,phi) = (atan2(x1(theta,phi), y1(theta,phi)) + phidash)%(360*unit(deg)) - 180*unit(deg)

plot 'worldmap.dat' using (phi1($2*unit(deg),$1*unit(deg))):(theta1($2*unit(deg),$1*unit(deg))) every ::1 with points pt 1 ps 0.02


This produces a nice map of the world centred on Antarctica:

On the way I produced this rather pretty but not very helpful version (prettymess.png):

That was from using the wrong inverse tangent function, atan rather than atan2.

I then played around with Flot for a bit, using some nice tutorials on getting started, making line graphs and what I was really interested in, making scatter plots.

I used this to produce my first Flot scatter plot:

Scatter plot test

<!--
<script src="jquery-1.11.1.js" language="javascript"></script><script src="jquery.flot.js" language="javascript"></script>

<script>// <![CDATA[
var d1 = [[0,1],[1,2],[3,8],[5,4],[2,10],[1.2,9],[9,2]];

$(document).ready(function(){$.plot($("#placeholder"), [d1], { series: { points: { radius: 3, show: true, fill: true, fillColor: "#058DC7" }, color: "#058DC7" } }); }); // ]]></script> <div id="placeholder"></div>  ## Wednesday 2nd July The next task was to get the longitude-latitude data (worldmap.dat) into a Flot-readable format, i.e. a JSON file that looks something like this: { label: "some stuff", data: [[0, 3], [10, 3], [20, 7]] }  First I needed to finally get round to cleaning all the ‘>’ lines out of the data file. Pyxplot complained about them but still ran so I never bothered to remove them. Luckily this is one thing I knew how to do in the terminal: sed -i 's/>//g' worldmap.dat Next, I used my Python file I/O practice to convert to Flot format, again using the shortened file shortmap.dat. My first attempt was coords = [] f = open('shortmap.dat', 'r') for line in f: line = line.split() # split each line of data file by whitespace coords.append(line) print coords  This gave me a list of strings: [['0.192440', '5.669954'], ['-0.114995', '5.515063'], ['-0.391921', '5.360171'], ['-0.657114', '5.195893'], ['-0.887104', '5.151303'], ['-1.262597', '5.062123'], ['-1.417488', '5.008146'], ['-1.560645', '4.874377'], ['-1.736658', '4.775809'], ['-1.936139', '4.68663'], ['-2.198985', '4.787544']]  To fix that, I changed the for loop to for line in f: line = line.split() # split each line of data file by whitespace longitude = float(line[0]) latitude = float(line[1]) coordentry = [longitude, latitude] coords.append(coordentry)  Probably not the neatest thing but it worked. Next I added the ‘label’ entry of the JSON format and exported it, giving me the final script # Get coordinate data and export in Flot-compatible JSON format import json # Get the coordinates into a Python list: coords = [] f = open('shortmap.dat', 'r') for line in f: line = line.split() # split each line of data file by whitespace longitude = float(line[0]) latitude = float(line[1]) coordentry = [longitude, latitude] coords.append(coordentry) mapdata = {'label':'World map', 'coords': coords} # Encode mapdata in JSON format and print to file with open("shortmap.json", "w") as outfile: json.dump(mapdata, outfile)  ## Thursday 3rd July Today I had a nice easy task of typing these notes into Markdown, as they were getting unwieldy in a big text file. I used the Dillinger.io online text editor as I was not too familiar with the syntax and some immediate visual feedback was useful. This was enjoyable as Markdown is very easy to use. The main challenge was to hold off reading up on the .io top level domain, which I didn’t realise was for the British Indian Ocean Territory. It looks like an interesting story, and not one the British are going to come out of well… ## Monday 7th July What an unmotivated day. I got to 3:14 pm having done nothing remotely useful. Time to work… I tried my Python script getdata.py from Wednesday on the longer data list (mapdata.dat) and see how it coped. It didn’t work because of whitespace in the data (there is a blank line between each continent/island). The whitespace may be useful later if I plot the data with lines between points, but for now just removed the whitespace: for line in f: line = line.strip() # get rid of blank lines if line: line = line.split() # split each line of data file by whitespace longitude = float(line[0]) latitude = float(line[1]) coordentry = [longitude, latitude] coords.append(coordentry)  That gave me a big old list of points. Next I wanted to plot this in Flot, which required figuring out how to plot an external JSON file in Flot. I found this example to test. After AGES of wondering what could possibly be wrong with it, I thought to try another browser. It worked in Firefox but not Chrome. After googling around I discovered Chrome doesn’t like to load local files for security reasons. I could apparently get around this by sorting out some kind of local server but didn’t want to figure that out right then. So Firefox it is. I got it to work with Scatter plot test <script src="jquery-1.11.1.js" language="javascript"></script><script src="jquery.flot.js" language="javascript"></script> <script>// <![CDATA[$(document).ready(function(){
$.getJSON("worldmap.json", function(json) { var plotarea =$("#placeholder");
var coords=json.coords;

\$.plot(plotarea , [{

data: coords,
points: {radius: 0.1, show: true, fill: true, fillColor: "#006600"},

}]
);
});
});

// ]]></script>

<div id="placeholder"></div>



I took a screenshot of a recognisable world map…

… but it was very, very slow as worldmap.dat is a huge file. Need to think about how to improve on this.

## Tuesday 8th July

After some thought, I realised I was getting to the point where the ‘making it up as I go along’ method was running out of steam (though I learnt a lot with it). Certainly the current method of plotting points in Flot was extremely slow, and I needed to use some more specialist mapping tools. So I looked back at the original paper I found and also did some unfocused browsing around, skimming through a bewildering range of tools and frameworks – HTML Canvas, Raphaël, WebGL, and some others I’ve now forgotten…

Then I got frustrated, procrastinated for a while, and somehow ended up clicking a trail of links to something useful, a post about someone else fighting to learn data visualisation methods. This is not my usual experience of procrastination! As well as amusing me with some familiar-sounding descriptions of the pain of learning something new, the specific library he was talking about, D3 (Data-Driven Documents), also looked like it was worth investigating… despite all the trouble he had learning it. The examples gallery looked brilliant, so I decided to risk it.

I found a tutorial by Mike Bostock, Let’s Build A Map, that looked very clear and hopefully useful for my project. I also quickly realised Mike Bostock wrote the Visualising Algorithms post I’ve seen linked to a couple of times and thought ‘that looks really interesting, I’ll read it later’. This time I will read that later… but not now, as there has been plenty of procrastination today already.

I was pleased to see that at least the first steps of the tutorial mentioned things I’d tried on the way – Natural Earth data and using ogr2ogr to convert to JSON form. I then installed Node.js and added TopoJSON (the specific data format he uses) and http-server (a local server like I wanted yesterday, so that I can test the html I write). To start the local server I typed

http-server -p 8008 &

in the terminal.

I plugged my way through the tutorial – can’t say I understood everything, but I started to grasp the basics.

My main question though was… why is Bath plotted and not Bristol?

I also found this d3noob tutorial on making a world map which should be useful.

## Wednesday 9th July

I started off with the d3noob tutorial. It’s useful that d3 works out the projection for you and can rotate it arbitrarily… I suppose I won’t be needing the maths from earlier. I also found another helpful set of examples, which I’ve linked here so I don’t forget about it.

It took me a while to get the tutorial to run – eventually I discovered that because I was using v1 of topojson rather than v0, I had to change one part of the code, so that

.data(topojson.object(topology, topology.objects.countries)
.geometries)


became

.data(topojson.feature(topology, topology.objects.countries)
.features)


Next, I tried to adapt click to center by projection example for my own purposes, by bodging it together with the world map I had from the tutorial.

I used this to make the map projection rotate when I clicked on a point, but with no understanding of what I was doing the rescaling may as well have been at random. I decided it was time to go back and understand more of the D3 syntax using Scott Murray’s introductory tutorials

This was definitely worth it: I started to grasp what all those ‘append’s, ‘attr’s and ‘enter’s meant.

## Thursday 10th and Friday 11th July

Didn’t spend much time on the project, but worked through a few more of the Scott Murray tutorials in the evenings. The syntax started to make sense, including the function arguments which were a bit of a mystery to me before. I also started to appreciate the power of D3: I’d only previously used plotting software, where there are a set of defaults for plotting bar charts, scatter plots etc. D3 is proper visualisation software where you design the plots from the ground up, starting with rectangles, circles and whatever else. As someone who already enjoys mucking around with SVGs in Inkscape, this was fun to learn …

## Tuesday 15th July

Success! I used a combination of the d3noob map tutorial and the click-to-centre example to create a Mercator projection map which recentres on a point you click on. The full script is below (1_recenter.html):

<script src="http://d3js.org/d3.v3.min.js"></script><script src="http://d3js.org/topojson.v1.min.js"></script>

<script>// <![CDATA[
var width = 960,
height = 500;

var projection = d3.geo.mercator()

var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height); // set SVG window

var path = d3.geo.path()
.projection(projection); // generate path

var g = svg.append("g");

d3.json("world-110m2.json", function(error, topology) {
g.selectAll("path")
.data(topojson.feature(topology, topology.objects.countries)
.features)
.enter()
.append("path")
.attr("d", path)
.on("click", clicked);
});

function clicked(d) {

var centroid = path.centroid(d);

projection.rotate([- projection.invert(centroid)[0], - projection.invert(centroid)[1]]);

// Transition to the new projection.

g.selectAll("path")
.transition()
.duration(0)
.attr("d", path);
}
// ]]></script>



It works nicely! Here’s a live version online.
I could think of a number of ways to improve or extend it, though, of varying difficulty:

1. Some kind of cursor to show where you are selecting as the centre would be good
2. It only works if you click on the land. Can the ocean be included?
3. It would be nice to have a pretty transition between the two views. However my most basic attempt at this (setting .transition().duration(500)) didn’t work, because of the problem of continents that are chopped in half in one view reforming in the new view, and vice versa. This might actually be a pain to redo.
4. More generally, I’d like a scrollable map that changes projection as you move around

Ideas 1. and 2. seemed more tractable and if I solved them I’d have a pretty good finished visualisation in its own right, so I started there. I can think about 3. and 4. if I come back to this project later.

For tackling 1., I started by trying to adapt the cursor from this graph example.

The main problem was getting the ordering of the clicks and mouseovers correct. The bit that took me ages to work out was adding pointer-events:none in the CSS for the cursor. Otherwise D3 didn’t ‘see through’ the cursor to the map below, so the click-to-recentre wasn’t working.

Problem 2. turned out to be easy… once I knew how. I just needed to move .on("click", clicked) from the map data to the svg window itself, as below:

var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousemove", mousemove)
.on("click", clicked); // new line



I also had to rewrite the recentring, ending up with something simpler anyway, using the mouse click coordinates rather than the path.centroid coordinates:

var newcentre = d3.mouse(this)
projection.rotate([- projection.invert(newcentre)[0], - projection.invert(newcentre)[1]]);

`

Anyway here is the final version! Pretty pleased with that…