From b9eff658a3244a92489ba60f725aaefc50034141 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 28 May 2011 00:13:53 -0700 Subject: [PATCH] Stereographic & orthographic projections! These now have arbitrary origins, as well. Still need to clip, though. --- d3.geo.js | 44 +++++++++----- d3.geo.min.js | 2 +- examples/azimuthal/azimuthal.css | 13 +---- examples/azimuthal/azimuthal.html | 96 ++++++++++++++++++++----------- examples/azimuthal/azimuthal.js | 72 ++++------------------- src/geo/azimuthal.js | 44 +++++++++----- 6 files changed, 137 insertions(+), 134 deletions(-) diff --git a/d3.geo.js b/d3.geo.js index 361b69f4..917f1051 100644 --- a/d3.geo.js +++ b/d3.geo.js @@ -1,26 +1,44 @@ (function(){d3.geo = {}; +// TODO clip input coordinates on opposite hemisphere d3.geo.azimuthal = function() { - var scale = 200, + var mode = "orthographic", // or stereographic + origin, + scale = 200, translate = [480, 250], - pole = 1; + x0, + y0, + cy0, + sy0; function azimuthal(coordinates) { - var p = p < 0 ? 0 : - p > 1 ? 1 : - 1 - pole * coordinates[1] / 90, - t = coordinates[0] * d3_radians, - x = p * Math.cos(t), - y = p * Math.sin(t); + var x1 = coordinates[0] * d3_radians - x0, + y1 = coordinates[1] * d3_radians, + cx1 = Math.cos(x1), + sx1 = Math.sin(x1), + cy1 = Math.cos(y1), + sy1 = Math.sin(y1), + k = mode == "stereographic" ? 1 / (1 + sy0 * sy1 + cy0 * cy1 * cx1) : 1, + x = k * cy1 * sx1, + y = k * (sy0 * cy1 * cx1 - cy0 * sy1); return [ scale * x + translate[0], scale * y + translate[1] ]; } - // 1 or -1 means N or S hemisphere - azimuthal.hemisphere = function(x) { - if (!arguments.length) return pole; - pole = +x; + azimuthal.mode = function(x) { + if (!arguments.length) return mode; + mode = x; + return azimuthal; + }; + + azimuthal.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + x0 = origin[0] * d3_radians; + y0 = origin[1] * d3_radians; + cy0 = Math.cos(y0); + sy0 = Math.sin(y0); return azimuthal; }; @@ -36,7 +54,7 @@ d3.geo.azimuthal = function() { return azimuthal; }; - return azimuthal; + return azimuthal.origin([0, 0]); }; // Derived from Tom Carden's Albers implementation for Protovis. // http://gist.github.com/476238 diff --git a/d3.geo.min.js b/d3.geo.min.js index 211bedd9..85f017f3 100644 --- a/d3.geo.min.js +++ b/d3.geo.min.js @@ -1 +1 @@ -(function(){function m(a,b){for(var c=a.coordinates[0],d=0,e=c.length;d1?1:1-d*e[1]/90,g=e[0]*a,h=f*Math.cos(g),i=f*Math.sin(g);return[b*h+c[0],b*i+c[1]]}var b=200,c=[480,250],d=1;e.hemisphere=function(a){if(!arguments.length)return d;d=+a;return e},e.scale=function(a){if(!arguments.length)return b;b=+a;return e},e.translate=function(a){if(!arguments.length)return c;c=[+a[0],+a[1]];return e};return e},d3.geo.albers=function(){function k(){var d=a*c[0],e=a*c[1],k=a*b[1],l=Math.sin(d),m=Math.cos(d);f=a*b[0],g=.5*(l+Math.sin(e)),h=m*m+2*g*l,i=Math.sqrt(h-2*g*Math.sin(k))/g;return j}function j(b){var c=g*(a*b[0]-f),j=Math.sqrt(h-2*g*Math.sin(a*b[1]))/g;return[d*j*Math.sin(c)+e[0],d*(j*Math.cos(c)-i)+e[1]]}var b=[-98,38],c=[29.5,45.5],d=1e3,e=[480,250],f,g,h,i;j.origin=function(a){if(!arguments.length)return b;b=[+a[0],+a[1]];return k()},j.parallels=function(a){if(!arguments.length)return c;c=[+a[0],+a[1]];return k()},j.scale=function(a){if(!arguments.length)return d;d=+a;return j},j.translate=function(a){if(!arguments.length)return e;e=[+a[0],+a[1]];return j};return k()},d3.geo.albersUsa=function(){function e(e){var f=e[0],g=e[1];return(g<25?f<-100?c:d:g>50?b:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);e.scale=function(f){if(!arguments.length)return a.scale();a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5);return e.translate(a.translate())},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]);return e};return e.scale(a.scale())};var a=Math.PI/180;d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-180/Math.PI*Math.log(Math.tan(Math.PI/4+c[1]*Math.PI/360))/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];c.scale=function(b){if(!arguments.length)return a;a=+b;return c},c.translate=function(a){if(!arguments.length)return b;b=[+a[0],+a[1]];return c};return c},d3.geo.path=function(){function n(a){return Math.abs(d3.geom.polygon(a.map(f)).area())}function l(a){var b=d3.geom.polygon(a[0].map(f)),c=b.centroid(1),d=c[0],e=c[1],g=Math.abs(b.area()),h=0,i=a.length;while(++hd&&(d=a),ef&&(f=e)});return[[b,c],[d,f]]};var f={Feature:g,FeatureCollection:h,LineString:i,MultiLineString:j,MultiPoint:i,MultiPolygon:k,Point:l,Polygon:m}})() \ No newline at end of file +(function(){function m(a,b){for(var c=a.coordinates[0],d=0,e=c.length;d50?b:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);e.scale=function(f){if(!arguments.length)return a.scale();a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5);return e.translate(a.translate())},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]);return e};return e.scale(a.scale())};var a=Math.PI/180;d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-180/Math.PI*Math.log(Math.tan(Math.PI/4+c[1]*Math.PI/360))/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];c.scale=function(b){if(!arguments.length)return a;a=+b;return c},c.translate=function(a){if(!arguments.length)return b;b=[+a[0],+a[1]];return c};return c},d3.geo.path=function(){function n(a){return Math.abs(d3.geom.polygon(a.map(f)).area())}function l(a){var b=d3.geom.polygon(a[0].map(f)),c=b.centroid(1),d=c[0],e=c[1],g=Math.abs(b.area()),h=0,i=a.length;while(++hd&&(d=a),ef&&(f=e)});return[[b,c],[d,f]]};var f={Feature:g,FeatureCollection:h,LineString:i,MultiLineString:j,MultiPoint:i,MultiPolygon:k,Point:l,Polygon:m}})() \ No newline at end of file diff --git a/examples/azimuthal/azimuthal.css b/examples/azimuthal/azimuthal.css index e0d05acf..f3da8dfc 100644 --- a/examples/azimuthal/azimuthal.css +++ b/examples/azimuthal/azimuthal.css @@ -11,22 +11,11 @@ svg { background: #eee; } -line { - stroke: brown; - stroke-dasharray: 4,2; -} - path { - fill: none; + fill: #ccc; stroke: #fff; } -path.grid { - fill: none; - stroke: #eee; - stroke-width: .5; -} - div { width: 960px; } diff --git a/examples/azimuthal/azimuthal.html b/examples/azimuthal/azimuthal.html index d8e4bd53..11bd1196 100644 --- a/examples/azimuthal/azimuthal.html +++ b/examples/azimuthal/azimuthal.html @@ -10,46 +10,74 @@ -

Polar Stereographic Projection

+

Azimuthal Projection

-

scale: 200

-

translate.x: 480

+

origin.longitude: 0
+
origin.latitude: 0

+

scale: 240

+

translate.x: 480
translate.y: 250
diff --git a/examples/azimuthal/azimuthal.js b/examples/azimuthal/azimuthal.js index a2800479..f9caf8b9 100644 --- a/examples/azimuthal/azimuthal.js +++ b/examples/azimuthal/azimuthal.js @@ -1,73 +1,23 @@ -// Our projection. -var xy = d3.geo.azimuthal(), - path = d3.geo.path().projection(xy); - -var geopath = function(d) { - var c0 = xy([0, 270]), c1 = xy([180, 270]), - r = Math.abs(c0[0] - c1[0]) / 2, - arc = " A " + r + "," + r + " 0 0,1 ", - useArc = false, - min = 360, - max = 0, - i = -1, - n = d.length; - while (++i < n) { - var lon = d[i][0]; - if (lon < min) min = lon; - if (lon > max) max = lon; - if (max - min >= 180) { - useArc = true; - break; - } - } - return path(d) + (useArc ? " M " + c0.join(",") + arc + c1.join(",") + arc + c0.join(",") + " z" : ""); -}; - -var states = d3.select("body") - .append("svg:svg") - .append("svg:g") - //.attr("fill-rule", "evenodd") - .attr("id", "states"); - -var grid = d3.select("svg") - .append('svg:g'); - -grid.selectAll('path') - .data( - d3.range(0, 90, 10).map(function(lat) { return d3.range(0, 370, 10).map(function(lon) { return [lon, lat] }) }).concat( - d3.range(0, 360, 10).map(function(lon) { return d3.range(0, 100, 10).map(function(lat) { return [lon, lat] }) })) - ) - .enter().append("svg:path") - .attr('class', 'grid') - .attr("d", d3.svg.line() - .x(function(d) { return xy(d)[0] }) - .y(function(d) { return xy(d)[1] }) - ); +var xy = d3.geo.azimuthal().scale(240).mode("stereographic"), + path = d3.geo.path().projection(xy), + svg = d3.select("body").append("svg:svg"); d3.json("../../data/world-countries.json", function(collection) { - - states - .selectAll("path") + svg.selectAll("path") .data(collection.features) .enter().append("svg:path") - .attr("d", geopath) + .attr("d", path) .append("svg:title") .text(function(d) { return d.properties.name; }); - }); function refresh() { - states - .selectAll("path") - .attr("d", geopath); - - grid - .selectAll('path') - .attr("d", d3.svg.line() - .x(function(d) { return xy(d)[0] }) - .y(function(d) { return xy(d)[1] }) - ); - + svg.selectAll("path") + .attr("d", path); + d3.select("#lon span") + .text(xy.origin()[0]); + d3.select("#lat span") + .text(xy.origin()[1]); d3.select("#scale span") .text(xy.scale()); d3.select("#translate-x span") diff --git a/src/geo/azimuthal.js b/src/geo/azimuthal.js index 3803b20f..92943480 100644 --- a/src/geo/azimuthal.js +++ b/src/geo/azimuthal.js @@ -1,25 +1,43 @@ +// TODO clip input coordinates on opposite hemisphere d3.geo.azimuthal = function() { - var scale = 200, + var mode = "orthographic", // or stereographic + origin, + scale = 200, translate = [480, 250], - pole = 1; + x0, + y0, + cy0, + sy0; function azimuthal(coordinates) { - var p = p < 0 ? 0 : - p > 1 ? 1 : - 1 - pole * coordinates[1] / 90, - t = coordinates[0] * d3_radians, - x = p * Math.cos(t), - y = p * Math.sin(t); + var x1 = coordinates[0] * d3_radians - x0, + y1 = coordinates[1] * d3_radians, + cx1 = Math.cos(x1), + sx1 = Math.sin(x1), + cy1 = Math.cos(y1), + sy1 = Math.sin(y1), + k = mode == "stereographic" ? 1 / (1 + sy0 * sy1 + cy0 * cy1 * cx1) : 1, + x = k * cy1 * sx1, + y = k * (sy0 * cy1 * cx1 - cy0 * sy1); return [ scale * x + translate[0], scale * y + translate[1] ]; } - // 1 or -1 means N or S hemisphere - azimuthal.hemisphere = function(x) { - if (!arguments.length) return pole; - pole = +x; + azimuthal.mode = function(x) { + if (!arguments.length) return mode; + mode = x; + return azimuthal; + }; + + azimuthal.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + x0 = origin[0] * d3_radians; + y0 = origin[1] * d3_radians; + cy0 = Math.cos(y0); + sy0 = Math.sin(y0); return azimuthal; }; @@ -35,5 +53,5 @@ d3.geo.azimuthal = function() { return azimuthal; }; - return azimuthal; + return azimuthal.origin([0, 0]); };