<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Projections</title> <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'> <script src="../../../../dist/iwmlib.3rdparty.js"></script> <script src="../../../../dist/iwmlib.js"></script> <script src="../../../../dist/iwmlib.pixi.js"></script> <style> section { display: flex; flex-direction: column; } .map-example { display: inline-block; width: 256px; margin: 5px; } .map-wrapper { position: relative; /* inline-block create additional space around child elements. */ display: inline-flex; margin: 100px; } .long { position: absolute; top: 0; bottom: 0; width: 1px; background-color: yellowgreen; } .point { top: 50%; left: 50%; transform: translate(-50%, -50%); position: absolute; width: 5px; height: 5px; background-color: red; border-radius: 50%; } .small.point { width: 2px; height: 2px; } .medium.point { width: 4px; height: 4px; opacity: 0.5; } .line { position: absolute; width: 100%; left: 0; border: 0.5px solid red; } </style> </head> <body onload="Doctest.run()"> <h1>Projections</h1> <p> Projections are used on the mapdata to translate coordinates to pixelpositions. There are various 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> <div class="center"> <div id="mercatorMap" class="map-wrapper"> <img src="../../assets/maps/osm/0/0/0.png" alt="" /> </div> </div> <script> let boundaries = [ { x: -90, y: -180 }, { x: 90, y: 180 }, { x: 0, y: 0 }, { x: 90, y: -180 }, // Eastern 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 }, // 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) { 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" } let relativePosition = projection.forward({ x: lat, y: lng }) let point = createPointAtPoisition(relativePosition, className, { backgroundColor: color }) robinsonMap.appendChild(point) } } } </script> <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> <script class="doctest"> ; (function () { //First we define the appropriate projection: let mercatorProjection = new Projection.Mercator() 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. Doctest.expectPointPrecision(coordinates, calculatedCoordinates) let point = createPointAtPoisition(relativePosition) mercatorMap.appendChild(point) } })() </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> <div class="center"> <div id="robinsonMap" class="map-wrapper"> <img src="../../assets/maps/wikimedia-world-robinson/2000px-BlankMap-World.png" alt="" width="512" /> </div> </div> <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> <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. */ for (let [key, position] of Object.entries(capitals)) { let relativePosition = robinsonProjection.forward(position) // Run Test Cases Doctest.expectPointPrecision(robinsonTruth[key], relativePosition,0) let point = createPointAtPoisition(relativePosition) robinsonMap.appendChild(point) } })() </script> </section> </body> </html>