Skip to content

Commit aef4d74

Browse files
rowanwinsmfedderly
andauthored
Overhaul approach for dissolve module (#2008)
* Overhaul approach for dissolve module * Update docs. Resolves #1806 * More tests * run prettier * remove es6 and update yarn lock * Fix trailing bracket * rerun prettier * Ditch spread operator * prettier * remove let * Finally found remaining build dodginess * add test for properties * convert multipolys to polys to avoid breaking changes * run prettier * update package versions * fix use of hasOwnProperty * run prettier * remove changes to yarn * add empty line end yarnlock * try update yarn lock again Co-authored-by: mfedderly <mdfedderly@mdfedderly.com>
1 parent 4a0ff38 commit aef4d74

15 files changed

+21810
-4118
lines changed

packages/turf-dissolve/README.md

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,65 @@ Note that [mulitpolygon][2] features within the collection are not supported
99

1010
**Parameters**
1111

12-
- `featureCollection` **[FeatureCollection][3]&lt;[Polygon][4]>** input feature collection to be dissolved
13-
- `options` **[Object][5]** Optional parameters (optional, default `{}`)
14-
- `options.propertyName` **[string][6]?** features with equals 'propertyName' in `properties` will be merged
12+
- `featureCollection` **[FeatureCollection][3]&lt;[Polygon][4]>** input feature collection to be dissolved
13+
- `options` **[Object][5]** Optional parameters (optional, default `{}`)
14+
- `options.propertyName` **[string][6]?** features with equals 'propertyName' in `properties` will be merged
1515

1616
**Examples**
1717

1818
```javascript
1919
var features = turf.featureCollection([
20-
turf.polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], {combine: 'yes'}),
21-
turf.polygon([[[0, -1], [0, 0], [1, 0], [1, -1], [0,-1]]], {combine: 'yes'}),
22-
turf.polygon([[[1,-1],[1, 0], [2, 0], [2, -1], [1, -1]]], {combine: 'no'}),
20+
turf.polygon(
21+
[
22+
[
23+
[0, 0],
24+
[0, 1],
25+
[1, 1],
26+
[1, 0],
27+
[0, 0],
28+
],
29+
],
30+
{ combine: "yes" }
31+
),
32+
turf.polygon(
33+
[
34+
[
35+
[0, -1],
36+
[0, 0],
37+
[1, 0],
38+
[1, -1],
39+
[0, -1],
40+
],
41+
],
42+
{ combine: "yes" }
43+
),
44+
turf.polygon(
45+
[
46+
[
47+
[1, -1],
48+
[1, 0],
49+
[2, 0],
50+
[2, -1],
51+
[1, -1],
52+
],
53+
],
54+
{ combine: "no" }
55+
),
2356
]);
2457

25-
var dissolved = turf.dissolve(features, {propertyName: 'combine'});
58+
var dissolved = turf.dissolve(features, { propertyName: "combine" });
2659

2760
//addToMap
28-
var addToMap = [features, dissolved]
61+
var addToMap = [features, dissolved];
2962
```
3063

3164
Returns **[FeatureCollection][3]&lt;[Polygon][4]>** a FeatureCollection containing the dissolved polygons
3265

3366
[1]: polygon
34-
3567
[2]: mulitpolygon
36-
3768
[3]: https://tools.ietf.org/html/rfc7946#section-3.3
38-
3969
[4]: https://tools.ietf.org/html/rfc7946#section-3.1.6
40-
4170
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
42-
4371
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
4472

4573
<!-- This file is automatically generated. Please don't edit it directly:

packages/turf-dissolve/bench.js

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,11 @@ const fixtures = fs.readdirSync(directory).map((filename) => {
1212
};
1313
});
1414

15-
/**
16-
* Single Process Benchmark
17-
*
18-
* polysByProperty: 64.173ms
19-
* polysWithoutProperty: 44.453ms
20-
*/
21-
for (const { name, geojson } of fixtures) {
22-
const propertyName = geojson.propertyName;
23-
console.time(name);
24-
dissolve(geojson, { propertyName });
25-
console.timeEnd(name);
26-
}
27-
2815
/**
2916
* Benchmark Results
3017
*
31-
* polysByProperty x 425 ops/sec ±7.64% (73 runs sampled)
32-
* polysWithoutProperty x 238 ops/sec ±11.04% (59 runs sampled)
18+
* polysByProperty x 6,366 ops/sec ±1.49% (89 runs sampled)
19+
* polysWithoutProperty x 4,224 ops/sec ±2.34% (77 runs sampled)
3320
*/
3421
const suite = new Benchmark.Suite("turf-dissolve");
3522
for (const { name, geojson } of fixtures) {

packages/turf-dissolve/index.js

Lines changed: 41 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import rbush from "geojson-rbush";
2-
import clone from "@turf/clone";
3-
import overlap from "@turf/boolean-overlap";
4-
import turfUnion from "@turf/union";
5-
import lineIntersect from "@turf/line-intersect";
6-
import { coordAll } from "@turf/meta";
1+
import { featureCollection, multiPolygon, isObject } from "@turf/helpers";
72
import { collectionOf } from "@turf/invariant";
8-
import { lineString, isObject } from "@turf/helpers";
9-
import { closestGreaterNumber } from "./lib/get-closest";
3+
import { featureEach } from "@turf/meta";
4+
import flatten from "@turf/flatten";
5+
import polygonClipping from "polygon-clipping";
106

117
/**
128
* Dissolves a FeatureCollection of {@link polygon} features, filtered by an optional property name:value.
@@ -15,7 +11,7 @@ import { closestGreaterNumber } from "./lib/get-closest";
1511
* @name dissolve
1612
* @param {FeatureCollection<Polygon>} featureCollection input feature collection to be dissolved
1713
* @param {Object} [options={}] Optional parameters
18-
* @param {string} [options.propertyName] features with equals 'propertyName' in `properties` will be merged
14+
* @param {string} [options.propertyName] features with the same `propertyName` value will be dissolved.
1915
* @returns {FeatureCollection<Polygon>} a FeatureCollection containing the dissolved polygons
2016
* @example
2117
* var features = turf.featureCollection([
@@ -29,118 +25,57 @@ import { closestGreaterNumber } from "./lib/get-closest";
2925
* //addToMap
3026
* var addToMap = [features, dissolved]
3127
*/
32-
function dissolve(featureCollection, options) {
28+
function dissolve(fc, options) {
3329
// Optional parameters
3430
options = options || {};
3531
if (!isObject(options)) throw new Error("options is invalid");
3632
var propertyName = options.propertyName;
3733

3834
// Input validation
39-
collectionOf(featureCollection, "Polygon", "dissolve");
35+
collectionOf(fc, "Polygon", "dissolve");
4036

4137
// Main
42-
var fc = clone(featureCollection);
43-
var features = fc.features;
44-
45-
var originalIndexOfItemsRemoved = [];
46-
47-
features.forEach(function (f, i) {
48-
f.properties.origIndexPosition = i;
49-
});
50-
var tree = rbush();
51-
tree.load(fc);
52-
53-
for (var i in features) {
54-
var polygon = features[i];
55-
56-
var featureChanged = false;
57-
58-
tree.search(polygon).features.forEach(function (potentialMatchingFeature) {
59-
polygon = features[i];
60-
61-
var matchFeaturePosition =
62-
potentialMatchingFeature.properties.origIndexPosition;
63-
38+
var outFeatures = [];
39+
if (!options.propertyName) {
40+
return flatten(
41+
multiPolygon(
42+
polygonClipping.union.apply(
43+
null,
44+
fc.features.map(function (f) {
45+
return f.geometry.coordinates;
46+
})
47+
)
48+
)
49+
);
50+
} else {
51+
var uniquePropertyVals = {};
52+
featureEach(fc, function (feature) {
6453
if (
65-
originalIndexOfItemsRemoved.length > 0 &&
66-
matchFeaturePosition !== 0
54+
!Object.prototype.hasOwnProperty.call(
55+
uniquePropertyVals,
56+
feature.properties[propertyName]
57+
)
6758
) {
68-
if (
69-
matchFeaturePosition >
70-
originalIndexOfItemsRemoved[originalIndexOfItemsRemoved.length - 1]
71-
) {
72-
matchFeaturePosition =
73-
matchFeaturePosition - originalIndexOfItemsRemoved.length;
74-
} else {
75-
var closestNumber = closestGreaterNumber(
76-
matchFeaturePosition,
77-
originalIndexOfItemsRemoved
78-
);
79-
if (closestNumber !== 0) {
80-
matchFeaturePosition = matchFeaturePosition - closestNumber;
81-
}
82-
}
59+
uniquePropertyVals[feature.properties[propertyName]] = [];
8360
}
84-
85-
if (matchFeaturePosition === +i) return;
86-
87-
var matchFeature = features[matchFeaturePosition];
88-
if (!matchFeature || !polygon) return;
89-
90-
if (
91-
propertyName !== undefined &&
92-
matchFeature.properties[propertyName] !==
93-
polygon.properties[propertyName]
94-
)
95-
return;
96-
97-
if (
98-
!overlap(polygon, matchFeature) ||
99-
!ringsIntersect(polygon, matchFeature)
100-
)
101-
return;
102-
103-
features[i] = turfUnion(polygon, matchFeature);
104-
105-
originalIndexOfItemsRemoved.push(
106-
potentialMatchingFeature.properties.origIndexPosition
107-
);
108-
originalIndexOfItemsRemoved.sort(function (a, b) {
109-
return a - b;
110-
});
111-
112-
tree.remove(potentialMatchingFeature);
113-
features.splice(matchFeaturePosition, 1);
114-
polygon.properties.origIndexPosition = i;
115-
tree.remove(polygon, function (a, b) {
116-
return (
117-
a.properties.origIndexPosition === b.properties.origIndexPosition
118-
);
119-
});
120-
featureChanged = true;
61+
uniquePropertyVals[feature.properties[propertyName]].push(feature);
12162
});
122-
123-
if (featureChanged) {
124-
if (!polygon) continue;
125-
polygon.properties.origIndexPosition = i;
126-
tree.insert(polygon);
127-
i--;
63+
var vals = Object.keys(uniquePropertyVals);
64+
for (var i = 0; i < vals.length; i++) {
65+
var mp = multiPolygon(
66+
polygonClipping.union.apply(
67+
null,
68+
uniquePropertyVals[vals[i]].map(function (f) {
69+
return f.geometry.coordinates;
70+
})
71+
)
72+
);
73+
mp.properties[propertyName] = vals[i];
74+
outFeatures.push(mp);
12875
}
12976
}
13077

131-
features.forEach(function (f) {
132-
delete f.properties.origIndexPosition;
133-
delete f.bbox;
134-
});
135-
136-
return fc;
137-
}
138-
139-
function ringsIntersect(poly1, poly2) {
140-
var line1 = lineString(coordAll(poly1));
141-
var line2 = lineString(coordAll(poly2));
142-
var points = lineIntersect(line1, line2).features;
143-
return points.length > 0;
78+
return flatten(featureCollection(outFeatures));
14479
}
14580

14681
export default dissolve;

packages/turf-dissolve/lib/get-closest.js

Lines changed: 0 additions & 112 deletions
This file was deleted.

0 commit comments

Comments
 (0)