source: Dev/trunk/d3/d3.geo.js @ 76

Last change on this file since 76 was 76, checked in by fpvanagthoven, 14 years ago

d3

File size: 14.7 KB
RevLine 
[76]1(function(){d3.geo = {};
2// TODO clip input coordinates on opposite hemisphere
3d3.geo.azimuthal = function() {
4  var mode = "orthographic", // or stereographic
5      origin,
6      scale = 200,
7      translate = [480, 250],
8      x0,
9      y0,
10      cy0,
11      sy0;
12
13  function azimuthal(coordinates) {
14    var x1 = coordinates[0] * d3_radians - x0,
15        y1 = coordinates[1] * d3_radians,
16        cx1 = Math.cos(x1),
17        sx1 = Math.sin(x1),
18        cy1 = Math.cos(y1),
19        sy1 = Math.sin(y1),
20        k = mode == "stereographic" ? 1 / (1 + sy0 * sy1 + cy0 * cy1 * cx1) : 1,
21        x = k * cy1 * sx1,
22        y = k * (sy0 * cy1 * cx1 - cy0 * sy1);
23    return [
24      scale * x + translate[0],
25      scale * y + translate[1]
26    ];
27  }
28
29  azimuthal.mode = function(x) {
30    if (!arguments.length) return mode;
31    mode = x;
32    return azimuthal;
33  };
34
35  azimuthal.origin = function(x) {
36    if (!arguments.length) return origin;
37    origin = x;
38    x0 = origin[0] * d3_radians;
39    y0 = origin[1] * d3_radians;
40    cy0 = Math.cos(y0);
41    sy0 = Math.sin(y0);
42    return azimuthal;
43  };
44
45  azimuthal.scale = function(x) {
46    if (!arguments.length) return scale;
47    scale = +x;
48    return azimuthal;
49  };
50
51  azimuthal.translate = function(x) {
52    if (!arguments.length) return translate;
53    translate = [+x[0], +x[1]];
54    return azimuthal;
55  };
56
57  return azimuthal.origin([0, 0]);
58};
59// Derived from Tom Carden's Albers implementation for Protovis.
60// http://gist.github.com/476238
61// http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html
62
63d3.geo.albers = function() {
64  var origin = [-98, 38],
65      parallels = [29.5, 45.5],
66      scale = 1000,
67      translate = [480, 250],
68      lng0, // d3_radians * origin[0]
69      n,
70      C,
71      p0;
72
73  function albers(coordinates) {
74    var t = n * (d3_radians * coordinates[0] - lng0),
75        p = Math.sqrt(C - 2 * n * Math.sin(d3_radians * coordinates[1])) / n;
76    return [
77      scale * p * Math.sin(t) + translate[0],
78      scale * (p * Math.cos(t) - p0) + translate[1]
79    ];
80  }
81
82  function reload() {
83    var phi1 = d3_radians * parallels[0],
84        phi2 = d3_radians * parallels[1],
85        lat0 = d3_radians * origin[1],
86        s = Math.sin(phi1),
87        c = Math.cos(phi1);
88    lng0 = d3_radians * origin[0];
89    n = .5 * (s + Math.sin(phi2));
90    C = c * c + 2 * n * s;
91    p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n;
92    return albers;
93  }
94
95  albers.origin = function(x) {
96    if (!arguments.length) return origin;
97    origin = [+x[0], +x[1]];
98    return reload();
99  };
100
101  albers.parallels = function(x) {
102    if (!arguments.length) return parallels;
103    parallels = [+x[0], +x[1]];
104    return reload();
105  };
106
107  albers.scale = function(x) {
108    if (!arguments.length) return scale;
109    scale = +x;
110    return albers;
111  };
112
113  albers.translate = function(x) {
114    if (!arguments.length) return translate;
115    translate = [+x[0], +x[1]];
116    return albers;
117  };
118
119  return reload();
120};
121
122// A composite projection for the United States, 960x500. The set of standard
123// parallels for each region comes from USGS, which is published here:
124// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
125// TODO allow the composite projection to be rescaled?
126d3.geo.albersUsa = function() {
127  var lower48 = d3.geo.albers();
128
129  var alaska = d3.geo.albers()
130      .origin([-160, 60])
131      .parallels([55, 65]);
132
133  var hawaii = d3.geo.albers()
134      .origin([-160, 20])
135      .parallels([8, 18]);
136
137  var puertoRico = d3.geo.albers()
138      .origin([-60, 10])
139      .parallels([8, 18]);
140
141  function albersUsa(coordinates) {
142    var lon = coordinates[0],
143        lat = coordinates[1];
144    return (lat < 25
145        ? (lon < -100 ? hawaii : puertoRico)
146        : (lat > 50 ? alaska : lower48))(coordinates);
147  }
148
149  albersUsa.scale = function(x) {
150    if (!arguments.length) return lower48.scale();
151    lower48.scale(x);
152    alaska.scale(x * .6);
153    hawaii.scale(x);
154    puertoRico.scale(x * 1.5);
155    return albersUsa.translate(lower48.translate());
156  };
157
158  albersUsa.translate = function(x) {
159    if (!arguments.length) return lower48.translate();
160    var dz = lower48.scale() / 1000,
161        dx = x[0],
162        dy = x[1];
163    lower48.translate(x);
164    alaska.translate([dx - 400 * dz, dy + 170 * dz]);
165    hawaii.translate([dx - 190 * dz, dy + 200 * dz]);
166    puertoRico.translate([dx + 580 * dz, dy + 430 * dz]);
167    return albersUsa;
168  };
169
170  return albersUsa.scale(lower48.scale());
171};
172
173var d3_radians = Math.PI / 180;
174d3.geo.mercator = function() {
175  var scale = 500,
176      translate = [480, 250];
177
178  function mercator(coordinates) {
179    var x = (coordinates[0]) / 360,
180        y = (-180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + coordinates[1] * Math.PI / 360))) / 360;
181    return [
182      scale * x + translate[0],
183      scale * Math.max(-.5, Math.min(.5, y)) + translate[1]
184    ];
185  }
186
187  mercator.scale = function(x) {
188    if (!arguments.length) return scale;
189    scale = +x;
190    return mercator;
191  };
192
193  mercator.translate = function(x) {
194    if (!arguments.length) return translate;
195    translate = [+x[0], +x[1]];
196    return mercator;
197  };
198
199  return mercator;
200};
201/**
202 * Returns a function that, given a GeoJSON object (e.g., a feature), returns
203 * the corresponding SVG path. The function can be customized by overriding the
204 * projection. Point features are mapped to circles with a default radius of
205 * 4.5px; the radius can be specified either as a constant or a function that
206 * is evaluated per object.
207 */
208d3.geo.path = function() {
209  var pointRadius = 4.5,
210      pointCircle = d3_path_circle(pointRadius),
211      projection = d3.geo.albersUsa();
212
213  function path(d, i) {
214    if (typeof pointRadius === "function") {
215      pointCircle = d3_path_circle(pointRadius.apply(this, arguments));
216    }
217    return d3_geo_pathType(pathTypes, d);
218  }
219
220  function project(coordinates) {
221    return projection(coordinates).join(",");
222  }
223
224  var pathTypes = {
225
226    FeatureCollection: function(f) {
227      var path = [],
228          features = f.features,
229          i = -1, // features.index
230          n = features.length;
231      while (++i < n) path.push(d3_geo_pathType(pathTypes, features[i].geometry));
232      return path.join("");
233    },
234
235    Feature: function(f) {
236      return d3_geo_pathType(pathTypes, f.geometry);
237    },
238
239    Point: function(o) {
240      return "M" + project(o.coordinates) + pointCircle;
241    },
242
243    MultiPoint: function(o) {
244      var path = [],
245          coordinates = o.coordinates,
246          i = -1, // coordinates.index
247          n = coordinates.length;
248      while (++i < n) path.push("M", project(coordinates[i]), pointCircle);
249      return path.join("");
250    },
251
252    LineString: function(o) {
253      var path = ["M"],
254          coordinates = o.coordinates,
255          i = -1, // coordinates.index
256          n = coordinates.length;
257      while (++i < n) path.push(project(coordinates[i]), "L");
258      path.pop();
259      return path.join("");
260    },
261
262    MultiLineString: function(o) {
263      var path = [],
264          coordinates = o.coordinates,
265          i = -1, // coordinates.index
266          n = coordinates.length,
267          subcoordinates, // coordinates[i]
268          j, // subcoordinates.index
269          m; // subcoordinates.length
270      while (++i < n) {
271        subcoordinates = coordinates[i];
272        j = -1;
273        m = subcoordinates.length;
274        path.push("M");
275        while (++j < m) path.push(project(subcoordinates[j]), "L");
276        path.pop();
277      }
278      return path.join("");
279    },
280
281    Polygon: function(o) {
282      var path = [],
283          coordinates = o.coordinates,
284          i = -1, // coordinates.index
285          n = coordinates.length,
286          subcoordinates, // coordinates[i]
287          j, // subcoordinates.index
288          m; // subcoordinates.length
289      while (++i < n) {
290        subcoordinates = coordinates[i];
291        j = -1;
292        m = subcoordinates.length;
293        path.push("M");
294        while (++j < m) path.push(project(subcoordinates[j]), "L");
295        path[path.length - 1] = "Z";
296      }
297      return path.join("");
298    },
299
300    MultiPolygon: function(o) {
301      var path = [],
302          coordinates = o.coordinates,
303          i = -1, // coordinates index
304          n = coordinates.length,
305          subcoordinates, // coordinates[i]
306          j, // subcoordinates index
307          m, // subcoordinates.length
308          subsubcoordinates, // subcoordinates[j]
309          k, // subsubcoordinates index
310          p; // subsubcoordinates.length
311      while (++i < n) {
312        subcoordinates = coordinates[i];
313        j = -1;
314        m = subcoordinates.length;
315        while (++j < m) {
316          subsubcoordinates = subcoordinates[j];
317          k = -1;
318          p = subsubcoordinates.length - 1;
319          path.push("M");
320          while (++k < p) path.push(project(subsubcoordinates[k]), "L");
321          path[path.length - 1] = "Z";
322        }
323      }
324      return path.join("");
325    },
326
327    GeometryCollection: function(o) {
328      var path = [],
329          geometries = o.geometries,
330          i = -1, // geometries index
331          n = geometries.length;
332      while (++i < n) path.push(d3_geo_pathType(pathTypes, geometries[i]));
333      return path.join("");
334    }
335
336  };
337
338  var areaTypes = {
339
340    FeatureCollection: function(f) {
341      var area = 0,
342          features = f.features,
343          i = -1, // features.index
344          n = features.length;
345      while (++i < n) area += d3_geo_pathType(areaTypes, features[i]);
346      return area;
347    },
348
349    Feature: function(f) {
350      return d3_geo_pathType(areaTypes, f.geometry);
351    },
352
353    Point: d3_geo_pathZero,
354    MultiPoint: d3_geo_pathZero,
355    LineString: d3_geo_pathZero,
356    MultiLineString: d3_geo_pathZero,
357
358    Polygon: function(o) {
359      return polygonArea(o.coordinates);
360    },
361
362    MultiPolygon: function(o) {
363      var sum = 0,
364          coordinates = o.coordinates,
365          i = -1, // coordinates index
366          n = coordinates.length;
367      while (++i < n) sum += polygonArea(coordinates[i]);
368      return sum;
369    },
370
371    GeometryCollection: function(o) {
372      var sum = 0,
373          geometries = o.geometries,
374          i = -1, // geometries index
375          n = geometries.length;
376      while (++i < n) sum += d3_geo_pathType(areaTypes, geometries[i]);
377      return sum;
378    }
379
380  };
381
382  function polygonArea(coordinates) {
383    var sum = area(coordinates[0]), // exterior ring
384        i = 0, // coordinates.index
385        n = coordinates.length;
386    while (++i < n) sum -= area(coordinates[i]); // holes
387    return sum;
388  }
389
390  function polygonCentroid(coordinates) {
391    var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring
392        centroid = polygon.centroid(1),
393        x = centroid[0],
394        y = centroid[1],
395        z = Math.abs(polygon.area()),
396        i = 0, // coordinates index
397        n = coordinates.length;
398    while (++i < n) {
399      polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes
400      centroid = polygon.centroid(1);
401      x -= centroid[0];
402      y -= centroid[1];
403      z -= Math.abs(polygon.area());
404    }
405    return [x, y, 6 * z]; // weighted centroid
406  }
407
408  var centroidTypes = {
409
410    // TODO FeatureCollection
411    // TODO Point
412    // TODO MultiPoint
413    // TODO LineString
414    // TODO MultiLineString
415    // TODO GeometryCollection
416
417    Feature: function(f) {
418      return d3_geo_pathType(centroidTypes, f.geometry);
419    },
420
421    Polygon: function(o) {
422      var centroid = polygonCentroid(o.coordinates);
423      return [centroid[0] / centroid[2], centroid[1] / centroid[2]];
424    },
425
426    MultiPolygon: function(o) {
427      var area = 0,
428          coordinates = o.coordinates,
429          centroid,
430          x = 0,
431          y = 0,
432          z = 0,
433          i = -1, // coordinates index
434          n = coordinates.length;
435      while (++i < n) {
436        centroid = polygonCentroid(coordinates[i]);
437        x += centroid[0];
438        y += centroid[1];
439        z += centroid[2];
440      }
441      return [x / z, y / z];
442    }
443
444  };
445
446
447  function area(coordinates) {
448    return Math.abs(d3.geom.polygon(coordinates.map(projection)).area());
449  }
450
451  path.projection = function(x) {
452    projection = x;
453    return path;
454  };
455
456  path.area = function(d) {
457    return d3_geo_pathType(areaTypes, d);
458  };
459
460  path.centroid = function(d) {
461    return d3_geo_pathType(centroidTypes, d);
462  };
463
464  path.pointRadius = function(x) {
465    if (typeof x === "function") pointRadius = x;
466    else {
467      pointRadius = +x;
468      pointCircle = d3_path_circle(pointRadius);
469    }
470    return path;
471  };
472
473  return path;
474};
475
476function d3_path_circle(radius) {
477  return "m0," + radius
478      + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius)
479      + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius)
480      + "z";
481}
482
483function d3_geo_pathZero() {
484  return 0;
485}
486
487function d3_geo_pathType(types, o) {
488  return o && o.type in types ? types[o.type](o) : "";
489}
490/**
491 * Given a GeoJSON object, returns the corresponding bounding box. The bounding
492 * box is represented by a two-dimensional array: [[left, bottom], [right,
493 * top]], where left is the minimum longitude, bottom is the minimum latitude,
494 * right is maximum longitude, and top is the maximum latitude.
495 */
496d3.geo.bounds = function(feature) {
497  var left = Infinity,
498      bottom = Infinity,
499      right = -Infinity,
500      top = -Infinity;
501  d3_geo_bounds(feature, function(x, y) {
502    if (x < left) left = x;
503    if (x > right) right = x;
504    if (y < bottom) bottom = y;
505    if (y > top) top = y;
506  });
507  return [[left, bottom], [right, top]];
508};
509
510function d3_geo_bounds(o, f) {
511  if (o.type in d3_geo_boundsTypes) d3_geo_boundsTypes[o.type](o, f);
512}
513
514var d3_geo_boundsTypes = {
515  Feature: d3_geo_boundsFeature,
516  FeatureCollection: d3_geo_boundsFeatureCollection,
517  LineString: d3_geo_boundsLineString,
518  MultiLineString: d3_geo_boundsMultiLineString,
519  MultiPoint: d3_geo_boundsLineString,
520  MultiPolygon: d3_geo_boundsMultiPolygon,
521  Point: d3_geo_boundsPoint,
522  Polygon: d3_geo_boundsPolygon
523};
524
525function d3_geo_boundsFeature(o, f) {
526  d3_geo_bounds(o.geometry, f);
527}
528
529function d3_geo_boundsFeatureCollection(o, f) {
530  for (var a = o.features, i = 0, n = a.length; i < n; i++) {
531    d3_geo_bounds(a[i].geometry, f);
532  }
533}
534
535function d3_geo_boundsLineString(o, f) {
536  for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
537    f.apply(null, a[i]);
538  }
539}
540
541function d3_geo_boundsMultiLineString(o, f) {
542  for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
543    for (var b = a[i], j = 0, m = b.length; j < m; j++) {
544      f.apply(null, b[j]);
545    }
546  }
547}
548
549function d3_geo_boundsMultiPolygon(o, f) {
550  for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
551    for (var b = a[i][0], j = 0, m = b.length; j < m; j++) {
552      f.apply(null, b[j]);
553    }
554  }
555}
556
557function d3_geo_boundsPoint(o, f) {
558  f.apply(null, o.coordinates);
559}
560
561function d3_geo_boundsPolygon(o, f) {
562  for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) {
563    f.apply(null, a[i]);
564  }
565}
566})();
Note: See TracBrowser for help on using the repository browser.