Skip to content

Commit

Permalink
Overhaul approach for dissolve module (#2008)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
rowanwins and mfedderly committed Jul 6, 2021
1 parent 4a0ff38 commit aef4d74
Show file tree
Hide file tree
Showing 15 changed files with 21,810 additions and 4,118 deletions.
54 changes: 41 additions & 13 deletions packages/turf-dissolve/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,65 @@ Note that [mulitpolygon][2] features within the collection are not supported

**Parameters**

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

**Examples**

```javascript
var features = turf.featureCollection([
turf.polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], {combine: 'yes'}),
turf.polygon([[[0, -1], [0, 0], [1, 0], [1, -1], [0,-1]]], {combine: 'yes'}),
turf.polygon([[[1,-1],[1, 0], [2, 0], [2, -1], [1, -1]]], {combine: 'no'}),
turf.polygon(
[
[
[0, 0],
[0, 1],
[1, 1],
[1, 0],
[0, 0],
],
],
{ combine: "yes" }
),
turf.polygon(
[
[
[0, -1],
[0, 0],
[1, 0],
[1, -1],
[0, -1],
],
],
{ combine: "yes" }
),
turf.polygon(
[
[
[1, -1],
[1, 0],
[2, 0],
[2, -1],
[1, -1],
],
],
{ combine: "no" }
),
]);

var dissolved = turf.dissolve(features, {propertyName: 'combine'});
var dissolved = turf.dissolve(features, { propertyName: "combine" });

//addToMap
var addToMap = [features, dissolved]
var addToMap = [features, dissolved];
```

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

[1]: polygon

[2]: mulitpolygon

[3]: https://tools.ietf.org/html/rfc7946#section-3.3

[4]: https://tools.ietf.org/html/rfc7946#section-3.1.6

[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

<!-- This file is automatically generated. Please don't edit it directly:
Expand Down
17 changes: 2 additions & 15 deletions packages/turf-dissolve/bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,11 @@ const fixtures = fs.readdirSync(directory).map((filename) => {
};
});

/**
* Single Process Benchmark
*
* polysByProperty: 64.173ms
* polysWithoutProperty: 44.453ms
*/
for (const { name, geojson } of fixtures) {
const propertyName = geojson.propertyName;
console.time(name);
dissolve(geojson, { propertyName });
console.timeEnd(name);
}

/**
* Benchmark Results
*
* polysByProperty x 425 ops/sec ±7.64% (73 runs sampled)
* polysWithoutProperty x 238 ops/sec ±11.04% (59 runs sampled)
* polysByProperty x 6,366 ops/sec ±1.49% (89 runs sampled)
* polysWithoutProperty x 4,224 ops/sec ±2.34% (77 runs sampled)
*/
const suite = new Benchmark.Suite("turf-dissolve");
for (const { name, geojson } of fixtures) {
Expand Down
147 changes: 41 additions & 106 deletions packages/turf-dissolve/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import rbush from "geojson-rbush";
import clone from "@turf/clone";
import overlap from "@turf/boolean-overlap";
import turfUnion from "@turf/union";
import lineIntersect from "@turf/line-intersect";
import { coordAll } from "@turf/meta";
import { featureCollection, multiPolygon, isObject } from "@turf/helpers";
import { collectionOf } from "@turf/invariant";
import { lineString, isObject } from "@turf/helpers";
import { closestGreaterNumber } from "./lib/get-closest";
import { featureEach } from "@turf/meta";
import flatten from "@turf/flatten";
import polygonClipping from "polygon-clipping";

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

// Input validation
collectionOf(featureCollection, "Polygon", "dissolve");
collectionOf(fc, "Polygon", "dissolve");

// Main
var fc = clone(featureCollection);
var features = fc.features;

var originalIndexOfItemsRemoved = [];

features.forEach(function (f, i) {
f.properties.origIndexPosition = i;
});
var tree = rbush();
tree.load(fc);

for (var i in features) {
var polygon = features[i];

var featureChanged = false;

tree.search(polygon).features.forEach(function (potentialMatchingFeature) {
polygon = features[i];

var matchFeaturePosition =
potentialMatchingFeature.properties.origIndexPosition;

var outFeatures = [];
if (!options.propertyName) {
return flatten(
multiPolygon(
polygonClipping.union.apply(
null,
fc.features.map(function (f) {
return f.geometry.coordinates;
})
)
)
);
} else {
var uniquePropertyVals = {};
featureEach(fc, function (feature) {
if (
originalIndexOfItemsRemoved.length > 0 &&
matchFeaturePosition !== 0
!Object.prototype.hasOwnProperty.call(
uniquePropertyVals,
feature.properties[propertyName]
)
) {
if (
matchFeaturePosition >
originalIndexOfItemsRemoved[originalIndexOfItemsRemoved.length - 1]
) {
matchFeaturePosition =
matchFeaturePosition - originalIndexOfItemsRemoved.length;
} else {
var closestNumber = closestGreaterNumber(
matchFeaturePosition,
originalIndexOfItemsRemoved
);
if (closestNumber !== 0) {
matchFeaturePosition = matchFeaturePosition - closestNumber;
}
}
uniquePropertyVals[feature.properties[propertyName]] = [];
}

if (matchFeaturePosition === +i) return;

var matchFeature = features[matchFeaturePosition];
if (!matchFeature || !polygon) return;

if (
propertyName !== undefined &&
matchFeature.properties[propertyName] !==
polygon.properties[propertyName]
)
return;

if (
!overlap(polygon, matchFeature) ||
!ringsIntersect(polygon, matchFeature)
)
return;

features[i] = turfUnion(polygon, matchFeature);

originalIndexOfItemsRemoved.push(
potentialMatchingFeature.properties.origIndexPosition
);
originalIndexOfItemsRemoved.sort(function (a, b) {
return a - b;
});

tree.remove(potentialMatchingFeature);
features.splice(matchFeaturePosition, 1);
polygon.properties.origIndexPosition = i;
tree.remove(polygon, function (a, b) {
return (
a.properties.origIndexPosition === b.properties.origIndexPosition
);
});
featureChanged = true;
uniquePropertyVals[feature.properties[propertyName]].push(feature);
});

if (featureChanged) {
if (!polygon) continue;
polygon.properties.origIndexPosition = i;
tree.insert(polygon);
i--;
var vals = Object.keys(uniquePropertyVals);
for (var i = 0; i < vals.length; i++) {
var mp = multiPolygon(
polygonClipping.union.apply(
null,
uniquePropertyVals[vals[i]].map(function (f) {
return f.geometry.coordinates;
})
)
);
mp.properties[propertyName] = vals[i];
outFeatures.push(mp);
}
}

features.forEach(function (f) {
delete f.properties.origIndexPosition;
delete f.bbox;
});

return fc;
}

function ringsIntersect(poly1, poly2) {
var line1 = lineString(coordAll(poly1));
var line2 = lineString(coordAll(poly2));
var points = lineIntersect(line1, line2).features;
return points.length > 0;
return flatten(featureCollection(outFeatures));
}

export default dissolve;
112 changes: 0 additions & 112 deletions packages/turf-dissolve/lib/get-closest.js

This file was deleted.

Loading

0 comments on commit aef4d74

Please sign in to comment.