alcatel/static/libraries/d3/d3.geo.js
changeset 27 8ca7f2cea729
equal deleted inserted replaced
26:94f586daa623 27:8ca7f2cea729
       
     1 (function(){d3.geo = {};
       
     2 
       
     3 var d3_geo_radians = Math.PI / 180;
       
     4 // TODO clip input coordinates on opposite hemisphere
       
     5 d3.geo.azimuthal = function() {
       
     6   var mode = "orthographic", // or stereographic, gnomonic, equidistant or equalarea
       
     7       origin,
       
     8       scale = 200,
       
     9       translate = [480, 250],
       
    10       x0,
       
    11       y0,
       
    12       cy0,
       
    13       sy0;
       
    14 
       
    15   function azimuthal(coordinates) {
       
    16     var x1 = coordinates[0] * d3_geo_radians - x0,
       
    17         y1 = coordinates[1] * d3_geo_radians,
       
    18         cx1 = Math.cos(x1),
       
    19         sx1 = Math.sin(x1),
       
    20         cy1 = Math.cos(y1),
       
    21         sy1 = Math.sin(y1),
       
    22         cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null,
       
    23         c,
       
    24         k = mode === "stereographic" ? 1 / (1 + cc)
       
    25           : mode === "gnomonic" ? 1 / cc
       
    26           : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0)
       
    27           : mode === "equalarea" ? Math.sqrt(2 / (1 + cc))
       
    28           : 1,
       
    29         x = k * cy1 * sx1,
       
    30         y = k * (sy0 * cy1 * cx1 - cy0 * sy1);
       
    31     return [
       
    32       scale * x + translate[0],
       
    33       scale * y + translate[1]
       
    34     ];
       
    35   }
       
    36 
       
    37   azimuthal.invert = function(coordinates) {
       
    38     var x = (coordinates[0] - translate[0]) / scale,
       
    39         y = (coordinates[1] - translate[1]) / scale,
       
    40         p = Math.sqrt(x * x + y * y),
       
    41         c = mode === "stereographic" ? 2 * Math.atan(p)
       
    42           : mode === "gnomonic" ? Math.atan(p)
       
    43           : mode === "equidistant" ? p
       
    44           : mode === "equalarea" ? 2 * Math.asin(.5 * p)
       
    45           : Math.asin(p),
       
    46         sc = Math.sin(c),
       
    47         cc = Math.cos(c);
       
    48     return [
       
    49       (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians,
       
    50       Math.asin(cc * sy0 - (p ? (y * sc * cy0) / p : 0)) / d3_geo_radians
       
    51     ];
       
    52   };
       
    53 
       
    54   azimuthal.mode = function(x) {
       
    55     if (!arguments.length) return mode;
       
    56     mode = x + "";
       
    57     return azimuthal;
       
    58   };
       
    59 
       
    60   azimuthal.origin = function(x) {
       
    61     if (!arguments.length) return origin;
       
    62     origin = x;
       
    63     x0 = origin[0] * d3_geo_radians;
       
    64     y0 = origin[1] * d3_geo_radians;
       
    65     cy0 = Math.cos(y0);
       
    66     sy0 = Math.sin(y0);
       
    67     return azimuthal;
       
    68   };
       
    69 
       
    70   azimuthal.scale = function(x) {
       
    71     if (!arguments.length) return scale;
       
    72     scale = +x;
       
    73     return azimuthal;
       
    74   };
       
    75 
       
    76   azimuthal.translate = function(x) {
       
    77     if (!arguments.length) return translate;
       
    78     translate = [+x[0], +x[1]];
       
    79     return azimuthal;
       
    80   };
       
    81 
       
    82   return azimuthal.origin([0, 0]);
       
    83 };
       
    84 // Derived from Tom Carden's Albers implementation for Protovis.
       
    85 // http://gist.github.com/476238
       
    86 // http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html
       
    87 
       
    88 d3.geo.albers = function() {
       
    89   var origin = [-98, 38],
       
    90       parallels = [29.5, 45.5],
       
    91       scale = 1000,
       
    92       translate = [480, 250],
       
    93       lng0, // d3_geo_radians * origin[0]
       
    94       n,
       
    95       C,
       
    96       p0;
       
    97 
       
    98   function albers(coordinates) {
       
    99     var t = n * (d3_geo_radians * coordinates[0] - lng0),
       
   100         p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n;
       
   101     return [
       
   102       scale * p * Math.sin(t) + translate[0],
       
   103       scale * (p * Math.cos(t) - p0) + translate[1]
       
   104     ];
       
   105   }
       
   106 
       
   107   albers.invert = function(coordinates) {
       
   108     var x = (coordinates[0] - translate[0]) / scale,
       
   109         y = (coordinates[1] - translate[1]) / scale,
       
   110         p0y = p0 + y,
       
   111         t = Math.atan2(x, p0y),
       
   112         p = Math.sqrt(x * x + p0y * p0y);
       
   113     return [
       
   114       (lng0 + t / n) / d3_geo_radians,
       
   115       Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians
       
   116     ];
       
   117   };
       
   118 
       
   119   function reload() {
       
   120     var phi1 = d3_geo_radians * parallels[0],
       
   121         phi2 = d3_geo_radians * parallels[1],
       
   122         lat0 = d3_geo_radians * origin[1],
       
   123         s = Math.sin(phi1),
       
   124         c = Math.cos(phi1);
       
   125     lng0 = d3_geo_radians * origin[0];
       
   126     n = .5 * (s + Math.sin(phi2));
       
   127     C = c * c + 2 * n * s;
       
   128     p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n;
       
   129     return albers;
       
   130   }
       
   131 
       
   132   albers.origin = function(x) {
       
   133     if (!arguments.length) return origin;
       
   134     origin = [+x[0], +x[1]];
       
   135     return reload();
       
   136   };
       
   137 
       
   138   albers.parallels = function(x) {
       
   139     if (!arguments.length) return parallels;
       
   140     parallels = [+x[0], +x[1]];
       
   141     return reload();
       
   142   };
       
   143 
       
   144   albers.scale = function(x) {
       
   145     if (!arguments.length) return scale;
       
   146     scale = +x;
       
   147     return albers;
       
   148   };
       
   149 
       
   150   albers.translate = function(x) {
       
   151     if (!arguments.length) return translate;
       
   152     translate = [+x[0], +x[1]];
       
   153     return albers;
       
   154   };
       
   155 
       
   156   return reload();
       
   157 };
       
   158 
       
   159 // A composite projection for the United States, 960x500. The set of standard
       
   160 // parallels for each region comes from USGS, which is published here:
       
   161 // http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
       
   162 // TODO allow the composite projection to be rescaled?
       
   163 d3.geo.albersUsa = function() {
       
   164   var lower48 = d3.geo.albers();
       
   165 
       
   166   var alaska = d3.geo.albers()
       
   167       .origin([-160, 60])
       
   168       .parallels([55, 65]);
       
   169 
       
   170   var hawaii = d3.geo.albers()
       
   171       .origin([-160, 20])
       
   172       .parallels([8, 18]);
       
   173 
       
   174   var puertoRico = d3.geo.albers()
       
   175       .origin([-60, 10])
       
   176       .parallels([8, 18]);
       
   177 
       
   178   function albersUsa(coordinates) {
       
   179     var lon = coordinates[0],
       
   180         lat = coordinates[1];
       
   181     return (lat > 50 ? alaska
       
   182         : lon < -140 ? hawaii
       
   183         : lat < 21 ? puertoRico
       
   184         : lower48)(coordinates);
       
   185   }
       
   186 
       
   187   albersUsa.scale = function(x) {
       
   188     if (!arguments.length) return lower48.scale();
       
   189     lower48.scale(x);
       
   190     alaska.scale(x * .6);
       
   191     hawaii.scale(x);
       
   192     puertoRico.scale(x * 1.5);
       
   193     return albersUsa.translate(lower48.translate());
       
   194   };
       
   195 
       
   196   albersUsa.translate = function(x) {
       
   197     if (!arguments.length) return lower48.translate();
       
   198     var dz = lower48.scale() / 1000,
       
   199         dx = x[0],
       
   200         dy = x[1];
       
   201     lower48.translate(x);
       
   202     alaska.translate([dx - 400 * dz, dy + 170 * dz]);
       
   203     hawaii.translate([dx - 190 * dz, dy + 200 * dz]);
       
   204     puertoRico.translate([dx + 580 * dz, dy + 430 * dz]);
       
   205     return albersUsa;
       
   206   };
       
   207 
       
   208   return albersUsa.scale(lower48.scale());
       
   209 };
       
   210 d3.geo.bonne = function() {
       
   211   var scale = 200,
       
   212       translate = [480, 250],
       
   213       x0, // origin longitude in radians
       
   214       y0, // origin latitude in radians
       
   215       y1, // parallel latitude in radians
       
   216       c1; // cot(y1)
       
   217 
       
   218   function bonne(coordinates) {
       
   219     var x = coordinates[0] * d3_geo_radians - x0,
       
   220         y = coordinates[1] * d3_geo_radians - y0;
       
   221     if (y1) {
       
   222       var p = c1 + y1 - y, E = x * Math.cos(y) / p;
       
   223       x = p * Math.sin(E);
       
   224       y = p * Math.cos(E) - c1;
       
   225     } else {
       
   226       x *= Math.cos(y);
       
   227       y *= -1;
       
   228     }
       
   229     return [
       
   230       scale * x + translate[0],
       
   231       scale * y + translate[1]
       
   232     ];
       
   233   }
       
   234 
       
   235   bonne.invert = function(coordinates) {
       
   236     var x = (coordinates[0] - translate[0]) / scale,
       
   237         y = (coordinates[1] - translate[1]) / scale;
       
   238     if (y1) {
       
   239       var c = c1 + y, p = Math.sqrt(x * x + c * c);
       
   240       y = c1 + y1 - p;
       
   241       x = x0 + p * Math.atan2(x, c) / Math.cos(y);
       
   242     } else {
       
   243       y *= -1;
       
   244       x /= Math.cos(y);
       
   245     }
       
   246     return [
       
   247       x / d3_geo_radians,
       
   248       y / d3_geo_radians
       
   249     ];
       
   250   };
       
   251 
       
   252   // 90° for Werner, 0° for Sinusoidal
       
   253   bonne.parallel = function(x) {
       
   254     if (!arguments.length) return y1 / d3_geo_radians;
       
   255     c1 = 1 / Math.tan(y1 = x * d3_geo_radians);
       
   256     return bonne;
       
   257   };
       
   258 
       
   259   bonne.origin = function(x) {
       
   260     if (!arguments.length) return [x0 / d3_geo_radians, y0 / d3_geo_radians];
       
   261     x0 = x[0] * d3_geo_radians;
       
   262     y0 = x[1] * d3_geo_radians;
       
   263     return bonne;
       
   264   };
       
   265 
       
   266   bonne.scale = function(x) {
       
   267     if (!arguments.length) return scale;
       
   268     scale = +x;
       
   269     return bonne;
       
   270   };
       
   271 
       
   272   bonne.translate = function(x) {
       
   273     if (!arguments.length) return translate;
       
   274     translate = [+x[0], +x[1]];
       
   275     return bonne;
       
   276   };
       
   277 
       
   278   return bonne.origin([0, 0]).parallel(45);
       
   279 };
       
   280 d3.geo.equirectangular = function() {
       
   281   var scale = 500,
       
   282       translate = [480, 250];
       
   283 
       
   284   function equirectangular(coordinates) {
       
   285     var x = coordinates[0] / 360,
       
   286         y = -coordinates[1] / 360;
       
   287     return [
       
   288       scale * x + translate[0],
       
   289       scale * y + translate[1]
       
   290     ];
       
   291   }
       
   292 
       
   293   equirectangular.invert = function(coordinates) {
       
   294     var x = (coordinates[0] - translate[0]) / scale,
       
   295         y = (coordinates[1] - translate[1]) / scale;
       
   296     return [
       
   297       360 * x,
       
   298       -360 * y
       
   299     ];
       
   300   };
       
   301 
       
   302   equirectangular.scale = function(x) {
       
   303     if (!arguments.length) return scale;
       
   304     scale = +x;
       
   305     return equirectangular;
       
   306   };
       
   307 
       
   308   equirectangular.translate = function(x) {
       
   309     if (!arguments.length) return translate;
       
   310     translate = [+x[0], +x[1]];
       
   311     return equirectangular;
       
   312   };
       
   313 
       
   314   return equirectangular;
       
   315 };
       
   316 d3.geo.mercator = function() {
       
   317   var scale = 500,
       
   318       translate = [480, 250];
       
   319 
       
   320   function mercator(coordinates) {
       
   321     var x = coordinates[0] / 360,
       
   322         y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360;
       
   323     return [
       
   324       scale * x + translate[0],
       
   325       scale * Math.max(-.5, Math.min(.5, y)) + translate[1]
       
   326     ];
       
   327   }
       
   328 
       
   329   mercator.invert = function(coordinates) {
       
   330     var x = (coordinates[0] - translate[0]) / scale,
       
   331         y = (coordinates[1] - translate[1]) / scale;
       
   332     return [
       
   333       360 * x,
       
   334       2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90
       
   335     ];
       
   336   };
       
   337 
       
   338   mercator.scale = function(x) {
       
   339     if (!arguments.length) return scale;
       
   340     scale = +x;
       
   341     return mercator;
       
   342   };
       
   343 
       
   344   mercator.translate = function(x) {
       
   345     if (!arguments.length) return translate;
       
   346     translate = [+x[0], +x[1]];
       
   347     return mercator;
       
   348   };
       
   349 
       
   350   return mercator;
       
   351 };
       
   352 function d3_geo_type(types, defaultValue) {
       
   353   return function(object) {
       
   354     return object && object.type in types ? types[object.type](object) : defaultValue;
       
   355   };
       
   356 }
       
   357 /**
       
   358  * Returns a function that, given a GeoJSON object (e.g., a feature), returns
       
   359  * the corresponding SVG path. The function can be customized by overriding the
       
   360  * projection. Point features are mapped to circles with a default radius of
       
   361  * 4.5px; the radius can be specified either as a constant or a function that
       
   362  * is evaluated per object.
       
   363  */
       
   364 d3.geo.path = function() {
       
   365   var pointRadius = 4.5,
       
   366       pointCircle = d3_path_circle(pointRadius),
       
   367       projection = d3.geo.albersUsa();
       
   368 
       
   369   function path(d, i) {
       
   370     if (typeof pointRadius === "function") {
       
   371       pointCircle = d3_path_circle(pointRadius.apply(this, arguments));
       
   372     }
       
   373     return pathType(d) || null;
       
   374   }
       
   375 
       
   376   function project(coordinates) {
       
   377     return projection(coordinates).join(",");
       
   378   }
       
   379 
       
   380   var pathType = d3_geo_type({
       
   381 
       
   382     FeatureCollection: function(o) {
       
   383       var path = [],
       
   384           features = o.features,
       
   385           i = -1, // features.index
       
   386           n = features.length;
       
   387       while (++i < n) path.push(pathType(features[i].geometry));
       
   388       return path.join("");
       
   389     },
       
   390 
       
   391     Feature: function(o) {
       
   392       return pathType(o.geometry);
       
   393     },
       
   394 
       
   395     Point: function(o) {
       
   396       return "M" + project(o.coordinates) + pointCircle;
       
   397     },
       
   398 
       
   399     MultiPoint: function(o) {
       
   400       var path = [],
       
   401           coordinates = o.coordinates,
       
   402           i = -1, // coordinates.index
       
   403           n = coordinates.length;
       
   404       while (++i < n) path.push("M", project(coordinates[i]), pointCircle);
       
   405       return path.join("");
       
   406     },
       
   407 
       
   408     LineString: function(o) {
       
   409       var path = ["M"],
       
   410           coordinates = o.coordinates,
       
   411           i = -1, // coordinates.index
       
   412           n = coordinates.length;
       
   413       while (++i < n) path.push(project(coordinates[i]), "L");
       
   414       path.pop();
       
   415       return path.join("");
       
   416     },
       
   417 
       
   418     MultiLineString: function(o) {
       
   419       var path = [],
       
   420           coordinates = o.coordinates,
       
   421           i = -1, // coordinates.index
       
   422           n = coordinates.length,
       
   423           subcoordinates, // coordinates[i]
       
   424           j, // subcoordinates.index
       
   425           m; // subcoordinates.length
       
   426       while (++i < n) {
       
   427         subcoordinates = coordinates[i];
       
   428         j = -1;
       
   429         m = subcoordinates.length;
       
   430         path.push("M");
       
   431         while (++j < m) path.push(project(subcoordinates[j]), "L");
       
   432         path.pop();
       
   433       }
       
   434       return path.join("");
       
   435     },
       
   436 
       
   437     Polygon: function(o) {
       
   438       var path = [],
       
   439           coordinates = o.coordinates,
       
   440           i = -1, // coordinates.index
       
   441           n = coordinates.length,
       
   442           subcoordinates, // coordinates[i]
       
   443           j, // subcoordinates.index
       
   444           m; // subcoordinates.length
       
   445       while (++i < n) {
       
   446         subcoordinates = coordinates[i];
       
   447         j = -1;
       
   448         if ((m = subcoordinates.length - 1) > 0) {
       
   449           path.push("M");
       
   450           while (++j < m) path.push(project(subcoordinates[j]), "L");
       
   451           path[path.length - 1] = "Z";
       
   452         }
       
   453       }
       
   454       return path.join("");
       
   455     },
       
   456 
       
   457     MultiPolygon: function(o) {
       
   458       var path = [],
       
   459           coordinates = o.coordinates,
       
   460           i = -1, // coordinates index
       
   461           n = coordinates.length,
       
   462           subcoordinates, // coordinates[i]
       
   463           j, // subcoordinates index
       
   464           m, // subcoordinates.length
       
   465           subsubcoordinates, // subcoordinates[j]
       
   466           k, // subsubcoordinates index
       
   467           p; // subsubcoordinates.length
       
   468       while (++i < n) {
       
   469         subcoordinates = coordinates[i];
       
   470         j = -1;
       
   471         m = subcoordinates.length;
       
   472         while (++j < m) {
       
   473           subsubcoordinates = subcoordinates[j];
       
   474           k = -1;
       
   475           if ((p = subsubcoordinates.length - 1) > 0) {
       
   476             path.push("M");
       
   477             while (++k < p) path.push(project(subsubcoordinates[k]), "L");
       
   478             path[path.length - 1] = "Z";
       
   479           }
       
   480         }
       
   481       }
       
   482       return path.join("");
       
   483     },
       
   484 
       
   485     GeometryCollection: function(o) {
       
   486       var path = [],
       
   487           geometries = o.geometries,
       
   488           i = -1, // geometries index
       
   489           n = geometries.length;
       
   490       while (++i < n) path.push(pathType(geometries[i]));
       
   491       return path.join("");
       
   492     }
       
   493 
       
   494   });
       
   495 
       
   496   var areaType = path.area = d3_geo_type({
       
   497 
       
   498     FeatureCollection: function(o) {
       
   499       var area = 0,
       
   500           features = o.features,
       
   501           i = -1, // features.index
       
   502           n = features.length;
       
   503       while (++i < n) area += areaType(features[i]);
       
   504       return area;
       
   505     },
       
   506 
       
   507     Feature: function(o) {
       
   508       return areaType(o.geometry);
       
   509     },
       
   510 
       
   511     Polygon: function(o) {
       
   512       return polygonArea(o.coordinates);
       
   513     },
       
   514 
       
   515     MultiPolygon: function(o) {
       
   516       var sum = 0,
       
   517           coordinates = o.coordinates,
       
   518           i = -1, // coordinates index
       
   519           n = coordinates.length;
       
   520       while (++i < n) sum += polygonArea(coordinates[i]);
       
   521       return sum;
       
   522     },
       
   523 
       
   524     GeometryCollection: function(o) {
       
   525       var sum = 0,
       
   526           geometries = o.geometries,
       
   527           i = -1, // geometries index
       
   528           n = geometries.length;
       
   529       while (++i < n) sum += areaType(geometries[i]);
       
   530       return sum;
       
   531     }
       
   532 
       
   533   }, 0);
       
   534 
       
   535   function polygonArea(coordinates) {
       
   536     var sum = area(coordinates[0]), // exterior ring
       
   537         i = 0, // coordinates.index
       
   538         n = coordinates.length;
       
   539     while (++i < n) sum -= area(coordinates[i]); // holes
       
   540     return sum;
       
   541   }
       
   542 
       
   543   function polygonCentroid(coordinates) {
       
   544     var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring
       
   545         area = polygon.area(),
       
   546         centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1),
       
   547         x = centroid[0],
       
   548         y = centroid[1],
       
   549         z = area,
       
   550         i = 0, // coordinates index
       
   551         n = coordinates.length;
       
   552     while (++i < n) {
       
   553       polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes
       
   554       area = polygon.area();
       
   555       centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1);
       
   556       x -= centroid[0];
       
   557       y -= centroid[1];
       
   558       z -= area;
       
   559     }
       
   560     return [x, y, 6 * z]; // weighted centroid
       
   561   }
       
   562 
       
   563   var centroidType = path.centroid = d3_geo_type({
       
   564 
       
   565     // TODO FeatureCollection
       
   566     // TODO Point
       
   567     // TODO MultiPoint
       
   568     // TODO LineString
       
   569     // TODO MultiLineString
       
   570     // TODO GeometryCollection
       
   571 
       
   572     Feature: function(o) {
       
   573       return centroidType(o.geometry);
       
   574     },
       
   575 
       
   576     Polygon: function(o) {
       
   577       var centroid = polygonCentroid(o.coordinates);
       
   578       return [centroid[0] / centroid[2], centroid[1] / centroid[2]];
       
   579     },
       
   580 
       
   581     MultiPolygon: function(o) {
       
   582       var area = 0,
       
   583           coordinates = o.coordinates,
       
   584           centroid,
       
   585           x = 0,
       
   586           y = 0,
       
   587           z = 0,
       
   588           i = -1, // coordinates index
       
   589           n = coordinates.length;
       
   590       while (++i < n) {
       
   591         centroid = polygonCentroid(coordinates[i]);
       
   592         x += centroid[0];
       
   593         y += centroid[1];
       
   594         z += centroid[2];
       
   595       }
       
   596       return [x / z, y / z];
       
   597     }
       
   598 
       
   599   });
       
   600 
       
   601   function area(coordinates) {
       
   602     return Math.abs(d3.geom.polygon(coordinates.map(projection)).area());
       
   603   }
       
   604 
       
   605   path.projection = function(x) {
       
   606     projection = x;
       
   607     return path;
       
   608   };
       
   609 
       
   610   path.pointRadius = function(x) {
       
   611     if (typeof x === "function") pointRadius = x;
       
   612     else {
       
   613       pointRadius = +x;
       
   614       pointCircle = d3_path_circle(pointRadius);
       
   615     }
       
   616     return path;
       
   617   };
       
   618 
       
   619   return path;
       
   620 };
       
   621 
       
   622 function d3_path_circle(radius) {
       
   623   return "m0," + radius
       
   624       + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius)
       
   625       + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius)
       
   626       + "z";
       
   627 }
       
   628 /**
       
   629  * Given a GeoJSON object, returns the corresponding bounding box. The bounding
       
   630  * box is represented by a two-dimensional array: [[left, bottom], [right,
       
   631  * top]], where left is the minimum longitude, bottom is the minimum latitude,
       
   632  * right is maximum longitude, and top is the maximum latitude.
       
   633  */
       
   634 d3.geo.bounds = function(feature) {
       
   635   var left = Infinity,
       
   636       bottom = Infinity,
       
   637       right = -Infinity,
       
   638       top = -Infinity;
       
   639   d3_geo_bounds(feature, function(x, y) {
       
   640     if (x < left) left = x;
       
   641     if (x > right) right = x;
       
   642     if (y < bottom) bottom = y;
       
   643     if (y > top) top = y;
       
   644   });
       
   645   return [[left, bottom], [right, top]];
       
   646 };
       
   647 
       
   648 function d3_geo_bounds(o, f) {
       
   649   if (o.type in d3_geo_boundsTypes) d3_geo_boundsTypes[o.type](o, f);
       
   650 }
       
   651 
       
   652 var d3_geo_boundsTypes = {
       
   653   Feature: d3_geo_boundsFeature,
       
   654   FeatureCollection: d3_geo_boundsFeatureCollection,
       
   655   LineString: d3_geo_boundsLineString,
       
   656   MultiLineString: d3_geo_boundsMultiLineString,
       
   657   MultiPoint: d3_geo_boundsLineString,
       
   658   MultiPolygon: d3_geo_boundsMultiPolygon,
       
   659   Point: d3_geo_boundsPoint,
       
   660   Polygon: d3_geo_boundsPolygon
       
   661 };
       
   662 
       
   663 function d3_geo_boundsFeature(o, f) {
       
   664   d3_geo_bounds(o.geometry, f);
       
   665 }
       
   666 
       
   667 function d3_geo_boundsFeatureCollection(o, f) {
       
   668   for (var a = o.features, i = 0, n = a.length; i < n; i++) {
       
   669     d3_geo_bounds(a[i].geometry, f);
       
   670   }
       
   671 }
       
   672 
       
   673 function d3_geo_boundsLineString(o, f) {
       
   674   for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
       
   675     f.apply(null, a[i]);
       
   676   }
       
   677 }
       
   678 
       
   679 function d3_geo_boundsMultiLineString(o, f) {
       
   680   for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
       
   681     for (var b = a[i], j = 0, m = b.length; j < m; j++) {
       
   682       f.apply(null, b[j]);
       
   683     }
       
   684   }
       
   685 }
       
   686 
       
   687 function d3_geo_boundsMultiPolygon(o, f) {
       
   688   for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
       
   689     for (var b = a[i][0], j = 0, m = b.length; j < m; j++) {
       
   690       f.apply(null, b[j]);
       
   691     }
       
   692   }
       
   693 }
       
   694 
       
   695 function d3_geo_boundsPoint(o, f) {
       
   696   f.apply(null, o.coordinates);
       
   697 }
       
   698 
       
   699 function d3_geo_boundsPolygon(o, f) {
       
   700   for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) {
       
   701     f.apply(null, a[i]);
       
   702   }
       
   703 }
       
   704 // TODO breakAtDateLine?
       
   705 
       
   706 d3.geo.circle = function() {
       
   707   var origin = [0, 0],
       
   708       degrees = 90 - 1e-2,
       
   709       radians = degrees * d3_geo_radians,
       
   710       arc = d3.geo.greatArc().target(Object);
       
   711 
       
   712   function circle() {
       
   713     // TODO render a circle as a Polygon
       
   714   }
       
   715 
       
   716   function visible(point) {
       
   717     return arc.distance(point) < radians;
       
   718   }
       
   719 
       
   720   circle.clip = function(d) {
       
   721     arc.source(typeof origin === "function" ? origin.apply(this, arguments) : origin);
       
   722     return clipType(d);
       
   723   };
       
   724 
       
   725   var clipType = d3_geo_type({
       
   726 
       
   727     FeatureCollection: function(o) {
       
   728       var features = o.features.map(clipType).filter(Object);
       
   729       return features && (o = Object.create(o), o.features = features, o);
       
   730     },
       
   731 
       
   732     Feature: function(o) {
       
   733       var geometry = clipType(o.geometry);
       
   734       return geometry && (o = Object.create(o), o.geometry = geometry, o);
       
   735     },
       
   736 
       
   737     Point: function(o) {
       
   738       return visible(o.coordinates) && o;
       
   739     },
       
   740 
       
   741     MultiPoint: function(o) {
       
   742       var coordinates = o.coordinates.filter(visible);
       
   743       return coordinates.length && {
       
   744         type: o.type,
       
   745         coordinates: coordinates
       
   746       };
       
   747     },
       
   748 
       
   749     LineString: function(o) {
       
   750       var coordinates = clip(o.coordinates);
       
   751       return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
       
   752     },
       
   753 
       
   754     MultiLineString: function(o) {
       
   755       var coordinates = o.coordinates.map(clip).filter(function(d) { return d.length; });
       
   756       return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
       
   757     },
       
   758 
       
   759     Polygon: function(o) {
       
   760       var coordinates = o.coordinates.map(clip);
       
   761       return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o);
       
   762     },
       
   763 
       
   764     MultiPolygon: function(o) {
       
   765       var coordinates = o.coordinates.map(function(d) { return d.map(clip); }).filter(function(d) { return d[0].length; });
       
   766       return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
       
   767     },
       
   768 
       
   769     GeometryCollection: function(o) {
       
   770       var geometries = o.geometries.map(clipType).filter(Object);
       
   771       return geometries.length && (o = Object.create(o), o.geometries = geometries, o);
       
   772     }
       
   773 
       
   774   });
       
   775 
       
   776   function clip(coordinates) {
       
   777     var i = -1,
       
   778         n = coordinates.length,
       
   779         clipped = [],
       
   780         p0,
       
   781         p1,
       
   782         p2,
       
   783         d0,
       
   784         d1;
       
   785 
       
   786     while (++i < n) {
       
   787       d1 = arc.distance(p2 = coordinates[i]);
       
   788       if (d1 < radians) {
       
   789         if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1)));
       
   790         clipped.push(p2);
       
   791         p0 = p1 = null;
       
   792       } else {
       
   793         p1 = p2;
       
   794         if (!p0 && clipped.length) {
       
   795           clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0)));
       
   796           p0 = p1;
       
   797         }
       
   798       }
       
   799       d0 = d1;
       
   800     }
       
   801 
       
   802     if (p1 && clipped.length) {
       
   803       d1 = arc.distance(p2 = clipped[0]);
       
   804       clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1)));
       
   805     }
       
   806 
       
   807     return resample(clipped);
       
   808   }
       
   809 
       
   810   // Resample coordinates, creating great arcs between each.
       
   811   function resample(coordinates) {
       
   812     var i = 0,
       
   813         n = coordinates.length,
       
   814         j,
       
   815         m,
       
   816         resampled = n ? [coordinates[0]] : coordinates,
       
   817         resamples,
       
   818         origin = arc.source();
       
   819 
       
   820     while (++i < n) {
       
   821       resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates;
       
   822       for (j = 0, m = resamples.length; ++j < m;) resampled.push(resamples[j]);
       
   823     }
       
   824 
       
   825     arc.source(origin);
       
   826     return resampled;
       
   827   }
       
   828 
       
   829   circle.origin = function(x) {
       
   830     if (!arguments.length) return origin;
       
   831     origin = x;
       
   832     return circle;
       
   833   };
       
   834 
       
   835   circle.angle = function(x) {
       
   836     if (!arguments.length) return degrees;
       
   837     radians = (degrees = +x) * d3_geo_radians;
       
   838     return circle;
       
   839   };
       
   840 
       
   841   // Precision is specified in degrees.
       
   842   circle.precision = function(x) {
       
   843     if (!arguments.length) return arc.precision();
       
   844     arc.precision(x);
       
   845     return circle;
       
   846   };
       
   847 
       
   848   return circle;
       
   849 }
       
   850 d3.geo.greatArc = function() {
       
   851   var source = d3_geo_greatArcSource,
       
   852       target = d3_geo_greatArcTarget,
       
   853       precision = 6 * d3_geo_radians;
       
   854 
       
   855   function greatArc() {
       
   856     var a = typeof source === "function" ? source.apply(this, arguments) : source,
       
   857         b = typeof target === "function" ? target.apply(this, arguments) : target,
       
   858         i = d3_geo_greatArcInterpolate(a, b),
       
   859         dt = precision / i.d,
       
   860         t = 0,
       
   861         coordinates = [a];
       
   862     while ((t += dt) < 1) coordinates.push(i(t));
       
   863     coordinates.push(b);
       
   864     return {
       
   865       type: "LineString",
       
   866       coordinates: coordinates
       
   867     };
       
   868   }
       
   869 
       
   870   // Length returned in radians; multiply by radius for distance.
       
   871   greatArc.distance = function() {
       
   872     var a = typeof source === "function" ? source.apply(this, arguments) : source,
       
   873         b = typeof target === "function" ? target.apply(this, arguments) : target;
       
   874      return d3_geo_greatArcInterpolate(a, b).d;
       
   875   };
       
   876 
       
   877   greatArc.source = function(x) {
       
   878     if (!arguments.length) return source;
       
   879     source = x;
       
   880     return greatArc;
       
   881   };
       
   882 
       
   883   greatArc.target = function(x) {
       
   884     if (!arguments.length) return target;
       
   885     target = x;
       
   886     return greatArc;
       
   887   };
       
   888 
       
   889   // Precision is specified in degrees.
       
   890   greatArc.precision = function(x) {
       
   891     if (!arguments.length) return precision / d3_geo_radians;
       
   892     precision = x * d3_geo_radians;
       
   893     return greatArc;
       
   894   };
       
   895 
       
   896   return greatArc;
       
   897 };
       
   898 
       
   899 function d3_geo_greatArcSource(d) {
       
   900   return d.source;
       
   901 }
       
   902 
       
   903 function d3_geo_greatArcTarget(d) {
       
   904   return d.target;
       
   905 }
       
   906 
       
   907 function d3_geo_greatArcInterpolate(a, b) {
       
   908   var x0 = a[0] * d3_geo_radians, cx0 = Math.cos(x0), sx0 = Math.sin(x0),
       
   909       y0 = a[1] * d3_geo_radians, cy0 = Math.cos(y0), sy0 = Math.sin(y0),
       
   910       x1 = b[0] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1),
       
   911       y1 = b[1] * d3_geo_radians, cy1 = Math.cos(y1), sy1 = Math.sin(y1),
       
   912       d = interpolate.d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))),
       
   913       sd = Math.sin(d);
       
   914 
       
   915   // From http://williams.best.vwh.net/avform.htm#Intermediate
       
   916   function interpolate(t) {
       
   917     var A = Math.sin(d - (t *= d)) / sd,
       
   918         B = Math.sin(t) / sd,
       
   919         x = A * cy0 * cx0 + B * cy1 * cx1,
       
   920         y = A * cy0 * sx0 + B * cy1 * sx1,
       
   921         z = A * sy0       + B * sy1;
       
   922     return [
       
   923       Math.atan2(y, x) / d3_geo_radians,
       
   924       Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians
       
   925     ];
       
   926   }
       
   927 
       
   928   return interpolate;
       
   929 }
       
   930 d3.geo.greatCircle = d3.geo.circle;
       
   931 })();