Added the maps-module to the iwmlib.
Migrated a ot of the content from the tuesch to the iwmlib. This is before the decoupeling of the layers.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import Projection from './projection.js'
|
||||
|
||||
/**
|
||||
* This is a projection file, that grants access to the
|
||||
* MERCATOR projection.
|
||||
*
|
||||
* Regulary only few Projections will be used in one
|
||||
* project, therefore only required one's should be
|
||||
* loaded.
|
||||
*/
|
||||
|
||||
export default class Mercator extends Projection {
|
||||
forward(coords) {
|
||||
let lat = coords.x
|
||||
let lng = coords.y
|
||||
|
||||
const PI_180 = Math.PI / 180.0
|
||||
const PI_4 = Math.PI * 4
|
||||
|
||||
const sinLatitude = Math.sin(lat * PI_180)
|
||||
let y = 0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / PI_4
|
||||
let x = (lng + 180) / 360
|
||||
|
||||
y = y < 0 ? 0 : y > 1 ? 1 : y
|
||||
|
||||
return new PIXI.Point(x, y)
|
||||
}
|
||||
|
||||
backward(point) {
|
||||
let lng = point.x * 360 - 180
|
||||
let lat = (Math.asin(-2 / (Math.exp(4 * Math.PI * (0.5 - point.y)) + 1) + 1) * 180) / Math.PI
|
||||
|
||||
return new PIXI.Point(lat, lng)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return 'Mercator Projection'
|
||||
}
|
||||
|
||||
get maxViewport() {
|
||||
return { min: new PIXI.Point(-85, -180), max: new PIXI.Point(85, 180) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* A projection determines how a geographical card has to
|
||||
* be interpreted to map coordinate to pixels.
|
||||
*
|
||||
* Most used transformation is the mercator projection,
|
||||
* which projects a sphere on a cylinder.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
|
||||
export default class Projection {
|
||||
/**
|
||||
* Transforms a coordinate to a normalized position on the map.
|
||||
*
|
||||
* @param {*} coords
|
||||
* @memberof Projection
|
||||
*/
|
||||
forward(coords) {
|
||||
console.error('You must override the forward function in ' + this.name + '.')
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a normalized point on the map to a coordinate.
|
||||
*
|
||||
* @param {*} point
|
||||
* @memberof Projection
|
||||
*/
|
||||
backward(point) {
|
||||
console.error('You must override the backward fuction in ' + this.name + '.')
|
||||
}
|
||||
|
||||
toString() {
|
||||
return 'Projection (abstract)'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.toString()
|
||||
}
|
||||
|
||||
get maxViewport() {
|
||||
return { min: new PIXI.Point(-90, -180), max: new PIXI.Point(90, 180) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Projections</title>
|
||||
<link rel="stylesheet" href="../../../3rdparty/highlight/styles/default.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>
|
||||
.inline-showcase {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.map-example {
|
||||
display: inline-block;
|
||||
width: 256px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.map-wrapper {
|
||||
position: relative;
|
||||
/* inline-block create additional space around child elements. */
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.long {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background-color: yellowgreen;
|
||||
}
|
||||
|
||||
.point {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
background-color: red;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.small.point {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.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 id="mercatorMap" class="map-wrapper">
|
||||
<img src="../../../examples/osm/0/0/0.png" alt="" />
|
||||
</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 = {}) {
|
||||
console.log(position)
|
||||
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 relativePosition = projection.forward({ x: lat, y: lng })
|
||||
let point = createPointAtPoisition(relativePosition, 'small', {
|
||||
backgroundColor: '#0000ff'
|
||||
})
|
||||
robinsonMap.appendChild(point)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script class="doctest">
|
||||
|
||||
; (function () {
|
||||
let mercatorProjection = new Projection.Mercator()
|
||||
|
||||
for (let position of Object.values(capitals)) {
|
||||
let relativePosition = mercatorProjection.forward(position)
|
||||
let point = createPointAtPoisition(relativePosition)
|
||||
mercatorMap.appendChild(point)
|
||||
}
|
||||
|
||||
boundaries.forEach(coord => {
|
||||
let relativePosition = mercatorProjection.forward(coord)
|
||||
let point = createPointAtPoisition(relativePosition, 'small', {
|
||||
backgroundColor: '#0000ff'
|
||||
})
|
||||
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 id="robinsonMap" class="map-wrapper">
|
||||
<img src="../../assets/maps/wikimedia-world-robinson/2000px-BlankMap-World.png" alt="" width="512" />
|
||||
</div>
|
||||
|
||||
<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 position of Object.values(capitals)) {
|
||||
let relativePosition = robinsonProjection.forward(position)
|
||||
let point = createPointAtPoisition(relativePosition)
|
||||
robinsonMap.appendChild(point)
|
||||
}
|
||||
|
||||
drawZeroLongitude(robinsonProjection, robinsonMap)
|
||||
|
||||
plotGridPoints(5, robinsonProjection, robinsonMap)
|
||||
|
||||
})()
|
||||
</script>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,148 @@
|
||||
import Projection from './projection'
|
||||
|
||||
/* */
|
||||
|
||||
export default class Robinson extends Projection {
|
||||
constructor(lng = 0) {
|
||||
super()
|
||||
|
||||
this.lng0 = lng
|
||||
this.lengthOfParallel = [
|
||||
1.0,
|
||||
0.9986,
|
||||
0.9954,
|
||||
0.99,
|
||||
0.9822,
|
||||
0.973,
|
||||
0.96,
|
||||
0.9427,
|
||||
0.9216,
|
||||
0.8962,
|
||||
0.8679,
|
||||
0.835,
|
||||
0.7986,
|
||||
0.7597,
|
||||
0.7186,
|
||||
0.6732,
|
||||
0.6213,
|
||||
0.5722,
|
||||
0.5322
|
||||
]
|
||||
this.distancesFromEquator = [
|
||||
0.0,
|
||||
0.062,
|
||||
0.124,
|
||||
0.186,
|
||||
0.248,
|
||||
0.31,
|
||||
0.372,
|
||||
0.434,
|
||||
0.4958,
|
||||
0.5571,
|
||||
0.6176,
|
||||
0.6769,
|
||||
0.7346,
|
||||
0.7903,
|
||||
0.8435,
|
||||
0.8936,
|
||||
0.9394,
|
||||
0.9761,
|
||||
1.0
|
||||
]
|
||||
}
|
||||
forward(coords) {
|
||||
let { x: lat, y: lng } = coords
|
||||
|
||||
lng = this._adjustLng(lng)
|
||||
|
||||
// Get the required indices, the remainder in between low and hight as ratio
|
||||
// and the sign of the found indices, as the tables are only in positive direction.
|
||||
let { low, high, ratio, sign } = this._getInterpolationValues(lat, 90)
|
||||
|
||||
// Values that lie inbetween two indices are interpolated.
|
||||
let y = this._interpolate(this.distancesFromEquator[low], this.distancesFromEquator[high], ratio)
|
||||
|
||||
// Reapply the sign to the vertical position.
|
||||
y *= sign
|
||||
|
||||
// The center of the projection is in the center of the map. Therefore we shift the
|
||||
// center to the top left corner.
|
||||
y = 1 - (y + 1) / 2
|
||||
|
||||
// The lengthOfParallel table provides us with the corresponding scaling factor
|
||||
// for a specific latitude. Inbetween values are interpolated as before.
|
||||
let proportionalLength = this._interpolate(this.lengthOfParallel[low], this.lengthOfParallel[high], ratio)
|
||||
|
||||
//To normalize the value to a range from -1 to 1.
|
||||
let x = (proportionalLength * lng) / 180
|
||||
|
||||
x = (x + 1) / 2
|
||||
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
backward(position) {
|
||||
let { x, y } = position
|
||||
|
||||
y = 1 - 2 * y
|
||||
let sign = Math.sign(y)
|
||||
y = Math.abs(y)
|
||||
|
||||
let low = 0
|
||||
let high = 0
|
||||
for (let i = 0; i < this.distancesFromEquator.length - 1 && y > this.distancesFromEquator[i]; i++) {
|
||||
low = i
|
||||
high = i + 1
|
||||
}
|
||||
|
||||
let lowDist = this.distancesFromEquator[low]
|
||||
let highDist = this.distancesFromEquator[high]
|
||||
|
||||
let ratio = highDist - lowDist == 0 ? 0 : (y - lowDist) / (highDist - lowDist)
|
||||
|
||||
let lat = low * 5 + ratio * 5
|
||||
|
||||
let parallelLengthMin = this.lengthOfParallel[low]
|
||||
let parallelLengthMax = this.lengthOfParallel[high]
|
||||
|
||||
let completeLength = parallelLengthMin + (parallelLengthMax - parallelLengthMin) * ratio
|
||||
|
||||
x = x * 2 - 1
|
||||
let normalizedLength = x / completeLength
|
||||
|
||||
let lng = normalizedLength * 180
|
||||
|
||||
return { x: lat * sign, y: this._adjustLng(lng, true) }
|
||||
}
|
||||
|
||||
_adjustLng(lng, inv = false) {
|
||||
let moved = inv ? lng + this.lng0 : lng - this.lng0
|
||||
if (moved < -180) moved += 360
|
||||
if (moved > 180) moved -= 360
|
||||
|
||||
return moved
|
||||
}
|
||||
|
||||
_interpolate(a, b, ratio) {
|
||||
return a * (1 - ratio) + b * ratio
|
||||
}
|
||||
_getInterpolationValues(value, max) {
|
||||
let sign = Math.sign(value)
|
||||
value = Math.min(Math.abs(value), max)
|
||||
// Note that min and max can be the same. Which is true
|
||||
// when lat is dividable by 5. This also covers the edge cases 0 and 90.
|
||||
let minIndex = Math.floor(value / 5)
|
||||
let maxIndex = Math.ceil(value / 5)
|
||||
let ratio = (value % 5) / 5
|
||||
// console.log({ value, minIndex, maxIndex, ratio })
|
||||
// console.log(this.lengthOfParallel.length)
|
||||
return { low: minIndex, high: maxIndex, ratio, sign }
|
||||
}
|
||||
|
||||
toString() {
|
||||
return
|
||||
}
|
||||
get name() {
|
||||
return 'Robinson Projection'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
|
||||
<link rel="stylesheet" href="../../../3rdparty/highlight/styles/default.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>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="solution">
|
||||
|
||||
</section>
|
||||
<script>
|
||||
|
||||
function write(msg) {
|
||||
|
||||
if (typeof msg === "object") {
|
||||
msg = JSON.stringify(msg)
|
||||
}
|
||||
|
||||
let log = document.createElement("p")
|
||||
log.innerHTML = msg
|
||||
solution.appendChild(log)
|
||||
}
|
||||
|
||||
|
||||
let coords = { x: -30, y: -30 }
|
||||
write(coords)
|
||||
|
||||
let robinson = new Projection.Robinson()
|
||||
|
||||
let pixels = robinson.forward(coords)
|
||||
write(pixels)
|
||||
|
||||
coords = robinson.backwards(pixels)
|
||||
write(coords)
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user