375 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!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>
 | 
						|
 | 
						|
 | 
						|
            window.examples = []
 | 
						|
 | 
						|
            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)
 | 
						|
 | 
						|
                        window.examples.push({ map: mercatorMap, projection: mercatorProjection })
 | 
						|
                    }
 | 
						|
 | 
						|
                })()
 | 
						|
        </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)
 | 
						|
                    }
 | 
						|
 | 
						|
                    window.examples.push({ projection: robinsonProjection, map: robinsonMap })
 | 
						|
                })()
 | 
						|
        </script>
 | 
						|
    </section>
 | 
						|
 | 
						|
    <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>
 | 
						|
</body>
 | 
						|
 | 
						|
</html> |