d3/d3.geo.js

364 строки
9.2 KiB
JavaScript

d3.geo = {};
// 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]
];
}
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])
.scale([600])
.translate([80, 420]);
var hawaii = d3.geo.albers()
.origin([-160, 20])
.parallels([8, 18])
.translate([290, 450]);
var puertoRico = d3.geo.albers()
.origin([-60, 10])
.parallels([8, 18])
.scale([1500])
.translate([1060, 680]);
return function(coordinates) {
var lon = coordinates[0],
lat = coordinates[1];
return (lat < 25
? (lon < -100 ? hawaii : puertoRico)
: (lat > 50 ? alaska : lower48))(coordinates);
};
};
var d3_radians = Math.PI / 180;
d3.geo.mercator = function() {
var scale = 500,
translate = [480, 250];
function mercator(coordinates) {
var x = (coordinates[0]) / 360,
y = (-180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + coordinates[1] * Math.PI / 360))) / 360;
return [
scale * x + translate[0],
scale * Math.max(-.5, Math.min(.5, y)) + translate[1]
];
}
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 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
* feature.
*/
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 type(featurePaths, d);
}
function project(coordinates) {
return projection(coordinates).join(",");
}
function type(types, o) {
return o && o.type in types
? types[o.type](o)
: "";
}
var featurePaths = {
FeatureCollection: function(f) {
var path = [],
features = f.features,
i = -1, // features.index
n = features.length;
while (++i < n) path.push(type(featurePaths, features[i]));
return path.join("");
},
Feature: function(f) {
return type(geometryPaths, f.geometry);
}
};
var geometryPaths = {
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(type(geometryPaths, geometries[i]));
return path.join("");
}
};
var featureAreas = {
FeatureCollection: function(f) {
var area = 0,
features = f.features,
i = -1, // features.index
n = features.length;
while (++i < n) area += type(featureAreas, features[i]);
return area;
},
Feature: function(f) {
return type(geometryAreas, f.geometry);
}
};
var geometryAreas = {
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 += type(geometryAreas, geometries[i]);
return sum;
}
};
function polygonArea(coordinates) {
var sum = area(coordinates[0]),
i = 0, // coordinates.index
n = coordinates.length;
while (++i < n) sum -= area(coordinates[i]);
return sum;
}
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 type(featureAreas, 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;
}