| 1 | import clipAntimeridian from "../clip/antimeridian.js";
|
|---|
| 2 | import clipCircle from "../clip/circle.js";
|
|---|
| 3 | import clipRectangle from "../clip/rectangle.js";
|
|---|
| 4 | import compose from "../compose.js";
|
|---|
| 5 | import identity from "../identity.js";
|
|---|
| 6 | import {cos, degrees, radians, sin, sqrt} from "../math.js";
|
|---|
| 7 | import {rotateRadians} from "../rotation.js";
|
|---|
| 8 | import {transformer} from "../transform.js";
|
|---|
| 9 | import {fitExtent, fitSize, fitWidth, fitHeight} from "./fit.js";
|
|---|
| 10 | import resample from "./resample.js";
|
|---|
| 11 |
|
|---|
| 12 | var transformRadians = transformer({
|
|---|
| 13 | point: function(x, y) {
|
|---|
| 14 | this.stream.point(x * radians, y * radians);
|
|---|
| 15 | }
|
|---|
| 16 | });
|
|---|
| 17 |
|
|---|
| 18 | function transformRotate(rotate) {
|
|---|
| 19 | return transformer({
|
|---|
| 20 | point: function(x, y) {
|
|---|
| 21 | var r = rotate(x, y);
|
|---|
| 22 | return this.stream.point(r[0], r[1]);
|
|---|
| 23 | }
|
|---|
| 24 | });
|
|---|
| 25 | }
|
|---|
| 26 |
|
|---|
| 27 | function scaleTranslate(k, dx, dy, sx, sy) {
|
|---|
| 28 | function transform(x, y) {
|
|---|
| 29 | x *= sx; y *= sy;
|
|---|
| 30 | return [dx + k * x, dy - k * y];
|
|---|
| 31 | }
|
|---|
| 32 | transform.invert = function(x, y) {
|
|---|
| 33 | return [(x - dx) / k * sx, (dy - y) / k * sy];
|
|---|
| 34 | };
|
|---|
| 35 | return transform;
|
|---|
| 36 | }
|
|---|
| 37 |
|
|---|
| 38 | function scaleTranslateRotate(k, dx, dy, sx, sy, alpha) {
|
|---|
| 39 | if (!alpha) return scaleTranslate(k, dx, dy, sx, sy);
|
|---|
| 40 | var cosAlpha = cos(alpha),
|
|---|
| 41 | sinAlpha = sin(alpha),
|
|---|
| 42 | a = cosAlpha * k,
|
|---|
| 43 | b = sinAlpha * k,
|
|---|
| 44 | ai = cosAlpha / k,
|
|---|
| 45 | bi = sinAlpha / k,
|
|---|
| 46 | ci = (sinAlpha * dy - cosAlpha * dx) / k,
|
|---|
| 47 | fi = (sinAlpha * dx + cosAlpha * dy) / k;
|
|---|
| 48 | function transform(x, y) {
|
|---|
| 49 | x *= sx; y *= sy;
|
|---|
| 50 | return [a * x - b * y + dx, dy - b * x - a * y];
|
|---|
| 51 | }
|
|---|
| 52 | transform.invert = function(x, y) {
|
|---|
| 53 | return [sx * (ai * x - bi * y + ci), sy * (fi - bi * x - ai * y)];
|
|---|
| 54 | };
|
|---|
| 55 | return transform;
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | export default function projection(project) {
|
|---|
| 59 | return projectionMutator(function() { return project; })();
|
|---|
| 60 | }
|
|---|
| 61 |
|
|---|
| 62 | export function projectionMutator(projectAt) {
|
|---|
| 63 | var project,
|
|---|
| 64 | k = 150, // scale
|
|---|
| 65 | x = 480, y = 250, // translate
|
|---|
| 66 | lambda = 0, phi = 0, // center
|
|---|
| 67 | deltaLambda = 0, deltaPhi = 0, deltaGamma = 0, rotate, // pre-rotate
|
|---|
| 68 | alpha = 0, // post-rotate angle
|
|---|
| 69 | sx = 1, // reflectX
|
|---|
| 70 | sy = 1, // reflectX
|
|---|
| 71 | theta = null, preclip = clipAntimeridian, // pre-clip angle
|
|---|
| 72 | x0 = null, y0, x1, y1, postclip = identity, // post-clip extent
|
|---|
| 73 | delta2 = 0.5, // precision
|
|---|
| 74 | projectResample,
|
|---|
| 75 | projectTransform,
|
|---|
| 76 | projectRotateTransform,
|
|---|
| 77 | cache,
|
|---|
| 78 | cacheStream;
|
|---|
| 79 |
|
|---|
| 80 | function projection(point) {
|
|---|
| 81 | return projectRotateTransform(point[0] * radians, point[1] * radians);
|
|---|
| 82 | }
|
|---|
| 83 |
|
|---|
| 84 | function invert(point) {
|
|---|
| 85 | point = projectRotateTransform.invert(point[0], point[1]);
|
|---|
| 86 | return point && [point[0] * degrees, point[1] * degrees];
|
|---|
| 87 | }
|
|---|
| 88 |
|
|---|
| 89 | projection.stream = function(stream) {
|
|---|
| 90 | return cache && cacheStream === stream ? cache : cache = transformRadians(transformRotate(rotate)(preclip(projectResample(postclip(cacheStream = stream)))));
|
|---|
| 91 | };
|
|---|
| 92 |
|
|---|
| 93 | projection.preclip = function(_) {
|
|---|
| 94 | return arguments.length ? (preclip = _, theta = undefined, reset()) : preclip;
|
|---|
| 95 | };
|
|---|
| 96 |
|
|---|
| 97 | projection.postclip = function(_) {
|
|---|
| 98 | return arguments.length ? (postclip = _, x0 = y0 = x1 = y1 = null, reset()) : postclip;
|
|---|
| 99 | };
|
|---|
| 100 |
|
|---|
| 101 | projection.clipAngle = function(_) {
|
|---|
| 102 | return arguments.length ? (preclip = +_ ? clipCircle(theta = _ * radians) : (theta = null, clipAntimeridian), reset()) : theta * degrees;
|
|---|
| 103 | };
|
|---|
| 104 |
|
|---|
| 105 | projection.clipExtent = function(_) {
|
|---|
| 106 | return arguments.length ? (postclip = _ == null ? (x0 = y0 = x1 = y1 = null, identity) : clipRectangle(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]), reset()) : x0 == null ? null : [[x0, y0], [x1, y1]];
|
|---|
| 107 | };
|
|---|
| 108 |
|
|---|
| 109 | projection.scale = function(_) {
|
|---|
| 110 | return arguments.length ? (k = +_, recenter()) : k;
|
|---|
| 111 | };
|
|---|
| 112 |
|
|---|
| 113 | projection.translate = function(_) {
|
|---|
| 114 | return arguments.length ? (x = +_[0], y = +_[1], recenter()) : [x, y];
|
|---|
| 115 | };
|
|---|
| 116 |
|
|---|
| 117 | projection.center = function(_) {
|
|---|
| 118 | return arguments.length ? (lambda = _[0] % 360 * radians, phi = _[1] % 360 * radians, recenter()) : [lambda * degrees, phi * degrees];
|
|---|
| 119 | };
|
|---|
| 120 |
|
|---|
| 121 | projection.rotate = function(_) {
|
|---|
| 122 | return arguments.length ? (deltaLambda = _[0] % 360 * radians, deltaPhi = _[1] % 360 * radians, deltaGamma = _.length > 2 ? _[2] % 360 * radians : 0, recenter()) : [deltaLambda * degrees, deltaPhi * degrees, deltaGamma * degrees];
|
|---|
| 123 | };
|
|---|
| 124 |
|
|---|
| 125 | projection.angle = function(_) {
|
|---|
| 126 | return arguments.length ? (alpha = _ % 360 * radians, recenter()) : alpha * degrees;
|
|---|
| 127 | };
|
|---|
| 128 |
|
|---|
| 129 | projection.reflectX = function(_) {
|
|---|
| 130 | return arguments.length ? (sx = _ ? -1 : 1, recenter()) : sx < 0;
|
|---|
| 131 | };
|
|---|
| 132 |
|
|---|
| 133 | projection.reflectY = function(_) {
|
|---|
| 134 | return arguments.length ? (sy = _ ? -1 : 1, recenter()) : sy < 0;
|
|---|
| 135 | };
|
|---|
| 136 |
|
|---|
| 137 | projection.precision = function(_) {
|
|---|
| 138 | return arguments.length ? (projectResample = resample(projectTransform, delta2 = _ * _), reset()) : sqrt(delta2);
|
|---|
| 139 | };
|
|---|
| 140 |
|
|---|
| 141 | projection.fitExtent = function(extent, object) {
|
|---|
| 142 | return fitExtent(projection, extent, object);
|
|---|
| 143 | };
|
|---|
| 144 |
|
|---|
| 145 | projection.fitSize = function(size, object) {
|
|---|
| 146 | return fitSize(projection, size, object);
|
|---|
| 147 | };
|
|---|
| 148 |
|
|---|
| 149 | projection.fitWidth = function(width, object) {
|
|---|
| 150 | return fitWidth(projection, width, object);
|
|---|
| 151 | };
|
|---|
| 152 |
|
|---|
| 153 | projection.fitHeight = function(height, object) {
|
|---|
| 154 | return fitHeight(projection, height, object);
|
|---|
| 155 | };
|
|---|
| 156 |
|
|---|
| 157 | function recenter() {
|
|---|
| 158 | var center = scaleTranslateRotate(k, 0, 0, sx, sy, alpha).apply(null, project(lambda, phi)),
|
|---|
| 159 | transform = scaleTranslateRotate(k, x - center[0], y - center[1], sx, sy, alpha);
|
|---|
| 160 | rotate = rotateRadians(deltaLambda, deltaPhi, deltaGamma);
|
|---|
| 161 | projectTransform = compose(project, transform);
|
|---|
| 162 | projectRotateTransform = compose(rotate, projectTransform);
|
|---|
| 163 | projectResample = resample(projectTransform, delta2);
|
|---|
| 164 | return reset();
|
|---|
| 165 | }
|
|---|
| 166 |
|
|---|
| 167 | function reset() {
|
|---|
| 168 | cache = cacheStream = null;
|
|---|
| 169 | return projection;
|
|---|
| 170 | }
|
|---|
| 171 |
|
|---|
| 172 | return function() {
|
|---|
| 173 | project = projectAt.apply(this, arguments);
|
|---|
| 174 | projection.invert = project.invert && invert;
|
|---|
| 175 | return recenter();
|
|---|
| 176 | };
|
|---|
| 177 | }
|
|---|