var vows = require(“vows”),

_ = require("../../"),
load = require("../load"),
assert = require("../assert");

var suite = vows.describe(“d3.geo.path”);

suite.addBatch({

"path": {
  topic: load("geo/path").expression("d3.geo.path"),

  "with an equirectangular projection": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .precision(0));
    },

    "renders a point": function(p) {
      p({
        type: "Point",
        coordinates: [-63, 18]
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 165, y: 160},
        {type: "arc", x: 165, y: 160, r: 4.5}
      ]);
    },

    "renders a multipoint": function(p) {
      p({
        type: "MultiPoint",
        coordinates: [[-63, 18], [-62, 18], [-62, 17]]
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 165, y: 160}, {type: "arc", x: 165, y: 160, r: 4.5},
        {type: "moveTo", x: 170, y: 160}, {type: "arc", x: 170, y: 160, r: 4.5},
        {type: "moveTo", x: 170, y: 165}, {type: "arc", x: 170, y: 165, r: 4.5}
      ]);
    },

    "renders a line string": function(p) {
      p({
        type: "Feature",
        geometry: {
          type: "LineString",
          coordinates: [[-63, 18], [-62, 18], [-62, 17]]
        }
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 165, y: 160},
        {type: "lineTo", x: 170, y: 160},
        {type: "lineTo", x: 170, y: 165}
      ]);
    },

    "renders a polygon": function(p) {
      p({
        type: "Feature",
        geometry: {
          type: "Polygon",
          coordinates: [[[-63, 18], [-62, 18], [-62, 17], [-63, 18]]]
        }
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 165, y: 160},
        {type: "lineTo", x: 170, y: 160},
        {type: "lineTo", x: 170, y: 165},
        {type: "closePath"}
      ]);
    },

    "renders a geometry collection": function(p) {
      p({
        type: "GeometryCollection",
        geometries: [{type: "Point", coordinates: [0, 0]}]
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 480, y: 250}, {type: "arc", x: 480, y: 250, r: 4.5}
      ]);
    },

    "renders a feature collection": function(p) {
      p({
        type: "FeatureCollection",
        features: [{type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}]
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 480, y: 250}, {type: "arc", x: 480, y: 250, r: 4.5}
      ]);
    },

    "longitudes wrap at ±180°": function(p) {
      p({type: "Point", coordinates: [180 + 1e-6, 0]});
      assert.deepEqual(testContext.buffer(), [{type: "moveTo", x: -420, y: 250}, {type: "arc", x: -420, y: 250, r: 4.5}]);
    },

    "observes the correct winding order of a tiny polygon": function(p) {
      p({type: "Polygon", coordinates: [[
        [-0.06904102953339501, 0.346043661846373],
        [-6.725674252975136e-15, 0.3981303360336475],
        [-6.742247658534323e-15, -0.08812465346531581],
        [-0.17301258217724075, -0.12278150669440671],
        [-0.06904102953339501, 0.346043661846373]]]});
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 1);
    },

    "area": {
      topic: function(p) {
        return p.area;
      },
      "of a polygon with no holes": function(area) {
        assert.strictEqual(area({type: "Polygon", coordinates: [[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]]]}), 25);
      },
      "of a polygon with holes": function(area) {
        assert.strictEqual(area({type: "Polygon", coordinates: [[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]], [[100.2, .2], [100.8, .2], [100.8, .8], [100.2, .8], [100.2, .2]]]}), 16);
      },
      "area of a Sphere": function(area) {
        assert.strictEqual(area({type: "Sphere"}), 1620000);
      }
    },

    "centroid": {
      topic: function(p) {
        return p.centroid;
      },
      "of a point": function(centroid) {
        assert.deepEqual(centroid({type: "Point", coordinates: [0, 0]}), [480, 250]);
      },
      "of an empty multipoint": function(centroid) {
        assert.ok(centroid({type: "MultiPoint", coordinates: []}).every(isNaN));
      },
      "of a singleton multipoint": function(centroid) {
        assert.deepEqual(centroid({type: "MultiPoint", coordinates: [[0, 0]]}), [480, 250]);
      },
      "of a multipoint with two points": function(centroid) {
        assert.deepEqual(centroid({type: "MultiPoint", coordinates: [[-122, 37], [-74, 40]]}), [-10, 57.5]);
      },
      "of an empty linestring": function(centroid) {
        assert.ok(centroid({type: "LineString", coordinates: []}).every(isNaN));
      },
      "of a linestring with two points": function(centroid) {
        assert.deepEqual(centroid({type: "LineString", coordinates: [[100, 0], [0, 0]]}), [730, 250]);
        assert.deepEqual(centroid({type: "LineString", coordinates: [[0, 0], [100, 0], [101, 0]]}), [732.5, 250]);
      },
      "of a linestring with two points, one unique": function(centroid) {
        assert.deepEqual(centroid({type: "LineString", coordinates: [[-122, 37], [-122, 37]]}), [-130, 65]);
        assert.deepEqual(centroid({type: "LineString", coordinates: [[-74, 40], [-74, 40]]}), [110, 50]);
      },
      "of a linestring with three points; two unique": function(centroid) {
        assert.deepEqual(centroid({type: "LineString", coordinates: [[-122, 37], [-74, 40], [-74, 40]]}), [-10, 57.5]);
      },
      "of a linestring with three points": function(centroid) {
        assert.inDelta(centroid({type: "LineString", coordinates: [[-122, 37], [-74, 40], [-100, 0]]}), [17.389135, 103.563545], 1e-6);
      },
      "of a multilinestring": function(centroid) {
        assert.deepEqual(centroid({type: "MultiLineString", coordinates: [[[100, 0], [0, 0]], [[-10, 0], [0, 0]]]}), [705, 250]);
      },
      "of a single-ring polygon": function(centroid) {
        assert.deepEqual(centroid({type: "Polygon", coordinates: [[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]]]}), [982.5, 247.5]);
      },
      "of a zero-area polygon": function(centroid) {
        assert.deepEqual(centroid({type: "Polygon", coordinates: [[[1, 0], [2, 0], [3, 0], [1, 0]]]}), [490, 250]);
      },
      "of a polygon with two rings, one with zero area": function(centroid) {
        assert.deepEqual(centroid({type: "Polygon", coordinates: [
          [[100,   0], [100,   1], [101,   1], [101,   0], [100, 0]],
          [[100.1, 0], [100.2, 0], [100.3, 0], [100.1, 0]
        ]]}), [982.5, 247.5]);
      },
      "of a polygon with clockwise exterior and anticlockwise interior": function(centroid) {
        assert.inDelta(centroid({
          type: "Polygon",
          coordinates: [
            [[-2, -2], [2, -2], [2, 2], [-2, 2], [-2, -2]].reverse(),
            [[ 0, -1], [1, -1], [1, 1], [ 0, 1], [ 0, -1]]
          ]
        }), [479.642857, 250], 1e-6);
      },
      "of an empty multipolygon": function(centroid) {
        assert.ok(centroid({type: "MultiPolygon", coordinates: []}).every(isNaN));
      },
      "of a singleton multipolygon": function(centroid) {
        assert.deepEqual(centroid({type: "MultiPolygon", coordinates: [[[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]]]]}), [982.5, 247.5]);
      },
      "of a multipolygon with two polygons": function(centroid) {
        assert.deepEqual(centroid({type: "MultiPolygon", coordinates: [
          [[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]]],
          [[[0, 0], [1, 0], [1, -1], [0, -1], [0, 0]]]
        ]}), [732.5, 250]);
      },
      "of a multipolygon with two polygons, one zero area": function(centroid) {
        assert.deepEqual(centroid({type: "MultiPolygon", coordinates: [
          [[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]]],
          [[[0, 0], [1, 0], [2, 0], [0, 0]]]
        ]}), [982.5, 247.5]);
      },
      "of a geometry collection with a single point": function(centroid) {
        assert.deepEqual(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}]}), [480, 250]);
      },
      "of a geometry collection with a point and a linestring": function(centroid) {
        assert.deepEqual(centroid({type: "GeometryCollection", geometries: [
          {type: "LineString", coordinates: [[179, 0], [180, 0]]},
          {type: "Point", coordinates: [0, 0]}
        ]}), [1377.5, 250]);
      },
      "of a geometry collection with a point, linestring and polygon": function(centroid) {
        assert.deepEqual(centroid({type: "GeometryCollection", geometries: [
          {type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]},
          {type: "LineString", coordinates: [[179, 0], [180, 0]]},
          {type: "Point", coordinates: [0, 0]}
        ]}), [-417.5, 247.5]);
      },
      "of a feature collection with a point": function(centroid) {
        assert.deepEqual(centroid({type: "FeatureCollection", features: [{type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}]}), [480, 250]);
      },
      "of a feature collection with a point and a line string": function(centroid) {
        assert.deepEqual(centroid({type: "FeatureCollection", features: [
          {type: "Feature", geometry: {type: "LineString", coordinates: [[179, 0], [180, 0]]}},
          {type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}
        ]}), [1377.5, 250]);
      },
      "of a feature collection with a point, line string and polygon": function(centroid) {
        assert.deepEqual(centroid({type: "FeatureCollection", features: [
          {type: "Feature", geometry: {type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]}},
          {type: "Feature", geometry: {type: "LineString", coordinates: [[179, 0], [180, 0]]}},
          {type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}
        ]}), [-417.5, 247.5]);
      },
      "of a sphere": function(centroid) {
        assert.deepEqual(centroid({type: "Sphere"}), [480, 250]);
      }
    }
  },

  "with a null (identity) projection": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(null);
    },
    "renders a Polygon": function(p) {
      p({
        type: "Feature",
        geometry: {
          type: "Polygon",
          coordinates: [[[-63, 18], [-62, 18], [-62, 17], [-63, 18]]]
        }
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: -63, y: 18},
        {type: "lineTo", x: -62, y: 18},
        {type: "lineTo", x: -62, y: 17},
        {type: "closePath"}
      ]);
    }
  },

  "with the default context (null) and an identity projection": {
    topic: function(path) {
      return path()
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .precision(0));
    },
    "returns null when passed null or undefined": function(p) {
      assert.equal(p(null), null);
      assert.equal(p(undefined), null);
      assert.equal(p(), null);
    },
    "returns null with bogus type name": function(p) {
      assert.equal(p({
        type: "Feature",
        geometry: {
          type: "__proto__",
          coordinates: [[[-63.03, 18.02], [-63.14, 18.06], [-63.01, 18.07], [-63.03, 18.02]]]
        }
      }), null);
    }
  },

  "with the default context (null) and default projection (albers-usa)": {
    topic: function(path) {
      return path();
    },
    "area of a polygon": function(p) {
      var area = p.area({type: "Polygon", coordinates: [[[-122, 37], [-71, 42], [-80, 25], [-122, 37]]]});
      assert.inDelta(area, 124884.274, 1e-3);
    },
    "bounds of a line string": function(p) {
      assert.inDelta(p.bounds({type: "LineString", coordinates: [[-122, 37], [-74, 40], [-100, 0]]}),
        [[109.378, 189.584], [797.758, 504.660]], 1e-3);
    },
    "centroid of a line string": function(p) {
      assert.inDelta(p.centroid({type: "LineString", coordinates: [[-122, 37], [-74, 40], [-100, 0]]}), [545.131, 253.860], 1e-3);
    }
  },

  "with an equirectangular projection rotated by [180, -248]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([-180, -248])
            .scale(900 / Math.PI)
            .precision(0));
    },
    "renders a polygon": function(p) {
      p({type: "Polygon",  coordinates: [[[-175.03150315031502, 66.57410661866186], [-174.34743474347434, 66.33097912391239], [-174.5994599459946, 67.0603616081608], [-171.86318631863185, 66.90406536153614], [-169.9189918991899, 65.96628788178816], [-170.89108910891088, 65.53213164116411], [-172.54725472547256, 65.42793414341432], [-172.5832583258326, 64.45542416441643], [-172.97929792979298, 64.2470291689169], [-173.91539153915392, 64.28176166816681], [-174.67146714671466, 64.62908666066605], [-176.003600360036, 64.90694665466546], [-176.21962196219621, 65.34110289528951], [-177.22772277227722, 65.51476539153916], [-178.37983798379838, 65.37583539453945], [-178.91989198919893, 65.72316038703869], [-178.7038703870387, 66.10521787878787], [-179.8919891989199, 65.8620903840384], [-179.45994599459945, 65.3932016441644], [-180, 64.97641165316531], [-180, 68.95328281728172], [-177.55175517551754, 68.18916783378336], [-174.95949594959495, 67.19929160516051], [-175.03150315031502, 66.57410661866186]]]});
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 1370, y: 243}]);
    }
  },

  "projection": {
    "returns the current projection when called with no arguments": function(path) {
      var p = path(), projection = _.geo.equirectangular();
      p.projection(projection);
      assert.strictEqual(p.projection(), projection);
    }
  },

  "pointRadius": {
    "returns the current point radius when called with no arguments": function(path) {
      var p = path(), radius = function() { return 5; };
      assert.strictEqual(p.pointRadius(), 4.5);
      assert.strictEqual(p.pointRadius(radius).pointRadius(), radius);
    },
    "coerces point radius to a number": {
      "when the radius is specified as a constant": function(path) {
        var p = path().projection(null).context(testContext).pointRadius("6");
        assert.strictEqual(p.pointRadius(), 6);
        p({type: "Point", coordinates: [0, 0]});
        assert.strictEqual(testContext.buffer().filter(function(d) { return d.type === "arc"; })[0].r, 6);
      },
      "when the radius is specified as a function": function(path) {
        var p = path().projection(null).context(testContext).pointRadius(function() { return "6"; });
        p({type: "Point", coordinates: [0, 0]});
        assert.strictEqual(testContext.buffer().filter(function(d) { return d.type === "arc"; })[0].r, 6);
      }
    }
  },

  "with an equirectangular projection clipped to 90°": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    "renders a point": function(p) {
      p({
        type: "Point",
        coordinates: [-63, 18]
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 165, y: 160}, {type: "arc", x: 165, y: 160, r: 4.5}
      ]);
    },
    "renders a multipoint": function(p) {
      p({
        type: "MultiPoint",
        coordinates: [[-63, 18], [-62, 18], [-62, 17]]
      });
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 165, y: 160}, {type: "arc", x: 165, y: 160, r: 4.5},
        {type: "moveTo", x: 170, y: 160}, {type: "arc", x: 170, y: 160, r: 4.5},
        {type: "moveTo", x: 170, y: 165}, {type: "arc", x: 170, y: 165, r: 4.5}
      ]);
    },
    "inserts exterior along clip edge if polygon interior surrounds it": function(p) {
      p({type: "Polygon", coordinates: [[[80, -80], [80, 80], [-80, 80], [-80, -80], [80, -80]]]});
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 2);
    },
    "inserts exterior along clip edge if polygon exterior surrounds it": function(p) {
      p({type: "Polygon", coordinates: [[[100, -80], [-100, -80], [-100, 80], [100, 80], [100, -80]]]});
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 1);
    },
    "renders a small circle of 60°": function(p) {
      p(_.geo.circle().angle(60)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 276, y: 493}]);
    },
    "renders a small circle of 120°": function(p) {
      p(_.geo.circle().angle(120)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 87, y: 700}]);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [-17°, -451°]": {
    "renders a polygon": function(path) {
      var pole = _.range(-180, 180, 10).map(function(x) { return [x, 70]; });
      pole.push(pole[0]);
      path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([-17, -451])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90))({type: "Polygon", coordinates: [pole]});
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
        {type: "moveTo", x: 510, y: 160},
        {type: "moveTo", x:  87, y: 700}
      ]);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [71.03°, 42.37°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([71.03, 42.37])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    /*
    "grid component": function(path) {
      var yStepsBig = _.range(-90, 90, 10);
      path({type: "LineString", coordinates: yStepsBig.map(function(y) { return [110, y]; })});
      assert.inDelta(testContext.buffer(), [[
        [109.538009, -90],
        [110, -80],
        [110, -70],
        [110, -60],
        [110, -50],
        [110, -47.625390]
      ]], 1e-6);
    },
    */
    "can completely clip a LineString": function(p) {
      p({type: "LineString", coordinates: [[90.0, -42.37], [95.0, -42.37], [90.0, -42.37]]});
      assert.deepEqual(testContext.buffer(), []);
    },
    "doesn't insert a duplicate point": function(p) {
      p({type: "LineString", coordinates: [[0, 0]]});
      assert.deepEqual(testContext.buffer(), [{type: "moveTo", x: 859, y: 187}]);
    },
    "renders a visible point": function(p) {
      p({type: "Point", coordinates: [0, 0]});
      assert.deepEqual(testContext.buffer(), [{type: "moveTo", x: 859, y: 187}, {type: "arc", x: 859, y: 187, r: 4.5}]);
    },
    "does not render an invisible point": function(p) {
      p({type: "Point", coordinates: [-180, 0]});
      assert.deepEqual(testContext.buffer(), []);
    },
    "renders a multipoint": function(p) {
      p({type: "MultiPoint", coordinates: [[0, 0], [-180, 0]]});
      assert.deepEqual(testContext.buffer(), [{type: "moveTo", x: 859, y: 187}, {type: "arc", x: 859, y: 187, r: 4.5}]);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [-24°, -175.5°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([-24, -175.5])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    "renders Antarctica with no gaps": function(p) {
      p(antarctica);
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 2);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [90°, 0°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([90, 0])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    "renders a small circle of 60°": function(p) {
      p(_.geo.circle().angle(60)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 930, y: 550}]);
    },
    "renders a small circle of 120°": function(p) {
      p(_.geo.circle().angle(120)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 30, y: 550}]);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [180°, 0°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([180, 0])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    "does not render a small circle of 60°": function(p) {
      p(_.geo.circle().angle(60)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), []);
    },
    "renders a small circle of 120° in two parts": function(p) {
      p(_.geo.circle().angle(120)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
        {type: "moveTo", x: 276, y: 493},
        {type: "moveTo", x:  87, y: 700}
      ]);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [270°, 0°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([270, 0])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    "renders a small circle of 60°": function(p) {
      p(_.geo.circle().angle(60)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 30, y: -50}]);
    },
    "renders a small circle of 120°": function(p) {
      p(_.geo.circle().angle(120)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 930, y: -50}]);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [210°, 1°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([210, 1])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    "renders a small circle of 120°": function(p) {
      p(_.geo.circle().angle(120)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 930, y: 250}]);
    }
  },

  "with an equirectangular projection clipped to 90° and rotated by [-150°, 60°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .rotate([-150, 60])
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(90));
    },
    "renders a small circle of 120°": function(p) {
      p(_.geo.circle().angle(120)());
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 30, y: -87}]);
    },
    "renders a sphere": function(p) {
      p({type: "Sphere"});
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [{type: "moveTo", x: 87, y: 700}]);
    }
  },

  "with an equirectangular projection clipped to 170°": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(170));
    },
    "renders stripes": function(p) {
      p(stripes(80, -80));
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
        {type: "moveTo", x: -420, y: -150},
        {type: "moveTo", x: -420, y:  650},
        {type: "moveTo", x: 1331, y:  259}
      ]);
    }
  },

  "with an equirectangular projection clipped to 170° and rotated by [0°, -90°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .rotate([0, -90])
            .precision(0)
            .clipAngle(170));
    },
    "renders stripes": function(p) {
      p(stripes(80, -80));
      assert.deepEqual(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }), [
        {type: "moveTo", x:  480, y: 200},
        {type: "moveTo", x: 1350, y: 210}
      ]);
    }
  },

  "with an equirectangular projection clipped to 30°": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(30));
    },
    "clips lines with two invisible endpoints and visible middle": function(p) {
      p({type: "LineString", coordinates: [[-45, 0], [45, 0]]});
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 330, y: 250},
        {type: "lineTo", x: 630, y: 250}
      ]);
    }
  },

  "with an equirectangular projection clipped to 150°": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .precision(0)
            .clipAngle(150));
    },
    "clips lines with two visible endpoints and invisible middle": function(p) {
      p({type: "LineString", coordinates: [[135, 0], [-135, 0]]});
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 1155, y: 250},
        {type: "lineTo", x: 1230, y: 250},
        {type: "moveTo", x: -270, y: 250},
        {type: "lineTo", x: -195, y: 250}
      ]);
    }
  },

  "with an equirectangular projection rotated by [98°, 0°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .rotate([98, 0])
            .precision(0));
    },
    "renders Keweenaw, a small U.S. county": function(p) {
      p({
        type: "Polygon",
        coordinates: [[[-88.23013, 47.198326], [-88.514931, 47.285957], [-88.383484, 47.285957], [-88.23013, 47.198326]]]
      });
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 1);
    },
    "renders Accomack, a small U.S. county": function(p) {
      p({
        type: "MultiPolygon",
        coordinates: [
          [[[-75.397659, 38.013497], [-75.244304, 38.029928], [-75.666029, 37.465803], [-75.939876, 37.547957], [-75.671506, 37.95325], [-75.622213, 37.991589], [-75.397659, 38.013497]]],
          [[[-76.016553, 37.95325], [-76.043938, 37.95325], [-75.994645, 37.95325], [-76.016553, 37.95325]]]
        ]
      });
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 2);
    },
    "renders Hopewell, a small U.S. county": function(p) {
      p({
        type: "Polygon",
        coordinates: [[[-77.298157, 37.312448], [-77.298157, 37.312448], [-77.336496, 37.312448], [-77.281726, 37.312448], [-77.298157, 37.312448]]]
      });
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 1);
    }
  },

  "with an equirectangular projection rotated by [330°, 232°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .rotate([330, 232])
            .precision(0));
    },
    "renders degenerate points for a small circle of 30°": function(p) {
      p(_.geo.circle().angle(30)());
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 2);
    }
  },

  "with an equirectangular projection rotated by [34.5°, 90°]": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .rotate([34.5, 90])
            .precision(0));
    },
    "observes proper clip point ordering for lines": function(p) {
      var line = _.range(-90,  180,  10).map(function(x) { return [x, 20]; })
         .concat(_.range(170, -100, -10).map(function(x) { return [x,  0]; }))
         .concat([[-90, 20]]);
      p({type: "Polygon", coordinates: [line]});
      assert.equal(testContext.buffer().filter(function(d) { return d.type === "moveTo"; }).length, 3);
    }
  },

  "with an equirectangular projection with the viewport clipped to 960×500": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.equirectangular()
            .scale(900 / Math.PI)
            .clipExtent([[0, 0], [960, 500]])
            .precision(0));
    },
    "doesn't generate a redundant closing point": function(p) {
      p({type: "Polygon", coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]});
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 480, y: 250},
        {type: "lineTo", x: 480, y: 245},
        {type: "lineTo", x: 485, y: 245},
        {type: "lineTo", x: 485, y: 250},
        {type: "closePath"}
      ]);
    }
  },

  "with a stereographic projection and adaptive resampling": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.stereographic()
            .precision(1));
    },
    "correctly resamples points on antimeridian": function(p) {
      p({type: "LineString", coordinates: [[0, 90], [90, 0]]});
      assert.deepEqual(testContext.buffer(), [
        {type: "moveTo", x: 480, y: 100},
        {type: "lineTo", x: 509, y: 103},
        {type: "lineTo", x: 537, y: 111},
        {type: "lineTo", x: 563, y: 125},
        {type: "lineTo", x: 586, y: 144},
        {type: "lineTo", x: 605, y: 167},
        {type: "lineTo", x: 619, y: 193},
        {type: "lineTo", x: 627, y: 221},
        {type: "lineTo", x: 630, y: 250}
      ]);
    }
  },

  "with an Albers projection and adaptive resampling": {
    "correctly resamples near the poles": function(path) {
      var p = path()
          .context(testContext)
          .projection(_.geo.albers()
            .scale(140)
            .rotate([0, 0])
            .precision(1));
      p({type: "LineString", coordinates: [[0, 88], [180, 89]]});
      assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
      p({type: "LineString", coordinates: [[180, 90], [1, 89.5]]});
      assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
    },
    "rotate([11.5, 285])": function(path) {
      var p = path()
          .context(testContext)
          .projection(_.geo.albers()
            .scale(140)
            .rotate([11.5, 285])
            .precision(1));
      p({type: "LineString", coordinates: [[170, 20], [170, 0]]});
      assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
    },
    "wavy projection": function(path) {
      var p = path()
          .context(testContext)
          .projection(_.geo.projection(function(λ, φ) {
              return [λ, Math.sin(λ * 4)];
            })
            .scale(140)
            .precision(1));
      p({type: "LineString", coordinates: [[-45, 0], [45, 0]]});
      assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
    }
  },

  "with an Albers projection rotated by [11.5°, 285°] and adaptive resampling": {
    topic: function(path) {
      return path()
          .context(testContext)
          .projection(_.geo.albers()
            .scale(140)
            .rotate([11.5, 285])
            .precision(1));
    },
    "correctly resamples near the poles": function(p) {
      p({type: "LineString", coordinates: [[170, 20], [170, 0]]});
      assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
    }
  }
}

});

var testBuffer = [];

var testContext = {

arc: function(x, y, r, a0, a1) { testBuffer.push({type: "arc", x: Math.round(x), y: Math.round(y), r: r}); },
moveTo: function(x, y) { testBuffer.push({type: "moveTo", x: Math.round(x), y: Math.round(y)}); },
lineTo: function(x, y) { testBuffer.push({type: "lineTo", x: Math.round(x), y: Math.round(y)}); },
closePath: function() { testBuffer.push({type: "closePath"}); },
buffer: function() { var result = testBuffer; testBuffer = []; return result; }

};

function stripes(a, b) {

return {type: "Polygon", coordinates: [a, b].map(function(d, i) {
  var stripe = _.range(-180, 180, 1).map(function(x) { return [x, d]; });
  stripe.push(stripe[0]);
  return i ? stripe.reverse() : stripe;
})};

}

var antarctica = {type: “Polygon”, coordinates: [[[-58.61414,-64.15246],,[-59.78934,-64.21122],,[-61.29741,-64.54432],,[-62.51176,-65.09302],,[-62.59012,-65.85721],,[-62.80556,-66.42550],,[-64.29410,-66.83700],,[-65.50842,-67.58161],,[-65.31254,-68.36533],,[-63.96110,-68.91398],,[-62.78595,-69.61941],,[-62.27673,-70.38366],,[-61.51290,-71.08904],,[-61.08197,-72.38235],,[-60.69026,-73.16617],,[-61.37580,-74.10674],,[-63.29520,-74.57699],,[-64.35283,-75.26284],,[-67.19281,-75.79191],,[-69.79772,-76.22299],,[-72.20677,-76.67366],,[-75.55597,-76.71288],,[-76.92697,-77.10480],,[-74.28287,-77.55542],,[-74.77253,-78.22163],,[-77.92585,-78.37841],,[-78.02378,-79.18183],,[-76.63322,-79.88721],,[-73.24485,-80.41633],,[-70.01316,-81.00415],,[-65.70427,-81.47445],,[-61.55202,-82.04269],,[-58.71212,-82.84610],,[-57.00811,-82.86569],,[-53.61977,-82.25823],,[-49.76134,-81.72917],,[-44.82570,-81.84673],,[-42.16202,-81.65082],,[-38.24481,-81.33730],,[-34.38639,-80.90617],,[-30.09709,-80.59265],,[-29.25490,-79.98519],,[-29.68580,-79.26022],,[-33.68132,-79.45613],,[-35.91410,-79.08385],,[-35.32654,-78.12365],,[-32.21236,-77.65345],,[-29.78373,-77.06557],,[-27.51175,-76.49734],,[-25.47482,-76.28180],,[-22.45859,-76.10543],,[-20.01037,-75.67434],,[-17.52298,-75.12569],,[-15.70149,-74.49860],,[-16.46532,-73.87161],,[-15.44685,-73.14654],,[-13.31197,-72.71545],,[-11.51006,-72.01007],,[-10.29577,-71.26541],,[-8.61138,-71.65733],,[-7.37745,-71.32422],,[-5.79098,-71.03028],,[-4.34166,-71.46137],,[-1.79549,-71.16743],,[-0.22863,-71.63774],,[1.88668,-71.12826],,[4.13905,-70.85391],,[6.27391,-70.46205],,[7.74286,-69.89376],,[9.52513,-70.01133],,[10.81782,-70.83433],,[12.40428,-70.24651],,[14.73499,-70.03091],,[15.94934,-70.03091],,[18.20171,-69.87418],,[20.37573,-70.01133],,[21.92303,-70.40324],,[23.66618,-70.52081],,[25.97730,-70.48163],,[28.09258,-70.32485],,[30.03158,-69.93293],,[31.99017,-69.65864],,[33.30244,-68.83564],,[34.90849,-68.65927],,[36.16201,-69.24714],,[37.90510,-69.52144],,[39.66789,-69.54107],,[40.92135,-68.93362],,[42.93870,-68.46331],,[44.89729,-68.05186],,[46.50334,-67.60119],,[48.34441,-67.36606],,[49.93088,-67.11130],,[50.94932,-66.52348],,[52.61413,-66.05317],,[54.53355,-65.81804],,[56.35504,-65.97478],,[57.25596,-66.68021],,[58.74450,-67.28767],,[60.60522,-67.67958],,[62.38748,-68.01269],,[64.05234,-67.40523],,[65.97171,-67.73834],,[67.89113,-67.93430],,[69.71262,-68.97279],,[69.55594,-69.67822],,[67.81273,-70.30526],,[69.06630,-70.67754],,[68.41998,-71.44178],,[68.71376,-72.16680],,[71.02489,-72.08841],,[71.90628,-71.32422],,[73.08141,-70.71676],,[73.86487,-69.87418],,[75.62755,-69.73703],,[77.64490,-69.46268],,[78.42837,-68.69844],,[80.09312,-68.07150],,[81.48379,-67.54238],,[82.77642,-67.20928],,[84.67620,-67.20928],,[86.75235,-67.15047],,[87.98628,-66.20991],,[88.82840,-66.95456],,[90.63036,-67.22886],,[92.60853,-67.18969],,[94.17541,-67.11130],,[95.78147,-67.38565],,[97.75964,-67.24850],,[99.71818,-67.24850],,[100.89335,-66.58223],,[102.83241,-65.56328],,[104.24255,-65.97478],,[106.18156,-66.93493],,[108.08139,-66.95456],,[110.23583,-66.69980],,[111.74395,-66.13156],,[113.60467,-65.87680],,[114.89730,-66.38628],,[116.69916,-66.66063],,[118.57946,-67.17011],,[120.87099,-67.18969],,[122.32036,-66.56265],,[124.12227,-66.62146],,[126.10039,-66.56265],,[127.88276,-66.66063],,[129.70425,-66.58223],,[131.79994,-66.38628],,[133.85646,-66.28830],,[135.03158,-65.72007],,[135.69748,-65.58286],,[136.20670,-66.44509],,[137.46027,-66.95456],,[139.90844,-66.87617],,[142.12169,-66.81736],,[144.37406,-66.83700],,[146.19555,-67.22886],,[146.64606,-67.89513],,[148.83962,-68.38502],,[151.48370,-68.71812],,[153.63819,-68.89450],,[155.16585,-68.83564],,[156.81113,-69.38429],,[159.18101,-69.59983],,[160.80665,-70.22687],,[162.68689,-70.73635],,[164.91968,-70.77552],,[167.30909,-70.83433],,[169.46358,-71.20666],,[171.20679,-71.69650],,[170.56042,-72.44115],,[169.75736,-73.24452],,[167.97510,-73.81280],,[166.09480,-74.38104],,[164.95885,-75.14528],,[163.82279,-75.87030],,[163.47026,-76.69330],,[164.05787,-77.45744],,[164.74346,-78.18251],,[166.99578,-78.75074],,[163.66621,-79.12302],,[160.92416,-79.73048],,[160.31696,-80.57306],,[161.12001,-81.27850],,[162.49099,-82.06227],,[165.09594,-82.70895],,[168.89566,-83.33599],,[172.28393,-84.04143],,[173.22408,-84.41371],,[178.27721,-84.47251],,[180.00000,-90.0],,[-180.0,-84.71338],,[-179.05867,-84.13941],,[-177.14080,-84.41794],,[-176.52395,-84.23181],,[-176.08467,-84.09925],,[-175.82988,-84.11791],,[-173.11655,-84.11791],,[-169.95122,-83.88464],,[-168.53019,-84.23739],,[-164.18214,-84.82520],,[-158.07137,-85.37391],,[-150.94209,-85.29551],,[-145.88891,-85.31510],,[-142.89227,-84.57049],,[-150.06073,-84.29614],,[-153.58620,-83.68868],,[-153.03775,-82.82652],,[-152.86151,-82.04269],,[-155.29017,-81.41565],,[-154.40878,-81.16093],,[-150.64829,-81.33730],,[-147.22074,-80.67104],,[-146.77028,-79.92643],,[-149.53190,-79.35820],,[-153.39032,-79.16224],,[-155.97566,-78.69193],,[-158.05176,-78.02567],,[-157.87547,-76.98723],,[-155.32937,-77.20272],,[-152.92024,-77.49666],,[-150.00194,-77.18314],,[-147.61248,-76.57573],,[-146.14352,-76.10543],,[-146.20230,-75.38041],,[-144.32203,-75.53719],,[-141.63876,-75.08647],,[-138.85759,-74.96891],,[-136.42890,-74.51824],,[-134.43119,-74.36145],,[-132.25716,-74.30269],,[-129.55428,-74.45943],,[-126.89062,-74.42026],,[-124.01149,-74.47901],,[-121.07361,-74.51824],,[-118.68414,-74.18508],,[-116.21631,-74.24389],,[-113.94433,-73.71482],,[-112.94545,-74.38104],,[-111.26105,-74.42026],,[-108.71490,-74.91010],,[-106.14914,-75.12569],,[-103.36794,-74.98849],,[-100.64553,-75.30201],,[-100.76304,-74.53782],,[-102.54533,-74.10674],,[-103.32875,-73.36208],,[-102.91748,-72.75467],,[-100.31252,-72.75467],,[-98.11888,-73.20534],,[-96.33659,-73.61684],,[-93.67290,-73.28374],,[-91.42056,-73.40130],,[-89.22695,-72.55872],,[-87.26833,-73.18576],,[-85.19223,-73.47969],,[-82.66564,-73.63643],,[-80.68744,-73.47969],,[-79.29688,-73.51887],,[-76.90736,-73.63643],,[-74.89004,-73.87161],,[-72.83353,-73.40130],,[-70.20904,-73.14654],,[-67.95662,-72.79385],,[-67.13403,-72.04924],,[-67.56494,-71.24583],,[-68.23084,-70.46205],,[-68.54420,-69.71739],,[-67.97623,-68.95320],,[-67.42784,-68.14984],,[-67.74118,-67.32684],,[-66.70318,-66.58223],,[-65.37132,-65.89639],,[-64.17654,-65.17142],,[-63.00139,-64.64230],,[-61.41492,-64.27003],,[-59.88726,-63.95651],,[-58.59455,-63.38822],,[-57.22358,-63.52542],,[-58.61414,-64.15246]]]};

suite.export(module);