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

TypeError: Cannot read properties of undefined (reading 'depth') #155

Open
platypii opened this issue Jan 2, 2023 · 4 comments
Open

TypeError: Cannot read properties of undefined (reading 'depth') #155

platypii opened this issue Jan 2, 2023 · 4 comments

Comments

@platypii
Copy link

platypii commented Jan 2, 2023

I think I found a bug that results in an exception being thrown. It happens when doing a union of two polygons. The polygons in this example have a co-linear edge, although I don't know if that's relevant.

martinez

I made a test case which demonstrates the problem. Add this under test/genericTestCases/colinear.json:

{
  "features": [
    {
      "geometry": {
        "coordinates": [
          [
            [ -5, 0 ],
            [ 4.707106781186547, 0.7071067811865477 ],
            [ 3.477592250072517, 0.7836116248912244 ],
            [ 4, 1 ],
            [ -5, 0 ]
          ]
        ],
        "type": "Polygon"
      },
      "properties": {},
      "type": "Feature"
    },
    {
      "geometry": {
        "coordinates": [
          [
            [ 4.707106781186547, 0.7071067811865477 ],
            [ 4, 1 ],
            [ 0, 1 ],
            [ 4.707106781186547, 0.7071067811865477 ]
          ]
        ],
        "type": "Polygon"
      },
      "properties": {},
      "type": "Feature"
    },
    {
      "geometry": {
        "coordinates": [
          [
            [
              [ -5, 0 ],
              [ 4.707106781186547, 0.7071067811865477 ],
              [ 4, 1 ],
              [ 0, 1 ],
              [ 2.564081902288895, 0.8404535446987661 ],
              [ -5, 0 ]
            ]
          ]
        ],
        "type": "MultiPolygon"
      },
      "properties": {"operation": "union"},
      "type": "Feature"
    }
  ],
  "type": "FeatureCollection"
}

Running npm test then gives the exception:

/code/martinez/src/connect_edges.js:121
      contour.depth = contours[lowerContourId].depth;
                                               ^

TypeError: Cannot read properties of undefined (reading 'depth')
    at initializeContourFromContext (/code/martinez/src/connect_edges.js:121:48)
    at connectEdges (/code/martinez/src/connect_edges.js:150:21)
    at boolean (/code/martinez/src/index.js:76:20)
    at Object.union [as op] (/code/martinez/index.js:10:10)

The exception happens on connect_edges.js line 121 here.

Tracing the error backwards reveals that lowerContourId is set to -1.

@platypii
Copy link
Author

platypii commented Jan 3, 2023

Some good old fashioned println in connect_edges.js helps to understand what's happening here. I mapped vertices to letters to make it easier to follow. The operation is a union of polygons ABEC and BCD.

colinear

connectEdges sortedEvents = AB,AF,DF,DC,FA,FD,FE,FC,EB,EC,EF,EB,CE,CF,CD,CB,BA,BE,BE,BC
connectEdges resultEvents AB,AF,DF,DC,FA,FD,EC,CE,CD,CB,BA,BC
connectEdges start contour AB
initializeContourFromContext AB
connectEdges process edge AB
connectEdges process edge BC
connectEdges process edge CD
connectEdges process edge DF
connectEdges process edge FA
connectEdges output contour ABCDFA
connectEdges unprocessed edges: EC,CE
connectEdges start contour EC
initializeContourFromContext EC
Error: invalid lowerContourId -1
TypeError: Cannot read properties of undefined (reading 'depth')

The subdivision step seems okay. Inside connectEdges, the implementation finds the correct polygon ABCDF first. But then there is a leftover edge EC, and the implementation doesn't handle it properly and crashes. I am still working on understanding the algorithm, but it seems like either 1) the edge EC should have been filtered out of resultEvents as part of the orderEvents function at the beginning, or 2) it should have been pruned when EC gets processed later.

@platypii
Copy link
Author

platypii commented Jan 9, 2023

Even simpler example:

colinear2

Which can be reproduced using:

const boolean = require('martinez-polygon-clipping')

const poly1 = [[
  [ 4.707106781186547, 0.7071067811865477 ], // B
  [ 4, 1 ], // C
  [ 3.05083120710486, 0.8101662414209719 ], // G
  [ 4.707106781186547, 0.7071067811865477 ] // B
]]
const poly2 = [[
  [ 2, 0.75 ], // A
  [ 4.707106781186547, 0.7071067811865477 ], // B
  [ 3.477592250072517, 0.7836116248912244 ], // E
  [ 4, 1 ], // C
  [ 2, 0.75 ] // A
]]

const out = boolean.union(poly1, poly2)
TypeError: Cannot read properties of undefined (reading 'depth')

There seems to be an issue with the order in which nodes are processed for splitting GB at point E. Will continue digging...

@platypii
Copy link
Author

I have isolated the cause of this bug. The implementation of segment_intersection returns no intersections for edges EB and GB.

A minimal failing test case can be added to segment_intersection.test.js:

  const E = [3.477592250072517, 0.7836116248912244];
  const B = [4.707106781186547, 0.7071067811865477];
  const G = [3.05083120710486, 0.8101662414209719];
  t.deepEqual(intersection(E, B, G, B, true), [E, B], 'partial overlap');

This happens because the check for parallel lines is strict if (sqrKross > 0). Unfortunately computing the cross product is prone to catastrophic cancellation. Since the cross product is the difference of two large numbers, floating point errors make it unlikely that the cross product comes out to exactly zero, even if the lines are actually parallel.

I wanted to see if this was a problem specific to w8r/martinez or if it was a more general problem across implementations. The reference C++ implementation, and the other javascript implementations all handled this case correctly. Interestingly, the only implementation I found that also crashed on this example was the rust 21re/rust-geo-booleanop which is ported directly from this repo.

I think the answer is to un-comment the epsilon check here. However, the mfogel implementation of getIntersection is quite interesting too.

@w8r
Copy link
Owner

w8r commented Jan 12, 2023 via email

wyxloading pushed a commit to wyxloading/martinez that referenced this issue Apr 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants