diff --git a/README.md b/README.md
index f810e32..1f7c3dd 100644
--- a/README.md
+++ b/README.md
@@ -138,6 +138,17 @@ Alan K. Philbrick’s interrupted sinu-Mollweide projection.
An interrupted sinusoidal projection with asymmetrical lobe boundaries.
+# d3.geoTwoPointEquidistant(point0, point1) · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)
+
+The two-point equidistant projection, displaying 99.9996% of the sphere thanks to polygon clipping.
+
+# d3.geoTwoPointEquidistantUsa() · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)
+
+[](https://observablehq.com/@d3/two-point-equidistant)
+
+The two-point equidistant projection with points [-158°, 21.5°] and [-77°, 39°], approximately representing Honolulu, HI and Washington, D.C.
+
+### New projections
New projections are introduced:
diff --git a/src/index.js b/src/index.js
index 92d605c..a77f1f1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -27,5 +27,7 @@ export {
geoInterruptedMollweide,
geoInterruptedMollweideHemispheres,
geoInterruptedSinuMollweide,
- geoInterruptedSinusoidal
+ geoInterruptedSinusoidal,
+ geoTwoPointEquidistant,
+ geoTwoPointEquidistantUsa
} from "./reclip.js";
diff --git a/src/reclip.js b/src/reclip.js
index 4e6c95c..2edd499 100644
--- a/src/reclip.js
+++ b/src/reclip.js
@@ -1,5 +1,5 @@
import {merge} from "d3-array";
-import {geoInterpolate} from "d3-geo";
+import {geoDistance, geoInterpolate} from "d3-geo";
import {
geoBerghaus as berghaus,
geoGingery as gingery,
@@ -11,6 +11,7 @@ import {
geoInterruptedMollweideHemispheres as interruptedMollweideHemispheres,
geoInterruptedSinuMollweide as interruptedSinuMollweide,
geoInterruptedSinusoidal as interruptedSinusoidal,
+ geoTwoPointEquidistant as twoPointEquidistant
} from "d3-geo-projection";
import geoClipPolygon from "./clip/polygon.js";
@@ -27,6 +28,8 @@ export function geoInterruptedMollweide() { return clipInterrupted(interruptedMo
export function geoInterruptedMollweideHemispheres() { return clipInterrupted(interruptedMollweideHemispheres.apply(this, arguments)); }
export function geoInterruptedSinuMollweide() { return clipInterrupted(interruptedSinuMollweide.apply(this, arguments)); }
export function geoInterruptedSinusoidal() { return clipInterrupted(interruptedSinusoidal.apply(this, arguments)); }
+export function geoTwoPointEquidistant() { return clipTwoPointEquidistant.apply(this, arguments); }
+export function geoTwoPointEquidistantUsa() { return geoTwoPointEquidistant([-158, 21.5], [-77, 39]); }
function reclip(projection, vertical = false) {
const {lobes} = projection;
@@ -97,3 +100,20 @@ function clipInterrupted(projection) {
return reset(projection);
}
+
+function clipTwoPointEquidistant(a, b) {
+ const epsilon = 1e-3;
+ const u = geoDistance(a, b) * 90 / Math.PI + epsilon;
+ const ellipse = {
+ type: "Polygon",
+ coordinates: [[
+ [180 - u, epsilon],
+ [180 - u, -epsilon],
+ [-180 + u, -epsilon],
+ [-180 + u, epsilon],
+ [180 - u, epsilon]
+ ]
+ ]
+ };
+ return twoPointEquidistant(a, b).preclip(geoClipPolygon(ellipse).clipPoint(false));
+}
diff --git a/test/snapshots.js b/test/snapshots.js
index 9bb59df..f853c57 100644
--- a/test/snapshots.js
+++ b/test/snapshots.js
@@ -29,6 +29,7 @@ import {
geoPolyhedralCollignon,
geoPolyhedralWaterman,
geoTetrahedralLee,
+ geoTwoPointEquidistantUsa
} from "../src/index.js";
const width = 960;
@@ -138,7 +139,7 @@ export async function tetrahedralLeeSouth() {
.rotate([-30, 0])
.angle(-30)
.precision(0.1)
- .fitSize([960, 500], { type: "Sphere" })
+ .fitSize([width, height], { type: "Sphere" })
);
}
@@ -152,19 +153,19 @@ export async function gingery() {
}
export async function berghaus7() {
- return renderWorld(geoBerghaus().lobes(7).fitSize([960, 500], { type: "Sphere" }));
+ return renderWorld(geoBerghaus().lobes(7).fitSize([width, height], { type: "Sphere" }));
}
export async function berghaus13() {
- return renderWorld(geoBerghaus().lobes(13).fitSize([960, 500], { type: "Sphere" }));
+ return renderWorld(geoBerghaus().lobes(13).fitSize([width, height], { type: "Sphere" }));
}
export async function gingery7() {
- return renderWorld(geoGingery().lobes(7).fitSize([960, 500], { type: "Sphere" }));
+ return renderWorld(geoGingery().lobes(7).fitSize([width, height], { type: "Sphere" }));
}
export async function gingery3() {
- return renderWorld(geoGingery().lobes(3).fitSize([960, 500], { type: "Sphere" }));
+ return renderWorld(geoGingery().lobes(3).fitSize([width, height], { type: "Sphere" }));
}
export async function goodeOcean() {
@@ -242,6 +243,13 @@ export async function interruptedSinusoidal() {
return renderWorld(geoInterruptedSinusoidal());
}
+// https://github.com/d3/d3-geo/issues/46
+export async function twoPointEquidistantUsa() {
+ return renderWorld(
+ geoTwoPointEquidistantUsa().fitSize([width, height], { type: "Sphere" })
+ );
+}
+
// more tests
// https://github.com/d3/d3-geo-polygon/issues/7
@@ -250,7 +258,7 @@ export async function cubic45() {
geoCubic()
.parents([-1, 2, 0, 2, 5, 2])
.rotate([0, 0, 45])
- .fitSize([960, 500], { type: "Sphere" })
+ .fitSize([width, height], { type: "Sphere" })
);
}
@@ -274,7 +282,7 @@ export async function rhombicHalf1() {
geoRhombic()
.parents([-1, 0, 6, 2, 1, 9, 11, 3, 4, 8, 6, 10])
.precision(0.1)
- .fitSize([960, 500], { type: "Sphere" })
+ .fitSize([width, height], { type: "Sphere" })
);
}
export async function rhombicHalf2() {
@@ -283,7 +291,7 @@ export async function rhombicHalf2() {
.parents([4, 0, 6, 2, 1, 9, 11, 3, 4, 8, -1, 10])
.angle(-19.5)
.precision(0.1)
- .fitSize([960, 500], { type: "Sphere" })
+ .fitSize([width, height], { type: "Sphere" })
);
}
diff --git a/test/snapshots/twoPointEquidistantUsa.png b/test/snapshots/twoPointEquidistantUsa.png
new file mode 100644
index 0000000..f00103f
Binary files /dev/null and b/test/snapshots/twoPointEquidistantUsa.png differ