Skip to content

Commit

Permalink
reclip interrupted projections from d3-geo-projection
Browse files Browse the repository at this point in the history
  • Loading branch information
Fil committed Jun 16, 2024
1 parent 5c4e03a commit 6e41076
Show file tree
Hide file tree
Showing 21 changed files with 310 additions and 14 deletions.
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Given two spherical arcs [point0, point1] and [point2, point3], returns their in

## Projections

d3-geo-polygon adds polygon clipping to the polyhedral projections from [d3-geo-projection](https://github.com/d3/d3-geo-projection). Thus, it supersedes the following symbols:
d3-geo-polygon adds polygon clipping to the polyhedral and interrupted projections from [d3-geo-projection](https://github.com/d3/d3-geo-projection). Thus, it supersedes the following symbols:

<a href="#geoPolyhedral" name="geoPolyhedral">#</a> d3.<b>geoPolyhedral</b>(<i>tree</i>, <i>face</i>) · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/polyhedral/index.js), [Examples](https://observablehq.com/@fil/polyhedral-projections-with-d3-geo-polygon)

Expand All @@ -78,6 +78,61 @@ The Collignon butterfly projection.

A butterfly projection inspired by Steve Waterman’s design.

<a href="#geoBerghaus" name="geoBerghaus">#</a> d3.<b>geoBerghaus</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoBerghaus.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

The Berghaus projection.

<a href="#geoGingery" name="geoGingery">#</a> d3.<b>geoGingery</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoGingery.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

The Gingery projection.

<a href="#geoHealpix" name="geoHealpix">#</a> d3.<b>geoHealpix</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoHealpix.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

The HEALPix projection.

<a href="#geoInterruptedBoggs" name="geoInterruptedBoggs">#</a> d3.<b>geoInterruptedBoggs</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoInterruptedBoggs.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

Bogg’s interrupted eumorphic projection.

<a href="#geoInterruptedHomolosine" name="geoInterruptedHomolosine">#</a> d3.<b>geoInterruptedHomolosine</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoInterruptedHomolosine.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

Goode’s interrupted homolosine projection.

<a href="#geoInterruptedMollweide" name="geoInterruptedMollweide">#</a> d3.<b>geoInterruptedMollweide</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoInterruptedMollweide.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

Goode’s interrupted Mollweide projection.

<a href="#geoInterruptedMollweideHemispheres" name="geoInterruptedMollweideHemispheres">#</a> d3.<b>geoInterruptedMollweideHemispheres</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoInterruptedMollweideHemispheres.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

The Mollweide projection interrupted into two (equal-area) hemispheres.

<a href="#geoInterruptedSinuMollweide" name="geoInterruptedSinuMollweide">#</a> d3.<b>geoInterruptedSinuMollweide</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoInterruptedSinuMollweide.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

Alan K. Philbrick’s interrupted sinu-Mollweide projection.

<a href="#geoInterruptedSinusoidal" name="geoInterruptedSinusoidal">#</a> d3.<b>geoInterruptedSinusoidal</b> · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/reclip.js)

[<img src="https://raw.githubusercontent.com/d3/d3-geo-polygon/main/test/snapshots/geoInterruptedSinusoidal.png" width="480" height="250">](https://observablehq.com/@d3/interrupted-clipped)

An interrupted sinusoidal projection with asymmetrical lobe boundaries.


New projections are introduced:

<a href="#geoPolyhedralVoronoi" name="geoPolyhedralVoronoi">#</a> d3.<b>geoPolyhedralVoronoi</b>([<i>parents</i>], [<i>polygons</i>], [<i>faceProjection</i>], [<i>faceFind</i>]) · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/polyhedral/voronoi.js)
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"sideEffects": false,
"dependencies": {
"d3-array": "2.5.0 - 3",
"d3-geo": "2 - 3"
"d3-geo": "2 - 3",
"d3-geo-projection": "4"
},
"devDependencies": {
"@rollup/plugin-terser": "0.4",
Expand Down
12 changes: 12 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,15 @@ export {default as geoRhombic} from "./rhombic.js";
export {default as geoDeltoidal} from "./deltoidal.js";
export {default as geoCahillKeyes, cahillKeyesRaw as geoCahillKeyesRaw} from "./cahillKeyes.js";
export {default as geoComplexLog, complexLogRaw as geoComplexLogRaw} from "./complexLog.js";
export {
geoBerghaus,
geoGingery,
geoHealpix,
geoInterrupt,
geoInterruptedBoggs,
geoInterruptedHomolosine,
geoInterruptedMollweide,
geoInterruptedMollweideHemispheres,
geoInterruptedSinuMollweide,
geoInterruptedSinusoidal
} from "./reclip.js";
99 changes: 99 additions & 0 deletions src/reclip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {merge} from "d3-array";
import {geoInterpolate} from "d3-geo";
import {
geoBerghaus as berghaus,
geoGingery as gingery,
geoHealpix as healpix,
geoInterrupt as interrupt,
geoInterruptedBoggs as interruptedBoggs,
geoInterruptedHomolosine as interruptedHomolosine,
geoInterruptedMollweide as interruptedMollweide,
geoInterruptedMollweideHemispheres as interruptedMollweideHemispheres,
geoInterruptedSinuMollweide as interruptedSinuMollweide,
geoInterruptedSinusoidal as interruptedSinusoidal,
} from "d3-geo-projection";
import geoClipPolygon from "./clip/polygon.js";

/**
* Reclip projections from d3-geo-projection
*/
export function geoBerghaus() { return reclip(berghaus.apply(this, arguments)); }
export function geoGingery() { return reclip(gingery.apply(this, arguments)); }
export function geoHealpix() { return reclip(healpix.apply(this, arguments), true); }
export function geoInterrupt() { return clipInterrupted(interrupt.apply(this, arguments)); }
export function geoInterruptedBoggs() { return clipInterrupted(interruptedBoggs.apply(this, arguments)); }
export function geoInterruptedHomolosine() { return clipInterrupted(interruptedHomolosine.apply(this, arguments)); }
export function geoInterruptedMollweide() { return clipInterrupted(interruptedMollweide.apply(this, arguments)); }
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)); }

function reclip(projection, vertical = false) {
const {lobes} = projection;
function reset(projection) {
const rotate = projection.rotate();
const scale = projection.scale();
const translate = projection.translate();
projection.rotate([0, 0]).translate([0, 0]);
projection.lobes = function (_) {
return !arguments.length ? lobes() : reset(lobes(_));
};

projection.preclip((stream) => stream); // clipNone
const R = 1 - 1e-7;
const Rx = vertical ? 1 : R;
let points = [];
projection
.stream({
point(x, y) {
points.push([x * Rx, y * R]);
},
lineStart() {},
lineEnd() {},
polygonStart() {},
polygonEnd() {},
sphere() {},
})
.sphere();

projection.scale(scale);
points = points.map(projection.invert);
points.push(points[0]);

return projection
.rotate(rotate)
.translate(translate)
.preclip(geoClipPolygon({ type: "Polygon", coordinates: [points] }));
}
return reset(projection);
}

function clipInterrupted(projection) {
const { lobes } = projection;
function reset(projection) {
const l = lobes?.();
const polygon = merge(
Array.from(l, (d, i) => {
const hemisphere = d.flatMap(
(q) => Array.from(q, (p) => geoInterpolate(p, [q[1][0], 0])(1e-9)) // pull inside each lobe
);
return i === 0
? hemisphere // north
: [...hemisphere].reverse();
})
);

projection.lobes = function (_) {
return !arguments.length ? lobes() : reset(lobes(_));
};

return projection.preclip(
geoClipPolygon({
type: "Polygon",
coordinates: [[...polygon, polygon[0]]],
})
);
}

return reset(projection);
}
135 changes: 125 additions & 10 deletions test/snapshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ import { Canvas } from "canvas";
import { readFile } from "fs/promises";
import { feature } from "topojson-client";
import { geoGraticule, geoPath } from "d3-geo";
import { geoHomolosineRaw } from "d3-geo-projection";
import {
geoAirocean,
geoBerghaus,
geoCox,
geoCahillKeyes,
geoComplexLog,
geoCubic,
geoDeltoidal,
geoGingery,
geoHealpix,
geoInterrupt,
geoInterruptedBoggs,
geoInterruptedHomolosine,
geoInterruptedMollweide,
geoInterruptedMollweideHemispheres,
geoInterruptedSinuMollweide,
geoInterruptedSinusoidal,
geoRhombic,
geoDodecahedral,
geoIcosahedral,
Expand All @@ -28,7 +39,9 @@ async function renderWorld(projection, { extent, clip = false } = {}) {
extent === undefined
? { type: "Sphere" }
: graticule.extent(extent).outline();
const world = JSON.parse(await readFile("./node_modules/world-atlas/world/50m.json"));
const world = JSON.parse(
await readFile("./node_modules/world-atlas/world/50m.json")
);
const canvas = new Canvas(width, height);
const context = canvas.getContext("2d");
const path = geoPath(projection, context);
Expand Down Expand Up @@ -122,6 +135,106 @@ export async function tetrahedralLeeSouth() {
);
}

// reclip
export async function berghaus() {
return renderWorld(geoBerghaus());
}

export async function gingery() {
return renderWorld(geoGingery());
}

export async function berghaus7() {
return renderWorld(geoBerghaus().lobes(7).fitSize([960, 500], { type: "Sphere" }));
}

export async function berghaus13() {
return renderWorld(geoBerghaus().lobes(13).fitSize([960, 500], { type: "Sphere" }));
}

export async function gingery7() {
return renderWorld(geoGingery().lobes(7).fitSize([960, 500], { type: "Sphere" }));
}

export async function gingery3() {
return renderWorld(geoGingery().lobes(3).fitSize([960, 500], { type: "Sphere" }));
}

export async function goodeOcean() {
return renderWorld(
geoInterrupt(geoHomolosineRaw, [
[
[
[-180, 0],
[-130, 90],
[-95, 0],
],
[
[-95, 0],
[-30, 90],
[55, 0],
],
[
[55, 0],
[120, 90],
[180, 0],
],
],
[
[
[-180, 0],
[-120, -90],
[-60, 0],
],
[
[-60, 0],
[20, -90],
[85, 0],
],
[
[85, 0],
[140, -90],
[180, 0],
],
],
])
.rotate([-204, 0])
.precision(0.1)
);
}

export async function interruptedBoggs() {
return renderWorld(geoInterruptedBoggs());
}

export async function healpix() {
return renderWorld(geoHealpix());
}

export async function healpix5() {
return renderWorld(geoHealpix().lobes(5));
}

export async function interruptedHomolosine() {
return renderWorld(geoInterruptedHomolosine());
}

export async function interruptedMollweide() {
return renderWorld(geoInterruptedMollweide());
}

export async function interruptedMollweideHemispheres() {
return renderWorld(geoInterruptedMollweideHemispheres());
}

export async function interruptedSinuMollweide() {
return renderWorld(geoInterruptedSinuMollweide());
}

export async function interruptedSinusoidal() {
return renderWorld(geoInterruptedSinusoidal());
}

// more tests

// https://github.com/d3/d3-geo-polygon/issues/7
Expand Down Expand Up @@ -150,17 +263,19 @@ export async function rhombic00() {

// https://github.com/d3/d3-geo-polygon/issues/62
export async function rhombicHalf1() {
return renderWorld(geoRhombic()
.parents([-1, 0, 6, 2, 1, 9, 11, 3, 4, 8, 6, 10])
.precision(0.1)
.fitSize([960, 500], { type: "Sphere" })
return renderWorld(
geoRhombic()
.parents([-1, 0, 6, 2, 1, 9, 11, 3, 4, 8, 6, 10])
.precision(0.1)
.fitSize([960, 500], { type: "Sphere" })
);
}
export async function rhombicHalf2() {
return renderWorld(geoRhombic()
.parents([4, 0, 6, 2, 1, 9, 11, 3, 4, 8, -1, 10])
.angle(-19.5)
.precision(0.1)
.fitSize([960, 500], { type: "Sphere" })
return renderWorld(
geoRhombic()
.parents([4, 0, 6, 2, 1, 9, 11, 3, 4, 8, -1, 10])
.angle(-19.5)
.precision(0.1)
.fitSize([960, 500], { type: "Sphere" })
);
}
Binary file added test/snapshots/berghaus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/berghaus13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/berghaus7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/gingery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/gingery3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/gingery7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/goodeOcean.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/healpix.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/healpix5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/interruptedBoggs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/interruptedHomolosine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/interruptedMollweide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/interruptedSinuMollweide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/snapshots/interruptedSinusoidal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 6e41076

Please sign in to comment.