Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify fix #903

Merged
merged 7 commits into from
Aug 17, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
implements all original tests
  • Loading branch information
stebogit committed Aug 15, 2017
commit 7fc4aaa41dd731019306b864b3efae3002a7d797
133 changes: 68 additions & 65 deletions packages/turf-simplify/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
var simplify = require('simplify-js');
var helpers = require('@turf/helpers');
var cleanCoords = require('@turf/clean-coords');
var newFeature = helpers.feature;
var lineString = helpers.lineString;
var multiLineString = helpers.multiLineString;
var polygon = helpers.polygon;
var multiPolygon = helpers.multiPolygon;

// supported GeoJSON geometries, used to check whether to wrap in simpleFeature()
// supported GeoJSON geometries
var supportedTypes = ['LineString', 'MultiLineString', 'Polygon', 'MultiPolygon'];

/**
* Takes a {@link LineString} or {@link Polygon} and returns a simplified version. Internally uses [simplify-js](http://mourner.github.io/simplify-js/) to perform simplification.
* Takes a {@link LineString} or {@link Polygon} and returns a simplified version. Internally uses
* [simplify-js](http://mourner.github.io/simplify-js/) to perform simplification.
*
* @name simplify
* @param {Feature<(LineString|Polygon|MultiLineString|MultiPolygon)>|FeatureCollection|GeometryCollection} feature feature to be simplified
* @param {number} [tolerance=1] simplification tolerance
* @param {boolean} [highQuality=false] whether or not to spend more time to create
* a higher-quality simplification with a different algorithm
* @param {boolean} [highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
* @returns {Feature<(LineString|Polygon|MultiLineString|MultiPolygon)>|FeatureCollection|GeometryCollection} a simplified feature
* @example
* var feature = turf.polygon([[
Expand Down Expand Up @@ -43,96 +50,92 @@ var supportedTypes = ['LineString', 'MultiLineString', 'Polygon', 'MultiPolygon'
* var addToMap = [feature, simplified]
*/
module.exports = function (feature, tolerance, highQuality) {
if (feature.type === 'Feature') {
return simpleFeature(
simplifyHelper(feature, tolerance, highQuality),
feature.properties);
} else if (feature.type === 'FeatureCollection') {
return {
// todo: add input validation
var output;

switch (feature.type) {
case 'Feature':
return newFeature(simplifyHelper(cleanCoords(feature), tolerance, highQuality), feature.properties);
case 'FeatureCollection':
output = {
type: 'FeatureCollection',
features: feature.features.map(function (f) {
var simplified = simplifyHelper(f, tolerance, highQuality);

// we create simpleFeature here because it doesn't apply to GeometryCollection
var simplified = simplifyHelper(cleanCoords(f), tolerance, highQuality);
// we create a new `Feature` here because it doesn't apply to GeometryCollection
// so we can't create it at simplifyHelper()
if (supportedTypes.indexOf(simplified.type) > -1) {
return simpleFeature(simplified, f.properties);
} else {
return simplified;
}
if (supportedTypes.indexOf(simplified.type) > -1) return newFeature(simplified, f.properties);
else return simplified;
})
};
} else if (feature.type === 'GeometryCollection') {
return {
if (feature.properties) output.properties = feature.properties;
return output;
case 'GeometryCollection':
output = {
type: 'GeometryCollection',
geometries: feature.geometries.map(function (g) {
if (supportedTypes.indexOf(g.type) > -1) {
return simplifyHelper({
type: 'Feature',
geometry: g
geometry: cleanCoords(g)
}, tolerance, highQuality);
}
return g;
})
};
} else {
if (feature.properties) output.properties = feature.properties;
return output;
default:
return feature;
}
};


/**
* Simplifies feature coordinates
*
* @private
* @param {Feature<(LineString|Polygon|MultiLineString|MultiPolygon)>|FeatureCollection|GeometryCollection} feature to be simplified
* @param {number} [tolerance=1] simplification tolerance
* @param {boolean} [highQuality=false] whether or not to spend more time to create a higher-quality simplification with a different algorithm
* @returns {Geometry} output
*/
function simplifyHelper(feature, tolerance, highQuality) {
if (feature.geometry.type === 'LineString') {
return {
type: 'LineString',
coordinates: simplifyLine(feature.geometry.coordinates, tolerance, highQuality)
};
} else if (feature.geometry.type === 'MultiLineString') {
return {
type: 'MultiLineString',
coordinates: feature.geometry.coordinates.map(function (lines) {
var type = feature.geometry.type;
// unsupported geometry types
if (type === 'Point' || type === 'MultiPoint') return feature;

var coordinates = feature.geometry.coordinates;
var simplified;
switch (type) {
case 'LineString':
simplified = simplifyLine(coordinates, tolerance, highQuality);
break;
case 'MultiLineString':
simplified = coordinates.map(function (lines) {
return simplifyLine(lines, tolerance, highQuality);
})
};
} else if (feature.geometry.type === 'Polygon') {
return {
type: 'Polygon',
coordinates: simplifyPolygon(feature.geometry.coordinates, tolerance, highQuality)
};
} else if (feature.geometry.type === 'MultiPolygon') {
return {
type: 'MultiPolygon',
coordinates: feature.geometry.coordinates.map(function (rings) {
});
break;
case 'Polygon':
simplified = simplifyPolygon(coordinates, tolerance, highQuality);
break;
case 'MultiPolygon':
simplified = coordinates.map(function (rings) {
return simplifyPolygon(rings, tolerance, highQuality);
})
};
} else {
// unsupported geometry type supplied
return feature;
});
}

return {
type: type,
coordinates: simplified
};
}

/*
* returns true if ring's first coordinate is the same as its last
*/
function checkValidity(ring) {
if (ring.length < 3) {
return false;
if (ring.length < 3) return false;
//if the last point is the same as the first, it's not a triangle
} else if (ring.length === 3 &&
((ring[2][0] === ring[0][0]) && (ring[2][1] === ring[0][1]))) {
return false;
} else {
return true;
}
}

function simpleFeature(geom, properties) {
return {
type: 'Feature',
geometry: geom,
properties: properties
};
return !(ring.length === 3 && ((ring[2][0] === ring[0][0]) && (ring[2][1] === ring[0][1])));
}

function simplifyLine(coordinates, tolerance, highQuality) {
Expand Down Expand Up @@ -163,7 +166,7 @@ function simplifyPolygon(coordinates, tolerance, highQuality) {
}
if (
(simpleRing[simpleRing.length - 1][0] !== simpleRing[0][0]) ||
(simpleRing[simpleRing.length - 1][1] !== simpleRing[0][1])) {
(simpleRing[simpleRing.length - 1][1] !== simpleRing[0][1])) {
simpleRing.push(simpleRing[0]);
}
return simpleRing;
Expand Down
9 changes: 8 additions & 1 deletion packages/turf-simplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,23 @@
"simplify"
],
"author": "Turf Authors",
"contributors": [
"Stefano Borghi <@stebogit>"
],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try including past contributors to this list when updating modules (@tmcw was heavily involved in the initial development of this module).

Click the history button on the index.js file.
/~https://github.com/Turfjs/turf/commits/b360f9beeec921c38468062fce893158e4a84988/packages/turf-simplify/index.js

"license": "MIT",
"bugs": {
"url": "/~https://github.com/Turfjs/turf/issues"
},
"homepage": "/~https://github.com/Turfjs/turf",
"devDependencies": {
"benchmark": "^2.1.4",
"tape": "^4.6.3"
"load-json-file": "^2.0.0",
"tape": "^4.6.3",
"write-json-file": "^2.0.0"
},
"dependencies": {
"@turf/clean-coords": "^4.6.0",
"@turf/helpers": "^4.6.0",
"simplify-js": "^1.2.1"
}
}
142 changes: 33 additions & 109 deletions packages/turf-simplify/test.js
Original file line number Diff line number Diff line change
@@ -1,112 +1,36 @@
var simplify = require('./');
var test = require('tape');
var fs = require('fs');

test('simplify -- line', function (t) {
var line = JSON.parse(fs.readFileSync(__dirname + '/test/fixtures/in/linestring.geojson'));

var simplified = simplify(line, 0.01, false);
t.ok(simplified);
t.equal(simplified.type, 'Feature');
t.equal(typeof simplified.geometry.coordinates[0][0], 'number');
fs.writeFileSync(__dirname + '/test/fixtures/out/linestring_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
});

test('simplify -- multiline', function (t) {
var multiline = JSON.parse(fs.readFileSync(__dirname + '/test/fixtures/in/multilinestring.geojson'));

var simplified = simplify(multiline, 0.01, false);
t.ok(simplified);
t.equal(simplified.type, 'Feature');
var len = multiline.geometry.coordinates.length,
i;
for (i = 0; i < len; i++) {
t.equal(typeof simplified.geometry.coordinates[i][0][0], 'number');
}
fs.writeFileSync(__dirname + '/test/fixtures/out/multilinestring_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
});

test('simplify -- polygon', function (t) {
var polygon = JSON.parse(fs.readFileSync(__dirname + '/test/fixtures/in/polygon.geojson'));

var simplified = simplify(polygon, 1, false);
t.equal(simplified.type, 'Feature');
t.equal(typeof simplified.geometry.coordinates[0][0][0], 'number');
fs.writeFileSync(__dirname + '/test/fixtures/out/polygon_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
});

test('simplify -- over simplify polygon', function (t) {
var polygon = JSON.parse(fs.readFileSync(__dirname + '/test/fixtures/in/simple.geojson'));

var simplified = simplify(polygon, 100, false);
t.equal(simplified.type, 'Feature');
t.equal(typeof simplified.geometry.coordinates[0][0][0], 'number');
fs.writeFileSync(__dirname + '/test/fixtures/out/simple_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
});

test('simplify -- multipolygon', function (t) {
var multipolygon = JSON.parse(fs.readFileSync(__dirname + '/test/fixtures/in/multipolygon.geojson'));

var simplified = simplify(multipolygon, 0.01, false);
t.equal(simplified.type, 'Feature');
var len = multipolygon.geometry.coordinates.length,
i;
for (i = 0; i < len; i++) {
t.equal(typeof simplified.geometry.coordinates[i][0][0][0], 'number');
}
fs.writeFileSync(__dirname + '/test/fixtures/out/multipolygon_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
});

test('simplify -- featurecollection', function (t) {
var featurecollection = JSON.parse((fs.readFileSync(__dirname + '/test/fixtures/in/featurecollection.geojson')));

var simplified = simplify(featurecollection, 0.01, false);
t.equal(simplified.type, 'FeatureCollection');

fs.writeFileSync(__dirname + '/test/fixtures/out/featurecollection_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
});

test('simplify -- geometrycollection', function (t) {
var geometrycollection = JSON.parse((fs.readFileSync(__dirname + '/test/fixtures/in/geometrycollection.geojson')));

var simplified = simplify(geometrycollection, 0.01, false);
t.equal(simplified.type, 'GeometryCollection');
simplified.geometries.forEach(function (g) {
if (g.type === 'LineString') {
t.equal(typeof g.coordinates[0][0], 'number');
} else if (g.type === 'MultiLineString' || g.type === 'Polygon') {
// intentionally only checking the first line for multilinestring, test covered elsewhere
t.equal(typeof g.coordinates[0][0][0], 'number');
} else if (g.type === 'MultiPolygon') {
// intentionally only checking the first ring, test covered elsewhere
t.equal(typeof g.coordinates[0][0][0][0], 'number');
const fs = require('fs');
const test = require('tape');
const path = require('path');
const load = require('load-json-file');
const write = require('write-json-file');
const {point, lineString, geometryCollection, featureCollection} = require('@turf/helpers');
const simplify = require('./');

const directories = {
in: path.join(__dirname, 'test', 'in') + path.sep,
out: path.join(__dirname, 'test', 'out') + path.sep
};

const fixtures = fs.readdirSync(directories.in).map(filename => {
return {
filename,
name: path.parse(filename).name,
geojson: load.sync(directories.in + filename)
};
});

test('simplify', t => {
for (const {filename, name, geojson} of fixtures) {
let {tolerance, highQuality} = geojson.properties || {};
tolerance = tolerance || 0.01;
highQuality = highQuality || false;

const simplified = simplify(geojson, tolerance, highQuality);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests look so much nicer now!! 😄

// const result = featureCollection([simplified, geojson]);

if (process.env.REGEN) write.sync(directories.out + filename, simplified);
t.deepEqual(simplified, load.sync(directories.out + filename), name);
}
});

fs.writeFileSync(__dirname + '/test/fixtures/out/geometrycollection_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
});

test('simplify -- argentina', function (t) {
var argentina = JSON.parse(fs.readFileSync(__dirname + '/test/fixtures/in/argentina.geojson'));

var simplified = simplify(argentina, 0.1, false);
t.equal(simplified.type, 'Feature');
t.equal(typeof simplified.geometry.coordinates[0][0][0], 'number');
fs.writeFileSync(__dirname + '/test/fixtures/out/argentina_out.geojson', JSON.stringify(simplified, null, 2));

t.end();
t.end();
});
1 change: 0 additions & 1 deletion packages/turf-simplify/test/fixtures/in/argentina.geojson

This file was deleted.

Loading