Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clipPoint #67

Merged
merged 1 commit into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

<a name="polygon" href="#polygon">#</a> clip.<b>polygon</b>()
<a name="polygon" href="#polygon">#</a> clip.<b>polygon</b>([<i>geometry</i>])

Given a clipPolygon function, returns the GeoJSON polygon.
If <i>geometry</i> is specified, sets the clipping polygon to the geometry and returns a new <i>clip</i> function. Otherwise returns the clipping polygon.

<a name="polygon" href="#clipPoint">#</a> clip.<b>clipPoint</b>([<i>clipPoint</i>])

Whether the projection should clip points. If <i>clipPoint</i> is false, the clip function only clips line and polygon geometries. If <i>clipPoint</i> 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.

<a name="geoIntersectArc" href="#geoIntersectArc">#</a> d3.<b>geoIntersectArc</b>(<i>arcs</i>) · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/intersect.js), [Examples](https://observablehq.com/@fil/spherical-intersection)

Expand All @@ -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.

<a href="#geoPolyhedral_tree" name="geoPolyhedral_tree">#</a> <i>polyhedral</i>.<b>tree</b>() returns the spanning tree of the polyhedron, from which one can infer the faces’ centers, polygons, shared edges etc.

<a href="#geoPolyhedralButterfly" name="geoPolyhedralButterfly">#</a> d3.<b>geoPolyhedralButterfly</b>() · [Source](https://github.com/d3/d3-geo-polygon/blob/main/src/polyhedral/butterfly.js)
Expand Down
4 changes: 2 additions & 2 deletions src/clip/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 10 additions & 14 deletions src/clip/polygon.js
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -28,7 +22,8 @@ export default function(geometry) {
clipLine(segments, pointVisible),
interpolate(segments, polygon),
polygon[0][0],
clipPolygonSort
clipPolygonSort,
{clipPoint}
);
});

Expand Down Expand Up @@ -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;
}
Expand Down
11 changes: 6 additions & 5 deletions src/polyhedral/index.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
Expand Down
55 changes: 54 additions & 1 deletion test/snapshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { geoHomolosineRaw } from "d3-geo-projection";
import {
geoAirocean,
geoBerghaus,
geoClipPolygon,
geoCox,
geoCahillKeyes,
geoComplexLog,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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],
]});
}
Binary file added test/snapshots/clipPointFalse.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/clipPointSmall.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/clipPointTrue.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/clipPointWorld.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading