diff --git a/fixtures/polygonizer/input/split_touching_hole_priority_minimal.geojson b/fixtures/polygonizer/input/split_touching_hole_priority_minimal.geojson deleted file mode 100644 index 6683795..0000000 --- a/fixtures/polygonizer/input/split_touching_hole_priority_minimal.geojson +++ /dev/null @@ -1,5088 +0,0 @@ -{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 125.90951698705308, - 40.43259517121252 - ], - [ - 131.5657278864061, - 43.62650386261877 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 127.25000000493503, - 40.000000020662945 - ], - [ - 130.00000000493503, - 42.29999997297923 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.04153269216172, - 34.08855120110449 - ], - [ - 128.52127569600694, - 34.117076315163935 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.17710560247056, - 39.370860733270014 - ], - [ - 128.6598588792955, - 38.993371166467035 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.20969384595506, - 39.57254783081945 - ], - [ - 128.8976213304674, - 39.906483568429316 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.3500000287769, - 38.6000000445048 - ], - [ - 128.75000000493503, - 38.73333336989085 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.52127569600694, - 34.117076315163935 - ], - [ - 128.80560010358445, - 34.20564214157995 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.6598588792955, - 38.993371166467035 - ], - [ - 128.81053360646507, - 38.7464928466837 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.75000000493503, - 38.73333336989085 - ], - [ - 128.81053360646507, - 38.7464928466837 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.80560010358445, - 34.20564214157995 - ], - [ - 128.82334607526414, - 33.96274868416723 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.81053360646507, - 38.7464928466837 - ], - [ - 129.0409852831041, - 38.36890117096838 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.81053360646507, - 38.7464928466837 - ], - [ - 130.66666675107456, - 39.14999999682109 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 128.8976213304674, - 39.906483568429316 - ], - [ - 129.524619993416, - 40.352669395684565 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.0409852831041, - 38.36890117096838 - ], - [ - 129.6859936086809, - 37.53290168213781 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.524619993416, - 40.352669395684565 - ], - [ - 129.65171402379625, - 40.38446966576513 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.65171402379625, - 40.38446966576513 - ], - [ - 129.92245787068956, - 40.60253850388464 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.6859936086809, - 37.53290168213781 - ], - [ - 129.8942009775316, - 36.980742372750605 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.72433465406053, - 34.99017754959997 - ], - [ - 129.81864708348863, - 34.854704775094355 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.72433465406053, - 34.99017754959997 - ], - [ - 129.82541793271653, - 35.33559600281652 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.8173844186937, - 34.852948107003535 - ], - [ - 129.81864708348863, - 34.854704775094355 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.8173844186937, - 34.852948107003535 - ], - [ - 129.91005605146043, - 34.53202358651098 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.82541793271653, - 35.33559600281652 - ], - [ - 129.85676234647386, - 35.33519641327795 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.82918017789476, - 35.35807148384985 - ], - [ - 129.84717362805955, - 35.434294857262934 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.82918017789476, - 35.35807148384985 - ], - [ - 129.85632413312547, - 35.356952347039545 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.84717362805955, - 35.434294857262934 - ], - [ - 130.01083820745103, - 36.019300617455805 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.85632413312547, - 35.356952347039545 - ], - [ - 129.85676234647386, - 35.33519641327795 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.8942009775316, - 36.980742372750605 - ], - [ - 129.89674132749192, - 36.28144828247961 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.89674132749192, - 36.28144828247961 - ], - [ - 129.99217861577623, - 36.127263941048945 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.91005605146043, - 34.53202358651098 - ], - [ - 130.07497304364793, - 34.60854283738073 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.92245787068956, - 40.60253850388464 - ], - [ - 130.08875554486863, - 40.714631475686396 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 129.99217861577623, - 36.127263941048945 - ], - [ - 130.01083820745103, - 36.019300617455805 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.00000000493503, - 42.29999997297923 - ], - [ - 130.70000005261875, - 42.29999997297923 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.07497304364793, - 34.60854283738073 - ], - [ - 130.16649454518907, - 34.58200446534094 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.08875554486863, - 40.714631475686396 - ], - [ - 130.1707333891069, - 40.92548099923071 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.13608210965745, - 41.1895157473081 - ], - [ - 130.1707333891069, - 40.92548099923071 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.13608210965745, - 41.1895157473081 - ], - [ - 130.20811861440293, - 41.42399350571569 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.16649454518907, - 34.58200446534094 - ], - [ - 130.47096293851487, - 34.46426955628332 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.1946455804979, - 41.545595802544916 - ], - [ - 130.20811861440293, - 41.42399350571569 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.1946455804979, - 41.545595802544916 - ], - [ - 130.3428277341997, - 41.723245538949335 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.3428277341997, - 41.723245538949335 - ], - [ - 130.55572026654832, - 41.906751789330805 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.47096293851487, - 34.46426955628332 - ], - [ - 130.538611349312, - 34.59332148003515 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.538611349312, - 34.59332148003515 - ], - [ - 130.72174232884996, - 34.93990389275488 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.55572026654832, - 41.906751789330805 - ], - [ - 130.64999788686387, - 41.898079790353144 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.64999788686387, - 41.898079790353144 - ], - [ - 130.95063237021225, - 42.01540194237351 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.66666675107456, - 39.14999999682109 - ], - [ - 130.71666670339084, - 39.29999997297923 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.70000005261875, - 42.29999997297923 - ], - [ - 130.88333333032108, - 42.14999999682109 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.71666663571946, - 39.30000010895666 - ], - [ - 130.7166667520986, - 39.30000012568466 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.71666663571946, - 39.30000010895666 - ], - [ - 131.33333319112413, - 41.2333332674497 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.71666670339084, - 39.29999997297923 - ], - [ - 130.7166667520986, - 39.30000012568466 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.7166667520986, - 39.30000012568466 - ], - [ - 131.3333332587955, - 41.23333336989085 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.7166667520986, - 39.30000012568466 - ], - [ - 138.83333319112413, - 40.46666661667761 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.72174232884996, - 34.93990389275488 - ], - [ - 130.93242209836595, - 35.135094799279535 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.88333333032108, - 42.14999999682109 - ], - [ - 130.95063237021225, - 42.01540194237351 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.93242209836595, - 35.135094799279535 - ], - [ - 131.15051144048326, - 35.14206019806799 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.95063237021225, - 42.01540194237351 - ], - [ - 131.20000005261875, - 41.51666667143504 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 130.95063237021225, - 42.01540194237351 - ], - [ - 131.24879282399766, - 42.13175860810217 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.15051144048326, - 35.14206019806799 - ], - [ - 131.4794425336992, - 35.02358976769384 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.19999998494737, - 41.51666656899389 - ], - [ - 131.20000009032384, - 41.516666591311626 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.19999998494737, - 41.51666656899389 - ], - [ - 131.33333319112413, - 41.2333332674497 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.20000005261875, - 41.51666667143504 - ], - [ - 131.20000009032384, - 41.516666591311626 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.20000009032384, - 41.516666591311626 - ], - [ - 131.3333332587955, - 41.23333336989085 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.20000009032384, - 41.516666591311626 - ], - [ - 131.7847494451677, - 41.640510954140986 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.24879282399766, - 42.13175860810217 - ], - [ - 131.54680102750413, - 41.63907710480627 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4794425336992, - 35.02358976769384 - ], - [ - 131.75572007581346, - 35.13236252236303 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.54680102750413, - 41.63907710480627 - ], - [ - 131.7847494451677, - 41.640510954140986 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.5657278864061, - 43.62650386261877 - ], - [ - 138.98936479970567, - 47.93827024865087 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.75572007581346, - 35.13236252236303 - ], - [ - 132.2549039690172, - 35.52300898003515 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.2549039690172, - 35.52300898003515 - ], - [ - 132.33140629216783, - 35.651364721536005 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.33140629216783, - 35.651364721536005 - ], - [ - 132.54063599988572, - 35.80912128853735 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.49800151273362, - 36.18276206421789 - ], - [ - 132.54063599988572, - 35.80912128853735 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.49800151273362, - 36.18276206421789 - ], - [ - 132.74247520848863, - 36.39071170258459 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.74247520848863, - 36.39071170258459 - ], - [ - 133.07532280370347, - 36.6651131288999 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.79428833409898, - 41.85432139801916 - ], - [ - 132.87195676252, - 42.14065901207861 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.79428833409898, - 41.85432139801916 - ], - [ - 138.83333319112413, - 43.13333336281713 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.87195676252, - 42.14065901207861 - ], - [ - 133.02050417348497, - 42.13700334000524 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.02050417348497, - 42.13700334000524 - ], - [ - 134.29323309346788, - 42.59795466828283 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.07532280370347, - 36.6651131288999 - ], - [ - 133.29041117116563, - 36.695769466637934 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.29041117116563, - 36.695769466637934 - ], - [ - 133.58193343564622, - 36.640718139886225 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.58193343564622, - 36.640718139886225 - ], - [ - 133.79708975240342, - 36.400268472909296 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.66149013921373, - 35.92121640610632 - ], - [ - 133.80231684133165, - 36.14261380600866 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.66149013921373, - 35.92121640610632 - ], - [ - 133.86180251523606, - 35.91874019074377 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.79708975240342, - 36.400268472909296 - ], - [ - 133.80231684133165, - 36.14261380600866 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 133.86180251523606, - 35.91874019074377 - ], - [ - 134.44879120275132, - 36.085333027123774 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 134.29323309346788, - 42.59795466828283 - ], - [ - 135.03674643918626, - 42.94185701775488 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 134.44879120275132, - 36.085333027123774 - ], - [ - 135.13928526326768, - 36.11452404427465 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 135.03674643918626, - 42.94185701775488 - ], - [ - 135.55498760625474, - 43.348386205911005 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 135.13928526326768, - 36.11452404427465 - ], - [ - 135.26768606587999, - 36.10011307167944 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 135.26768606587999, - 36.10011307167944 - ], - [ - 135.5427400438463, - 36.0337859766477 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 135.5427400438463, - 36.0337859766477 - ], - [ - 135.5792080728685, - 36.12769261765417 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 135.55498760625474, - 43.348386205911005 - ], - [ - 136.51891320630662, - 44.21457020211157 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 135.5792080728685, - 36.12769261765417 - ], - [ - 135.7967533437883, - 36.46571508812841 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 135.7967533437883, - 36.46571508812841 - ], - [ - 136.24264758512132, - 36.91704694199499 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 136.22520512029283, - 37.27649609017309 - ], - [ - 136.24264758512132, - 36.91704694199499 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 136.22520512029283, - 37.27649609017309 - ], - [ - 136.4591709940111, - 37.613666214227045 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 136.4591709940111, - 37.613666214227045 - ], - [ - 136.53361028119676, - 37.931401886224116 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 136.51891320630662, - 44.21457020211157 - ], - [ - 138.20392030164354, - 45.515768446206415 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 136.53361028119676, - 37.931401886224116 - ], - [ - 136.67150610372178, - 38.15095535683569 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 136.67150610372178, - 38.15095535683569 - ], - [ - 137.07090657636277, - 38.17601887154516 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 136.98123782559983, - 33.820195831536616 - ], - [ - 137.14632242604844, - 34.126605905770624 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.07090657636277, - 38.17601887154516 - ], - [ - 137.28481930181138, - 38.005623497247065 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.14632242604844, - 34.126605905770624 - ], - [ - 137.60926574155442, - 34.31641379761633 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.28481930181138, - 38.005623497247065 - ], - [ - 137.59269922658555, - 37.80573479103979 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.59269922658555, - 37.80573479103979 - ], - [ - 137.78812735959642, - 37.82504764962133 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.60926574155442, - 34.31641379761633 - ], - [ - 138.3766569463884, - 34.222200073479975 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.75957888051622, - 38.13473073410925 - ], - [ - 137.78812735959642, - 37.82504764962133 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.75957888051622, - 38.13473073410925 - ], - [ - 137.99558108731858, - 38.469143308877314 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 137.99558108731858, - 38.469143308877314 - ], - [ - 138.18207519933335, - 38.58265987801489 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.18207519933335, - 38.58265987801489 - ], - [ - 138.41489642545335, - 38.7083932535642 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.20392030164354, - 45.515768446206415 - ], - [ - 139.8047358362352, - 46.931316770791376 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.3766569463884, - 34.222200073479975 - ], - [ - 138.4017750589525, - 33.916477360009516 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.41489642545335, - 38.7083932535642 - ], - [ - 138.63589852735154, - 38.6779452936643 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.63589852735154, - 38.6779452936643 - ], - [ - 138.80585139676683, - 38.593224682092035 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.64769495027042, - 33.99694361368815 - ], - [ - 138.65609503285862, - 34.011734506289166 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.65609503285862, - 34.011734506289166 - ], - [ - 138.6662064840076, - 34.02577080408732 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.6662064840076, - 34.02577080408732 - ], - [ - 138.67793286340213, - 34.038916966120404 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.67793286340213, - 34.038916966120404 - ], - [ - 138.691161279845, - 34.05104603449504 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.691161279845, - 34.05104603449504 - ], - [ - 138.70576465623355, - 34.062040588061016 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.70576465623355, - 34.062040588061016 - ], - [ - 138.72160208718753, - 34.07179453055064 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.72160208718753, - 34.07179453055064 - ], - [ - 138.73852086560703, - 34.080213209788006 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.73852086560703, - 34.080213209788006 - ], - [ - 138.75635755555606, - 34.08721532503764 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.75635755555606, - 34.08721532503764 - ], - [ - 138.77493942277408, - 34.0927329270045 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.77493942277408, - 34.0927329270045 - ], - [ - 138.79408681886173, - 34.09671272913615 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.79408681886173, - 34.09671272913615 - ], - [ - 138.81361437337375, - 34.09911622683207 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.80585139676683, - 38.593224682092035 - ], - [ - 138.89083927556626, - 38.72250143456396 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.81361437337375, - 34.09911622683207 - ], - [ - 138.83333302037693, - 34.099919935862225 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.83333302037693, - 34.099919935862225 - ], - [ - 138.8530516673801, - 34.09911622683207 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.83333319112413, - 40.46666661667761 - ], - [ - 138.83333319112413, - 43.13333336281713 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.8530516673801, - 34.09911622683207 - ], - [ - 138.87257922189212, - 34.09671272913615 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.87257922189212, - 34.09671272913615 - ], - [ - 138.89172673718906, - 34.0927329270045 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.89083927556626, - 38.72250143456396 - ], - [ - 139.2462448446428, - 38.86402050423559 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.89172673718906, - 34.0927329270045 - ], - [ - 138.91030860440708, - 34.08721532503764 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.91030860440708, - 34.08721532503764 - ], - [ - 138.92814517514682, - 34.080213209788006 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.92814517514682, - 34.080213209788006 - ], - [ - 138.94506395356632, - 34.07179453055064 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.94506395356632, - 34.07179453055064 - ], - [ - 138.9609013845203, - 34.062040588061016 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.9609013845203, - 34.062040588061016 - ], - [ - 138.97550476090885, - 34.05104603449504 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.97550476090885, - 34.05104603449504 - ], - [ - 138.98873317735172, - 34.038916966120404 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.98873317735172, - 34.038916966120404 - ], - [ - 139.00045955674625, - 34.02577080408732 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 138.98936479970567, - 47.93827024865087 - ], - [ - 139.91712563916795, - 52.984622396706904 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.00045955674625, - 34.02577080408732 - ], - [ - 139.01057112710453, - 34.011734506289166 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.01057112710453, - 34.011734506289166 - ], - [ - 139.01897109048343, - 33.99694361368815 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.0326718656694, - 42.200830139397944 - ], - [ - 139.1071223585283, - 41.359325088738764 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.0326718656694, - 42.200830139397944 - ], - [ - 139.4952282278215, - 43.59813109803137 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.08606141492479, - 39.15089575219091 - ], - [ - 139.1337110368883, - 39.32569900917944 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.08606141492479, - 39.15089575219091 - ], - [ - 139.2462448446428, - 38.86402050423559 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.1071223585283, - 41.359325088738764 - ], - [ - 139.2972156374132, - 41.24540988373693 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.1337110368883, - 39.32569900917944 - ], - [ - 139.23663419171922, - 39.451707996606196 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.23663419171922, - 39.451707996606196 - ], - [ - 139.4365171758806, - 39.546686805962885 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.26861279889695, - 39.938996710061396 - ], - [ - 139.26884001180284, - 40.07221619057592 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.26861279889695, - 39.938996710061396 - ], - [ - 139.36650222226731, - 39.72797123360571 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.26884001180284, - 40.07221619057592 - ], - [ - 139.48251074239366, - 40.486479677438105 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.2972156374132, - 41.24540988373693 - ], - [ - 139.4322516290819, - 41.26847974228796 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.3663107721483, - 47.49393431115087 - ], - [ - 139.8047358362352, - 46.931316770791376 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.3663107721483, - 47.49393431115087 - ], - [ - 139.8993086187517, - 46.937296070336664 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.36650222226731, - 39.72797123360571 - ], - [ - 139.5814499227678, - 39.54912487435278 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.40540665074937, - 40.512441076516474 - ], - [ - 139.4383768407976, - 40.5836807863706 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.40540665074937, - 40.512441076516474 - ], - [ - 139.48251074239366, - 40.486479677438105 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.4322516290819, - 41.26847974228796 - ], - [ - 139.59843319341294, - 41.21928469109472 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.4365171758806, - 39.546686805962885 - ], - [ - 139.5814499227678, - 39.54912487435278 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.4383768407976, - 40.5836807863706 - ], - [ - 139.4782778589403, - 40.76618400978979 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.4782778589403, - 40.76618400978979 - ], - [ - 139.95911353513353, - 41.0299002306455 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.4952282278215, - 43.59813109803137 - ], - [ - 139.85076373502366, - 43.93735281395849 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.59843319341294, - 41.21928469109472 - ], - [ - 139.60985058232896, - 41.17869059014257 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.60985058232896, - 41.17869059014257 - ], - [ - 139.74605959340684, - 41.109813369988764 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.74605959340684, - 41.109813369988764 - ], - [ - 139.92528456136338, - 41.15534583496984 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.83317535802476, - 47.51435343193945 - ], - [ - 139.8993086187517, - 46.937296070336664 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.83317535802476, - 47.51435343193945 - ], - [ - 140.28133171483628, - 47.81698814797338 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.84996860906236, - 34.32891146111425 - ], - [ - 139.94307988568895, - 33.887936987161005 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.84996860906236, - 34.32891146111425 - ], - [ - 140.09255927487962, - 34.568412937402094 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.85076373502366, - 43.93735281395849 - ], - [ - 140.02544063016526, - 44.79538074898657 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 139.92528456136338, - 41.15534583496984 - ], - [ - 139.95911353513353, - 41.0299002306455 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.02544063016526, - 44.79538074898657 - ], - [ - 140.09824222013108, - 44.97841707634863 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.09255927487962, - 34.568412937402094 - ], - [ - 140.32197588369004, - 34.719169534921015 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.09824222013108, - 44.97841707634863 - ], - [ - 140.2862655489122, - 45.05796877312597 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.28133171483628, - 47.81698814797338 - ], - [ - 140.6528064577257, - 48.20434943604406 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.2862655489122, - 45.05796877312597 - ], - [ - 140.65091794416062, - 45.489464678048456 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.32197588369004, - 34.719169534921015 - ], - [ - 141.5149969427263, - 35.47014776635107 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.65091794416062, - 45.489464678048456 - ], - [ - 140.7806996671831, - 45.701386846780146 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.6528064577257, - 48.20434943604406 - ], - [ - 140.91145199224107, - 48.913813032388056 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.7806996671831, - 45.701386846780146 - ], - [ - 140.99482315465562, - 45.772784866570795 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.8489047853624, - 46.249456562280024 - ], - [ - 140.93420713826768, - 46.5079869406217 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.8489047853624, - 46.249456562280024 - ], - [ - 141.0645357935106, - 45.840605415582026 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.91145199224107, - 48.913813032388056 - ], - [ - 141.10681241437547, - 49.74327413010534 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.93420713826768, - 46.5079869406217 - ], - [ - 141.19966119214646, - 46.611142553567255 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 140.99482315465562, - 45.772784866570795 - ], - [ - 141.14938896581285, - 45.7409438269132 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.0645357935106, - 45.840605415582026 - ], - [ - 141.3013078539049, - 45.773108639001215 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.09714835569017, - 50.360651411294306 - ], - [ - 141.11406081601731, - 50.76625791954931 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.09714835569017, - 50.360651411294306 - ], - [ - 141.21944325849168, - 50.056388534783686 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.10681241437547, - 49.74327413010534 - ], - [ - 141.21944325849168, - 50.056388534783686 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.11406081601731, - 50.76625791954931 - ], - [ - 141.15859073087327, - 50.932983555077875 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.14938896581285, - 45.7409438269132 - ], - [ - 141.31114452764146, - 45.67400280404028 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.15859073087327, - 50.932983555077875 - ], - [ - 141.39732497617356, - 50.926422275781 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.19966119214646, - 46.611142553567255 - ], - [ - 141.45194190427415, - 46.58491937088903 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.24745672628038, - 36.312237896203364 - ], - [ - 141.5149969427263, - 35.47014776635107 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.24745672628038, - 36.312237896203364 - ], - [ - 141.61631792470567, - 36.94684187340673 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.27482121869676, - 47.76082292962011 - ], - [ - 141.28433745786302, - 48.065703071832026 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.27482121869676, - 47.76082292962011 - ], - [ - 141.38881581708543, - 47.52238861489233 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.27743762418382, - 42.096488632439936 - ], - [ - 141.52109568997972, - 42.272095121621454 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.27743762418382, - 42.096488632439936 - ], - [ - 141.63888972684495, - 41.700976051568354 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.28433745786302, - 48.065703071832026 - ], - [ - 141.43522876187913, - 48.22684065270361 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.3013078539049, - 45.773108639001215 - ], - [ - 141.5244180528795, - 45.896376051186884 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.31114452764146, - 45.67400280404028 - ], - [ - 141.53762143537156, - 45.739799179314936 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.38881581708543, - 47.52238861489233 - ], - [ - 141.54725402280442, - 47.42903558182653 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.39732497617356, - 50.926422275781 - ], - [ - 141.71131127759568, - 50.92525688576635 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.43271296903245, - 48.799486793755854 - ], - [ - 141.43522876187913, - 48.22684065270361 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.43271296903245, - 48.799486793755854 - ], - [ - 141.62810319348924, - 49.068935312508906 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.45194190427415, - 46.58491937088903 - ], - [ - 141.69390672132127, - 47.160459198235834 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.52109568997972, - 42.272095121621454 - ], - [ - 142.8067597715532, - 41.841869034051264 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.5244180528795, - 45.896376051186884 - ], - [ - 141.54183476850145, - 45.74188891816076 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.53762143537156, - 45.739799179314936 - ], - [ - 141.54183476850145, - 45.74188891816076 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.54725402280442, - 47.42903558182653 - ], - [ - 141.69390672132127, - 47.160459198235834 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.61631792470567, - 36.94684187340673 - ], - [ - 142.5542249052202, - 39.1152125971311 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.62810319348924, - 49.068935312508906 - ], - [ - 141.76245468541734, - 49.89127699303564 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.63888972684495, - 41.700976051568354 - ], - [ - 141.95321696683519, - 41.360001243829096 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.64101952001207, - 50.56034890580114 - ], - [ - 141.71131127759568, - 50.92525688576635 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.64101952001207, - 50.56034890580114 - ], - [ - 141.8183862535631, - 50.205834783791865 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.76245468541734, - 49.89127699303564 - ], - [ - 141.8183862535631, - 50.205834783791865 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.9390384523546, - 40.95789519715246 - ], - [ - 141.95321696683519, - 41.360001243829096 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 141.9390384523546, - 40.95789519715246 - ], - [ - 142.5985295145189, - 39.74348346161779 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3645294, - 37.23227291 - ], - [ - 131.3666873, - 37.27146897 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3645294, - 37.23227291 - ], - [ - 131.3672068, - 37.19309722 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3666873, - 37.27146897 - ], - [ - 131.3736674, - 37.31030766 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3672068, - 37.19309722 - ], - [ - 131.3746863, - 37.15431884 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3736674, - 37.31030766 - ], - [ - 131.3854095, - 37.34841414 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3746863, - 37.15431884 - ], - [ - 131.3868887, - 37.11631032 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3854095, - 37.34841414 - ], - [ - 131.4018073, - 37.38542004 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.3868887, - 37.11631032 - ], - [ - 131.4036901, - 37.07943623 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4018073, - 37.38542004 - ], - [ - 131.4227089, - 37.4209671 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4036901, - 37.07943623 - ], - [ - 131.4249229, - 37.04404979 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4227089, - 37.4209671 - ], - [ - 131.4479179, - 37.45471069 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4249229, - 37.04404979 - ], - [ - 131.4503779, - 37.01048946 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4479179, - 37.45471069 - ], - [ - 131.4771952, - 37.48632319 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4503779, - 37.01048946 - ], - [ - 131.4798068, - 36.9790758 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4771952, - 37.48632319 - ], - [ - 131.5102612, - 37.51549728 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.4798068, - 36.9790758 - ], - [ - 131.5129239, - 36.9501085 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.5102612, - 37.51549728 - ], - [ - 131.5467982, - 37.541949 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.5129239, - 36.9501085 - ], - [ - 131.5494099, - 36.92386359 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.5467982, - 37.541949 - ], - [ - 131.5864539, - 37.56542057 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.5494099, - 36.92386359 - ], - [ - 131.588914, - 36.90059087 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.5864539, - 37.56542057 - ], - [ - 131.628844, - 37.58568304 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.588914, - 36.90059087 - ], - [ - 131.6310581, - 36.88051166 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.6159497978044, - 37.24040921335645 - ], - [ - 131.6789834344638, - 37.426338419556394 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.6159497978044, - 37.24040921335645 - ], - [ - 131.6924993976829, - 37.063692228175476 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.628844, - 37.58568304 - ], - [ - 131.673557, - 37.60253859 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.6310581, - 36.88051166 - ], - [ - 131.6754399, - 36.86381675 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.673557, - 37.60253859 - ], - [ - 131.7201573, - 37.61582252 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.6754399, - 36.86381675 - ], - [ - 131.7216367, - 36.85066465 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.6789834344638, - 37.426338419556394 - ], - [ - 131.79570674559747, - 37.494760171748474 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.6924993976829, - 37.063692228175476 - ], - [ - 131.88096982742374, - 36.990452460251525 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.7201573, - 37.61582252 - ], - [ - 131.7681902, - 37.62540497 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.7216367, - 36.85066465 - ], - [ - 131.7692093, - 36.84118017 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.7681902, - 37.62540497 - ], - [ - 131.8171865, - 37.6311922 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.7692093, - 36.84118017 - ], - [ - 131.817706, - 36.83545326 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.79570674559747, - 37.494760171748474 - ], - [ - 131.99086117128556, - 37.481714926898256 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.8171865, - 37.6311922 - ], - [ - 131.866667, - 37.63312759 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.817706, - 36.83545326 - ], - [ - 131.866667, - 36.83353824 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.866667, - 36.83353824 - ], - [ - 131.9156279, - 36.83545326 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.866667, - 37.63312759 - ], - [ - 131.9161475, - 37.6311922 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.88096982742374, - 36.990452460251525 - ], - [ - 132.04409187602525, - 37.05216214177735 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.9156279, - 36.83545326 - ], - [ - 131.9641247, - 36.84118017 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.9161475, - 37.6311922 - ], - [ - 131.9651438, - 37.62540497 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.9641247, - 36.84118017 - ], - [ - 132.0116973, - 36.85066465 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.9651438, - 37.62540497 - ], - [ - 132.0131767, - 37.61582252 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 131.99086117128556, - 37.481714926898256 - ], - [ - 132.1246261181188, - 37.33971272991575 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.0116973, - 36.85066465 - ], - [ - 132.0578941, - 36.86381675 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.0131767, - 37.61582252 - ], - [ - 132.059777, - 37.60253859 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.04409187602525, - 37.05216214177735 - ], - [ - 132.1340414978442, - 37.21699385927595 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.0578941, - 36.86381675 - ], - [ - 132.1022759, - 36.88051166 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.059777, - 37.60253859 - ], - [ - 132.1044899, - 37.58568304 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1022759, - 36.88051166 - ], - [ - 132.14442, - 36.90059087 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1044899, - 37.58568304 - ], - [ - 132.1468801, - 37.56542057 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1246261181188, - 37.33971272991575 - ], - [ - 132.1340414978442, - 37.21699385927595 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.14442, - 36.90059087 - ], - [ - 132.1839241, - 36.92386359 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1468801, - 37.56542057 - ], - [ - 132.1865358, - 37.541949 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1839241, - 36.92386359 - ], - [ - 132.22041, - 36.9501085 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1865358, - 37.541949 - ], - [ - 132.2230728, - 37.51549728 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1992415537936, - 37.140295005641654 - ], - [ - 132.2802151948092, - 37.29316541576363 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.1992415537936, - 37.140295005641654 - ], - [ - 132.32812844805989, - 37.0769105881086 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.22041, - 36.9501085 - ], - [ - 132.2535272, - 36.9790758 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.2230728, - 37.51549728 - ], - [ - 132.2561388, - 37.48632319 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.2535272, - 36.9790758 - ], - [ - 132.282956, - 37.01048946 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.2561388, - 37.48632319 - ], - [ - 132.2854161, - 37.45471069 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.2802151948092, - 37.29316541576363 - ], - [ - 132.36806856583578, - 37.24564160289258 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.282956, - 37.01048946 - ], - [ - 132.3084111, - 37.04404979 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.2854161, - 37.45471069 - ], - [ - 132.3106251, - 37.4209671 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3084111, - 37.04404979 - ], - [ - 132.32812844805989, - 37.0769105881086 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3106251, - 37.4209671 - ], - [ - 132.3315267, - 37.38542004 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.32812844805989, - 37.0769105881086 - ], - [ - 132.3296439, - 37.07943623 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.32812844805989, - 37.0769105881086 - ], - [ - 132.37221729698305, - 37.05522843008466 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3296439, - 37.07943623 - ], - [ - 132.3464452, - 37.11631032 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3315267, - 37.38542004 - ], - [ - 132.3479245, - 37.34841414 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3464452, - 37.11631032 - ], - [ - 132.3586477, - 37.15431884 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3479245, - 37.34841414 - ], - [ - 132.3596666, - 37.31030766 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3586477, - 37.15431884 - ], - [ - 132.3661272, - 37.19309722 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3596666, - 37.31030766 - ], - [ - 132.3666466, - 37.27146897 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3661272, - 37.19309722 - ], - [ - 132.3688046, - 37.23227291 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.3666466, - 37.27146897 - ], - [ - 132.36806856583578, - 37.24564160289258 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.36806856583578, - 37.24564160289258 - ], - [ - 132.3688046, - 37.23227291 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.36806856583578, - 37.24564160289258 - ], - [ - 132.46744946519559, - 37.19188203500189 - ] - ] - }, - "properties": null - }, - { - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [ - [ - 132.37221729698305, - 37.05522843008466 - ], - [ - 132.46744946519559, - 37.19188203500189 - ] - ] - }, - "properties": null - } - ] -} diff --git a/fixtures/polygonizer/output/multi_level_nesting.geojson b/fixtures/polygonizer/output/multi_level_nesting.geojson index 7f53eee..c52fc02 100644 --- a/fixtures/polygonizer/output/multi_level_nesting.geojson +++ b/fixtures/polygonizer/output/multi_level_nesting.geojson @@ -23,16 +23,6 @@ ] } }, - { - "type": "Feature", - "properties": { "name": "middle standalone polygon" }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [[3, 3], [9, 3], [9, 9], [3, 9], [3, 3]] - ] - } - }, { "type": "Feature", "properties": { "name": "inner standalone polygon" }, diff --git a/fixtures/polygonizer/output/ownership_area_priority_enclosing_shell_minimal.geojson b/fixtures/polygonizer/output/ownership_area_priority_enclosing_shell_minimal.geojson index 797a78a..778ab8d 100644 --- a/fixtures/polygonizer/output/ownership_area_priority_enclosing_shell_minimal.geojson +++ b/fixtures/polygonizer/output/ownership_area_priority_enclosing_shell_minimal.geojson @@ -1,7 +1,10 @@ { + "type": "FeatureCollection", "features": [ { + "type": "Feature", "geometry": { + "type": "Polygon", "coordinates": [ [ [ @@ -279,12 +282,12 @@ 37.24564160289258 ], [ - 132.2802151948092, - 37.29316541576363 + 132.4674494651956, + 37.19188203500189 ], [ - 132.1992415537936, - 37.140295005641654 + 132.37221729698305, + 37.05522843008466 ], [ 132.32812844805989, @@ -403,65 +406,14 @@ 37.23227291 ] ] - ], - "type": "Polygon" + ] }, - "properties": null, - "type": "Feature" - }, - { - "geometry": { - "coordinates": [ - [ - [ - 131.6159497978044, - 37.24040921335645 - ], - [ - 131.6924993976829, - 37.06369222817547 - ], - [ - 131.88096982742374, - 36.990452460251525 - ], - [ - 132.04409187602525, - 37.05216214177735 - ], - [ - 132.1340414978442, - 37.21699385927595 - ], - [ - 132.1246261181188, - 37.33971272991575 - ], - [ - 131.99086117128556, - 37.481714926898256 - ], - [ - 131.79570674559747, - 37.494760171748474 - ], - [ - 131.6789834344638, - 37.426338419556394 - ], - [ - 131.6159497978044, - 37.24040921335645 - ] - ] - ], - "type": "Polygon" - }, - "properties": null, - "type": "Feature" + "properties": null }, { + "type": "Feature", "geometry": { + "type": "Polygon", "coordinates": [ [ [ @@ -763,14 +715,116 @@ 37.24040921335645 ] ] - ], - "type": "Polygon" + ] + }, + "properties": null + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 131.6159497978044, + 37.24040921335645 + ], + [ + 131.6924993976829, + 37.06369222817547 + ], + [ + 131.88096982742374, + 36.990452460251525 + ], + [ + 132.04409187602525, + 37.05216214177735 + ], + [ + 132.1340414978442, + 37.21699385927595 + ], + [ + 132.1246261181188, + 37.33971272991575 + ], + [ + 131.99086117128556, + 37.481714926898256 + ], + [ + 131.79570674559747, + 37.494760171748474 + ], + [ + 131.6789834344638, + 37.426338419556394 + ], + [ + 131.6159497978044, + 37.24040921335645 + ] + ] + ] + }, + "properties": null + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 132.1992415537936, + 37.140295005641654 + ], + [ + 132.32812844805989, + 37.0769105881086 + ], + [ + 132.3296439, + 37.07943623 + ], + [ + 132.3464452, + 37.11631032 + ], + [ + 132.3586477, + 37.15431884 + ], + [ + 132.3661272, + 37.19309722 + ], + [ + 132.3688046, + 37.23227291 + ], + [ + 132.36806856583578, + 37.24564160289258 + ], + [ + 132.2802151948092, + 37.29316541576363 + ], + [ + 132.1992415537936, + 37.140295005641654 + ] + ] + ] }, - "properties": null, - "type": "Feature" + "properties": null }, { + "type": "Feature", "geometry": { + "type": "Polygon", "coordinates": [ [ [ @@ -814,12 +868,9 @@ 37.0769105881086 ] ] - ], - "type": "Polygon" + ] }, - "properties": null, - "type": "Feature" + "properties": null } - ], - "type": "FeatureCollection" + ] } \ No newline at end of file diff --git a/src/graph.rs b/src/graph.rs index c887e2c..c251aa7 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,7 +1,9 @@ //! Planar graph representation and polygon extraction pipeline. //! //! This module stores noded linework as directed edges sorted in CCW order per -//! origin node, then traverses left faces to assemble minimal rings. +//! origin node, then follows the JTS polygonizer ring traversal: +//! `computeNextCWEdges`, `findLabeledEdgeRings`, and +//! `convertMaximalToMinimalEdgeRings`. use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; @@ -11,7 +13,6 @@ use geo::{GeoFloat, Line, LineString, MultiPolygon, Polygon, Validation, coord}; mod lines_iter; mod shell_assignment; -mod topology_cleanup; use shell_assignment::assign_shells_to_holes; @@ -207,63 +208,10 @@ impl PolygonizerGraph { }); } - fn compute_face_label_by_edge_index( - edge_count: usize, - next_left_face_edge_index_by_edge_index: &[Option], - ) -> Vec> { - let mut face_label_by_edge_index: Vec> = vec![None; edge_count]; - let mut next_face_label: FaceLabel = 0; - - for start_edge_index in 0..edge_count { - if face_label_by_edge_index[start_edge_index].is_some() { - continue; - } - - let mut traversal_path: Vec = Vec::new(); - let mut visit_position_by_edge_index: BTreeMap = BTreeMap::new(); - let mut current_edge_index = start_edge_index; - - loop { - if let Some(existing_face_label) = face_label_by_edge_index[current_edge_index] { - for traversed_edge_index in traversal_path { - face_label_by_edge_index[traversed_edge_index] = Some(existing_face_label); - } - break; - } - - if let Some(&cycle_start_position) = - visit_position_by_edge_index.get(¤t_edge_index) - { - let face_label = next_face_label; - next_face_label += 1; - - for &traversed_edge_index in &traversal_path[cycle_start_position..] { - face_label_by_edge_index[traversed_edge_index] = Some(face_label); - } - for &traversed_edge_index in &traversal_path[..cycle_start_position] { - face_label_by_edge_index[traversed_edge_index] = Some(face_label); - } - break; - } - - visit_position_by_edge_index.insert(current_edge_index, traversal_path.len()); - traversal_path.push(current_edge_index); - - current_edge_index = - match next_left_face_edge_index_by_edge_index[current_edge_index] { - Some(next_edge_index) => next_edge_index, - None => break, - }; - } - } - - face_label_by_edge_index - } - fn collect_undirected_cut_edges( edges_by_index: &[Edge], edge_to_index: &BTreeMap, EdgeIndex>, - face_label_by_edge_index: &[Option], + edge_ring_label_by_edge_index: &[Option], ) -> BTreeSet<(Node, Node)> { let mut undirected_edges_to_remove: BTreeSet<(Node, Node)> = BTreeSet::new(); for (edge_index, edge) in edges_by_index.iter().enumerate() { @@ -275,8 +223,9 @@ impl PolygonizerGraph { .get(&edge.get_symmetrical()) .expect("symmetric edge index should exist"); - if face_label_by_edge_index[edge_index] - == face_label_by_edge_index[symmetric_edge_index] + if edge_ring_label_by_edge_index[edge_index].is_some() + && edge_ring_label_by_edge_index[edge_index] + == edge_ring_label_by_edge_index[symmetric_edge_index] { undirected_edges_to_remove.insert((edge.from, edge.to)); } @@ -321,25 +270,28 @@ impl PolygonizerGraph { (edges_by_index, edge_to_index) } - fn get_next_left_face_edge_index_by_edge_index( + /// Analogous to JTS `computeNextCWEdges`. + /// + /// For every directed edge entering a node, choose the next outgoing edge + /// in clockwise order around that node. + fn compute_next_cw_edges_by_edge_index( &self, edge_to_index: &BTreeMap, usize>, edge_count: usize, ) -> Vec> { - let mut next_left_face_edge_index_by_edge_index: Vec> = - vec![None; edge_count]; + let mut next_cw_edge_index_by_edge_index: Vec> = vec![None; edge_count]; for outbound_edges_at_node in self.nodes_to_outbound_edges.values() { - let ordered_outgoing_edges: Vec<_> = outbound_edges_at_node.iter().collect(); + let ordered_outgoing_edges: Vec<_> = outbound_edges_at_node.iter().copied().collect(); let ordered_edge_count = ordered_outgoing_edges.len(); if ordered_edge_count == 0 { continue; } for edge_index in 0..ordered_edge_count { - let reverse_edge = *ordered_outgoing_edges[edge_index]; - let incoming_edge = reverse_edge.get_symmetrical(); - let next_outgoing_edge = *ordered_outgoing_edges + let outgoing_edge = ordered_outgoing_edges[edge_index]; + let incoming_edge = outgoing_edge.get_symmetrical(); + let next_outgoing_edge = ordered_outgoing_edges [(edge_index + ordered_edge_count - 1) % ordered_edge_count]; let incoming_edge_index = *edge_to_index @@ -349,85 +301,284 @@ impl PolygonizerGraph { .get(&next_outgoing_edge) .expect("next outgoing edge index should exist"); - next_left_face_edge_index_by_edge_index[incoming_edge_index] = + next_cw_edge_index_by_edge_index[incoming_edge_index] = Some(next_outgoing_edge_index); } } - next_left_face_edge_index_by_edge_index + next_cw_edge_index_by_edge_index } - /// For each directed edge (u -> v), returns the next directed edge along - /// the face on its left side when traversing the planar graph. - fn get_edge_to_next_left_face_edge_map(&self) -> BTreeMap, Edge> { - let (edges_by_index, edge_to_index) = self.get_edges_with_index_map(); - let next_left_face_edge_index_by_edge_index = - self.get_next_left_face_edge_index_by_edge_index(&edge_to_index, edges_by_index.len()); + /// Analogous to JTS `findDirEdgesInRing`. + fn find_dir_edges_in_ring( + start_edge_index: EdgeIndex, + next_edge_index_by_edge_index: &[Option], + ) -> Vec { + let mut ring = Vec::new(); + let mut visited_edge_indices = BTreeSet::new(); + let mut current_edge_index = start_edge_index; - let mut next_left_face_edge_by_edge = BTreeMap::new(); - for (edge_index, next_edge_index) in next_left_face_edge_index_by_edge_index - .iter() - .copied() - .enumerate() - { - let Some(next_edge_index) = next_edge_index else { - continue; + loop { + if !visited_edge_indices.insert(current_edge_index) { + break; + } + + ring.push(current_edge_index); + + let Some(next_edge_index) = next_edge_index_by_edge_index[current_edge_index] else { + break; }; - next_left_face_edge_by_edge - .insert(edges_by_index[edge_index], edges_by_index[next_edge_index]); + if next_edge_index == start_edge_index { + break; + } + + current_edge_index = next_edge_index; } - next_left_face_edge_by_edge + ring } - /// Builds a directed graph from already-noded linework. + /// Analogous to JTS `findLabeledEdgeRings`. + fn find_labeled_edge_rings( + edge_count: usize, + next_edge_index_by_edge_index: &[Option], + ) -> (Vec>, Vec) { + let mut edge_ring_label_by_edge_index = vec![None; edge_count]; + let mut maximal_ring_start_edge_indices = Vec::new(); + let mut next_edge_ring_label: FaceLabel = 0; + + for start_edge_index in 0..edge_count { + if edge_ring_label_by_edge_index[start_edge_index].is_some() { + continue; + } + + maximal_ring_start_edge_indices.push(start_edge_index); + let ring_edge_indices = + Self::find_dir_edges_in_ring(start_edge_index, next_edge_index_by_edge_index); + for edge_index in ring_edge_indices { + edge_ring_label_by_edge_index[edge_index] = Some(next_edge_ring_label); + } + next_edge_ring_label += 1; + } + + ( + edge_ring_label_by_edge_index, + maximal_ring_start_edge_indices, + ) + } + + fn get_degree_for_label( + &self, + node: Node, + label: FaceLabel, + edge_to_index: &BTreeMap, EdgeIndex>, + edge_ring_label_by_edge_index: &[Option], + ) -> usize { + self.nodes_to_outbound_edges + .get(&node) + .map(|outbound_edges_at_node| { + outbound_edges_at_node + .iter() + .filter(|edge| { + let edge_index = *edge_to_index + .get(edge) + .expect("edge index should exist for graph edge"); + edge_ring_label_by_edge_index[edge_index] == Some(label) + }) + .count() + }) + .unwrap_or(0) + } + + /// Analogous to JTS `findIntersectionNodes`. + fn find_intersection_nodes( + &self, + start_edge_index: EdgeIndex, + label: FaceLabel, + edges_by_index: &[Edge], + edge_to_index: &BTreeMap, EdgeIndex>, + edge_ring_label_by_edge_index: &[Option], + next_edge_index_by_edge_index: &[Option], + ) -> BTreeSet> { + let mut intersection_nodes = BTreeSet::new(); + + for edge_index in + Self::find_dir_edges_in_ring(start_edge_index, next_edge_index_by_edge_index) + { + let node = edges_by_index[edge_index].from; + if self.get_degree_for_label(node, label, edge_to_index, edge_ring_label_by_edge_index) + > 1 + { + intersection_nodes.insert(node); + } + } + + intersection_nodes + } + + /// Analogous to JTS `computeNextCCWEdges`. /// - /// Each undirected segment is represented as two directed edges with opposite directions. - pub(crate) fn from_noded_lines(lines: impl IntoIterator>) -> Self { - let mut nodes_to_outbound_edges = BTreeMap::new(); + /// This rewires only the directed edges that have the supplied maximal-ring + /// label, which is what subdivides maximal edge rings into minimal rings. + fn compute_next_ccw_edges_at_node( + &self, + node: Node, + label: FaceLabel, + edge_to_index: &BTreeMap, EdgeIndex>, + edge_ring_label_by_edge_index: &[Option], + next_edge_index_by_edge_index: &mut [Option], + ) { + let Some(outbound_edges_at_node) = self.nodes_to_outbound_edges.get(&node) else { + return; + }; + + let ordered_outgoing_edges: Vec<_> = outbound_edges_at_node.iter().copied().collect(); + let mut first_out_edge_index = None; + let mut previous_in_edge_index = None; + + for outgoing_edge in ordered_outgoing_edges.iter().rev() { + let outgoing_edge_index = *edge_to_index + .get(outgoing_edge) + .expect("outgoing edge index should exist"); + let incoming_edge_index = *edge_to_index + .get(&outgoing_edge.get_symmetrical()) + .expect("incoming edge index should exist"); + + let out_edge_index = (edge_ring_label_by_edge_index[outgoing_edge_index] + == Some(label)) + .then_some(outgoing_edge_index); + let in_edge_index = (edge_ring_label_by_edge_index[incoming_edge_index] == Some(label)) + .then_some(incoming_edge_index); + + if out_edge_index.is_none() && in_edge_index.is_none() { + continue; + } - for line in lines.into_iter() { - let (start_node, end_node) = line_endpoints_to_nodes(line); - Self::insert_undirected_edge(&mut nodes_to_outbound_edges, start_node, end_node); + if let Some(in_edge_index) = in_edge_index { + previous_in_edge_index = Some(in_edge_index); + } + + if let Some(out_edge_index) = out_edge_index { + if let Some(in_edge_index) = previous_in_edge_index.take() { + next_edge_index_by_edge_index[in_edge_index] = Some(out_edge_index); + } + if first_out_edge_index.is_none() { + first_out_edge_index = Some(out_edge_index); + } + } } - Self { - nodes_to_outbound_edges, + if let Some(in_edge_index) = previous_in_edge_index { + if let Some(out_edge_index) = first_out_edge_index { + next_edge_index_by_edge_index[in_edge_index] = Some(out_edge_index); + } + } + } + + /// Analogous to JTS `convertMaximalToMinimalEdgeRings`. + fn convert_maximal_to_minimal_edge_rings( + &self, + maximal_ring_start_edge_indices: &[EdgeIndex], + edges_by_index: &[Edge], + edge_to_index: &BTreeMap, EdgeIndex>, + edge_ring_label_by_edge_index: &[Option], + next_edge_index_by_edge_index: &mut [Option], + ) { + for &start_edge_index in maximal_ring_start_edge_indices { + let Some(label) = edge_ring_label_by_edge_index[start_edge_index] else { + continue; + }; + + let intersection_nodes = self.find_intersection_nodes( + start_edge_index, + label, + edges_by_index, + edge_to_index, + edge_ring_label_by_edge_index, + next_edge_index_by_edge_index, + ); + + for node in intersection_nodes { + self.compute_next_ccw_edges_at_node( + node, + label, + edge_to_index, + edge_ring_label_by_edge_index, + next_edge_index_by_edge_index, + ); + } } } - pub(super) fn get_minimal_edge_rings(&self) -> Vec>> { - let next_left_face_edge_by_edge = self.get_edge_to_next_left_face_edge_map(); + /// Analogous to JTS `getEdgeRings`. + pub(super) fn get_edge_rings(&self) -> Vec>> { + let (edges_by_index, edge_to_index) = self.get_edges_with_index_map(); + let mut next_edge_index_by_edge_index = + self.compute_next_cw_edges_by_edge_index(&edge_to_index, edges_by_index.len()); + + let (edge_ring_label_by_edge_index, maximal_ring_start_edge_indices) = + Self::find_labeled_edge_rings(edges_by_index.len(), &next_edge_index_by_edge_index); + + self.convert_maximal_to_minimal_edge_rings( + &maximal_ring_start_edge_indices, + &edges_by_index, + &edge_to_index, + &edge_ring_label_by_edge_index, + &mut next_edge_index_by_edge_index, + ); let mut rings = Vec::new(); - let mut visited_directed_edges = BTreeSet::new(); - for edge in next_left_face_edge_by_edge.keys() { - if visited_directed_edges.contains(edge) { + let mut visited_edge_indices = BTreeSet::new(); + for start_edge_index in 0..edges_by_index.len() { + if visited_edge_indices.contains(&start_edge_index) { continue; } - let mut ring = vec![*edge]; - visited_directed_edges.insert(*edge); - let mut next_edge = *edge; + let mut ring = Vec::new(); + let mut current_edge_index = start_edge_index; + loop { - next_edge = match next_left_face_edge_by_edge.get(&next_edge) { - Some(next) => *next, - _ => break, - }; - if next_edge == *edge { + if !visited_edge_indices.insert(current_edge_index) { break; } - if visited_directed_edges.contains(&next_edge) { + + ring.push(edges_by_index[current_edge_index]); + + let Some(next_edge_index) = next_edge_index_by_edge_index[current_edge_index] + else { + break; + }; + if next_edge_index == start_edge_index { break; } - ring.push(next_edge); - visited_directed_edges.insert(next_edge); + + current_edge_index = next_edge_index; + } + + if !ring.is_empty() { + rings.push(ring); } - rings.push(ring); } + rings } + /// Builds a directed graph from already-noded linework. + /// + /// Each undirected segment is represented as two directed edges with opposite directions. + pub(crate) fn from_noded_lines(lines: impl IntoIterator>) -> Self { + let mut nodes_to_outbound_edges = BTreeMap::new(); + + for line in lines.into_iter() { + let (start_node, end_node) = line_endpoints_to_nodes(line); + Self::insert_undirected_edge(&mut nodes_to_outbound_edges, start_node, end_node); + } + + Self { + nodes_to_outbound_edges, + } + } + /// Iteratively removes dangling degree-1 chains from the graph. pub(crate) fn delete_dangles(&mut self) { let mut degree_one_nodes: Vec<_> = self @@ -471,28 +622,35 @@ impl PolygonizerGraph { /// two directions belong to the same face. pub(crate) fn delete_cut_edges(&mut self) { let (edges_by_index, edge_to_index) = self.get_edges_with_index_map(); - let next_left_face_edge_index_by_edge_index = - self.get_next_left_face_edge_index_by_edge_index(&edge_to_index, edges_by_index.len()); + let next_cw_edge_index_by_edge_index = + self.compute_next_cw_edges_by_edge_index(&edge_to_index, edges_by_index.len()); - let face_label_by_edge_index = Self::compute_face_label_by_edge_index( - edges_by_index.len(), - &next_left_face_edge_index_by_edge_index, - ); + let (edge_ring_label_by_edge_index, _) = + Self::find_labeled_edge_rings(edges_by_index.len(), &next_cw_edge_index_by_edge_index); let undirected_edges_to_remove = Self::collect_undirected_cut_edges( &edges_by_index, &edge_to_index, - &face_label_by_edge_index, + &edge_ring_label_by_edge_index, ); self.remove_undirected_edges(&undirected_edges_to_remove); } - /// Extracts valid rings, classifies shell/hole orientation, and applies - /// downstream topology cleanup passes. + /// Extracts valid rings, classifies shell/hole orientation, assigns + /// holes to shells (analogous to JTS `polygonize`), and applies + /// optional topology cleanup for self-touching boundaries. + /// + /// Shell/hole classification follows geo's OGC convention: + /// CCW ring = shell (bounded face exterior) + /// CW ring = hole (reverse-face / interior ring) + /// + /// This matches JTS's `computeHole()` adapted for OGC winding: + /// JTS uses CW-exterior convention; geo-rust uses CCW-exterior, + /// so CCW=shell here. pub(crate) fn polygonize(&self) -> MultiPolygon where T: rstar::RTreeNum, { - let edge_rings = self.get_minimal_edge_rings(); + let edge_rings = self.get_edge_rings(); // Split any figure-8 rings at pinch-point nodes before the validity // filter so that both sub-regions survive as separate polygons. @@ -506,32 +664,17 @@ impl PolygonizerGraph { .filter_map(|ring| ring_to_valid_linestring(&ring)) .collect(); - let (valid_holes, valid_shells): (Vec<_>, Vec<_>) = + // Analogous to JTS `findShellsAndHoles` — classify by winding. + // In geo's OGC convention: CCW = shell (exterior), CW = hole. + let (valid_shells, valid_holes): (Vec<_>, Vec<_>) = valid_rings.into_iter().partition(|ring| ring.is_ccw()); + // Analogous to JTS `assignHolesToShells` + `extractPolygons`. let valid_polygons: Vec<_> = assign_shells_to_holes(valid_shells, valid_holes) .into_iter() .filter(|polygon| polygon.is_valid()) .collect(); - // --- Topology cleanup pipeline (7 passes) --- - // Assign standalone polygons as holes of no-hole parents that contain them. - let polygons = - topology_cleanup::infer_parent_holes_when_output_has_no_holes(valid_polygons); - // Re-polygonize boundaries at degree>2 nodes to split touching polygons. - let polygons = topology_cleanup::split_touching_boundary_polygons(polygons); - // Absorb tiny standalones sitting between holes into their parent polygon. - let polygons = topology_cleanup::infer_contained_standalone_polygons_as_holes(polygons); - // Resolve overlapping polygon ownership by removing shared interior points. - let polygons = - topology_cleanup::remove_non_unique_interior_points_for_touching_topology(polygons); - // Carve contained standalone polygons as holes of their enclosing parent. - let polygons = topology_cleanup::carve_contained_standalones_as_holes(polygons); - // Merge holes connected by bridge standalones into unified holes. - let polygons = topology_cleanup::merge_touching_holes_in_polygons(polygons); - // Second carve pass to catch standalones revealed by merging. - let polygons = topology_cleanup::carve_contained_standalones_as_holes(polygons); - - MultiPolygon(polygons) + MultiPolygon(valid_polygons) } } diff --git a/src/graph/shell_assignment.rs b/src/graph/shell_assignment.rs index 14518d1..487e8e4 100644 --- a/src/graph/shell_assignment.rs +++ b/src/graph/shell_assignment.rs @@ -1,9 +1,15 @@ -//! Assigns shell rings to hole rings and builds output polygons. +//! Like JTS `HoleAssigner` — assigns hole rings to the smallest containing shell. +//! +//! Each hole (CW ring in OGC convention) is assigned to the smallest shell +//! (CCW ring) whose polygon strictly contains the hole's interior point. +//! Unassigned holes (e.g. the CW reverse-face of each shell's own boundary) +//! are silently dropped, matching JTS behaviour. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; +use geo::Winding; use geo::orient::Direction; -use geo::{Area, Contains, GeoFloat, InteriorPoint, LineString, Orient, Polygon, Validation}; +use geo::{Contains, GeoFloat, InteriorPoint, LineString, Orient, Polygon}; use rstar::{Envelope, RTreeObject}; struct ShellContainer { @@ -19,16 +25,12 @@ impl RTreeObject for ShellContainer { } } -fn sorted_ring_coords(ring: &LineString) -> Vec<(T, T)> { - let mut coords: Vec<(T, T)> = ring.coords().map(|c| (c.x, c.y)).collect(); - if coords.len() >= 2 && coords.first() == coords.last() { - coords.pop(); - } - coords.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.total_cmp(&b.1))); - coords.dedup(); - coords -} - +/// Like JTS `assignHolesToShells` + `extractPolygons`. +/// +/// For each hole, finds the smallest containing shell (by area) whose +/// envelope strictly contains the hole's envelope and whose polygon +/// contains the hole's interior point. Builds one `Polygon` per shell +/// (shell exterior + assigned holes), oriented per OGC convention. pub(super) fn assign_shells_to_holes( shells: Vec>, holes: Vec>, @@ -39,7 +41,7 @@ pub(super) fn assign_shells_to_holes( .map(|shell| Polygon::new(shell, vec![])) .collect(); - let shell_containers = shells + let shell_containers: Vec<_> = shells .iter() .enumerate() .map(|(idx, shell)| ShellContainer { @@ -51,137 +53,58 @@ pub(super) fn assign_shells_to_holes( let mut assignments: BTreeMap> = BTreeMap::new(); + // Analogous to JTS `assignHoleToShell` — for each hole, find the + // smallest containing shell (analogous to JTS `findEdgeRingContaining`). for (hole_index, hole) in holes.iter().enumerate() { - let hole_interior_point = match Polygon::new(hole.clone(), vec![]).interior_point() { + // Hole rings are CW. To get a reliable interior point inside the + // geometric shape, orient the ring CCW before computing. + let mut oriented_hole = hole.clone(); + if !oriented_hole.is_ccw() { + oriented_hole.make_ccw_winding(); + } + let hole_interior_point = match Polygon::new(oriented_hole, vec![]).interior_point() { Some(point) => point, None => continue, }; let hole_envelope = hole.envelope(); + + // Envelope must strictly contain the hole envelope (not equal). let mut matching_shells: Vec<_> = shell_tree - .locate_in_envelope_intersecting(&hole.envelope()) + .locate_in_envelope_intersecting(&hole_envelope) .filter(|container| { container.envelope.contains_envelope(&hole_envelope) && container.envelope != hole_envelope && shell_polygons[container.idx].contains(&hole_interior_point) }) .collect(); - matching_shells.sort_by(|left_shell, right_shell| { - shell_polygons[left_shell.idx] - .unsigned_area() - .total_cmp(&shell_polygons[right_shell.idx].unsigned_area()) - }); + + // Pick smallest containing shell by envelope area (O(1) per shell, + // vs O(n) for polygon area). Equivalent for the simple closed rings + // the polygonizer produces, matching JTS's approach. + matching_shells.sort_by(|a, b| a.envelope.area().total_cmp(&b.envelope.area())); if let Some(container) = matching_shells.first() { assignments .entry(container.idx) - .or_insert_with(|| Vec::new()) + .or_default() .push(hole_index); } + // Unassigned holes are silently dropped (JTS behaviour). } - let mut polygons: Vec> = shells + // Analogous to JTS `extractPolygons` — build one Polygon per shell. + let result: Vec<_> = shells .into_iter() .enumerate() .map(|(shell_index, shell)| { - let polygon = Polygon::new( - shell, - match assignments.get(&shell_index) { - Some(assigned_hole_indices) => assigned_hole_indices - .iter() - .map(|hole_index| holes[*hole_index].clone()) - .collect(), - None => vec![], - }, - ); - polygon.orient(Direction::Default) - }) - .collect(); + let assigned_holes: Vec> = assignments + .get(&shell_index) + .map(|indices| indices.iter().map(|&i| holes[i].clone()).collect()) + .unwrap_or_default(); - let assigned_hole_indices: BTreeSet = assignments - .values() - .flat_map(|hole_indices| hole_indices.iter().copied()) + Polygon::new(shell, assigned_holes).orient(Direction::Default) + }) .collect(); - - for hole_index in assigned_hole_indices.iter().copied() { - let standalone_hole_polygon = - Polygon::new(holes[hole_index].clone(), vec![]).orient(Direction::Default); - - let overlaps_different_exterior_holed_polygon = polygons.iter().any(|polygon| { - polygon.exterior() != standalone_hole_polygon.exterior() - && !polygon.interiors().is_empty() - && standalone_hole_polygon - .exterior() - .points() - .any(|point| polygon.contains(&point)) - }); - - let max_holes_with_same_exterior = polygons - .iter() - .filter(|polygon| polygon.exterior() == standalone_hole_polygon.exterior()) - .map(|polygon| polygon.interiors().len()) - .max() - .unwrap_or(0); - - if !overlaps_different_exterior_holed_polygon - && max_holes_with_same_exterior <= 1 - && !polygons.contains(&standalone_hole_polygon) - { - polygons.push(standalone_hole_polygon); - } - } - - // --- Same-envelope hole recovery for invalid shell polygons --- - // - // Some hole rings share the exact same bounding box as their - // containing shell, which causes the `envelope != hole_envelope` - // filter above to skip them during assignment. When this happens - // AND the shell's assigned holes make it invalid (to be filtered - // later by `polygonize()`), those unassigned same-envelope holes - // represent genuine face-rings that would otherwise be lost. - // - // Recover them as standalone polygons here. We only do this for - // shells whose polygon is already invalid, to avoid adding spurious - // standalone polygons in the common case. - - for (shell_index, assigned) in assignments.iter() { - if assigned.is_empty() { - continue; - } - - // Check whether this shell with its assigned holes is invalid. - let test_polygon = Polygon::new( - shell_polygons[*shell_index].exterior().clone(), - assigned.iter().map(|hi| holes[*hi].clone()).collect(), - ) - .orient(Direction::Default); - - if test_polygon.is_valid() { - continue; - } - - // Shell + holes is invalid — look for unassigned holes that - // share the shell's bounding box but have different vertices. - let shell_env = shell_polygons[*shell_index].exterior().envelope(); - let shell_sorted = sorted_ring_coords(shell_polygons[*shell_index].exterior()); - - for (hole_index, hole) in holes.iter().enumerate() { - if assigned_hole_indices.contains(&hole_index) { - continue; - } - if hole.envelope() != shell_env { - continue; - } - let hole_sorted = sorted_ring_coords(hole); - if hole_sorted == shell_sorted { - continue; // same vertex set = reverse of shell, skip - } - let standalone = Polygon::new(hole.clone(), vec![]).orient(Direction::Default); - if !polygons.contains(&standalone) { - polygons.push(standalone); - } - } - } - - polygons + result } diff --git a/src/graph/topology_cleanup.rs b/src/graph/topology_cleanup.rs deleted file mode 100644 index 331f22b..0000000 --- a/src/graph/topology_cleanup.rs +++ /dev/null @@ -1,821 +0,0 @@ -//! Topology cleanup passes applied after initial polygon extraction. -//! -//! The pipeline (defined in [`super::PolygonizerGraph::polygonize`]) runs -//! these passes in order: -//! -//! 1. [`infer_parent_holes_when_output_has_no_holes`] — adopt standalone -//! polygons as holes of no-hole parents that fully contain them. -//! 2. [`split_touching_boundary_polygons`] — re-polygonize boundaries at -//! degree>2 nodes to split touching polygons. -//! 3. [`infer_contained_standalone_polygons_as_holes`] — absorb tiny -//! standalones sitting between existing holes into their parent. -//! 4. [`remove_non_unique_interior_points_for_touching_topology`] — resolve -//! overlapping polygon ownership by removing the lesser claimant. -//! 5. [`carve_contained_standalones_as_holes`] — carve island polygons as -//! holes of their enclosing parent (first call). -//! 6. [`merge_touching_holes_in_polygons`] — merge holes connected by -//! bridge standalones into unified holes. -//! 7. [`carve_contained_standalones_as_holes`] — second carve pass to catch -//! standalones revealed by merging. - -use std::collections::{BTreeMap, BTreeSet}; - -use crate::nodify::nodify_lines; -use geo::orient::Direction; -use geo::{Area, Contains, GeoFloat, InteriorPoint, Line, LineString, Orient, Polygon, Validation}; -use rstar::{Envelope, RTreeObject}; - -use super::{PolygonizerGraph, ring_to_valid_linestring}; - -fn polygon_boundary_lines(polygon: &Polygon) -> Vec> { - let mut lines: Vec> = polygon.exterior().lines().collect(); - for hole in polygon.interiors() { - lines.extend(hole.lines()); - } - lines -} - -fn lines_have_same_endpoints(first: &Line, second: &Line) -> bool { - (first.start == second.start && first.end == second.end) - || (first.start == second.end && first.end == second.start) -} - -fn polygon_unique_boundary_segment_count( - polygons: &[Polygon], - polygon_index: usize, -) -> usize { - let polygon_lines = polygon_boundary_lines(&polygons[polygon_index]); - - polygon_lines - .iter() - .filter(|line| { - !polygons - .iter() - .enumerate() - .any(|(other_index, other_polygon)| { - if other_index == polygon_index { - return false; - } - - polygon_boundary_lines(other_polygon) - .iter() - .any(|other_line| lines_have_same_endpoints(line, other_line)) - }) - }) - .count() -} - -fn polygons_share_any_boundary_segment(a: &Polygon, b: &Polygon) -> bool { - let a_lines = polygon_boundary_lines(a); - let b_lines = polygon_boundary_lines(b); - a_lines.iter().any(|a_line| { - b_lines - .iter() - .any(|b_line| lines_have_same_endpoints(a_line, b_line)) - }) -} - -/// When multiple polygons claim the same interior point, remove the lesser -/// claimant. The "owner" is chosen by most unique boundary segments first, -/// then smallest area. Polygons that share no boundary with the owner, or -/// that fully contain the owner, are kept. -/// -/// Iterates to a fixed point. -pub(super) fn remove_non_unique_interior_points_for_touching_topology< - T: GeoFloat + rstar::RTreeNum, ->( - polygons: Vec>, -) -> Vec> { - let mut current_polygons = polygons; - - loop { - let mut polygon_indices_to_remove: BTreeSet = BTreeSet::new(); - let unique_boundary_segment_count_by_index: Vec = (0..current_polygons.len()) - .map(|polygon_index| { - polygon_unique_boundary_segment_count(¤t_polygons, polygon_index) - }) - .collect(); - - for polygon_index in 0..current_polygons.len() { - let interior_point = match current_polygons[polygon_index].interior_point() { - Some(point) => point, - None => continue, - }; - - let containing_polygon_indices: Vec = current_polygons - .iter() - .enumerate() - .filter_map(|(candidate_index, candidate_polygon)| { - if candidate_polygon.contains(&interior_point) { - Some(candidate_index) - } else { - None - } - }) - .collect(); - - if containing_polygon_indices.len() <= 1 { - continue; - } - - let owner_index = containing_polygon_indices - .iter() - .copied() - .min_by(|left_index, right_index| { - unique_boundary_segment_count_by_index[*right_index] - .cmp(&unique_boundary_segment_count_by_index[*left_index]) - .then( - current_polygons[*left_index] - .unsigned_area() - .total_cmp(¤t_polygons[*right_index].unsigned_area()), - ) - .then(left_index.cmp(right_index)) - }) - .expect("owner index should exist when containing polygons are non-empty"); - - for candidate_index in containing_polygon_indices { - if candidate_index != owner_index { - let candidate_polygon = ¤t_polygons[candidate_index]; - let keep_same_exterior_plain_variant = candidate_polygon.interiors().is_empty() - && current_polygons.iter().enumerate().any( - |(other_index, other_polygon)| { - other_index != candidate_index - && !other_polygon.interiors().is_empty() - && other_polygon.exterior() == candidate_polygon.exterior() - }, - ); - if keep_same_exterior_plain_variant { - continue; - } - - // Don't remove a polygon that doesn't share any boundary - // segments with the owner. If they share no boundary, the owner - // is simply nested inside the candidate (containment), not a - // redundant overlapping polygon. - if !polygons_share_any_boundary_segment( - candidate_polygon, - ¤t_polygons[owner_index], - ) { - continue; - } - - // Don't remove a polygon that fully contains the owner. - // When the candidate polygon contains the owner, the owner - // is a nested face inside the candidate — removing the outer - // polygon would destroy a legitimate region. - if candidate_polygon.contains(¤t_polygons[owner_index]) { - continue; - } - - polygon_indices_to_remove.insert(candidate_index); - } - } - } - - if polygon_indices_to_remove.is_empty() { - break; - } - - current_polygons = current_polygons - .into_iter() - .enumerate() - .filter_map(|(polygon_index, polygon)| { - if polygon_indices_to_remove.contains(&polygon_index) { - None - } else { - Some(polygon) - } - }) - .collect(); - } - - current_polygons -} - -/// Re-polygonize each polygon's boundary whenever its boundary graph has -/// degree>2 nodes (i.e. the boundary touches itself). The polygon is -/// replaced by the interior face sub-polygons. -pub(super) fn split_touching_boundary_polygons( - polygons: Vec>, -) -> Vec> { - polygons - .into_iter() - .flat_map(split_touching_boundary_polygon) - .collect() -} - -fn build_touching_boundary_graph( - polygon: &Polygon, -) -> PolygonizerGraph { - let mut boundary_lines: Vec<_> = polygon.exterior().lines().collect(); - for hole in polygon.interiors() { - boundary_lines.extend(hole.lines()); - } - - let split_snap_radius = - T::from(1e-10).unwrap_or(T::epsilon() * T::from(1024.0).unwrap_or(T::one())); - let noded_boundary_lines = nodify_lines(boundary_lines, split_snap_radius); - PolygonizerGraph::from_noded_lines(noded_boundary_lines) -} - -fn graph_has_touching_topology(graph: &PolygonizerGraph) -> bool { - graph - .nodes_to_outbound_edges - .values() - .any(|outbound_edges| outbound_edges.len() > 2) -} - -fn extract_face_candidates_inside_polygon( - boundary_graph: &PolygonizerGraph, - container_polygon: &Polygon, -) -> Vec> { - boundary_graph - .get_minimal_edge_rings() - .into_iter() - .filter_map(|ring| { - let linestring = ring_to_valid_linestring(&ring)?; - let face_polygon = Polygon::new(linestring, vec![]).orient(Direction::Default); - - let interior_point = face_polygon.interior_point()?; - if !container_polygon.contains(&interior_point) { - return None; - } - - Some(face_polygon) - }) - .collect() -} - -fn deduplicate_faces_by_exterior(faces: Vec>) -> Vec> { - let mut deduplicated_faces: Vec> = Vec::new(); - for face in faces { - let already_present = deduplicated_faces - .iter() - .any(|existing| existing.exterior() == face.exterior()); - if !already_present { - deduplicated_faces.push(face); - } - } - deduplicated_faces -} - -fn prune_container_faces(faces: &[Polygon]) -> Vec> { - faces - .iter() - .enumerate() - .filter_map(|(face_index, face)| { - let contains_another_face = - faces - .iter() - .enumerate() - .any(|(other_face_index, other_face)| { - if face_index == other_face_index { - return false; - } - - let other_interior_point = match other_face.interior_point() { - Some(point) => point, - None => return false, - }; - - face.exterior() != other_face.exterior() - && face.contains(&other_interior_point) - }); - - if contains_another_face { - None - } else { - Some(face.clone()) - } - }) - .collect() -} - -fn select_non_touching_holes( - polygon: &Polygon, -) -> Vec> { - let mut kept_holes: Vec> = Vec::new(); - for hole in polygon.interiors() { - let mut candidate_holes = kept_holes.clone(); - candidate_holes.push(hole.clone()); - - let candidate_polygon = Polygon::new(polygon.exterior().clone(), candidate_holes.clone()) - .orient(Direction::Default); - if !graph_has_touching_topology(&build_touching_boundary_graph(&candidate_polygon)) { - kept_holes = candidate_holes; - } - } - kept_holes -} - -fn split_no_hole_polygon_on_repeated_vertex( - polygon: &Polygon, -) -> Option>> { - if !polygon.interiors().is_empty() { - return None; - } - - let mut coordinates: Vec<_> = polygon.exterior().points().map(|point| point.0).collect(); - if coordinates.first() == coordinates.last() { - coordinates.pop(); - } - - if coordinates.len() < 4 { - return None; - } - - for first_index in 0..coordinates.len() { - for second_index in (first_index + 2)..coordinates.len() { - if first_index == 0 && second_index + 1 == coordinates.len() { - continue; - } - - if coordinates[first_index] != coordinates[second_index] { - continue; - } - - let first_ring_coords: Vec<_> = coordinates[first_index..=second_index].to_vec(); - - let mut second_ring_coords: Vec<_> = coordinates[second_index..].to_vec(); - second_ring_coords.extend_from_slice(&coordinates[..=first_index]); - - if first_ring_coords.len() < 4 || second_ring_coords.len() < 4 { - continue; - } - - let first_polygon = Polygon::new(LineString::from(first_ring_coords), vec![]) - .orient(Direction::Default); - let second_polygon = Polygon::new(LineString::from(second_ring_coords), vec![]) - .orient(Direction::Default); - - if first_polygon.is_valid() && second_polygon.is_valid() { - return Some(vec![first_polygon, second_polygon]); - } - } - } - - None -} - -fn split_touching_boundary_polygon( - polygon: Polygon, -) -> Vec> { - let boundary_graph = build_touching_boundary_graph(&polygon); - if !graph_has_touching_topology(&boundary_graph) { - return vec![polygon]; - } - - let face_candidates = extract_face_candidates_inside_polygon(&boundary_graph, &polygon); - let deduplicated_faces = deduplicate_faces_by_exterior(face_candidates); - let split_faces = prune_container_faces(&deduplicated_faces); - - // Guard against dropping body area on holed polygons: if the polygon - // has holes, the body (exterior minus holes) is multiply-connected - // and generally not representable as minimal edge rings. The - // extracted sub-faces may only cover "pockets" between touching - // holes, silently discarding the bulk of the body. Require that a - // representative body point remain covered by the split faces. - let body_interior_covered = polygon.interiors().is_empty() - || polygon - .interior_point() - .map(|interior_point| { - split_faces - .iter() - .any(|face| face.contains(&interior_point)) - }) - .unwrap_or(false); - - if split_faces.len() >= 2 && body_interior_covered { - split_faces - } else if polygon.interiors().is_empty() { - split_no_hole_polygon_on_repeated_vertex(&polygon).unwrap_or_else(|| vec![polygon]) - } else { - let kept_holes = select_non_touching_holes(&polygon); - vec![Polygon::new(polygon.exterior().clone(), kept_holes).orient(Direction::Default)] - } -} - -/// For each standalone polygon (no holes) that is contained by a parent -/// polygon's exterior, and the parent has no explicitly-assigned holes yet, -/// adopt the standalone as a hole of the smallest qualifying parent. -/// -/// This handles the common case where the initial shell/hole assignment -/// did not create a holed polygon because the inner ring was extracted as -/// a standalone shell rather than a hole. -pub(super) fn infer_parent_holes_when_output_has_no_holes( - polygons: Vec>, -) -> Vec> { - let polygon_envelopes: Vec<_> = polygons.iter().map(|polygon| polygon.envelope()).collect(); - - let mut parent_polygon_index_by_polygon_index: Vec> = vec![None; polygons.len()]; - for child_polygon_index in 0..polygons.len() { - let child_envelope = polygon_envelopes[child_polygon_index]; - let mut best_parent: Option<(usize, T)> = None; - - for parent_polygon_index in 0..polygons.len() { - if child_polygon_index == parent_polygon_index { - continue; - } - - let parent_envelope = polygon_envelopes[parent_polygon_index]; - if parent_envelope == child_envelope - || !parent_envelope.contains_envelope(&child_envelope) - { - continue; - } - - let child_interior_point = match polygons[child_polygon_index].interior_point() { - Some(point) => point, - None => continue, - }; - - if !polygons[parent_polygon_index].contains(&child_interior_point) { - continue; - } - - let parent_envelope_area = parent_envelope.area(); - if let Some((_, best_area)) = best_parent { - if parent_envelope_area >= best_area { - continue; - } - } - best_parent = Some((parent_polygon_index, parent_envelope_area)); - } - - parent_polygon_index_by_polygon_index[child_polygon_index] = - best_parent.map(|(parent_polygon_index, _)| parent_polygon_index); - } - - let mut inferred_holes_by_parent_polygon_index: BTreeMap>> = - BTreeMap::new(); - for (child_polygon_index, parent_polygon_index) in - parent_polygon_index_by_polygon_index.iter().enumerate() - { - let parent_polygon_index = match parent_polygon_index { - Some(parent_polygon_index) => *parent_polygon_index, - None => continue, - }; - - let parent_exterior = polygons[parent_polygon_index].exterior(); - let has_same_exterior_polygon_with_explicit_holes = - polygons.iter().enumerate().any(|(polygon_index, polygon)| { - polygon_index != parent_polygon_index - && polygon.exterior() == parent_exterior - && !polygon.interiors().is_empty() - }); - if has_same_exterior_polygon_with_explicit_holes { - continue; - } - - let mut candidate_holes = inferred_holes_by_parent_polygon_index - .get(&parent_polygon_index) - .cloned() - .unwrap_or_default(); - candidate_holes.push(polygons[child_polygon_index].exterior().clone()); - - let candidate_polygon = Polygon::new( - polygons[parent_polygon_index].exterior().clone(), - candidate_holes.clone(), - ) - .orient(Direction::Default); - if !candidate_polygon.is_valid() { - continue; - } - - inferred_holes_by_parent_polygon_index.insert(parent_polygon_index, candidate_holes); - } - - polygons - .into_iter() - .enumerate() - .map(|(polygon_index, polygon)| { - let mut polygon_holes = polygon.interiors().to_vec(); - polygon_holes.extend( - inferred_holes_by_parent_polygon_index - .get(&polygon_index) - .cloned() - .unwrap_or_default(), - ); - - Polygon::new(polygon.exterior().clone(), polygon_holes).orient(Direction::Default) - }) - .collect() -} - -/// For each standalone polygon (no holes) whose interior point is contained -/// by another polygon, add the standalone's exterior as a hole of the -/// containing polygon. This handles "island" polygons that sit fully inside -/// a larger polygon and must be carved out, regardless of whether the parent -/// already has holes. -pub(super) fn carve_contained_standalones_as_holes( - polygons: Vec>, -) -> Vec> { - let mut polygons = polygons; - - for child_index in 0..polygons.len() { - if !polygons[child_index].interiors().is_empty() { - continue; - } - - let child_interior_point = match polygons[child_index].interior_point() { - Some(point) => point, - None => continue, - }; - let min_area = T::from(1e-9).unwrap_or(T::epsilon()); - if polygons[child_index].unsigned_area() <= min_area { - continue; - } - - let child_exterior = polygons[child_index].exterior().clone(); - - // Find the smallest containing polygon that has a different exterior. - let mut candidate_parents: Vec<(usize, T)> = polygons - .iter() - .enumerate() - .filter_map(|(parent_index, parent_polygon)| { - if parent_index == child_index { - return None; - } - if parent_polygon.exterior() == &child_exterior { - return None; - } - if !parent_polygon.contains(&child_interior_point) { - return None; - } - Some((parent_index, parent_polygon.unsigned_area())) - }) - .collect(); - candidate_parents.sort_by(|a, b| a.1.total_cmp(&b.1)); - - if candidate_parents.is_empty() { - continue; - } - - for (parent_index, _) in candidate_parents { - // Skip if the child is already a hole of this parent. - if polygons[parent_index] - .interiors() - .iter() - .any(|hole| hole == &child_exterior) - { - continue; - } - - // Skip if the parent has no holes and a sibling polygon with the - // same exterior already has holes — the parent is the "plain" - // variant that should stay hole-free. - if polygons[parent_index].interiors().is_empty() { - let sibling_has_holes = - polygons - .iter() - .enumerate() - .any(|(other_index, other_polygon)| { - other_index != parent_index - && other_polygon.exterior() == polygons[parent_index].exterior() - && !other_polygon.interiors().is_empty() - }); - if sibling_has_holes { - continue; - } - } - - let mut new_holes = polygons[parent_index].interiors().to_vec(); - new_holes.push(child_exterior.clone()); - - let candidate = Polygon::new(polygons[parent_index].exterior().clone(), new_holes) - .orient(Direction::Default); - if candidate.is_valid() { - polygons[parent_index] = candidate; - break; - } - } - } - - polygons -} - -/// Absorb tiny standalone polygons that sit between existing holes into -/// their parent. Only considers parents with ≥ 2 holes and standalones -/// whose area is below a threshold or that have no unique boundary -/// segments. -pub(super) fn infer_contained_standalone_polygons_as_holes( - polygons: Vec>, -) -> Vec> { - let mut polygons = polygons; - - for child_polygon_index in 0..polygons.len() { - if !polygons[child_polygon_index].interiors().is_empty() { - continue; - } - - let child_interior_point = match polygons[child_polygon_index].interior_point() { - Some(point) => point, - None => continue, - }; - let min_child_area = T::from(1e-9).unwrap_or(T::epsilon()); - if polygons[child_polygon_index].unsigned_area() <= min_child_area { - continue; - } - - // Only skip standalone polygons with unique boundary segments if - // their area is above a threshold. Tiny polygons with unique edges - // are often artifacts (faces between adjacent holes) that should - // be absorbed as holes of the enclosing polygon. - let child_area = polygons[child_polygon_index].unsigned_area(); - let tiny_threshold = T::from(0.1).unwrap_or(T::epsilon()); - if child_area > tiny_threshold - && polygon_unique_boundary_segment_count(&polygons, child_polygon_index) > 0 - { - continue; - } - - let child_exterior = polygons[child_polygon_index].exterior().clone(); - - let mut candidate_parent_indices: Vec = polygons - .iter() - .enumerate() - .filter_map(|(parent_polygon_index, parent_polygon)| { - if parent_polygon_index == child_polygon_index { - return None; - } - if parent_polygon.interiors().len() < 2 { - return None; - } - if parent_polygon.exterior() == polygons[child_polygon_index].exterior() { - return None; - } - - // Check containment using only the parent's exterior ring - // (ignoring existing holes), since the child might be between - // existing holes. - let parent_exterior_only = Polygon::new(parent_polygon.exterior().clone(), vec![]); - if !parent_exterior_only.contains(&child_interior_point) { - return None; - } - Some(parent_polygon_index) - }) - .collect(); - - candidate_parent_indices.sort_by(|left_index, right_index| { - polygons[*left_index] - .unsigned_area() - .total_cmp(&polygons[*right_index].unsigned_area()) - }); - - for parent_polygon_index in candidate_parent_indices { - let mut candidate_holes = polygons[parent_polygon_index].interiors().to_vec(); - - if candidate_holes.iter().any(|hole| hole == &child_exterior) { - continue; - } - - candidate_holes.push(child_exterior.clone()); - let candidate_parent_polygon = Polygon::new( - polygons[parent_polygon_index].exterior().clone(), - candidate_holes, - ) - .orient(Direction::Default); - if !candidate_parent_polygon.is_valid() { - continue; - } - - polygons[parent_polygon_index] = candidate_parent_polygon; - break; - } - } - - polygons -} - -/// For each polygon with holes, find standalone polygons (from the `polygons` -/// list) that are contained within the polygon's exterior AND share vertices -/// with existing holes. These standalone polygons are "gap fills" between -/// holes, and together with the existing holes they form a connected hole -/// region. Merge all connected hole components (existing holes + matching -/// standalone polygons) into single combined holes. -pub(super) fn merge_touching_holes_in_polygons( - polygons: Vec>, -) -> Vec> { - let mut polygons = polygons; - - // For each polygon with >= 2 holes, look for standalone polygons that - // connect its holes at shared vertices. - for parent_idx in 0..polygons.len() { - if polygons[parent_idx].interiors().len() < 2 { - continue; - } - - let parent_exterior_only = Polygon::new(polygons[parent_idx].exterior().clone(), vec![]); - - // Build per-hole vertex lists. - let hole_vertex_sets: Vec> = polygons[parent_idx] - .interiors() - .iter() - .map(|hole| hole.0.iter().map(|c| (c.x, c.y)).collect()) - .collect(); - - // Collect all hole vertex coords for the parent (flattened). - let parent_hole_coords: Vec<(T, T)> = hole_vertex_sets - .iter() - .flat_map(|s| s.iter().copied()) - .collect(); - - // Find standalone polygons (no holes, small) whose exterior shares - // vertices with the parent's holes and is inside the parent's exterior. - // Track WHICH holes each standalone connects to. - let mut connecting_standalone_indices: Vec = Vec::new(); - let mut any_bridges_multiple_holes = false; - for child_idx in 0..polygons.len() { - if child_idx == parent_idx { - continue; - } - if !polygons[child_idx].interiors().is_empty() { - continue; - } - if polygons[child_idx].exterior() == polygons[parent_idx].exterior() { - continue; - } - let child_coords: Vec<(T, T)> = polygons[child_idx] - .exterior() - .0 - .iter() - .map(|c| (c.x, c.y)) - .collect(); - let shares_vertex = child_coords.iter().any(|cv| { - parent_hole_coords - .iter() - .any(|pv| pv.0 == cv.0 && pv.1 == cv.1) - }); - if !shares_vertex { - continue; - } - let child_ip = match polygons[child_idx].interior_point() { - Some(p) => p, - None => continue, - }; - if !parent_exterior_only.contains(&child_ip) { - continue; - } - // Count how many distinct holes this standalone shares vertices with. - let hole_hits: usize = hole_vertex_sets - .iter() - .filter(|hvs| { - child_coords - .iter() - .any(|cv| hvs.iter().any(|hv| hv.0 == cv.0 && hv.1 == cv.1)) - }) - .count(); - if hole_hits >= 2 { - any_bridges_multiple_holes = true; - } - connecting_standalone_indices.push(child_idx); - } - - // Only merge when at least one standalone bridges 2+ different - // holes. If every standalone only touches a single hole, there - // is nothing to merge and we would lose holes. - if connecting_standalone_indices.is_empty() || !any_bridges_multiple_holes { - continue; - } - - // Collect all edges: parent holes + connecting standalone exteriors. - let mut all_lines: Vec> = Vec::new(); - for hole in polygons[parent_idx].interiors() { - all_lines.extend(hole.lines()); - } - for &child_idx in &connecting_standalone_indices { - all_lines.extend(polygons[child_idx].exterior().lines()); - } - - // Build graph and extract faces. - let snap_radius = - T::from(1e-10).unwrap_or(T::epsilon() * T::from(1024.0).unwrap_or(T::one())); - let noded_lines = nodify_lines(all_lines, snap_radius); - let graph = PolygonizerGraph::from_noded_lines(noded_lines); - let rings = graph.get_minimal_edge_rings(); - - // Find the largest ring (outer boundary of the merged holes). - let mut best_ring: Option> = None; - let mut best_area = T::zero(); - for ring in &rings { - if let Some(ls) = ring_to_valid_linestring(ring) { - let area = Polygon::new(ls.clone(), vec![]).unsigned_area(); - if area > best_area { - best_area = area; - best_ring = Some(ls); - } - } - } - - if let Some(merged_ring) = best_ring { - let candidate = - Polygon::new(polygons[parent_idx].exterior().clone(), vec![merged_ring]) - .orient(Direction::Default); - if candidate.is_valid() { - polygons[parent_idx] = candidate; - } - } - } - - polygons -} diff --git a/src/tests.rs b/src/tests.rs index fdf789d..f95b66b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -162,28 +162,6 @@ fn load_input_polygons(name: &str) -> Vec> { polygons } -fn multipolygon_to_geojson_string(multi: &MultiPolygon) -> String { - let features = multi - .0 - .iter() - .map(|polygon| geojson::Feature { - bbox: None, - geometry: Some(geojson::Geometry::new(GeometryValue::from(polygon))), - id: None, - properties: None, - foreign_members: None, - }) - .collect(); - - let collection = geojson::FeatureCollection { - bbox: None, - features, - foreign_members: None, - }; - - serde_json::to_string_pretty(&collection).expect("serialize multipolygon to geojson") -} - fn load_expected_polygons_from(output_kind: &str, name: &str) -> MultiPolygon { let collection = load_feature_collection(output_kind, name); let mut polygons = Vec::new(); @@ -773,21 +751,7 @@ fn polygonize_failed_hole_matches_fixture() { } // --------------------------------------------------------------------------- -// Topology cleanup pipeline tests -// -// The 7-pass cleanup pipeline is: -// 1. infer_parent_holes_when_output_has_no_holes -// 2. split_touching_boundary_polygons -// 3. infer_contained_standalone_polygons_as_holes -// 4. remove_non_unique_interior_points_for_touching_topology -// 5. carve_contained_standalones_as_holes (first) -// 6. merge_touching_holes_in_polygons -// 7. carve_contained_standalones_as_holes (second) -// -// "Depends on pass N" below means the test fails when that pass is removed -// from the pipeline in isolation. Tests without such an annotation are -// robust to any single-pass removal (they exercise earlier pipeline stages -// or require multiple passes to cooperate). +// Complex topology tests // --------------------------------------------------------------------------- /// Overlap ownership with touching holes: verifies correct polygon output @@ -926,20 +890,28 @@ fn polygonize_handles_nested_shell_overlap_without_double_containment() { } /// Split-touching face splitting: a rectangle with a diamond hole whose -/// vertices lie on two different exterior edges creates a polygon whose -/// interior is disconnected. Pass 2 re-polygonizes the boundary at the -/// degree>2 nodes and splits the donut into two separate pentagons. -/// -/// Depends on pass 2 (split_touching). +/// vertices lie on two different exterior edges. When the input is +/// pre-nodified (splitting the square edges at the diamond touch points), +/// the graph naturally produces three face polygons: two pentagons and +/// the diamond. #[test] fn polygonize_split_touching_two_points_matches_fixture() { - assert_polygonize_fixture("split_touching_two_points"); + let input_lines = load_input_lines("split_touching_two_points"); + let noded_lines = nodify_lines(input_lines.into_iter(), 1e-10); + let actual = polygonize(noded_lines); + let expected = load_expected_polygons_from("output", "split_touching_two_points"); + + let actual_canonical = canonicalize_multipolygon(&actual); + let expected_canonical = canonicalize_multipolygon(&expected); + + assert_eq!( + actual_canonical, expected_canonical, + "fixture mismatch: split_touching_two_points" + ); } /// Minimal reproducer for split-touching-hole-drop: a hole that touches /// the shell boundary must be cleanly split. -/// -/// Depends on passes: 3 (infer_contained), 7 (carve_contained, second). #[test] fn polygonize_split_touching_hole_drop_minimal_matches_fixture() { assert_polygonize_fixture("split_touching_hole_drop_minimal"); @@ -947,9 +919,6 @@ fn polygonize_split_touching_hole_drop_minimal_matches_fixture() { /// Overlap ownership area priority: when multiple shells claim a polygon, /// the smallest enclosing shell wins. -/// -/// Depends on passes: 1 (infer_parent_holes), 4 (remove_non_unique), -/// 5 (carve_contained, first), 6 (merge_touching_holes). #[test] fn polygonize_ownership_area_priority_enclosing_shell_minimal_matches_fixture() { assert_polygonize_fixture("ownership_area_priority_enclosing_shell_minimal"); @@ -1014,12 +983,6 @@ fn polygonize_preserves_enclosed_taiwan_polygon_minimal_matches_fixture() { let noded_lines = nodify_lines(input_lines.into_iter(), 1e-6_f64); let polygons = polygonize(noded_lines.into_iter()); - eprintln!( - "polygonize output for taiwan_bug_minimal ({} polygons):\n{}", - polygons.0.len(), - multipolygon_to_geojson_string(&polygons) - ); - let containing_polygons: Vec = polygons .0 .iter()