Last active
May 29, 2019 21:02
-
-
Save zanarmstrong/caa2da1ea1558cdc3357 to your computer and use it in GitHub Desktop.
Which is bigger: Africa or North America?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<link rel="stylesheet" href="overlay.css"> | |
<link href='http://fonts.googleapis.com/css?family=Raleway:400,700' rel='stylesheet' type='text/css'> | |
<body> | |
<div id = "description"> | |
<h2>Is Africa bigger than North America?</h2> | |
<p><strong>Yes!</strong> In fact, North America, including United States, Canada, Mexico, and Greenland, could easily fit inside Africa with plenty of room left to add Central America, Argentina, Chile, and Bolivia too.</p> | |
<p>Most of the maps we use day to day distort the relative sizes of countries, making countries near the equator look relatively small and countries near the north and south pole look relatively huge. However, we can compare the true sizes of countries by using a different type of map.</p> | |
<div><button name="Sweden,Madagascar">Sweden vs Madagascar</button><button name="Australia,Antarctica">Australia vs Antarctica</button><button name="Europe,Brazil">Europe vs Brazil</button><button name="US,Australia">United States vs Australia</button><button name="South_America,Greenland">South America vs Greenland</button><button name="Brazil,US">Brazil vs United States</button><button name="Africa,North_America">Africa vs North America</button><button name="North_Africa,Russia">Africa vs Russia</button><button name="Saudi_Arabia,Alaska">Saudi Arabia vs Alaska</button><button name="Europe,Antarctica">Europe vs Antarctica</button></div> | |
<h3>Drag on the small world maps to compare different parts of the world.</h3> | |
</div> | |
<div id="tool"><table><tr><td id="small0"></td><td id="large" rowspan="2"></td></tr><tr><td id="small1"></td></tr></table></div> | |
<br> | |
<div id="longDesc"> | |
<h2>What's going on?</h2> | |
<p>The earth is a sphere, but maps are flat. This means that when we create maps, we have to somehow flatten the sphere. This flattening must create some distortion. This distortion could affect the relative angles, shapes of countries, or sizes, or all of these. While we can't avoid distortion, we can flatten the sphere in different ways, based on the "projection" we choose, and depending on what we're going to use the map for.</p> | |
<p>One of the most commonly used projections is the <a href="http://en.wikipedia.org/wiki/Mercator_projection">Mercator projection</a>. The Mercator was incredibly useful for ocean navigation because it keeps angles accurate, making it easier for sailers to take compass bearings and navigate the open ocean. The (significant) downside is that it distrorts how large things are. That said, many maps found online, like Google Maps, Open Street Maps, Mapquest, and others, use a variation of Mercator known as "Web Mercator. Advantages include the fact that north is "up" and that meridians (lines of longitude) are equally spaced vertical lines."</p> | |
<p>Many of us imagine the world as it was depicted on the map on the wall in our grade school classrooms or the maps we use online. But, this can lead to incorrect assumptions about the world around us. I was inspired to create this after reading <a href="https://iansierraleone2015.wordpress.com/">a friend's account of his time fighting Ebola in Sierra Leone</a>. He was frustrated with misunderstanding about the disease, including that a "school in New Jersey that panicked and refused to admit two elementary school children from Rwanda. Never mind that Rwanda is 2,600 miles from the epidemic area in West Africa. That’s the distance from my apartment in DC to Lake Tahoe." Misunderstanding scale in the world around us can have a real impact on our opinions and decisions. I hope that this website plays a small part in helping us better understand our wonderful world.</p> | |
<p>If you liked this, you might also enjoy the <a href="http://kai.subblue.com/en/africa.html">True Size of Africa</a> or the <a href="https://gmaps-samples.googlecode.com/svn/trunk/poly/puzzledrag.html">Mercator Puzzle</a>.</p> | |
</div> | |
<div id="testMobile"> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script> | |
<script src="http://d3js.org/topojson.v1.min.js"></script> | |
<script src="overlay.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
background: #FFFCF8; | |
font-family: 'Raleway', sans-serif; | |
font-size: 14pt; | |
} | |
button { | |
margin-right: 20px; | |
font-size: 12px; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
#description { | |
margin-left: 50px; | |
width: 905px; | |
} | |
#longDesc { | |
margin-left: 50px; | |
width: 905px; | |
} | |
#tool { | |
margin-left: 50px; | |
width: 905px; | |
} | |
.stroke { | |
fill: none; | |
stroke: #000; | |
stroke-width: 1px; | |
} | |
.fill { | |
fill: #fff; | |
} | |
.mainSVG { | |
} | |
.g_0 { | |
touch-action: none; | |
} | |
.g_2 { | |
touch-action: none; | |
} | |
.graticule { | |
fill: none; | |
stroke: lightgrey; | |
stroke-width: .5px; | |
stroke-opacity: .5; | |
touch-action: none; | |
} | |
.land_0 { | |
fill: black; | |
opacity: .8; | |
touch-action: none; | |
} | |
.land_1 { | |
fill: lightgreen; | |
opacity: .8; | |
touch-action: none; | |
} | |
.large_land_0 { | |
fill: black; | |
opacity: 1; | |
fill-rule: evenodd; | |
} | |
.large_land_1 { | |
fill: lightgreen; | |
opacity: .9; | |
fill-rule: evenodd; | |
} | |
.boundary_0 { | |
fill: none; | |
stroke: grey; | |
stroke-width: 1px; | |
} | |
.boundary_1 { | |
fill: none; | |
stroke: white; | |
stroke-width: 1px; | |
opacity: .9; | |
} | |
.largeBackgroundRect { | |
touch-action: none; | |
fill-opacity: 0; | |
stroke-opacity: 1; | |
stroke: black; | |
/*touch-action: none;*/ | |
} | |
@media screen and (max-device-width: 480px) and (orientation: portrait){ | |
#testMobile { display: none; } | |
} | |
@media screen and (max-device-width: 640px) and (orientation: landscape){ | |
#testMobile { display: none; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Zan Armstrong - May, 2015 | |
// parts of the code adapted from Mike Bostock's Lambert Azimuthal Equal Area http://bl.ocks.org/mbostock/3757101 | |
"use strict"; | |
// set up variables | |
var smallWidth = 300, | |
smallHeight = 300, | |
largeWidth = 600, | |
largeHeight = 600; | |
var margin = { | |
top: 0 | |
} | |
var padding = 5; | |
var transitionDuration = 800; | |
// to hold land and border data | |
var land; | |
var borders; | |
// land colors | |
var colors = ["black", "lightgreen"] | |
var comparisons = { | |
"Sweden,Madagascar": { | |
scale: 1120, | |
latLon: [{ | |
lat: 62.6, | |
lon: 16.3 | |
}, { | |
lat: -19, | |
lon: 46.7 | |
}] | |
}, | |
"Australia,Antarctica": { | |
scale: 470, | |
latLon: [{ | |
lat: -27, | |
lon: 130 | |
}, { | |
lat: -90, | |
lon: 0 | |
}] | |
}, | |
"Europe,Brazil": { | |
scale: 527, | |
latLon: [{ | |
lat: 56.44, | |
lon: 17.1 | |
}, { | |
lat: -6.07833, | |
lon: -53.2052 | |
}] | |
}, | |
"US,Australia": { | |
scale: 640, | |
latLon: [{ | |
lat: 35.65, | |
lon: -97.24 | |
}, { | |
lat: -27, | |
lon: 130 | |
}] | |
}, | |
"South_America,Greenland": { | |
scale: 458, | |
latLon: [{ | |
lat: -15.97, | |
lon: -52.87 | |
}, { | |
lat: 65.88, | |
lon: -42.21 | |
}] | |
}, | |
"Brazil,US": { | |
scale: 360, | |
latLon: [{ | |
lat: -16.7833, | |
lon: -53.2052 | |
}, { | |
lat: 35.65, | |
lon: -97.24 | |
}] | |
}, | |
"Africa,North_America": { | |
scale: 350, | |
latLon: [{ | |
lat: 6.52865, | |
lon: 20.3586336 | |
}, { | |
lat: 48.2392291, | |
lon: -98.9443219 | |
}] | |
}, | |
"North_Africa,Russia": { | |
scale: 470, | |
latLon: [{ | |
lat: 15.0, | |
lon: 18.82 | |
}, { | |
lat: 60.65, | |
lon: 95.995 | |
}] | |
}, | |
"Saudi_Arabia,Alaska": { | |
scale: 713, | |
latLon: [{ | |
lat: 22.389, | |
lon: 46.59 | |
}, { | |
lat: 64.23, | |
lon: -149.862 | |
}] | |
}, | |
"Europe,Antarctica": { | |
scale: 454, | |
latLon: [{ | |
lat: 56.44, | |
lon: 17.1 | |
}, { | |
lat: -90, | |
lon: 0 | |
}] | |
} | |
} | |
// define state, with default values | |
var state = { | |
scale: 450, | |
latLon: [{ | |
lat: 6.52865, | |
lon: 20.3586336 | |
}, { | |
lat: 48.2392291, | |
lon: -98.9443219 | |
}] | |
} | |
// use hash fragment from URl to set state | |
if (window.location.hash.split("&").length != 0) { | |
var windowState = window.location.hash.split("&"); | |
for (var i = 0; i < windowState.length; i++) { | |
var k = windowState[i].replace('#', '').split('='); | |
if (k[0] == "scale") { | |
state.scale = +k[1]; | |
} else if (k[0] == "center0") { | |
state.latLon[0] = { | |
lat: k[1].split(",")[0], | |
lon: k[1].split(",")[1] | |
}; | |
} else if (k[0] == "center1") { | |
state.latLon[1] = { | |
lat: k[1].split(",")[0], | |
lon: k[1].split(",")[1] | |
}; | |
} | |
} | |
} | |
// define slider | |
var zoomRange = [220, 1600] | |
var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]); | |
var slider = d3.select("#tool") | |
.append("p") | |
.append("input") | |
.attr("type", "range") | |
.style("margin-left", "650px") | |
.style("width", 240 + "px") | |
.attr("min", zoomRange[0]) | |
.attr("max", zoomRange[1]) | |
.attr("step", (zoomRange[1] - zoomRange[0]) / 400) | |
.on("input", slided); | |
slider.property("value", state.scale) | |
function slided(d) { | |
var duration = 0; | |
//updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(d3.select(this).property("value")), duration) | |
state.scale = d3.select(this).property("value"); | |
updateLargeScaleOnly(); | |
updateHash() | |
} | |
// define canvas object and context for small canvases | |
var canvasObj = [ | |
d3.select("#small0").append("canvas") | |
.attr("width", smallWidth) | |
.attr("height", smallHeight), | |
d3.select("#small1").append("canvas") | |
.attr("width", smallWidth) | |
.attr("height", smallHeight) | |
] | |
var smallContext = [ | |
canvasObj[0].node().getContext("2d"), | |
canvasObj[1].node().getContext("2d") | |
] | |
// set opacity | |
smallContext[0].globalAlpha = 0.8 | |
smallContext[1].globalAlpha = 0.8 | |
// define canvas object and context for large canvas | |
var largeCanvasObj = d3.select("#large").append("canvas") | |
.attr("width", largeWidth) | |
.attr("height", largeHeight) | |
var largeCanvasContext = largeCanvasObj.node().getContext("2d") | |
largeCanvasContext.globalAlpha = .9 | |
var graticule = d3.geo.graticule()() | |
var mapObjects = [] | |
var largeMapObjects = [] | |
// set up ranges/scales | |
var zoomRange = [220, 1600] | |
// var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]); | |
// set up initial visable state | |
for (var i = 0; i < 2; i++) { | |
mapObjects[i] = setUpSmallMaps(state.latLon[i].lat, state.latLon[i].lon, i) | |
largeMapObjects[i] = setUpLargeMaps(state.latLon[i].lat, state.latLon[i].lon, i) | |
} | |
// set up small maps | |
function setUpSmallMaps(lat, lon, name) { | |
var projectionSmall = d3.geo.orthographic() | |
.translate([smallWidth / 2, smallHeight / 2]) | |
.scale(smallWidth / 2 * .9) | |
.center([0, 0]) | |
.rotate([-lon, -lat]) | |
.clipAngle(90) | |
.precision(.7); | |
var path = d3.geo.path() | |
.projection(projectionSmall) | |
.context(smallContext[name]); | |
canvasObj[name].call(dragSetupSmall(name)) | |
return { | |
"projection": projectionSmall, | |
"path": path, | |
} | |
} | |
function setUpLargeMaps(lat, lon, name) { | |
var projectionLarge = d3.geo.azimuthalEqualArea() | |
.translate([largeWidth / 2, largeHeight / 2]) | |
.scale(state.scale) | |
.center([0, 0]) | |
.clipAngle(180 - 1e-3) | |
.clipExtent([ | |
[2 * padding, 2 * padding], | |
[largeWidth - 2 * padding, largeHeight - 2 * padding] | |
]) | |
.rotate([-lon, -lat]) | |
.precision(.7); | |
var path = d3.geo.path() | |
.projection(projectionLarge) | |
.context(largeCanvasContext); | |
largeCanvasObj.call(dragSetupLarge()) | |
return { | |
"projection": projectionLarge, | |
"path": path, | |
} | |
} | |
// what to draw on the canvas for large/small | |
var drawCanvasLarge = function() { | |
largeCanvasContext.clearRect(0, 0, largeWidth, largeHeight); | |
largeCanvasContext.strokeStyle = "#333", largeCanvasContext.lineWidth = 1, largeCanvasContext.strokeRect(2 * padding, 2 * padding, largeWidth - 4 * padding, largeHeight - 4 * padding); | |
largeCanvasContext.fillStyle = "white", largeCanvasContext.fillRect(2 * padding, 2 * padding, largeWidth - 4 * padding, largeHeight - 4 * padding); | |
largeCanvasContext.fillStyle = colors[0], largeCanvasContext.beginPath(), largeMapObjects[0].path(land), largeCanvasContext.fill(); | |
largeCanvasContext.strokeStyle = "#fff", largeCanvasContext.lineWidth = .5, largeCanvasContext.beginPath(), largeMapObjects[0].path(borders), largeCanvasContext.stroke(); | |
largeCanvasContext.fillStyle = colors[1], largeCanvasContext.beginPath(), largeMapObjects[1].path(land), largeCanvasContext.fill(); | |
largeCanvasContext.strokeStyle = "#fff", largeCanvasContext.lineWidth = .5, largeCanvasContext.beginPath(), largeMapObjects[1].path(borders), largeCanvasContext.stroke(); | |
} | |
var drawCanvasSmall = function(i) { | |
smallContext[i].clearRect(0, 0, smallWidth, smallHeight); | |
smallContext[i].fillStyle = "white", smallContext[i].strokeStyle = "#333", smallContext[i].beginPath(), smallContext[i].arc(smallWidth / 2, smallHeight / 2, smallWidth / 2 - 3 * padding, 0, 2 * Math.PI, false), smallContext[i].stroke(), smallContext[i].fill(); | |
smallContext[i].strokeStyle = "#ccc", smallContext[i].lineWidth = .5, smallContext[i].beginPath(), mapObjects[i].path(graticule), smallContext[i].stroke(); | |
smallContext[i].fillStyle = colors[i], smallContext[i].beginPath(), mapObjects[i].path(land), smallContext[i].fill(); | |
} | |
d3.json("world-110m.json", function(error, world) { | |
land = topojson.feature(world, world.objects.land); | |
borders = topojson.mesh(world, world.objects.countries) | |
// use data to draw paths on small maps | |
drawCanvasLarge() | |
drawCanvasSmall(0) | |
addBoxSmall(0, zoomToBoxScale(largeMapObjects[0].projection.scale())) | |
drawCanvasSmall(1) | |
addBoxSmall(1, zoomToBoxScale(largeMapObjects[1].projection.scale())) | |
}) | |
// updates on button click | |
d3.selectAll("button").on("click", function() { | |
// update state, view, hash | |
state.latLon[0] = comparisons[this.name].latLon[0]; | |
state.latLon[1] = comparisons[this.name].latLon[1]; | |
state.scale = comparisons[this.name].scale; | |
rotateAndScale() | |
updateHash() | |
}) | |
// updating just the small context w/ rotation | |
var rotateSmallTween = function(iter) { | |
return function(d) { | |
var r = d3.interpolate(mapObjects[iter].projection.rotate(), [-state.latLon[iter].lon, -state.latLon[iter].lat]); | |
var halfSideTween = d3.interpolate(zoomToBoxScale(largeMapObjects[iter].projection.scale()), zoomToBoxScale(state.scale)) | |
return function(t) { | |
mapObjects[iter].projection.rotate(r(t)); | |
drawCanvasSmall(iter) | |
addBoxSmall(iter, halfSideTween(t)) | |
}; | |
} | |
} | |
var addBoxSmall = function(iter, halfSide) { | |
smallContext[iter].strokeStyle = "#333"; | |
smallContext[iter].strokeRect( | |
smallWidth / 2 - halfSide, | |
smallHeight / 2 - halfSide, | |
2 * halfSide, | |
2 * halfSide); | |
} | |
var updateSmallCanvasOverDuration = function(iter) { | |
d3.select("#small" + iter) | |
.select("canvas") | |
.transition() | |
.duration(transitionDuration) | |
.tween("rotate", rotateSmallTween(iter)) | |
} | |
// update large & small smallContext, based on update function (either rotationAndScaleTween or rotationTween) | |
function rotateAndScale() { | |
(function transition() { | |
updateSmallCanvasOverDuration(0) | |
updateSmallCanvasOverDuration(1) | |
d3.select("#large").select("canvas") | |
.transition() | |
.duration(transitionDuration) | |
.tween("d", rotationAndScaleTween) | |
})() | |
} | |
// update only scale, and therefore only large canvas | |
function updateLargeScaleOnly() { | |
(function transition() { | |
d3.select("#large").select("canvas") | |
.transition() | |
.duration(0) | |
.tween("d", scaleTween) | |
})() | |
} | |
// update large & small canvases, based on update function (rotationAndScaleTween or rotationTween) | |
function updateRotationFromSmallPan(name) { | |
drawCanvasSmall(name) | |
addBoxSmall(name, zoomToBoxScale(largeMapObjects[name].projection.scale())) | |
drawCanvasLarge() | |
} | |
function updateRotationFromLargePan() { | |
drawCanvasSmall(0) | |
addBoxSmall(0, zoomToBoxScale(largeMapObjects[0].projection.scale())) | |
drawCanvasSmall(1) | |
addBoxSmall(1, zoomToBoxScale(largeMapObjects[1].projection.scale())) | |
drawCanvasLarge() | |
} | |
function rotationTween() { | |
var r1 = d3.interpolate(largeMapObjects[0].projection.rotate(), [-state.latLon[0].lon, -state.latLon[0].lat]); | |
var r2 = d3.interpolate(largeMapObjects[1].projection.rotate(), [-state.latLon[1].lon, -state.latLon[1].lat]); | |
return function(t) { | |
// update rotation | |
largeMapObjects[0].projection.rotate(r1(t)); | |
largeMapObjects[1].projection.rotate(r2(t)); | |
drawCanvasLarge() | |
}; | |
} | |
function scaleTween() { | |
var interpolateScale = d3.interpolate(largeMapObjects[0].projection.scale(), state.scale); | |
var halfSideTween = d3.interpolate(zoomToBoxScale(largeMapObjects[0].projection.scale()), zoomToBoxScale(state.scale)) | |
return function(t) { | |
// update scale for large projections | |
largeMapObjects[0].projection.scale(interpolateScale(t)); | |
largeMapObjects[1].projection.scale(interpolateScale(t)); | |
// and redraw large | |
drawCanvasLarge() | |
// redraw small canvases, with box | |
drawCanvasSmall(0) | |
addBoxSmall(0, halfSideTween(t)) | |
drawCanvasSmall(1) | |
addBoxSmall(1, halfSideTween(t)) | |
}; | |
} | |
function rotationAndScaleTween() { | |
var r1 = d3.interpolate(largeMapObjects[0].projection.rotate(), [-state.latLon[0].lon, -state.latLon[0].lat]); | |
var r2 = d3.interpolate(largeMapObjects[1].projection.rotate(), [-state.latLon[1].lon, -state.latLon[1].lat]); | |
var interpolateScale = d3.interpolate(largeMapObjects[0].projection.scale(), state.scale); | |
return function(t) { | |
// update rotation | |
largeMapObjects[0].projection.rotate(r1(t)); | |
largeMapObjects[1].projection.rotate(r2(t)); | |
// update scale | |
largeMapObjects[0].projection.scale(interpolateScale(t)); | |
largeMapObjects[1].projection.scale(interpolateScale(t)); | |
drawCanvasLarge() | |
}; | |
} | |
// update functions | |
var updateHash = function() { | |
window.location.hash = "scale=" + state.scale + "¢er0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "¢er1=" + state.latLon[1].lat + "," + state.latLon[1].lon; | |
slider.property("value", state.scale) | |
} | |
// DRAG | |
function dragSetupSmall(name) { | |
function resetDrag() { | |
dragDistance = { | |
x: 0, | |
y: 0 | |
}; | |
} | |
var dragDistance = { | |
x: 0, | |
y: 0 | |
}; | |
return d3.behavior.drag() | |
.on("dragstart", function() { | |
d3.event.sourceEvent.preventDefault(); | |
}) | |
.on("drag", function() { | |
dragDistance.x = dragDistance.x + d3.event.dx; | |
dragDistance.y = dragDistance.y + d3.event.dy; | |
updateRotateFromSmallDrag(dragDistance, name) | |
resetDrag() | |
}) | |
.on("dragend", function() { | |
updateRotateFromSmallDrag(dragDistance, name) | |
resetDrag() | |
}) | |
} | |
function dragSetupLarge() { | |
function resetDrag() { | |
dragDistance = { | |
x: 0, | |
y: 0 | |
}; | |
} | |
var dragDistance = { | |
x: 0, | |
y: 0 | |
}; | |
return d3.behavior.drag() | |
.on("dragstart", function() { | |
d3.event.sourceEvent.preventDefault(); | |
}) | |
.on("drag", function() { | |
dragDistance.x = dragDistance.x + d3.event.dx; | |
dragDistance.y = dragDistance.y + d3.event.dy; | |
updateRotateFromLargeDrag(dragDistance) | |
resetDrag() | |
}) | |
.on("dragend", function() { | |
updateRotateFromLargeDrag(dragDistance) | |
resetDrag() | |
}); | |
} | |
function updateRotateFromSmallDrag(pixelDifference, name) { | |
var newRotate = pixelDiff_to_rotation_small(mapObjects[name].projection, pixelDifference) | |
// set new rotate | |
mapObjects[name].projection.rotate(newRotate) | |
largeMapObjects[name].projection.rotate(newRotate) | |
updateStateRotation(newRotate, name) | |
updateRotationFromSmallPan(name) | |
} | |
function updateRotateFromLargeDrag(pixelDifference) { | |
var newRotate0 = pixelDiff_to_rotation_large(largeMapObjects[0].projection, pixelDifference) | |
var newRotate1 = pixelDiff_to_rotation_large(largeMapObjects[1].projection, pixelDifference) | |
// set new rotate | |
mapObjects[0].projection.rotate(newRotate0) | |
mapObjects[1].projection.rotate(newRotate1) | |
largeMapObjects[0].projection.rotate(newRotate0) | |
largeMapObjects[1].projection.rotate(newRotate1) | |
updateStateRotation(newRotate0, 0) | |
updateStateRotation(newRotate1, 1) | |
updateRotationFromLargePan(name) | |
} | |
function pixelDiff_to_rotation_small(projection, pxDiff) { | |
var k = projection.rotate() | |
return ([k[0] + pxDiff.x / 136 * 90, k[1] - pxDiff.y, k[2]]) | |
} | |
function pixelDiff_to_rotation_large(projection, pxDiff) { | |
var k = projection.invert([largeWidth / 2 - pxDiff.x, largeHeight / 2 - pxDiff.y]) | |
return [-k[0], -k[1], 0] | |
} | |
function updateStateRotation(rotateCoord, name) { | |
state.latLon[name] = { | |
lon: rotateCoord[0], | |
lat: rotateCoord[1] | |
} | |
updateHash() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment