2019-11-04 10:59:08 +01:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
<title>Projections</title>
|
2019-11-05 16:02:27 +01:00
|
|
|
|
|
|
|
<script src="../../../3rdparty/highlight/highlight.pack.js"></script>
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="../../../../fonts/material-icon-font/material-icons.css">
|
|
|
|
<link rel='stylesheet' href='../../../3rdparty/highlight/styles/vs2015.css'>
|
|
|
|
<link rel='stylesheet' href='../../../../css/doctest.css'>
|
2019-11-04 10:59:08 +01:00
|
|
|
|
|
|
|
<script src="../../../../dist/iwmlib.3rdparty.js"></script>
|
|
|
|
<script src="../../../../dist/iwmlib.js"></script>
|
|
|
|
<script src="../../../../dist/iwmlib.pixi.js"></script>
|
|
|
|
|
|
|
|
<style>
|
2019-11-05 16:02:27 +01:00
|
|
|
section {
|
2019-11-04 10:59:08 +01:00
|
|
|
display: flex;
|
2019-11-05 16:02:27 +01:00
|
|
|
flex-direction: column;
|
2019-11-04 10:59:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
.map-example {
|
|
|
|
display: inline-block;
|
|
|
|
width: 256px;
|
|
|
|
margin: 5px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.map-wrapper {
|
|
|
|
position: relative;
|
|
|
|
/* inline-block create additional space around child elements. */
|
|
|
|
display: inline-flex;
|
2019-11-05 16:02:27 +01:00
|
|
|
margin: 100px;
|
2019-11-04 10:59:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
.long {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
bottom: 0;
|
|
|
|
width: 1px;
|
|
|
|
background-color: yellowgreen;
|
|
|
|
}
|
|
|
|
|
|
|
|
.point {
|
|
|
|
top: 50%;
|
|
|
|
left: 50%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
|
|
|
position: absolute;
|
2019-11-05 16:02:27 +01:00
|
|
|
width: 5px;
|
|
|
|
height: 5px;
|
2019-11-04 10:59:08 +01:00
|
|
|
background-color: red;
|
|
|
|
border-radius: 50%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.small.point {
|
2019-11-05 16:02:27 +01:00
|
|
|
width: 2px;
|
|
|
|
height: 2px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.medium.point {
|
|
|
|
width: 4px;
|
|
|
|
height: 4px;
|
|
|
|
opacity: 0.5;
|
2019-11-04 10:59:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
.line {
|
|
|
|
position: absolute;
|
|
|
|
width: 100%;
|
|
|
|
left: 0;
|
|
|
|
border: 0.5px solid red;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body onload="Doctest.run()">
|
|
|
|
<h1>Projections</h1>
|
|
|
|
<p>
|
2019-12-11 16:45:26 +01:00
|
|
|
Projections are used on the map projection to translate coordinates to pixelpositions. There are various
|
2019-11-04 10:59:08 +01:00
|
|
|
projections that can be used. All implemented ones are showcased here.
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<section id="mercator">
|
|
|
|
<h2>Mercator Projection</h2>
|
|
|
|
<p>The most commonly used projection is the mercator projection.</p>
|
|
|
|
|
2019-11-05 16:02:27 +01:00
|
|
|
<div class="center">
|
|
|
|
<div id="mercatorMap" class="map-wrapper">
|
|
|
|
<img src="../../assets/maps/osm/0/0/0.png" alt="" />
|
|
|
|
</div>
|
2019-11-04 10:59:08 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<script>
|
2019-11-25 18:04:11 +01:00
|
|
|
|
|
|
|
|
|
|
|
window.examples = []
|
|
|
|
|
2019-11-04 10:59:08 +01:00
|
|
|
let boundaries = [
|
2019-11-05 16:02:27 +01:00
|
|
|
{ x: -90, y: -180 },
|
|
|
|
{ x: 90, y: 180 },
|
|
|
|
{ x: 0, y: 0 },
|
|
|
|
{ x: 90, y: -180 },
|
2019-11-04 10:59:08 +01:00
|
|
|
|
|
|
|
// Eastern Boundaries
|
2019-11-05 16:02:27 +01:00
|
|
|
{ x: -22, y: 180 },
|
|
|
|
{ x: -43, y: 180 },
|
|
|
|
{ x: -63, y: 180 },
|
|
|
|
{ x: -73, y: 180 },
|
|
|
|
{ x: -83, y: 180 },
|
|
|
|
{ x: -87, y: 180 },
|
|
|
|
|
|
|
|
{ x: 0, y: 180 },
|
|
|
|
{ x: 22, y: 180 },
|
|
|
|
{ x: 43, y: 180 },
|
|
|
|
{ x: 63, y: 180 },
|
|
|
|
{ x: 73, y: 180 },
|
|
|
|
{ x: 83, y: 180 },
|
2019-11-04 10:59:08 +01:00
|
|
|
{ x: 87, y: 180 },
|
2019-11-05 16:02:27 +01:00
|
|
|
{ x: -90, y: 180 },
|
2019-11-04 10:59:08 +01:00
|
|
|
|
|
|
|
// Western Boundaries
|
|
|
|
{ x: -22, y: -180 },
|
|
|
|
{ x: -43, y: -180 },
|
|
|
|
{ x: -63, y: -180 },
|
|
|
|
{ x: -73, y: -180 },
|
|
|
|
{ x: -83, y: -180 },
|
|
|
|
{ x: -87, y: -180 },
|
|
|
|
|
|
|
|
{ x: 0, y: -180 },
|
|
|
|
{ x: 22, y: -180 },
|
|
|
|
{ x: 43, y: -180 },
|
|
|
|
{ x: 63, y: -180 },
|
|
|
|
{ x: 73, y: -180 },
|
|
|
|
{ x: 83, y: -180 },
|
|
|
|
{ x: 87, y: -180 },
|
|
|
|
{ x: -90, y: -180 }
|
|
|
|
]
|
|
|
|
|
|
|
|
let capitals = {
|
|
|
|
abidjan: { x: 5, y: -5 },
|
|
|
|
canberra: { x: -35.312146, y: 149.121539 },
|
|
|
|
berlin: { x: 52.52543, y: 13.385291 },
|
|
|
|
capetown: { x: -33.925448, y: 18.416962 },
|
|
|
|
moscow: { x: 55.750892, y: 37.622799 },
|
|
|
|
washington: { x: 38.89565, y: -77.031407 },
|
|
|
|
rio: { x: -22.8714, y: -43.28049 },
|
|
|
|
tokio: { x: 35.696278, y: 139.731366 }
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
function createPointAtPoisition(position, className = '', style = {}) {
|
|
|
|
let point = document.createElement('div')
|
|
|
|
point.className = 'point ' + className
|
|
|
|
|
|
|
|
Object.assign(point.style, {
|
|
|
|
backgroundColor: "0xff0000"
|
|
|
|
}, style, {
|
|
|
|
left: position.x * 100 + '%',
|
|
|
|
top: position.y * 100 + '%'
|
|
|
|
})
|
|
|
|
|
|
|
|
return point
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws a vertical line at the zero longitude of a map.
|
|
|
|
*/
|
|
|
|
function drawZeroLongitude(projection, parent) {
|
|
|
|
let element = document.createElement("div")
|
|
|
|
element.className = "long"
|
|
|
|
let position = projection.forward({ x: 0, y: 0 })
|
|
|
|
element.style.left = position.x * 100 + "%"
|
|
|
|
|
|
|
|
parent.appendChild(element)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Plots a point for a specific interval on the map. Distance defines that interval.
|
|
|
|
*/
|
|
|
|
function plotGridPoints(distance = 10, projection, parent) {
|
|
|
|
for (let lat = -90; lat <= 90; lat += distance) {
|
|
|
|
for (let lng = -180; lng <= 180; lng += distance) {
|
2019-11-05 16:02:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
let color = "#0000ff"
|
|
|
|
let className = 'small'
|
|
|
|
if (lng == 180) {
|
|
|
|
color = '#ff0000'
|
|
|
|
lng = (lng == 180) ? 180 : lng
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lng == -180) {
|
|
|
|
color = '#00ff00'
|
|
|
|
lng = (lng == 180) ? 180 : lng
|
|
|
|
className = "medium"
|
|
|
|
}
|
|
|
|
|
2019-11-04 10:59:08 +01:00
|
|
|
let relativePosition = projection.forward({ x: lat, y: lng })
|
2019-11-05 16:02:27 +01:00
|
|
|
let point = createPointAtPoisition(relativePosition, className, {
|
|
|
|
backgroundColor: color
|
2019-11-04 10:59:08 +01:00
|
|
|
})
|
|
|
|
robinsonMap.appendChild(point)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
2019-11-05 16:02:27 +01:00
|
|
|
<script class="doctest" data-collapsible data-collapsed data-title="Test Data">
|
|
|
|
let mercatorTruth = {
|
|
|
|
abidjan: { x: 0.4861111111111111, y: 0.4860934491519468 },
|
|
|
|
canberra: { x: 0.9142264972222222, y: 0.6049626977761389 },
|
|
|
|
berlin: { x: 0.5371813638888889, y: 0.327930370913594 },
|
|
|
|
capetown: { x: 0.5511582277777778, y: 0.6002818272799257 },
|
|
|
|
moscow: { x: 0.604507775, y: 0.3126267957774014 },
|
|
|
|
washington: { x: 0.28602386944444447, y: 0.3825518808752881 },
|
|
|
|
rio: { x: 0.3797764166666667, y: 0.5652894059707362 },
|
|
|
|
tokio: { x: 0.8881426833333332, y: 0.39372656277794527 }
|
|
|
|
}
|
|
|
|
</script>
|
2019-11-04 10:59:08 +01:00
|
|
|
<script class="doctest">
|
|
|
|
|
2019-11-05 16:02:27 +01:00
|
|
|
; (function () {
|
2019-11-04 10:59:08 +01:00
|
|
|
|
2019-11-05 16:02:27 +01:00
|
|
|
//First we define the appropriate projection:
|
|
|
|
let mercatorProjection = new Projection.Mercator()
|
2019-11-04 10:59:08 +01:00
|
|
|
|
2019-11-05 16:02:27 +01:00
|
|
|
|
|
|
|
for (let [name, coordinates] of Object.entries(capitals)) {
|
|
|
|
|
|
|
|
// Every projection has a forward and a backward function.
|
|
|
|
|
|
|
|
// The forward function transforms coordinates in the form of
|
|
|
|
// {x: lat, y: lng} into relative screen coordinates in the form of {x,y}.
|
|
|
|
let relativePosition = mercatorProjection.forward(coordinates)
|
|
|
|
|
|
|
|
// Testcases for the forward transformation.
|
|
|
|
Doctest.expect(mercatorTruth[name], relativePosition)
|
|
|
|
|
|
|
|
// The backward function transforms relative positions on the screen
|
|
|
|
// to geographic coordinates.
|
|
|
|
let calculatedCoordinates = mercatorProjection.backward(relativePosition)
|
|
|
|
|
|
|
|
// Testcases for the backwards transformation.
|
2019-11-13 12:42:06 +01:00
|
|
|
Doctest.expectPointPrecision(coordinates, calculatedCoordinates)
|
2019-11-05 16:02:27 +01:00
|
|
|
|
|
|
|
let point = createPointAtPoisition(relativePosition)
|
|
|
|
mercatorMap.appendChild(point)
|
2019-11-25 18:04:11 +01:00
|
|
|
|
|
|
|
window.examples.push({ map: mercatorMap, projection: mercatorProjection })
|
2019-11-05 16:02:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
})()
|
2019-11-04 10:59:08 +01:00
|
|
|
</script>
|
|
|
|
</section>
|
|
|
|
<section id="robinson">
|
|
|
|
<h2>Robinson Projection</h2>
|
|
|
|
<p>
|
|
|
|
The robinson projection is a more 'artistic' approach, as it follows a table instead of a strict
|
|
|
|
formula. It has severe distortions at the poles, but that distortion declines rapidly heading towards
|
|
|
|
the equator.
|
|
|
|
</p>
|
2019-11-05 16:02:27 +01:00
|
|
|
<div class="center">
|
|
|
|
<div id="robinsonMap" class="map-wrapper">
|
|
|
|
<img src="../../assets/maps/wikimedia-world-robinson/2000px-BlankMap-World.png" alt="" width="512" />
|
|
|
|
</div>
|
2019-11-04 10:59:08 +01:00
|
|
|
</div>
|
2019-11-05 16:02:27 +01:00
|
|
|
<script class="doctest" data-collapsible data-collapsed data-title="Test Data">
|
|
|
|
let robinsonTruth = {
|
|
|
|
"abidjan": {
|
|
|
|
"x": 0.45839166666666664,
|
|
|
|
"y": 0.469
|
|
|
|
},
|
|
|
|
"canberra": {
|
|
|
|
"x": 0.8637961558795233,
|
|
|
|
"y": 0.71892906228
|
|
|
|
},
|
|
|
|
"berlin": {
|
|
|
|
"x": 0.5080051098978837,
|
|
|
|
"y": 0.17622420010000006
|
|
|
|
},
|
|
|
|
"capetown": {
|
|
|
|
"x": 0.522127677557207,
|
|
|
|
"y": 0.7103377776
|
|
|
|
},
|
|
|
|
"moscow": {
|
|
|
|
"x": 0.5636501036295355,
|
|
|
|
"y": 0.15721735316000007
|
|
|
|
},
|
|
|
|
"washington": {
|
|
|
|
"x": 0.27607293856102144,
|
|
|
|
"y": 0.25892488299999994
|
|
|
|
},
|
|
|
|
"rio": {
|
|
|
|
"x": 0.35541500995592845,
|
|
|
|
"y": 0.6418026800000001
|
|
|
|
},
|
|
|
|
"tokio": {
|
|
|
|
"x": 0.8386571387301243,
|
|
|
|
"y": 0.27869700196
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
2019-11-04 10:59:08 +01:00
|
|
|
|
|
|
|
<script class="doctest">
|
|
|
|
; (function () {
|
|
|
|
|
|
|
|
// Create the robinson projection.
|
|
|
|
let robinsonProjection = new Projection.Robinson(10)
|
|
|
|
// Note: The center on this map is at approximately latitude 10,
|
|
|
|
// therefore the projection must be shifted.
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a point for each capital.
|
|
|
|
*/
|
2019-11-05 16:02:27 +01:00
|
|
|
for (let [key, position] of Object.entries(capitals)) {
|
2019-11-04 10:59:08 +01:00
|
|
|
let relativePosition = robinsonProjection.forward(position)
|
2019-11-05 16:02:27 +01:00
|
|
|
|
|
|
|
// Run Test Cases
|
2019-11-25 18:04:11 +01:00
|
|
|
Doctest.expectPointPrecision(robinsonTruth[key], relativePosition, 0)
|
2019-11-05 16:02:27 +01:00
|
|
|
|
2019-11-04 10:59:08 +01:00
|
|
|
let point = createPointAtPoisition(relativePosition)
|
|
|
|
robinsonMap.appendChild(point)
|
|
|
|
}
|
2019-11-25 18:04:11 +01:00
|
|
|
|
|
|
|
window.examples.push({ projection: robinsonProjection, map: robinsonMap })
|
2019-11-04 10:59:08 +01:00
|
|
|
})()
|
|
|
|
</script>
|
|
|
|
</section>
|
2019-11-25 18:04:11 +01:00
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
|
|
// Put it in a self executing anonymous function to not pollute the window element.
|
|
|
|
(function () {
|
|
|
|
window.examples.forEach(({ map, projection }) => {
|
|
|
|
let display = document.createElement("p")
|
|
|
|
Object.assign(display.style, {
|
|
|
|
position: "absolute",
|
|
|
|
left: 0,
|
|
|
|
top: 0
|
|
|
|
})
|
|
|
|
map.parentNode.appendChild(display)
|
|
|
|
|
|
|
|
Object.assign(map.parentNode.style, {
|
|
|
|
position: "relative"
|
|
|
|
})
|
|
|
|
display.innerText = "Hover over Map to display coordinates."
|
|
|
|
|
|
|
|
map.addEventListener("mousemove", (event) => {
|
|
|
|
let mousePosition = { x: event.offsetX, y: event.offsetY }
|
|
|
|
|
|
|
|
let normalizedPosition = {
|
|
|
|
x: mousePosition.x / map.offsetWidth,
|
|
|
|
y: mousePosition.y / map.offsetHeight
|
|
|
|
}
|
|
|
|
|
|
|
|
let coordinates = projection.backward(normalizedPosition)
|
|
|
|
|
|
|
|
display.innerHTML = `<b>lat:</b> ${coordinates.x.toFixed(3)} <br><b>lng:</b> ${coordinates.y.toFixed(3)}`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
})()
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|
2019-11-04 10:59:08 +01:00
|
|
|
</body>
|
|
|
|
|
|
|
|
</html>
|