Demo of Customizable Wagner using d3-geo (PoC)

by kartenprojektionen.de

This is a demo of the Customizable Wagner, using the d3-geo scripts with
d3.geoWagner7Customizable().poleline(67.5).parallels(75).inflation(0).ratio(205.128)
A variation of Wagner VII, presented by Karlheinz Wagner himself in 1941 (along with that variant that is called Wagner VII nowadays). It shows a shorter pole line, a stronger curvature of the parallels and is stretched horizontally while keeping the equal-area property.

Böhm Notation of this example: 67.5-75-60-0-205.128. Show in WVG-7.

For more information about Wagner’s transformation method, read this blogpost or the notes at the Wagner Variations Generator (WVG-7) or the article Das Umbeziffern – The Wagner Transformation Method.

Currently, this implementation is only a proof of concept!
It hasn’t been tested thoroughly yet, furthermore I’m not sure if it complies with d3 coding conventions.

HTML/JavaScript source code for this example

        <div id="svgs"></div>
<script>
    var width = 1000,
        height = 660;

    var projection = d3.geoWagner7Customizable()
        .scale(170)
        .translate([width / 2, height / 2])
        .precision(.1)
        .poleline(67.5)
        .parallels(75)
        .inflation(0)
        .ratio(205.128)

    var path = d3.geoPath()
        .projection(projection);

    var graticule = d3.geoGraticule()
        .step([15,15]);

    var svg = d3.select("#svgs").append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g");

    svg.append("defs").append("path")
        .datum({type: "Sphere"})
        .attr("id", "sphere")
        .attr("d", path);

    svg.append("use")
        .attr("class", "stroke")
        .attr("xlink:href", "#sphere");

    svg.append("use")
        .attr("class", "fill")
        .attr("xlink:href", "#sphere");

    svg.append("path")
        .datum(graticule)
        .attr("class", "graticule")
        .attr("d", path);

            
    d3.json("world-50m.json", function(error, world) {
        if (error) throw error;
        svg.insert("path", ".graticule")
            .datum(topojson.feature(world, world.objects.land))
            .attr("class", "land")
            .attr("d", path);
    });

    d3.select('svg').style("height", height + "px");
</script>    
    

Source code of d3-geo-projection.customwagner.0.2.js

View/Download d3-geo-projection.customwagner.0.2.js

        // ===============================================================================
// the first section is taken from:
// https://d3js.org/d3-geo-projection/ Version 2.3.2. Copyright 2013-2017 Mike Bostock.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of contributors may be used to
//   endorse or promote products derived from this software without specific prior
//   written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ===============================================================================
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-geo'), require('d3-array')) :
    typeof define === 'function' && define.amd ? define(['exports', 'd3-geo', 'd3-array'], factory) :
    (factory((global.d3 = global.d3 || {}),global.d3,global.d3));
}(this, (function (exports,d3Geo,d3Array) { 'use strict';

var abs = Math.abs;
var atan = Math.atan;
var atan2 = Math.atan2;

var cos = Math.cos;
var exp = Math.exp;
var floor = Math.floor;
var log = Math.log;
var max = Math.max;
var min = Math.min;
var pow = Math.pow;
var round = Math.round;
var sign = Math.sign || function(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; };
var sin = Math.sin;
var tan = Math.tan;

var epsilon = 1e-6;
var epsilon2 = 1e-12;
var pi = Math.PI;
var halfPi = pi / 2;
var quarterPi = pi / 4;
var sqrt1_2 = Math.SQRT1_2;
var sqrt2 = sqrt(2);
var sqrtPi = sqrt(pi);
var tau = pi * 2;
var degrees = 180 / pi;
var radians = pi / 180;

function sinci(x) {
  return x ? x / Math.sin(x) : 1;
}

function asin(x) {
  return x > 1 ? halfPi : x < -1 ? -halfPi : Math.asin(x);
}

function acos(x) {
  return x > 1 ? 0 : x < -1 ? pi : Math.acos(x);
}

function sqrt(x) {
  return x > 0 ? Math.sqrt(x) : 0;
}

function tanh(x) {
  x = exp(2 * x);
  return (x - 1) / (x + 1);
}

function sinh(x) {
  return (exp(x) - exp(-x)) / 2;
}

function cosh(x) {
  return (exp(x) + exp(-x)) / 2;
}

function arsinh(x) {
  return log(x + sqrt(x * x + 1));
}

function arcosh(x) {
  return log(x + sqrt(x * x - 1));
}

// ===============================================================================
// END of original d3 functions
// ===============================================================================
// ===============================================================================
// below:
// Functions for Customizable Wagner VII/VIII (v0.2) for D3.js <https://d3js.org/>
// Copyright (c) 2018 Tobias Jung, map-projections.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ===============================================================================
// NOTE: I really just wanted to get this thing done, so I
// did NOT check properly if this piece of code complies with
// the d3 coding conventions.
// Thus it's only a proof of concept for now.
// ===============================================================================
// v0.2 - renamed parameters
// psi1() => poleline()
// lambda1() => parallels()
// s60() => inflation()
// p() => ratio()
//
// v0.1 - initial release
// ===============================================================================

// rewriting values from Böhm notation to the notation needed in the formula
function wagner7CustomizableRaw(poleline, parallels, inflation, ratio) {
    // 60 is always used as reference parallel
    var phi1 = 60;
    // needed vor the caluclations:
    var r90  = 90 * radians,
    r180 = 180 * radians;
    
    // sanitizing the input values
    // poleline and parallels may approximate but never equal 0 
    poleline = max(poleline, 1e-10);
    parallels = max(parallels, 1e-10);
    // poleline must be <= 90; parallels may approximate but never equal 180
    poleline = min(poleline, 90);
    parallels = min(parallels, 179.99);
    // 0 <= inflation <= 99.999
    inflation = max(inflation, 0);
    inflation = min(inflation, 99.999);
    // ratio > 0.
    // MAYBE this should be limited to _halfway_ sensible values, i.e. something that renders a map
    // which still can be recognozed as world map, e.g. 20 <= ratio <= 1000.
    ratio = max(ratio, 1e-10);

    var radpoleline  = poleline * radians,
    radparallels = parallels * radians,
    radPhi1  = phi1 * radians;

    // now actually converting values from boehm notation
    // areal inflation e.g. from 0 to 1  or  20 to 1.2:
    var vinflation = inflation/100 + 1;
    // axial ratio e.g. from 200 to 2:
    var vratio  = ratio / 100;
    // the other ones are a bit more complicated...
    var m2 = (acos(vinflation * cos(radPhi1))) / radPhi1,
        m1 = sin(radpoleline) / sin(m2*r90),
        n = radparallels / r180,
        k = sqrt( (vratio * sin(radpoleline / 2)) / (sin(radparallels / 2)) ),
        cx = k / sqrt(n * m1 * m2),
        cy = 1 / ( k * sqrt(n * m1 * m2) );
    
    // ready to call the acutal formula:
    return wagnerFormula(cx, cy, m1, m2, n);
}

function wagner7Customizable() {
    // default values generate wagner viii
    var poleline = 65,
        parallels = 60,
        inflation = 20,
        ratio = 200,
        mutate = d3.geoProjectionMutator(wagner7CustomizableRaw),
        projection = mutate(poleline, parallels, inflation, ratio);
    
    projection.poleline = function(_) {
      return arguments.length ? mutate(poleline = +_, parallels, inflation, ratio) : poleline;
    };
    
    projection.parallels = function(_) {
      return arguments.length ? mutate(poleline, parallels = +_, inflation, ratio) : parallels;
    };
    projection.inflation = function(_) {
      return arguments.length ? mutate(poleline, parallels, inflation = +_, ratio) : inflation;
    };
    projection.ratio = function(_) {
      return arguments.length ? mutate(poleline, parallels, inflation, ratio = +_) : ratio;
    };
    
    return projection
    .scale(172.632);
}

// if you prefer not to use the Böhm notation but to work directly with the desired values
// of cx, cy, m1, m2 and n, use this:
function wagner7CustomizableDirect() {
    // default values generate wagner viii
    var cx = 2.811481,
    cy = 1.308153,
    m1 = 0.921166,
    m2 = 0.885502,
    n  = 0.333333,
    mutate = d3.geoProjectionMutator(wagnerFormula),
    projection = mutate(cx, cy, m1, m2, n);
    
    
    projection.cx = function(_) {
      return arguments.length ? mutate(cx = +_, cy, m1, m2, n) : cx;
    };
    projection.cy = function(_) {
      return arguments.length ? mutate(cx, cy = +_, m1, m2, n) : cy;
    };
    projection.m1 = function(_) {
      return arguments.length ? mutate(cx, cy, m1 = +_, m2, n) : m1;
    };
    projection.m2 = function(_) {
      return arguments.length ? mutate(cx, cy, m1, m2 = +_, n) : m2;
    };
    projection.n = function(_) {
      return arguments.length ? mutate(cx, cy, m1, m2, n = +_) : n;
    };
    
    
    
    return projection;
}

// the actual formula that renders the projection
function wagnerFormula(cx, cy, m1, m2, n) {
    function forward(lambda, phi) {
    var s = m1 * sin(m2 * phi),
        c0 = sqrt(1 - s * s),
        c1 = sqrt(2 / (1 + c0 * cos(lambda *= n)));
    return [
      cx * c0 * c1 * sin(lambda),
      cy * s * c1
    ];
    };
    
    // IMPORTANT NOTE: as of yet, the invert function is untestet!
    forward.invert = function(x, y) {
    var t1 = x / cx,
        t2 = y / cy,
        p = sqrt(t1 * t1 + t2 * t2),
        c = 2 * asin(p / 2);
    return [
      n * atan2(x * tan(c), cx * p),
      p && asin(y * sin(c) / (cy * m1 * p)) / m2
    ];
    };
    
    return forward;
}

// ===============================================================================
// END of functions for Customizable Wagner VII/VIII for D3.js <https://d3js.org/>
// ===============================================================================

exports.geoWagner7CustomizableDirect = wagner7CustomizableDirect;
exports.geoWagner7Customizable = wagner7Customizable;
exports.geoWagner7CustomizableRaw = wagner7CustomizableRaw;


Object.defineProperty(exports, '__esModule', { value: true });

})));        
    

License

Released under the GNU General Public License, version 3.

kartenprojektionen.de: Startseite · Impressum · Datenschutz