729 строки
19 KiB
JavaScript
729 строки
19 KiB
JavaScript
(function(){d3.geo = {};
|
|
// TODO clip input coordinates on opposite hemisphere
|
|
d3.geo.azimuthal = function() {
|
|
var mode = "orthographic", // or stereographic
|
|
origin,
|
|
scale = 200,
|
|
translate = [480, 250],
|
|
x0,
|
|
y0,
|
|
cy0,
|
|
sy0;
|
|
|
|
function azimuthal(coordinates) {
|
|
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]
|
|
];
|
|
}
|
|
|
|
azimuthal.invert = function(coordinates) {
|
|
var x = (coordinates[0] - translate[0]) / scale,
|
|
y = (coordinates[1] - translate[1]) / scale,
|
|
p = Math.sqrt(x * x + y * y),
|
|
c = mode === "stereographic" ? 2 * Math.atan(p) : Math.asin(p),
|
|
sc = Math.sin(c),
|
|
cc = Math.cos(c);
|
|
return [
|
|
(x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_radians,
|
|
Math.asin(cc * sy0 - (y * sc * cy0) / p) / d3_radians
|
|
];
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
azimuthal.scale = function(x) {
|
|
if (!arguments.length) return scale;
|
|
scale = +x;
|
|
return azimuthal;
|
|
};
|
|
|
|
azimuthal.translate = function(x) {
|
|
if (!arguments.length) return translate;
|
|
translate = [+x[0], +x[1]];
|
|
return azimuthal;
|
|
};
|
|
|
|
return azimuthal.origin([0, 0]);
|
|
};
|
|
// Derived from Tom Carden's Albers implementation for Protovis.
|
|
// http://gist.github.com/476238
|
|
// http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html
|
|
|
|
d3.geo.albers = function() {
|
|
var origin = [-98, 38],
|
|
parallels = [29.5, 45.5],
|
|
scale = 1000,
|
|
translate = [480, 250],
|
|
lng0, // d3_radians * origin[0]
|
|
n,
|
|
C,
|
|
p0;
|
|
|
|
function albers(coordinates) {
|
|
var t = n * (d3_radians * coordinates[0] - lng0),
|
|
p = Math.sqrt(C - 2 * n * Math.sin(d3_radians * coordinates[1])) / n;
|
|
return [
|
|
scale * p * Math.sin(t) + translate[0],
|
|
scale * (p * Math.cos(t) - p0) + translate[1]
|
|
];
|
|
}
|
|
|
|
albers.invert = function(coordinates) {
|
|
var x = (coordinates[0] - translate[0]) / scale,
|
|
y = (coordinates[1] - translate[1]) / scale,
|
|
p0y = p0 + y,
|
|
t = Math.atan2(x, p0y),
|
|
p = Math.sqrt(x * x + p0y * p0y);
|
|
return [
|
|
(lng0 + t / n) / d3_radians,
|
|
Math.asin((C - p * p * n * n) / (2 * n)) / d3_radians
|
|
];
|
|
};
|
|
|
|
function reload() {
|
|
var phi1 = d3_radians * parallels[0],
|
|
phi2 = d3_radians * parallels[1],
|
|
lat0 = d3_radians * origin[1],
|
|
s = Math.sin(phi1),
|
|
c = Math.cos(phi1);
|
|
lng0 = d3_radians * origin[0];
|
|
n = .5 * (s + Math.sin(phi2));
|
|
C = c * c + 2 * n * s;
|
|
p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n;
|
|
return albers;
|
|
}
|
|
|
|
albers.origin = function(x) {
|
|
if (!arguments.length) return origin;
|
|
origin = [+x[0], +x[1]];
|
|
return reload();
|
|
};
|
|
|
|
albers.parallels = function(x) {
|
|
if (!arguments.length) return parallels;
|
|
parallels = [+x[0], +x[1]];
|
|
return reload();
|
|
};
|
|
|
|
albers.scale = function(x) {
|
|
if (!arguments.length) return scale;
|
|
scale = +x;
|
|
return albers;
|
|
};
|
|
|
|
albers.translate = function(x) {
|
|
if (!arguments.length) return translate;
|
|
translate = [+x[0], +x[1]];
|
|
return albers;
|
|
};
|
|
|
|
return reload();
|
|
};
|
|
|
|
// A composite projection for the United States, 960x500. The set of standard
|
|
// parallels for each region comes from USGS, which is published here:
|
|
// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
|
|
// TODO allow the composite projection to be rescaled?
|
|
d3.geo.albersUsa = function() {
|
|
var lower48 = d3.geo.albers();
|
|
|
|
var alaska = d3.geo.albers()
|
|
.origin([-160, 60])
|
|
.parallels([55, 65]);
|
|
|
|
var hawaii = d3.geo.albers()
|
|
.origin([-160, 20])
|
|
.parallels([8, 18]);
|
|
|
|
var puertoRico = d3.geo.albers()
|
|
.origin([-60, 10])
|
|
.parallels([8, 18]);
|
|
|
|
function albersUsa(coordinates) {
|
|
var lon = coordinates[0],
|
|
lat = coordinates[1];
|
|
return (lat > 50 ? alaska
|
|
: lon < -140 ? hawaii
|
|
: lat < 21 ? puertoRico
|
|
: lower48)(coordinates);
|
|
}
|
|
|
|
albersUsa.scale = function(x) {
|
|
if (!arguments.length) return lower48.scale();
|
|
lower48.scale(x);
|
|
alaska.scale(x * .6);
|
|
hawaii.scale(x);
|
|
puertoRico.scale(x * 1.5);
|
|
return albersUsa.translate(lower48.translate());
|
|
};
|
|
|
|
albersUsa.translate = function(x) {
|
|
if (!arguments.length) return lower48.translate();
|
|
var dz = lower48.scale() / 1000,
|
|
dx = x[0],
|
|
dy = x[1];
|
|
lower48.translate(x);
|
|
alaska.translate([dx - 400 * dz, dy + 170 * dz]);
|
|
hawaii.translate([dx - 190 * dz, dy + 200 * dz]);
|
|
puertoRico.translate([dx + 580 * dz, dy + 430 * dz]);
|
|
return albersUsa;
|
|
};
|
|
|
|
return albersUsa.scale(lower48.scale());
|
|
};
|
|
|
|
var d3_radians = Math.PI / 180;
|
|
d3.geo.equirectangular = function() {
|
|
var scale = 500,
|
|
translate = [480, 250];
|
|
|
|
function equirectangular(coordinates) {
|
|
var x = coordinates[0] / 360,
|
|
y = -coordinates[1] / 360;
|
|
return [
|
|
scale * x + translate[0],
|
|
scale * y + translate[1]
|
|
];
|
|
}
|
|
|
|
equirectangular.invert = function(coordinates) {
|
|
var x = (coordinates[0] - translate[0]) / scale,
|
|
y = (coordinates[1] - translate[1]) / scale;
|
|
return [
|
|
360 * x,
|
|
-360 * y
|
|
];
|
|
};
|
|
|
|
equirectangular.scale = function(x) {
|
|
if (!arguments.length) return scale;
|
|
scale = +x;
|
|
return equirectangular;
|
|
};
|
|
|
|
equirectangular.translate = function(x) {
|
|
if (!arguments.length) return translate;
|
|
translate = [+x[0], +x[1]];
|
|
return equirectangular;
|
|
};
|
|
|
|
return equirectangular;
|
|
};
|
|
d3.geo.mercator = function() {
|
|
var scale = 500,
|
|
translate = [480, 250];
|
|
|
|
function mercator(coordinates) {
|
|
var x = coordinates[0] / 360,
|
|
y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_radians / 2)) / d3_radians) / 360;
|
|
return [
|
|
scale * x + translate[0],
|
|
scale * Math.max(-.5, Math.min(.5, y)) + translate[1]
|
|
];
|
|
}
|
|
|
|
mercator.invert = function(coordinates) {
|
|
var x = (coordinates[0] - translate[0]) / scale,
|
|
y = (coordinates[1] - translate[1]) / scale;
|
|
return [
|
|
360 * x,
|
|
2 * Math.atan(Math.exp(-360 * y * d3_radians)) / d3_radians - 90
|
|
];
|
|
};
|
|
|
|
mercator.scale = function(x) {
|
|
if (!arguments.length) return scale;
|
|
scale = +x;
|
|
return mercator;
|
|
};
|
|
|
|
mercator.translate = function(x) {
|
|
if (!arguments.length) return translate;
|
|
translate = [+x[0], +x[1]];
|
|
return mercator;
|
|
};
|
|
|
|
return mercator;
|
|
};
|
|
/**
|
|
* Returns a function that, given a GeoJSON object (e.g., a feature), returns
|
|
* the corresponding SVG path. The function can be customized by overriding the
|
|
* projection. Point features are mapped to circles with a default radius of
|
|
* 4.5px; the radius can be specified either as a constant or a function that
|
|
* is evaluated per object.
|
|
*/
|
|
d3.geo.path = function() {
|
|
var pointRadius = 4.5,
|
|
pointCircle = d3_path_circle(pointRadius),
|
|
projection = d3.geo.albersUsa();
|
|
|
|
function path(d, i) {
|
|
if (typeof pointRadius === "function") {
|
|
pointCircle = d3_path_circle(pointRadius.apply(this, arguments));
|
|
}
|
|
return d3_geo_pathType(pathTypes, d);
|
|
}
|
|
|
|
function project(coordinates) {
|
|
return projection(coordinates).join(",");
|
|
}
|
|
|
|
var pathTypes = {
|
|
|
|
FeatureCollection: function(f) {
|
|
var path = [],
|
|
features = f.features,
|
|
i = -1, // features.index
|
|
n = features.length;
|
|
while (++i < n) path.push(d3_geo_pathType(pathTypes, features[i].geometry));
|
|
return path.join("");
|
|
},
|
|
|
|
Feature: function(f) {
|
|
return d3_geo_pathType(pathTypes, f.geometry);
|
|
},
|
|
|
|
Point: function(o) {
|
|
return "M" + project(o.coordinates) + pointCircle;
|
|
},
|
|
|
|
MultiPoint: function(o) {
|
|
var path = [],
|
|
coordinates = o.coordinates,
|
|
i = -1, // coordinates.index
|
|
n = coordinates.length;
|
|
while (++i < n) path.push("M", project(coordinates[i]), pointCircle);
|
|
return path.join("");
|
|
},
|
|
|
|
LineString: function(o) {
|
|
var path = ["M"],
|
|
coordinates = o.coordinates,
|
|
i = -1, // coordinates.index
|
|
n = coordinates.length;
|
|
while (++i < n) path.push(project(coordinates[i]), "L");
|
|
path.pop();
|
|
return path.join("");
|
|
},
|
|
|
|
MultiLineString: function(o) {
|
|
var path = [],
|
|
coordinates = o.coordinates,
|
|
i = -1, // coordinates.index
|
|
n = coordinates.length,
|
|
subcoordinates, // coordinates[i]
|
|
j, // subcoordinates.index
|
|
m; // subcoordinates.length
|
|
while (++i < n) {
|
|
subcoordinates = coordinates[i];
|
|
j = -1;
|
|
m = subcoordinates.length;
|
|
path.push("M");
|
|
while (++j < m) path.push(project(subcoordinates[j]), "L");
|
|
path.pop();
|
|
}
|
|
return path.join("");
|
|
},
|
|
|
|
Polygon: function(o) {
|
|
var path = [],
|
|
coordinates = o.coordinates,
|
|
i = -1, // coordinates.index
|
|
n = coordinates.length,
|
|
subcoordinates, // coordinates[i]
|
|
j, // subcoordinates.index
|
|
m; // subcoordinates.length
|
|
while (++i < n) {
|
|
subcoordinates = coordinates[i];
|
|
j = -1;
|
|
m = subcoordinates.length;
|
|
path.push("M");
|
|
while (++j < m) path.push(project(subcoordinates[j]), "L");
|
|
path[path.length - 1] = "Z";
|
|
}
|
|
return path.join("");
|
|
},
|
|
|
|
MultiPolygon: function(o) {
|
|
var path = [],
|
|
coordinates = o.coordinates,
|
|
i = -1, // coordinates index
|
|
n = coordinates.length,
|
|
subcoordinates, // coordinates[i]
|
|
j, // subcoordinates index
|
|
m, // subcoordinates.length
|
|
subsubcoordinates, // subcoordinates[j]
|
|
k, // subsubcoordinates index
|
|
p; // subsubcoordinates.length
|
|
while (++i < n) {
|
|
subcoordinates = coordinates[i];
|
|
j = -1;
|
|
m = subcoordinates.length;
|
|
while (++j < m) {
|
|
subsubcoordinates = subcoordinates[j];
|
|
k = -1;
|
|
p = subsubcoordinates.length - 1;
|
|
path.push("M");
|
|
while (++k < p) path.push(project(subsubcoordinates[k]), "L");
|
|
path[path.length - 1] = "Z";
|
|
}
|
|
}
|
|
return path.join("");
|
|
},
|
|
|
|
GeometryCollection: function(o) {
|
|
var path = [],
|
|
geometries = o.geometries,
|
|
i = -1, // geometries index
|
|
n = geometries.length;
|
|
while (++i < n) path.push(d3_geo_pathType(pathTypes, geometries[i]));
|
|
return path.join("");
|
|
}
|
|
|
|
};
|
|
|
|
var areaTypes = {
|
|
|
|
FeatureCollection: function(f) {
|
|
var area = 0,
|
|
features = f.features,
|
|
i = -1, // features.index
|
|
n = features.length;
|
|
while (++i < n) area += d3_geo_pathType(areaTypes, features[i]);
|
|
return area;
|
|
},
|
|
|
|
Feature: function(f) {
|
|
return d3_geo_pathType(areaTypes, f.geometry);
|
|
},
|
|
|
|
Point: d3_geo_pathZero,
|
|
MultiPoint: d3_geo_pathZero,
|
|
LineString: d3_geo_pathZero,
|
|
MultiLineString: d3_geo_pathZero,
|
|
|
|
Polygon: function(o) {
|
|
return polygonArea(o.coordinates);
|
|
},
|
|
|
|
MultiPolygon: function(o) {
|
|
var sum = 0,
|
|
coordinates = o.coordinates,
|
|
i = -1, // coordinates index
|
|
n = coordinates.length;
|
|
while (++i < n) sum += polygonArea(coordinates[i]);
|
|
return sum;
|
|
},
|
|
|
|
GeometryCollection: function(o) {
|
|
var sum = 0,
|
|
geometries = o.geometries,
|
|
i = -1, // geometries index
|
|
n = geometries.length;
|
|
while (++i < n) sum += d3_geo_pathType(areaTypes, geometries[i]);
|
|
return sum;
|
|
}
|
|
|
|
};
|
|
|
|
function polygonArea(coordinates) {
|
|
var sum = area(coordinates[0]), // exterior ring
|
|
i = 0, // coordinates.index
|
|
n = coordinates.length;
|
|
while (++i < n) sum -= area(coordinates[i]); // holes
|
|
return sum;
|
|
}
|
|
|
|
function polygonCentroid(coordinates) {
|
|
var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring
|
|
centroid = polygon.centroid(1),
|
|
x = centroid[0],
|
|
y = centroid[1],
|
|
z = Math.abs(polygon.area()),
|
|
i = 0, // coordinates index
|
|
n = coordinates.length;
|
|
while (++i < n) {
|
|
polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes
|
|
centroid = polygon.centroid(1);
|
|
x -= centroid[0];
|
|
y -= centroid[1];
|
|
z -= Math.abs(polygon.area());
|
|
}
|
|
return [x, y, 6 * z]; // weighted centroid
|
|
}
|
|
|
|
var centroidTypes = {
|
|
|
|
// TODO FeatureCollection
|
|
// TODO Point
|
|
// TODO MultiPoint
|
|
// TODO LineString
|
|
// TODO MultiLineString
|
|
// TODO GeometryCollection
|
|
|
|
Feature: function(f) {
|
|
return d3_geo_pathType(centroidTypes, f.geometry);
|
|
},
|
|
|
|
Polygon: function(o) {
|
|
var centroid = polygonCentroid(o.coordinates);
|
|
return [centroid[0] / centroid[2], centroid[1] / centroid[2]];
|
|
},
|
|
|
|
MultiPolygon: function(o) {
|
|
var area = 0,
|
|
coordinates = o.coordinates,
|
|
centroid,
|
|
x = 0,
|
|
y = 0,
|
|
z = 0,
|
|
i = -1, // coordinates index
|
|
n = coordinates.length;
|
|
while (++i < n) {
|
|
centroid = polygonCentroid(coordinates[i]);
|
|
x += centroid[0];
|
|
y += centroid[1];
|
|
z += centroid[2];
|
|
}
|
|
return [x / z, y / z];
|
|
}
|
|
|
|
};
|
|
|
|
|
|
function area(coordinates) {
|
|
return Math.abs(d3.geom.polygon(coordinates.map(projection)).area());
|
|
}
|
|
|
|
path.projection = function(x) {
|
|
projection = x;
|
|
return path;
|
|
};
|
|
|
|
path.area = function(d) {
|
|
return d3_geo_pathType(areaTypes, d);
|
|
};
|
|
|
|
path.centroid = function(d) {
|
|
return d3_geo_pathType(centroidTypes, d);
|
|
};
|
|
|
|
path.pointRadius = function(x) {
|
|
if (typeof x === "function") pointRadius = x;
|
|
else {
|
|
pointRadius = +x;
|
|
pointCircle = d3_path_circle(pointRadius);
|
|
}
|
|
return path;
|
|
};
|
|
|
|
return path;
|
|
};
|
|
|
|
function d3_path_circle(radius) {
|
|
return "m0," + radius
|
|
+ "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius)
|
|
+ "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius)
|
|
+ "z";
|
|
}
|
|
|
|
function d3_geo_pathZero() {
|
|
return 0;
|
|
}
|
|
|
|
function d3_geo_pathType(types, o) {
|
|
return o && o.type in types ? types[o.type](o) : "";
|
|
}
|
|
/**
|
|
* Given a GeoJSON object, returns the corresponding bounding box. The bounding
|
|
* box is represented by a two-dimensional array: [[left, bottom], [right,
|
|
* top]], where left is the minimum longitude, bottom is the minimum latitude,
|
|
* right is maximum longitude, and top is the maximum latitude.
|
|
*/
|
|
d3.geo.bounds = function(feature) {
|
|
var left = Infinity,
|
|
bottom = Infinity,
|
|
right = -Infinity,
|
|
top = -Infinity;
|
|
d3_geo_bounds(feature, function(x, y) {
|
|
if (x < left) left = x;
|
|
if (x > right) right = x;
|
|
if (y < bottom) bottom = y;
|
|
if (y > top) top = y;
|
|
});
|
|
return [[left, bottom], [right, top]];
|
|
};
|
|
|
|
function d3_geo_bounds(o, f) {
|
|
if (o.type in d3_geo_boundsTypes) d3_geo_boundsTypes[o.type](o, f);
|
|
}
|
|
|
|
var d3_geo_boundsTypes = {
|
|
Feature: d3_geo_boundsFeature,
|
|
FeatureCollection: d3_geo_boundsFeatureCollection,
|
|
LineString: d3_geo_boundsLineString,
|
|
MultiLineString: d3_geo_boundsMultiLineString,
|
|
MultiPoint: d3_geo_boundsLineString,
|
|
MultiPolygon: d3_geo_boundsMultiPolygon,
|
|
Point: d3_geo_boundsPoint,
|
|
Polygon: d3_geo_boundsPolygon
|
|
};
|
|
|
|
function d3_geo_boundsFeature(o, f) {
|
|
d3_geo_bounds(o.geometry, f);
|
|
}
|
|
|
|
function d3_geo_boundsFeatureCollection(o, f) {
|
|
for (var a = o.features, i = 0, n = a.length; i < n; i++) {
|
|
d3_geo_bounds(a[i].geometry, f);
|
|
}
|
|
}
|
|
|
|
function d3_geo_boundsLineString(o, f) {
|
|
for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
|
|
f.apply(null, a[i]);
|
|
}
|
|
}
|
|
|
|
function d3_geo_boundsMultiLineString(o, f) {
|
|
for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
|
|
for (var b = a[i], j = 0, m = b.length; j < m; j++) {
|
|
f.apply(null, b[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function d3_geo_boundsMultiPolygon(o, f) {
|
|
for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
|
|
for (var b = a[i][0], j = 0, m = b.length; j < m; j++) {
|
|
f.apply(null, b[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function d3_geo_boundsPoint(o, f) {
|
|
f.apply(null, o.coordinates);
|
|
}
|
|
|
|
function d3_geo_boundsPolygon(o, f) {
|
|
for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) {
|
|
f.apply(null, a[i]);
|
|
}
|
|
}
|
|
// From http://williams.best.vwh.net/avform.htm#Intermediate
|
|
d3.geo.greatCircle = function() {
|
|
var source = d3_geo_greatCircleSource,
|
|
target = d3_geo_greatCircleTarget,
|
|
n = 100,
|
|
radius = 6371; // Mean radius of Earth, in km.
|
|
// TODO: breakAtDateLine?
|
|
|
|
function greatCircle(d, i) {
|
|
var from = source.call(this, d, i),
|
|
to = target.call(this, d, i),
|
|
x0 = from[0] * d3_radians,
|
|
y0 = from[1] * d3_radians,
|
|
x1 = to[0] * d3_radians,
|
|
y1 = to[1] * d3_radians,
|
|
cx0 = Math.cos(x0), sx0 = Math.sin(x0),
|
|
cy0 = Math.cos(y0), sy0 = Math.sin(y0),
|
|
cx1 = Math.cos(x1), sx1 = Math.sin(x1),
|
|
cy1 = Math.cos(y1), sy1 = Math.sin(y1),
|
|
d = Math.acos(sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)),
|
|
sd = Math.sin(d),
|
|
f = d / (n - 1),
|
|
e = -f,
|
|
path = [],
|
|
i = -1;
|
|
|
|
while (++i < n) {
|
|
e += f;
|
|
var A = Math.sin(d - e) / sd,
|
|
B = Math.sin(e) / sd,
|
|
x = A * cy0 * cx0 + B * cy1 * cx1,
|
|
y = A * cy0 * sx0 + B * cy1 * sx1,
|
|
z = A * sy0 + B * sy1;
|
|
path[i] = [
|
|
Math.atan2(y, x) / d3_radians,
|
|
Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians
|
|
];
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
greatCircle.source = function(x) {
|
|
if (!arguments.length) return source;
|
|
source = x;
|
|
return greatCircle;
|
|
};
|
|
|
|
greatCircle.target = function(x) {
|
|
if (!arguments.length) return target;
|
|
target = x;
|
|
return greatCircle;
|
|
};
|
|
|
|
greatCircle.n = function(x) {
|
|
if (!arguments.length) return n;
|
|
n = +x;
|
|
return greatCircle;
|
|
};
|
|
|
|
greatCircle.radius = function(x) {
|
|
if (!arguments.length) return radius;
|
|
radius = +x;
|
|
return greatCircle;
|
|
};
|
|
|
|
// Haversine formula for great-circle distance.
|
|
greatCircle.distance = function(d, i) {
|
|
var from = source.call(this, d, i),
|
|
to = target.call(this, d, i),
|
|
x0 = from[0] * d3_radians,
|
|
y0 = from[1] * d3_radians,
|
|
x1 = to[0] * d3_radians,
|
|
y1 = to[1] * d3_radians,
|
|
sy = Math.sin((y1 - y0) / 2),
|
|
sx = Math.sin((x1 - x0) / 2),
|
|
a = sy * sy + Math.cos(y0) * Math.cos(y1) * sx * sx;
|
|
|
|
return radius * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
};
|
|
|
|
return greatCircle;
|
|
};
|
|
|
|
function d3_geo_greatCircleSource(d) {
|
|
return d.source;
|
|
}
|
|
|
|
function d3_geo_greatCircleTarget(d) {
|
|
return d.target;
|
|
}
|
|
})();
|