diff --git a/draftlogs/7921_fix.md b/draftlogs/7921_fix.md new file mode 100644 index 00000000000..abf160f7c7f --- /dev/null +++ b/draftlogs/7921_fix.md @@ -0,0 +1 @@ +- Prevent degenerate MultiPolygon features from aborting choropleth rendering [[#7921](https://github.com/plotly/plotly.js/pull/7921)] diff --git a/src/lib/geo_location_utils.js b/src/lib/geo_location_utils.js index 004100f72e5..68d495f13c3 100644 --- a/src/lib/geo_location_utils.js +++ b/src/lib/geo_location_utils.js @@ -331,6 +331,9 @@ function findCentroid(feature) { poly = geometry; } + // Degenerate MultiPolygons may not contain a positive-area polygon. + if (!poly) return [NaN, NaN]; + return turfCentroid(poly).geometry.coordinates; } diff --git a/test/jasmine/tests/lib_geo_location_utils_test.js b/test/jasmine/tests/lib_geo_location_utils_test.js index 4cd05e425a5..3465cee32a8 100644 --- a/test/jasmine/tests/lib_geo_location_utils_test.js +++ b/test/jasmine/tests/lib_geo_location_utils_test.js @@ -1,4 +1,67 @@ -const { getFitboundsLonRange, unwrapLonRange, doesCrossAntiMeridian } = require('../../../src/lib/geo_location_utils'); +const { + extractTraceFeature, + getFitboundsLonRange, + unwrapLonRange, + doesCrossAntiMeridian +} = require('../../../src/lib/geo_location_utils'); + +describe('Test geo_location_utils.extractTraceFeature', () => { + it('keeps degenerate MultiPolygons without affecting valid features', () => { + const trace = { + _length: 2, + geojson: { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + id: 'degenerate', + geometry: { + type: 'MultiPolygon', + coordinates: [ + [ + [ + [0, 0], + [1, 1], + [0, 0], + [0, 0] + ] + ] + ] + } + }, + { + type: 'Feature', + id: 'valid', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ] + ] + } + } + ] + } + }; + const calcTrace = [ + { loc: 'degenerate', trace }, + { loc: 'valid', trace } + ]; + + const features = extractTraceFeature(calcTrace); + + expect(features.length).toBe(2); + expect(features[0].id).toBe('degenerate'); + expect(features[0].properties.ct.every(Number.isNaN)).toBe(true); + expect(features[1].id).toBe('valid'); + expect(features[1].properties.ct.every(Number.isFinite)).toBe(true); + }); +}); describe('Test geo_location_utils.getFitboundsLonRange', () => { it('returns the compact crossing range when point data straddles the antimeridian', () => {