diff --git a/packages/turf-line-overlap/LICENSE b/packages/turf-line-overlap/LICENSE new file mode 100644 index 0000000000..96ce51b76f --- /dev/null +++ b/packages/turf-line-overlap/LICENSE @@ -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. diff --git a/packages/turf-line-overlap/README.md b/packages/turf-line-overlap/README.md new file mode 100644 index 0000000000..cb0f562ab8 --- /dev/null +++ b/packages/turf-line-overlap/README.md @@ -0,0 +1,62 @@ +# @turf/line-overlap + +# lineOverlap + +Takes any LineString or Polygon and returns the overlapping lines between both features. + +**Parameters** + +- `line1` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<([LineString](http://geojson.org/geojson-spec.html#linestring) \| [MultiLineString](http://geojson.org/geojson-spec.html#multilinestring) \| [Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))>** any LineString or Polygon +- `line2` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<([LineString](http://geojson.org/geojson-spec.html#linestring) \| [MultiLineString](http://geojson.org/geojson-spec.html#multilinestring) \| [Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))>** any LineString or Polygon + +**Examples** + +```javascript +var line1 = { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [[115, -35], [125, -30], [135, -30], [145, -35] + ] + } +} +var line2 = { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [[115, -25], [125, -30], [135, -30], [145, -25] + ] + } +} +var overlapping = turf.lineOverlap(line1, line2); +//= overlapping +``` + +Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** lines(s) that are overlapping between both features + + + +--- + +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/line-overlap +``` + +Or install the Turf module that includes it as a function: + +```sh +$ npm install @turf/turf +``` diff --git a/packages/turf-line-overlap/bench.js b/packages/turf-line-overlap/bench.js new file mode 100644 index 0000000000..e9ee9e3d06 --- /dev/null +++ b/packages/turf-line-overlap/bench.js @@ -0,0 +1,32 @@ +const Benchmark = require('benchmark'); +const path = require('path'); +const fs = require('fs'); +const load = require('load-json-file'); +const lineOverlap = require('./'); + +const directory = path.join(__dirname, 'test', 'in') + path.sep; +const fixtures = fs.readdirSync(directory).map(filename => { + return { + filename, + name: path.parse(filename).name, + geojson: load.sync(directory + filename) + }; +}); + +/** + * Benchmark Results + * + * polygons x 3,567 ops/sec ±1.61% (85 runs sampled) + * simple1 x 9,013 ops/sec ±1.15% (86 runs sampled) + * simple2 x 10,278 ops/sec ±1.52% (86 runs sampled) + * simple3 x 13,124 ops/sec ±1.37% (85 runs sampled) + */ +const suite = new Benchmark.Suite('turf-line-overlap'); +for (const {name, geojson} of fixtures) { + suite.add(name, () => lineOverlap(geojson.features[0], geojson.features[1])); +} + +suite + .on('cycle', e => { console.log(String(e.target)); }) + .on('complete', () => {}) + .run(); diff --git a/packages/turf-line-overlap/index.d.ts b/packages/turf-line-overlap/index.d.ts new file mode 100644 index 0000000000..eba86f2284 --- /dev/null +++ b/packages/turf-line-overlap/index.d.ts @@ -0,0 +1,11 @@ +/// + +type LineStrings = GeoJSON.FeatureCollection; +type Line = GeoJSON.Feature; + +/** + * http://turfjs.org/docs/#lineoverlap + */ +declare function lineOverlap(source: Line, target: Line, precision?: number): LineStrings; +declare namespace lineOverlap {} +export = lineOverlap; diff --git a/packages/turf-line-overlap/index.js b/packages/turf-line-overlap/index.js new file mode 100644 index 0000000000..03f4cf7061 --- /dev/null +++ b/packages/turf-line-overlap/index.js @@ -0,0 +1,99 @@ +var lineSegment = require('@turf/line-segment'); +var getCoords = require('@turf/invariant').getCoords; +var rbush = require('geojson-rbush'); +var equal = require('deep-equal'); +var featureCollection = require('@turf/helpers').featureCollection; +var featureEach = require('@turf/meta').featureEach; + +/** + * Takes any LineString or Polygon and returns the overlapping lines between both features. + * + * @name lineOverlap + * @param {Feature} line1 any LineString or Polygon + * @param {Feature} line2 any LineString or Polygon + * @returns {FeatureCollection} lines(s) that are overlapping between both features + * @example + * var line1 = { + * "type": "Feature", + * "properties": {}, + * "geometry": { + * "type": "LineString", + * "coordinates": [[115, -35], [125, -30], [135, -30], [145, -35] + * ] + * } + * } + * var line2 = { + * "type": "Feature", + * "properties": {}, + * "geometry": { + * "type": "LineString", + * "coordinates": [[115, -25], [125, -30], [135, -30], [145, -25] + * ] + * } + * } + * var overlapping = turf.lineOverlap(line1, line2); + * //= overlapping + */ +module.exports = function (line1, line2) { + var results = []; + + // Create Spatial Index + var tree = rbush(); + tree.load(lineSegment(line1)); + var overlaps; + + // Iterate over line segments + featureEach(lineSegment(line2), function (segment) { + var doesOverlaps = false; + featureEach(tree.search(segment), function (match) { + if (doesOverlaps === false) { + var coords1 = getCoords(segment).sort(); + var coords2 = getCoords(match).sort(); + + // Segment overlaps feature + if (equal(coords1, coords2)) { + doesOverlaps = true; + // Overlaps already exists - only append last coordinate of segment + if (overlaps) overlaps = concatSegment(overlaps, segment); + else overlaps = segment; + } + } + }); + // Segment doesn't overlap - add overlaps to results & reset + if (doesOverlaps === false && overlaps) { + results.push(overlaps); + overlaps = undefined; + } + }); + // Add last segment if exists + if (overlaps) results.push(overlaps); + + return featureCollection(results); +}; + + +/** + * Concat Segment + * + * @private + * @param {Feature} line LineString + * @param {Feature} segment 2-vertex LineString + * @returns {Feature} concat linestring + */ +function concatSegment(line, segment) { + var coords = getCoords(segment); + var lineCoords = getCoords(line); + var start = lineCoords[0]; + var end = lineCoords[lineCoords.length - 1]; + + if (equal(coords[0], start)) { + line.geometry.coordinates.unshift(coords[1]); + } else if (equal(coords[0], end)) { + line.geometry.coordinates.push(coords[1]); + } else if (equal(coords[1], start)) { + line.geometry.coordinates.unshift(coords[0]); + } else if (equal(coords[1], end)) { + line.geometry.coordinates.push(coords[0]); + } + return line; +} diff --git a/packages/turf-line-overlap/package.json b/packages/turf-line-overlap/package.json new file mode 100644 index 0000000000..d8d1956636 --- /dev/null +++ b/packages/turf-line-overlap/package.json @@ -0,0 +1,46 @@ +{ + "name": "@turf/line-overlap", + "version": "4.0.0", + "description": "turf line-overlap 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", + "geojson", + "gis", + "line", + "overlap" + ], + "author": "Denis Carriere", + "license": "MIT", + "bugs": { + "url": "https://github.com/Turfjs/turf/issues" + }, + "homepage": "https://github.com/Turfjs/turf", + "devDependencies": { + "benchmark": "^2.1.3", + "load-json-file": "^2.0.0", + "tape": "^3.5.0", + "write-json-file": "^2.0.0" + }, + "dependencies": { + "@turf/helpers": "^4.0.1", + "@turf/invariant": "^4.0.1", + "@turf/line-segment": "^4.0.1", + "@turf/meta": "^4.0.1", + "deep-equal": "^1.0.1", + "geojson-rbush": "^1.0.1" + } +} diff --git a/packages/turf-line-overlap/test.js b/packages/turf-line-overlap/test.js new file mode 100644 index 0000000000..de18c0d13a --- /dev/null +++ b/packages/turf-line-overlap/test.js @@ -0,0 +1,49 @@ +const test = require('tape'); +const fs = require('fs'); +const path = require('path'); +const load = require('load-json-file'); +const write = require('write-json-file'); +const featureEach = require('@turf/meta').featureEach; +const featureCollection = require('@turf/helpers').featureCollection; +const lineOverlap = 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-line-overlap', t => { + for (const {filename, name, geojson} of fixtures) { + const source = colorize(geojson.features[0], '#00F').features[0]; + const target = colorize(geojson.features[1], '#00F').features[0]; + const shared = colorize(lineOverlap(source, target), '#F00'); + + const results = featureCollection([target, source].concat(shared.features)); + + if (process.env.REGEN) write.sync(directories.out + filename, results); + t.deepEquals(results, load.sync(directories.out + filename), name); + } + t.end(); +}); + +function colorize(features, color = '#F00', width = 6) { + const results = []; + featureEach(features, feature => { + feature.properties = { + stroke: color, + fill: color, + 'stroke-width': width, + 'fill-opacity': 0.1 + }; + results.push(feature); + }); + return featureCollection(results); +} diff --git a/packages/turf-line-overlap/test/in/polygons.geojson b/packages/turf-line-overlap/test/in/polygons.geojson new file mode 100644 index 0000000000..f77924873e --- /dev/null +++ b/packages/turf-line-overlap/test/in/polygons.geojson @@ -0,0 +1,131 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 120.14179343574, + -17.48153498141 + ], + [ + 120.1456007834, + -30.96997373479 + ], + [ + 130.34864729879, + -30.96772909058 + ], + [ + 131.64065935144, + -29.20137372199 + ], + [ + 129.5926694017, + -28.04905296815 + ], + [ + 130.34739288016, + -26.68605235913 + ], + [ + 125.76890918238, + -26.68710193815 + ], + [ + 125.76762773192, + -22.1408865296 + ], + [ + 130.52038135971, + -22.13975701932 + ], + [ + 131.37809653737, + -20.30857774535 + ], + [ + 129.67954157692, + -18.92360398694 + ], + [ + 130.51910988115, + -17.47899540098 + ], + [ + 120.14179343574, + -17.48153498141 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 139.69468150776, + -17.47674988707 + ], + [ + 130.51910988115, + -17.47899540098 + ], + [ + 129.67954157692, + -18.92360398694 + ], + [ + 131.37809653737, + -20.30857774535 + ], + [ + 130.52038135971, + -22.13975701932 + ], + [ + 135.29428724786, + -22.138622473 + ], + [ + 135.29556869831, + -26.68491802042 + ], + [ + 130.34739288016, + -26.68605235913 + ], + [ + 129.5926694017, + -28.04905296815 + ], + [ + 131.64065935144, + -29.20137372199 + ], + [ + 130.34864729879, + -30.96772909058 + ], + [ + 139.69848885541, + -30.96567210295 + ], + [ + 139.69468150776, + -17.47674988707 + ] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/packages/turf-line-overlap/test/in/simple1.geojson b/packages/turf-line-overlap/test/in/simple1.geojson new file mode 100644 index 0000000000..ca194dfc9d --- /dev/null +++ b/packages/turf-line-overlap/test/in/simple1.geojson @@ -0,0 +1,59 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -35 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -25 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ], + [ + 145, + -25 + ] + ] + } + } + ] +} diff --git a/packages/turf-line-overlap/test/in/simple2.geojson b/packages/turf-line-overlap/test/in/simple2.geojson new file mode 100644 index 0000000000..db7ba08901 --- /dev/null +++ b/packages/turf-line-overlap/test/in/simple2.geojson @@ -0,0 +1,55 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -35 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -25 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + } + ] +} diff --git a/packages/turf-line-overlap/test/in/simple3.geojson b/packages/turf-line-overlap/test/in/simple3.geojson new file mode 100644 index 0000000000..7b90dcf09e --- /dev/null +++ b/packages/turf-line-overlap/test/in/simple3.geojson @@ -0,0 +1,51 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ], + [ + 145, + -25 + ] + ] + } + } + ] +} diff --git a/packages/turf-line-overlap/test/out/polygons.geojson b/packages/turf-line-overlap/test/out/polygons.geojson new file mode 100644 index 0000000000..8ce60eafd7 --- /dev/null +++ b/packages/turf-line-overlap/test/out/polygons.geojson @@ -0,0 +1,215 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 139.69468150776, + -17.47674988707 + ], + [ + 130.51910988115, + -17.47899540098 + ], + [ + 129.67954157692, + -18.92360398694 + ], + [ + 131.37809653737, + -20.30857774535 + ], + [ + 130.52038135971, + -22.13975701932 + ], + [ + 135.29428724786, + -22.138622473 + ], + [ + 135.29556869831, + -26.68491802042 + ], + [ + 130.34739288016, + -26.68605235913 + ], + [ + 129.5926694017, + -28.04905296815 + ], + [ + 131.64065935144, + -29.20137372199 + ], + [ + 130.34864729879, + -30.96772909058 + ], + [ + 139.69848885541, + -30.96567210295 + ], + [ + 139.69468150776, + -17.47674988707 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 120.14179343574, + -17.48153498141 + ], + [ + 120.1456007834, + -30.96997373479 + ], + [ + 130.34864729879, + -30.96772909058 + ], + [ + 131.64065935144, + -29.20137372199 + ], + [ + 129.5926694017, + -28.04905296815 + ], + [ + 130.34739288016, + -26.68605235913 + ], + [ + 125.76890918238, + -26.68710193815 + ], + [ + 125.76762773192, + -22.1408865296 + ], + [ + 130.52038135971, + -22.13975701932 + ], + [ + 131.37809653737, + -20.30857774535 + ], + [ + 129.67954157692, + -18.92360398694 + ], + [ + 130.51910988115, + -17.47899540098 + ], + [ + 120.14179343574, + -17.48153498141 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "fill": "#F00", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 130.52038135971, + -22.13975701932 + ], + [ + 131.37809653737, + -20.30857774535 + ], + [ + 129.67954157692, + -18.92360398694 + ], + [ + 130.51910988115, + -17.47899540098 + ] + ] + }, + "bbox": [ + 129.67954157692, + -18.92360398694, + 130.51910988115, + -17.47899540098 + ], + "id": 1 + }, + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "fill": "#F00", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 130.34864729879, + -30.96772909058 + ], + [ + 131.64065935144, + -29.20137372199 + ], + [ + 129.5926694017, + -28.04905296815 + ], + [ + 130.34739288016, + -26.68605235913 + ] + ] + }, + "bbox": [ + 129.5926694017, + -28.04905296815, + 130.34739288016, + -26.68605235913 + ], + "id": 7 + } + ] +} diff --git a/packages/turf-line-overlap/test/out/simple1.geojson b/packages/turf-line-overlap/test/out/simple1.geojson new file mode 100644 index 0000000000..6080e7d337 --- /dev/null +++ b/packages/turf-line-overlap/test/out/simple1.geojson @@ -0,0 +1,102 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -25 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ], + [ + 145, + -25 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -35 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "fill": "#F00", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + }, + "bbox": [ + 125, + -30, + 135, + -30 + ], + "id": 1 + } + ] +} diff --git a/packages/turf-line-overlap/test/out/simple2.geojson b/packages/turf-line-overlap/test/out/simple2.geojson new file mode 100644 index 0000000000..5eca688cc4 --- /dev/null +++ b/packages/turf-line-overlap/test/out/simple2.geojson @@ -0,0 +1,98 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -25 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 115, + -35 + ], + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "fill": "#F00", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + }, + "bbox": [ + 125, + -30, + 135, + -30 + ], + "id": 1 + } + ] +} diff --git a/packages/turf-line-overlap/test/out/simple3.geojson b/packages/turf-line-overlap/test/out/simple3.geojson new file mode 100644 index 0000000000..1aa066cd31 --- /dev/null +++ b/packages/turf-line-overlap/test/out/simple3.geojson @@ -0,0 +1,94 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ], + [ + 145, + -25 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "fill": "#F00", + "stroke-width": 6, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 125, + -30 + ], + [ + 135, + -30 + ], + [ + 145, + -35 + ] + ] + }, + "bbox": [ + 125, + -30, + 135, + -30 + ], + "id": 0 + } + ] +}