Customizable Wagner Projections using d3-geo – Seven Of Nine

by map-projections.net
Go to German Version

Between 1932 and 1949, Karlheinz Wagner presented nine map projections. Each of them is customizable, i.e. they can easily be modified in certain regards to meet the wishes and needs of the user. Unfortunately, these options have been largely ignored in cartographic literature, with a few exceptions.

The projections nowadays known as Wagner I to IX are merely examples, special cases that Wagner found particularly advantageous.
Here, I’m showing a way to employ all possible instances of seven of the nine projections using d3-geo-projection.

A description of the functions and their parameters as well as the reason for differences in certain parameters is given in the blogpost “More Umbeziffern for d3-geo-projections”.

For general information about Wagner’s transformation method, read this blogpost or the notes at the Wagner-Variationen-Generator (WVG-7) and in the article Das Umbeziffern – Wagners Transformation Method.

Examples

Each of the examples shows a single instance of a customizable Wagner projection to convey an idea of the possibilities. They also present the source code that generates the image. This might be helpful for people who haven’t worked with d3-geo-projection before.

In the interactive demo, you can render all possible instances of a certain customizable Wagner projection by dragging a few sliders.

Except for the customizable Wagner VII/VIII, all variants are PROPOSALS.
There might be bugs. There might be changes. They might never make it into the official release of d3-geo-projection.

Source code

The customizable Wagner VII/VIII is part of current d3-geo-projection releases.

Full source code: View/Download d3-geo-projection.umbeziffern.rfc.js
 
Here is an excerpt showing the new code only:

        // wagner2 und wagner3 by Tobias Jung
///////////////////////////////////////////////////////////
function wagnerEquallySpacedParallelsRaw(poleline, parallels, ratio, xfactor, parentProjection) {
    // sanitizing the input values
    // poleline and parallels may approximate but never equal 0.
    // making the min value even smaller (e.g. 0.0001 or 1e-10) will render bugs.
    poleline = max(poleline, 0.001);
    parallels = max(parallels, 0.001);
    // poleline must be <= 90; parallels may approximate but never equal 180
    poleline = min(poleline, 90);
    parallels = min(parallels, 179.99);
    // ratio is max. 100, min 1
    ratio = min(ratio, 600);
    ratio = max(ratio, 1);
    
    var p  = ratio / 100,
        ca = xfactor / 100,
        k  = sqrt( (p * poleline) / parallels ),
        cm = poleline/90,
        cn = parallels/180,
        cx = k / (  sqrt( cm * cn ) ),
        cy = 1 / (k*sqrt( cm * cn ) );
    
    
        // console.log("Wagner's notation");
        // console.log("cm  = " + cm);
        // console.log("cn  = " + cn);
        // console.log("cx = " + cx);
        // console.log("cy = " + cy);
        // // console.log("k  = " + k);
        // // console.log("p  = " + 2 * pow(k, 2) * cn/cm);
        // console.log("ca  = " + ca);
        
        console.log('ca = ' + ca + ', cm = ' + cm +', cn = ' + cn +', cx = ' + cx +', cy = ' + cy);
    
    // ready to call the acutal formula:
    return wagnerEquallySpacedParallelsFormula(ca, cm, cn, cx, cy, parentProjection);
}

function wagnerEquallySpacedParallels() {
  var poleline = 70,
      parallels = 50,
      ratio = 200,
      xfactor = 100,
      parentProjection = 9,
      mutate = d3Geo.geoProjectionMutator(wagnerEquallySpacedParallelsRaw),
      projection = mutate(poleline, parallels, ratio, xfactor, parentProjection);

     
    projection.poleline = function(_) {
      return arguments.length ? mutate(poleline = +_, parallels, ratio, xfactor, parentProjection) : poleline;
    };

    projection.parallels = function(_) {
      return arguments.length ? mutate(poleline, parallels = +_, ratio, xfactor, parentProjection) : parallels;
    };
    projection.ratio = function(_) {
      return arguments.length ? mutate(poleline, parallels, ratio = +_, xfactor, parentProjection) : ratio;
    };
    projection.xfactor = function(_) {
      return arguments.length ? mutate(poleline, parallels, ratio, xfactor = +_, parentProjection) : xfactor;
    };
    projection.parentProjection = function(_) {
      return arguments.length ? mutate(poleline, parallels, ratio, xfactor, parentProjection = +_) : parentProjection;
    };
  
return projection
  .scale(112.314);
}


function wagnerEquallySpacedParallelsFormula(ca, cm, cn, cx, cy, parentProjection) {
    if (parentProjection == 3) {
      var pp = d3Geo.geoSinusoidalRaw;
    } else if (parentProjection == 6) {
      var pp = d3Geo.geoApian2Raw;
    } else if (parentProjection == 9) {
      var pp = d3Geo.geoAzimuthalEquidistantRaw;
    } else if (parentProjection == 10) {
        var pp = d3Geo.geoVanDerGrinten4Raw;
    } else if (parentProjection == 11) {
        var pp = d3Geo.geoPolyconicRaw;
    } else if (parentProjection == 12) {
        var pp = d3Geo.geoNicolosiRaw;
    } else if (parentProjection == 13) {
        var pp = d3Geo.geoRectangularPolyconicRaw(0);
    }
    

  function forward(lambda, phi) {
    var xy = pp(cn * lambda, cm * phi),
        x  = ca * cx * xy[0],
        y  = cy * xy[1];
        return [x, y];
  }

  forward.invert = function(x, y) {
    var lambda_phi = pp.invert(x / (ca * cx), y / cy),
        lambda     = (1 / cn) * lambda_phi[0],
        phi        = (1 / cm) * lambda_phi[1];
    return [lambda, phi];
  };

  return forward;
}


// wagner ii
function wagner2Formula(cx, cy, m1, m2) {
    function forward(lambda, phi) {
        phi = asin(m1 * sin(m2 * phi));
        var x = cx * lambda * cos(phi),
        y = (cy * phi);
            
        return [ x, y ];
    }
    
    forward.invert = function(x, y) {
    	y /= cy;
        
        return [
            x / (cx * cos(y)),
            asin(sin(y) / m1) / m2
        ];
    };
    
    return forward;
}

function wagner2Raw(poleline, inflation, ratio) {
    // 60 is always used as reference parallel
    var phi1 = pi / 3;
    // 0 < poleline < 1
    poleline = max(poleline, epsilon);
    poleline = min(poleline, 1 - epsilon);

    ratio = 100/ratio;
    ratio = max(ratio, 0.1);
    // ratio isn't really limited to 3,
    // but this is about where it stops to make any sense...
    ratio = min(ratio, 3);
    inflation = inflation/100 + 1;
    // 1 <= inflation < 2
    inflation = max(inflation, 0);
    inflation = min(inflation, 2 - epsilon);
    
    var m2 = (acos(inflation * cos(phi1))) / phi1,
    m1 = sqrt(1 - pow(poleline, 2)) / sin(m2 * (pi / 2)),
    n = (asin(sqrt(1-pow(poleline, 2)))) / (ratio * pi),
    cx = n / sqrt(n * m1 * m2),
    cy = cx / n;
    
    return wagner2Formula(cx, cy, m1, m2);
}

function wagner2() {
  // default values generate wagner2
  var poleline = 0.5,
      inflation = 20,
      ratio = 200,
    mutate = d3Geo.geoProjectionMutator(wagner2Raw),
    projection = mutate(poleline, inflation, ratio);
    
  
  projection.poleline = function(_) {
    return arguments.length ? mutate(poleline = +_, inflation, ratio) : poleline;
  };

  projection.inflation = function(_) {
    return arguments.length ? mutate(poleline, inflation = +_, ratio) : inflation;
  };
  projection.ratio = function(_) {
    return arguments.length ? mutate(poleline, inflation, ratio = +_) : ratio;
  };

  return projection
    .scale(150);
}

// wagner iii (alternative approach)
function wagner3(poleline, ratio, phi0) {
    // default values render the original wagner iii
    var poleline = 0.5,
    ratio = 200,
    phi0 = 0;
    
    var mutate = d3Geo.geoProjectionMutator(wagner3Raw),
    projection = mutate(poleline, ratio, phi0);
    
    projection.poleline = function(_) {
      return arguments.length ? mutate(poleline = +_, ratio, phi0) : poleline;
    };
    projection.ratio = function(_) {
      return arguments.length ? mutate(poleline, ratio = +_, phi0) : ratio;
    };
    projection.phi0 = function(_) {
      return arguments.length ? mutate(poleline, ratio, phi0 = +_) : phi0;
    };
    
  return projection
    .scale(176.84);
}

function wagner3Raw(poleline, ratio, phi0) {
    ratio = ratio/100;
    var cm = (2/pi) * acos(poleline), 
    cn = (ratio*cm)/2,
    cy = cn / sqrt(cm*cn),
    phi0 = phi0 * radians,
    cosPhi = cos(phi0),
    ca = cos(phi0) / ( cy * cos(cm*phi0)),
    cx = cm / sqrt(cm*cn);
    
    return wagner3Formula(cx, cy, ca, cm);
}

function wagner3Formula(cx, cy, ca, cm) {
    function forward(lambda, phi) {
        var y = cx * phi,
        x = ca * cy * lambda * cos(cm*phi);
        return [x,y];
    }
    
    forward.invert = function(x, y) {
        y /= cx;
        return [
            x / (cy * cos(cm*y) * ca),
            y
        ];
    }
    
    
    return forward;
}

function apian2Raw(lambda, phi) {
  return [lambda * sqrt(1 - pow(phi / halfPi, 2)), phi];
}

apian2Raw.invert = function(x, y) {
  return [x / sqrt(1 - pow(y / halfPi, 2)), y];
};

function apian2() {
  return d3Geo.geoProjection(apian2Raw)
      .scale(112.314);
}


// NOTE: The following function renders Frank Canters' optimization of Wagner IX.
// Actually it is not needed, because you can render this projection with:
// d3.geoWagnerEquallySpacedParallels().parentProjection(9).poleline(67.131).parallels(86.562).ratio(206.7978).xfactor(80.7486)
// However, Canters used a different mathematical approach which is reproduced here.
// If you're interested in the mathematics behind map projections, you might enjoy seeing his approach,
// but I guess it's not necassary to add this to d3-geo-projections.
function cantersW09Raw(cm, cn, k1, k2) {
    var k  = k2,
        ca = k1 / k2,
        p  = 1/(2 * k1 * k2) * cm/cn,
        cx = k / (  sqrt( cm * cn ) ),
        cy = 1 / (k*sqrt( cm * cn ) );
    
    // console.log("Canters's notation");
    // console.log("m  = " + cm);
    // console.log("n  = " + cn);
    // console.log("k1 = " + k1);
    // console.log("k2 = " + k2);
    // console.log("p  = " + p);
    // console.log("");
    // console.log("Wagner's notation");
    // console.log("m  = " + cm);
    // console.log("n  = " + cn);
    // console.log("Cx = " + cx);
    // console.log("Cy = " + cy);
    // console.log("k  = " + k);
    // console.log("p  = " + 2 * pow(k, 2) * cn/cm);
    // console.log("a  = " + ca);
    
    // ready to call the actual formula:
    return wagnerEquallySpacedParallelsFormula(ca, cm, cn, cx, cy, 9);
}

function cantersW09() {
  // default values generate the original Canters W09
  var cm = 0.7459,
      cn = 0.4809,
      k1 = 1.0226,
      k2 = 1.2664,
      mutate = d3Geo.geoProjectionMutator(cantersW09Raw),
      projection = mutate(cm, cn, k1, k2);
  
  projection.cm = function(_) {
    return arguments.length ? mutate(cm = +_, cn, k1, k2) : cm;
  };
  projection.cn = function(_) {
    return arguments.length ? mutate(cm, cn = +_, k1, k2) : cn;
  };
  projection.k1 = function(_) {
    return arguments.length ? mutate(cm, cn, k1 = +_, k2) : k1;
  };
  projection.k2 = function(_) {
    return arguments.length ? mutate(cm, cn, k1, k2 = +_) : k2;
  };        
    

License

Released under the GNU General Public License, version 3.

map-projections.net: Home · Legal Disclosure · Privacy Policy