diff --git a/README.md b/README.md
index 0627251..f810e32 100644
--- a/README.md
+++ b/README.md
@@ -42,9 +42,13 @@ const projection = d3.geoEquirectangular()
Given a GeoJSON *polygon* or *multipolygon*, returns a clip function suitable for [_projection_.preclip](https://github.com/d3/d3-geo#preclip).
-# clip.polygon()
+# clip.polygon([geometry])
-Given a clipPolygon function, returns the GeoJSON polygon.
+If geometry is specified, sets the clipping polygon to the geometry and returns a new clip function. Otherwise returns the clipping polygon.
+
+# clip.clipPoint([clipPoint])
+
+Whether the projection should clip points. If clipPoint is false, the clip function only clips line and polygon geometries. If clipPoint is true, points outside the clipping polygon are not projected. Typically set to false when the projection covers the whole sphere, to make sure that all points —even those on the edge of the clipping polygon— get projected.
# d3.geoIntersectArc(arcs) · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/intersect.js), [Examples](https://observablehq.com/@fil/spherical-intersection)
@@ -58,6 +62,8 @@ d3-geo-polygon adds polygon clipping to the polyhedral and interrupted projectio
Defines a new polyhedral projection. The *tree* is a spanning tree of polygon face nodes; each *node* is assigned a *node*.transform matrix. The *face* function returns the appropriate *node* for a given *lambda* and *phi* in radians.
+Polyhedral projections’ default **clipPoint** depends on whether the clipping polygon covers the whole sphere. When the polygon’s area is almost complete (larger than 4π minus .1 steradian), clipPoint is set to false, and all point geometries are displayed, even if they (technically) fall outside the clipping polygon. For smaller polygons, clipPoint defaults to true, thus hiding points outside the clipping region.
+
# polyhedral.tree() returns the spanning tree of the polyhedron, from which one can infer the faces’ centers, polygons, shared edges etc.
# d3.geoPolyhedralButterfly() · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/polyhedral/butterfly.js)
diff --git a/src/clip/index.js b/src/clip/index.js
index 1ea704d..20c1758 100644
--- a/src/clip/index.js
+++ b/src/clip/index.js
@@ -4,7 +4,7 @@ import {epsilon, halfPi} from "../math.js";
import polygonContains from "../polygonContains.js";
import {merge} from "d3-array";
-export default function(pointVisible, clipLine, interpolate, start, sort) {
+export default function(pointVisible, clipLine, interpolate, start, sort, {clipPoint = false} = {}) {
if (typeof sort === "undefined") sort = compareIntersection;
return function(sink) {
@@ -47,7 +47,7 @@ export default function(pointVisible, clipLine, interpolate, start, sort) {
};
function point(lambda, phi) {
- if (pointVisible(lambda, phi)) sink.point(lambda, phi);
+ if ((!clipPoint && !ring) || pointVisible(lambda, phi)) sink.point(lambda, phi);
}
function pointLine(lambda, phi) {
diff --git a/src/clip/polygon.js b/src/clip/polygon.js
index 08f1668..daac353 100644
--- a/src/clip/polygon.js
+++ b/src/clip/polygon.js
@@ -7,19 +7,13 @@ import polygonContains from "../polygonContains.js";
const clipNone = (stream) => stream;
// clipPolygon
-export default function(geometry) {
- function clipGeometry(geometry) {
- let polygons;
-
- if (geometry.type === "MultiPolygon") {
- polygons = geometry.coordinates;
- } else if (geometry.type === "Polygon") {
- polygons = [geometry.coordinates];
- } else {
- return clipNone;
- }
+export default function (geometry) {
+ let clipPoint = true;
- const clips = polygons.map((polygon) => {
+ function clipGeometry(geometry) {
+ if (geometry.type === "Polygon") geometry = {type: "MultiPolygon", coordinates: [geometry.coordinates]};
+ if (geometry.type !== "MultiPolygon") return clipNone;
+ const clips = geometry.coordinates.map((polygon) => {
polygon = polygon.map(ringRadians);
const pointVisible = visible(polygon);
const segments = ringSegments(polygon[0]); // todo holes?
@@ -28,7 +22,8 @@ export default function(geometry) {
clipLine(segments, pointVisible),
interpolate(segments, polygon),
polygon[0][0],
- clipPolygonSort
+ clipPolygonSort,
+ {clipPoint}
);
});
@@ -56,7 +51,8 @@ export default function(geometry) {
};
}
- clipPolygon.polygon = (_) => _ ? ((geometry = _), clipGeometry(geometry)) : geometry;
+ clipPolygon.polygon = (_) => _ !== undefined ? clipGeometry(geometry = _) : geometry;
+ clipPolygon.clipPoint = (_) => _ !== undefined ? ((clipPoint = !!_), clipGeometry(geometry)) : clipPoint;
return clipPolygon;
}
diff --git a/src/polyhedral/index.js b/src/polyhedral/index.js
index 1c6607a..1349a61 100644
--- a/src/polyhedral/index.js
+++ b/src/polyhedral/index.js
@@ -1,4 +1,4 @@
-import {geoBounds as bounds, geoCentroid as centroid, geoInterpolate as interpolate, geoProjection as projection} from "d3-geo";
+import {geoArea, geoBounds as bounds, geoCentroid as centroid, geoInterpolate as interpolate, geoProjection as projection} from "d3-geo";
import clipPolygon from "../clip/polygon.js";
import {abs, degrees, epsilon, radians} from "../math.js";
import matrix, {multiply, inverse} from "./matrix.js";
@@ -78,10 +78,11 @@ export default function(tree, face) {
const proj = projection(forward);
// run around the mesh of faces and stream all vertices to create the clipping polygon
- const polygon = [];
- outline({point: function(lambda, phi) { polygon.push([lambda, phi]); }}, tree);
- polygon.push(polygon[0]);
- proj.preclip(clipPolygon({ type: "Polygon", coordinates: [ polygon ] }));
+ const p = [];
+ const geometry = {type: "MultiPolygon", coordinates: [[p]]};
+ outline({point: (lambda, phi) => p.push([lambda, phi])}, tree);
+ p.push(p[0]);
+ proj.preclip(clipPolygon(geometry).clipPoint(geoArea(geometry) < 4 * Math.PI - 0.1));
proj.tree = function() { return tree; };
return proj;
diff --git a/test/snapshots.js b/test/snapshots.js
index 7fed37a..9bb59df 100644
--- a/test/snapshots.js
+++ b/test/snapshots.js
@@ -6,6 +6,7 @@ import { geoHomolosineRaw } from "d3-geo-projection";
import {
geoAirocean,
geoBerghaus,
+ geoClipPolygon,
geoCox,
geoCahillKeyes,
geoComplexLog,
@@ -33,7 +34,7 @@ import {
const width = 960;
const height = 500;
-async function renderWorld(projection, { extent, clip = false } = {}) {
+async function renderWorld(projection, { points, extent, clip = false } = {}) {
const graticule = geoGraticule();
const outline =
extent === undefined
@@ -66,6 +67,12 @@ async function renderWorld(projection, { extent, clip = false } = {}) {
path(outline);
context.strokeStyle = "#000";
context.stroke();
+ if (points) {
+ context.beginPath();
+ path({type: "MultiPoint", coordinates: points});
+ context.fillStyle = "steelblue";
+ context.fill();
+ }
return canvas;
}
@@ -279,3 +286,49 @@ export async function rhombicHalf2() {
.fitSize([960, 500], { type: "Sphere" })
);
}
+
+// https://github.com/d3/d3-geo-polygon/issues/4
+export async function clipPointWorld() {
+ return renderWorld(geoRhombic(), {points: [
+ [0, 0],
+ [10 - 0.0001, 0],
+ [10 + 0.0001, 0],
+ [10, -10],
+ [10, -20],
+ ]});
+}
+export async function clipPointTrue() {
+ const projection = geoRhombic();
+ const polygon = projection.preclip().polygon();
+ projection.preclip(geoClipPolygon(polygon).clipPoint(true));
+ return renderWorld(projection, {points: [
+ [0, 0],
+ [10 - 0.0001, 0],
+ [10 + 0.0001, 0],
+ [10, -10],
+ [10, -20],
+ ]});
+}
+export async function clipPointSmall() {
+ const projection = geoRhombic().parents([-1, 0, 6, 2, 1, 9, 11, 3, 4, 8, 6, 10]);
+ return renderWorld(projection, {points: [
+ [0, 0],
+ [10 - 0.0001, 0],
+ [10 + 0.0001, 0],
+ [10, -10],
+ [10, -20],
+ ]});
+}
+export async function clipPointFalse() {
+ const projection = geoRhombic();
+ projection.preclip(geoClipPolygon(
+ geoRhombic().parents([-1, 0, 6, 2, 1, 9, 11, 3, 4, 8, 6, 10]).preclip().polygon()).clipPoint(false)
+ );
+ return renderWorld(projection, {points: [
+ [0, 0],
+ [10 - 0.0001, 0],
+ [10 + 0.0001, 0],
+ [10, -10],
+ [10, -20],
+ ]});
+}
diff --git a/test/snapshots/clipPointFalse.png b/test/snapshots/clipPointFalse.png
new file mode 100644
index 0000000..8597c75
Binary files /dev/null and b/test/snapshots/clipPointFalse.png differ
diff --git a/test/snapshots/clipPointSmall.png b/test/snapshots/clipPointSmall.png
new file mode 100644
index 0000000..03c81c3
Binary files /dev/null and b/test/snapshots/clipPointSmall.png differ
diff --git a/test/snapshots/clipPointTrue.png b/test/snapshots/clipPointTrue.png
new file mode 100644
index 0000000..ad2c822
Binary files /dev/null and b/test/snapshots/clipPointTrue.png differ
diff --git a/test/snapshots/clipPointWorld.png b/test/snapshots/clipPointWorld.png
new file mode 100644
index 0000000..e3f5e8d
Binary files /dev/null and b/test/snapshots/clipPointWorld.png differ