-
Notifications
You must be signed in to change notification settings - Fork 937
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
New module @turf/clean-coords
#875
Changes from 5 commits
195cadd
fcdda7b
8cdd1a3
79a9b57
93b2dd3
27103e2
69dd6b4
7b34a56
8c94d5f
cc1f7a0
cf079ef
ce1e3a7
45e8ab1
36ca0d1
d684814
d4a296d
11f20ee
54018e6
b718b5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2017 TurfJS | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
this software and associated documentation files (the "Software"), to deal in | ||
the Software without restriction, including without limitation the rights to | ||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
the Software, and to permit persons to whom the Software is furnished to do so, | ||
subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# @turf/clean-coords | ||
|
||
# cleanCoords | ||
|
||
Removes redundant coordinates from a (Multi)LineString or (Multi)Polygon; ignores (Multi)Point. | ||
|
||
**Parameters** | ||
|
||
- `geojson` **([Geometry](http://geojson.org/geojson-spec.html#geometry) \| [Feature](http://geojson.org/geojson-spec.html#feature-objects)<any>)** Feature or Geometry | ||
- `mutate` **\[[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** allows GeoJSON input to be mutated (optional, default `false`) | ||
|
||
**Examples** | ||
|
||
```javascript | ||
var line = trf.lineString([[0, 0], [0, 2], [0, 5], [0, 8], [0, 8], [0, 10]]); | ||
|
||
var cleaned = turf.cleanCoords(line).geometry.coordinates; | ||
//= [[0, 0], [0, 10]] | ||
``` | ||
|
||
Returns **([Geometry](http://geojson.org/geojson-spec.html#geometry) \| [Feature](http://geojson.org/geojson-spec.html#feature-objects)<any>)** the cleaned input Feature/Geometry | ||
|
||
<!-- This file is automatically generated. Please don't edit it directly: | ||
if you find an error, edit the source file (likely index.js), and re-run | ||
./scripts/generate-readmes in the turf project. --> | ||
|
||
--- | ||
|
||
This module is part of the [Turfjs project](http://turfjs.org/), an open source | ||
module collection dedicated to geographic algorithms. It is maintained in the | ||
[Turfjs/turf](https://github.com/Turfjs/turf) repository, where you can create | ||
PRs and issues. | ||
|
||
### Installation | ||
|
||
Install this module individually: | ||
|
||
```sh | ||
$ npm install @turf/clean-coords | ||
``` | ||
|
||
Or install the Turf module that includes it as a function: | ||
|
||
```sh | ||
$ npm install @turf/turf | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const path = require('path'); | ||
const glob = require('glob'); | ||
const load = require('load-json-file'); | ||
const Benchmark = require('benchmark'); | ||
const cleanCoords = require('./'); | ||
|
||
/** | ||
* Benchmark Results | ||
* | ||
* multiline: 1.030ms | ||
* multipoint: 0.078ms | ||
* multipolygon: 0.329ms | ||
* point: 0.060ms | ||
* polygon-with-hole: 0.191ms | ||
* polygon: 0.073ms | ||
* simple-line: 0.064ms | ||
* triangle: 0.105ms | ||
* multiline x 86,027 ops/sec ±2.78% (72 runs sampled) | ||
* multipoint x 112,741 ops/sec ±3.84% (74 runs sampled) | ||
* multipolygon x 49,881 ops/sec ±2.92% (75 runs sampled) | ||
* point x 163,172 ops/sec ±6.79% (68 runs sampled) | ||
* polygon-with-hole x 67,728 ops/sec ±3.33% (73 runs sampled) | ||
* polygon x 111,302 ops/sec ±3.23% (76 runs sampled) | ||
* simple-line x 136,383 ops/sec ±2.53% (76 runs sampled) | ||
* triangle x 93,747 ops/sec ±3.09% (76 runs sampled) | ||
*/ | ||
const suite = new Benchmark.Suite('turf-clean-coords'); | ||
glob.sync(path.join(__dirname, 'test', 'in', '*.geojson')).forEach(filepath => { | ||
const {name} = path.parse(filepath); | ||
const geojson = load.sync(filepath); | ||
console.time(name); | ||
cleanCoords(geojson); | ||
console.timeEnd(name); | ||
suite.add(name, () => cleanCoords(geojson)); | ||
}); | ||
|
||
suite | ||
.on('cycle', e => console.log(String(e.target))) | ||
.on('complete', () => {}) | ||
.run(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/// <reference types="geojson" /> | ||
|
||
type Feature = GeoJSON.Feature<any> | GeoJSON.GeometryObject; | ||
|
||
/** | ||
* http://turfjs.org/docs/#cleancoords | ||
*/ | ||
declare function cleanCoords(feature: Feature, mutate?: boolean): Feature; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stebogit This one gets a bit tricky, one thing to keep in mind is trying to preserve the Input/Output type as much as possible. At the moment, this definition is saying the input is any Feature or Geometry and it outputs either Geometry or a Feature. Using the type GeometryObject = GeoJSON.GeometryObject
type Feature = GeoJSON.Feature<any>
/**
* http://turfjs.org/docs/#cleancoords
*/
declare function cleanCoords<T extends GeometryObject|Feature>(feature: T, mutate?: boolean): T;
declare namespace cleanCoords {}
export = cleanCoords; |
||
declare namespace cleanCoords {} | ||
export = cleanCoords; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
var clone = require('@turf/clone'); | ||
var invariant = require('@turf/invariant'); | ||
var getCoords = invariant.getCoords; | ||
var getGeomType = invariant.getGeomType; | ||
|
||
/** | ||
* Removes redundant coordinates from a (Multi)LineString or (Multi)Polygon; ignores (Multi)Point. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added MultiPoint support, we can simply say this now:
|
||
* | ||
* @name cleanCoords | ||
* @param {Geometry|Feature<any>} geojson Feature or Geometry | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been dropping the |
||
* @param {boolean} [mutate=false] allows GeoJSON input to be mutated | ||
* @returns {Geometry|Feature<any>} the cleaned input Feature/Geometry | ||
* @example | ||
* var line = trf.lineString([[0, 0], [0, 2], [0, 5], [0, 8], [0, 8], [0, 10]]); | ||
* | ||
* var cleaned = turf.cleanCoords(line).geometry.coordinates; | ||
* //= [[0, 0], [0, 10]] | ||
*/ | ||
module.exports = function (geojson, mutate) { | ||
if (!geojson) throw new Error('geojson is required'); | ||
var type = getGeomType(geojson); | ||
var newPoints = []; | ||
|
||
var coords = getCoords(geojson); | ||
|
||
switch (type) { | ||
case 'LineString': | ||
newPoints = cleanCoords(geojson, mutate); | ||
break; | ||
case 'MultiLineString': | ||
case 'Polygon': | ||
for (var i = 0; i < coords.length; i++) { | ||
var line = coords[i]; | ||
newPoints.push(cleanCoords(line)); | ||
} | ||
break; | ||
case 'MultiPolygon': | ||
for (var j = 0; j < coords.length; j++) { | ||
var polys = coords[j]; | ||
var polyPoints = []; | ||
for (var p = 0; p < polys.length; p++) { | ||
var ring = polys[p]; | ||
polyPoints.push(cleanCoords(ring)); | ||
} | ||
newPoints.push(polyPoints); | ||
} | ||
break; | ||
case 'Point': | ||
case 'MultiPoint': | ||
newPoints = coords; | ||
break; | ||
default: | ||
throw new Error(type + ' geometry not supported'); | ||
} | ||
|
||
var output = (mutate === true) ? geojson : clone(geojson, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bit of "black magic" how clone works, but you don't need to define a new variable if you apply This below is valid: if (mutate !== true) geojson = clone(geojson, false); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2nd - Since we aren't mutating There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DenisCarriere that's right! Now that I looked better at it,
I really like this, it's much nicer to look at, even if for some reason it's always really hard for me to decode... 🤔 |
||
|
||
if (output.coordinates) output.coordinates = newPoints; | ||
else output.geometry.coordinates = newPoints; | ||
|
||
return output; | ||
}; | ||
|
||
function cleanCoords(line) { | ||
var points = getCoords(line); | ||
|
||
var prevPoint, point, nextPoint; | ||
var newPoints = []; | ||
var secondToLast = points.length - 1; | ||
|
||
newPoints.push(points[0]); | ||
for (var i = 1; i < secondToLast; i++) { | ||
prevPoint = points[i - 1]; | ||
point = points[i]; | ||
nextPoint = points[i + 1]; | ||
|
||
if (!isPointOnLineSegment(prevPoint, nextPoint, point)) { | ||
newPoints.push(point); | ||
} | ||
} | ||
newPoints.push(nextPoint); | ||
return newPoints; | ||
} | ||
|
||
/** | ||
* Returns if `point` is on the segment between `start` and `end`. | ||
* Borrowed from `@turf/boolean-point-on-line` to speed up the evaluation | ||
* | ||
* @private | ||
* @param {Array<number>} start coord pair of start of line | ||
* @param {Array<number>} end coord pair of end of line | ||
* @param {Array<number>} point coord pair of point to check | ||
* @returns {boolean} true/false | ||
*/ | ||
function isPointOnLineSegment(start, end, point) { | ||
var x = point[0], y = point[1]; | ||
var startX = start[0], startY = start[1]; | ||
var endX = end[0], endY = end[1]; | ||
|
||
var dxc = x - startX; | ||
var dyc = y - startY; | ||
var dxl = endX - startX; | ||
var dyl = endY - startY; | ||
var cross = dxc * dyl - dyc * dxl; | ||
|
||
if (cross !== 0) return false; | ||
else if (Math.abs(dxl) >= Math.abs(dyl)) return dxl > 0 ? startX <= x && x <= endX : endX <= x && x <= startX; | ||
else return dyl > 0 ? startY <= y && y <= endY : endY <= y && y <= startY; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "@turf/clean-coords", | ||
"version": "4.5.0", | ||
"description": "turf clean-coords module", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"files": [ | ||
"index.js", | ||
"index.d.ts" | ||
], | ||
"scripts": { | ||
"test": "node test.js", | ||
"bench": "node bench.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/Turfjs/turf.git" | ||
}, | ||
"keywords": [ | ||
"turf", | ||
"gis", | ||
"clean-coords" | ||
], | ||
"author": "Turf Authors", | ||
"contributors": [ | ||
"Stefano Borghi <@stebogit>" | ||
], | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/Turfjs/turf/issues" | ||
}, | ||
"homepage": "https://github.com/Turfjs/turf", | ||
"devDependencies": { | ||
"@turf/helpers": "4.5.2", | ||
"benchmark": "^2.1.4", | ||
"write-json-file": "^2.2.0", | ||
"load-json-file": "^2.0.0", | ||
"tape": "^4.6.3" | ||
}, | ||
"dependencies": { | ||
"@turf/clone": "4.5.2", | ||
"@turf/invariant": "4.5.2" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
const fs = require('fs'); | ||
const test = require('tape'); | ||
const path = require('path'); | ||
const load = require('load-json-file'); | ||
const {lineString, multiPolygon} = require('@turf/helpers'); | ||
const write = require('write-json-file'); | ||
const cleanCoords = 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('turf-clean-coords', t => { | ||
for (const {filename, name, geojson} of fixtures) { | ||
const results = cleanCoords(geojson); | ||
|
||
if (process.env.REGEN) write.sync(directories.out + filename, results); | ||
t.deepEqual(results, load.sync(directories.out + filename), name); | ||
} | ||
t.end(); | ||
}); | ||
|
||
test('turf-clean-coords -- throws', t => { | ||
t.throws(() => cleanCoords(null), /geojson is required/, 'missing geojson'); | ||
t.end(); | ||
}); | ||
|
||
test('turf-clean-coords -- prevent input mutation', t => { | ||
const line = lineString([[0, 0], [1, 1], [2, 2]], {foo: 'bar'}); | ||
cleanCoords(line); | ||
const lineBefore = JSON.parse(JSON.stringify(line)); | ||
t.deepEqual(lineBefore, line, 'line should NOT be mutated'); | ||
|
||
const multiPoly = multiPolygon([ | ||
[[[0, 0], [1, 1], [2, 2], [2, 0], [0, 0]]], | ||
[[[0, 0], [0, 5], [5, 5], [5, 5], [5, 0], [0, 0]]] | ||
], {hello: 'world'}); | ||
const multiPolyBefore = JSON.parse(JSON.stringify(multiPoly)); | ||
cleanCoords(multiPoly); | ||
t.deepEqual(multiPolyBefore, multiPoly, 'multiPolygon should NOT be mutated'); | ||
|
||
const cleanLine = cleanCoords(line, true); | ||
t.deepEqual(cleanLine, line, 'line should be mutated'); | ||
t.end(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For internal benchmark tests, we can add
mutate=true
, that way we get the "real" benchmarks results.