d3 / d3-contour

Compute contour polygons using marching squares.
https://d3js.org/d3-contour
ISC License
493 stars 62 forks source link

Option to remove points along straight lines? #14

Open mbostock opened 7 years ago

mbostock commented 7 years ago

We currently emit one point per pixel in the grid, which leads to many extra coordinates along straight edges. For example this:

{
  "type": "MultiPolygon",
  "value": 0.5,
  "coordinates": [
    [
      [[6, 7.5], [6, 6.5], [6, 5.5], [6, 4.5], [6, 3.5], [5.5, 3], [4.5, 3],
       [3.5, 3], [3, 3.5], [3, 4.5], [3, 5.5], [3, 6.5], [3, 7.5], [3.5, 8],
       [4.5, 8], [5.5, 8], [6, 7.5]]
    ]
  ]
}

Could be reduce to this:

{
  "type": "MultiPolygon",
  "value": 0.5,
  "coordinates": [
    [
      [[6, 7.5], [6, 3.5], [5.5, 3], [3.5, 3], [3, 3.5], [3, 7.5], [3.5, 8], [5.5, 8], [6, 7.5]]
    ]
  ]
}
mbostock commented 7 years ago

The tricky thing is that for geographic coordinates (as in GeoTIFF Contours II) we want to retain the intersecting points along the antimeridian, which may not be possible if we eliminate all the straight line points. Also, parallels are not great arcs, so removing these points changes the interpretation in spherical coordinates.

purejoy commented 4 years ago

Is it possible to add an option to remove the rectangular outer border when it's part of the generated path? RaumZeit/MarchingSquares.js provided a noFrameoption to remove it.

Fil commented 4 years ago

Contour simplification could be an option of d3-contour, but I believe it's enough to leave this to topojson as in https://observablehq.com/d/4fe65b865ccf545d

The tricky thing is for geographic coordinates

Shameless plug: for spatial contours we can use d3-geo-voronoi’s geoContour.

Fil commented 4 years ago

Regarding noFrame it seems that the solution is to filter out the multipolygons that have exactly 1 polygon with 1 ring with an area equal to n * m - 1/2, like so:

      p.coordinates.length === 1 &&
      p.coordinates[0].length === 1 &&
      2 * d3.polygonArea(p.coordinates[0][0]) === 2 * n * m - 1

The solution works both with smoothed/non smoothed contours. It seems to work for all n, m but we can fortify it against calculation drift by testing d3.polygonArea(p.coordinates[0][0]) > n * m - 3/4

The test is fast enough (exits immediately on most polygons, and 2*area is computed anyway), so it could be added at no cost as a property to the polygons as a property polygon.sphere or polygon.isFrame, leaving the API otherwise unchanged. It can also be left to the user as shown here.

Fil commented 4 years ago

Re: "noFrame", I think that returning the area (since we've computed it) as p.area could be the simplest solution. Beyond "filtering out frames", it can be used to compute stats. (Implemented in #47)

Re: contour simplification, I propose to leave that to topojson.

All in all, closing this issue. Feel free to comment/reopen etc as necessary.

mbostock commented 4 years ago

I don’t think #47 is sufficiently helpful here: the points along straight lines occur not only when a contour polygon covers the entire frame, but whenever any contour polygon abuts the frame. In other words the darker blue polygons in the #47 example also have many points along straight lines that could be removed.

I think what I want here is to detect perfectly horizontal (x1 === x2) or perfectly vertical (y1 === y2) line segments and remove the extraneous intermediate points. But per https://github.com/d3/d3-contour/issues/14#issuecomment-310909722 this should be optional.

Fil commented 4 years ago

Yes, #47 was only about the "noFrame" sub-issue.