diff --git a/packages/turf-line-offset/LICENSE b/packages/turf-line-offset/LICENSE
new file mode 100644
index 0000000000..96ce51b76f
--- /dev/null
+++ b/packages/turf-line-offset/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-offset/README.md b/packages/turf-line-offset/README.md
new file mode 100644
index 0000000000..23633785a7
--- /dev/null
+++ b/packages/turf-line-offset/README.md
@@ -0,0 +1,56 @@
+# @turf/line-offset
+
+# lineOffset
+
+Takes a [line](http://geojson.org/geojson-spec.html#linestring) and returns a [line](http://geojson.org/geojson-spec.html#linestring) at offset by the specified distance.
+
+**Parameters**
+
+- `line` **([Geometry](http://geojson.org/geojson-spec.html#geometry) \| [Feature](http://geojson.org/geojson-spec.html#feature-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>)** input line
+- `offset` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** distance to offset the line (can be of negative value)
+- `units` **\[[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** can be degrees, radians, miles, kilometers, inches, yards, meters (optional, default `kilometers`)
+
+**Examples**
+
+```javascript
+var line = {
+ "type": "Feature",
+ "properties": {},
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [[-83, 30], [-84, 36], [-78, 41]]
+ }
+};
+
+var offsetLine = turf.lineOffset(line, 2, 'miles');
+
+//addToMap
+var addToMap = [offsetLine, line]
+```
+
+Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** Line offset from the input line
+
+
+
+---
+
+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-offset
+```
+
+Or install the Turf module that includes it as a function:
+
+```sh
+$ npm install @turf/turf
+```
diff --git a/packages/turf-line-offset/bench.js b/packages/turf-line-offset/bench.js
new file mode 100644
index 0000000000..00dce3c4dc
--- /dev/null
+++ b/packages/turf-line-offset/bench.js
@@ -0,0 +1,33 @@
+const fs = require('fs');
+const path = require('path');
+const load = require('load-json-file');
+const Benchmark = require('benchmark');
+const lineOffset = require('./');
+
+const directory = path.join(__dirname, 'test', 'in') + path.sep;
+let fixtures = fs.readdirSync(directory).map(filename => {
+ return {
+ filename,
+ name: path.parse(filename).name,
+ geojson: load.sync(directory + filename)
+ };
+});
+// fixtures = fixtures.filter(fixture => fixture.name === 'polygon');
+
+/**
+ * Benchmark Results
+ *
+ * line-horizontal x 1,816,451 ops/sec ±15.31% (62 runs sampled)
+ * linestring-long x 144,640 ops/sec ±3.35% (82 runs sampled)
+ * linestring-singleSegmentOnly x 2,649,959 ops/sec ±1.54% (76 runs sampled)
+ * linestring-straight x 1,857,452 ops/sec ±5.83% (77 runs sampled)
+ */
+const suite = new Benchmark.Suite('turf-line-offset');
+for (const {name, geojson} of fixtures) {
+ suite.add(name, () => lineOffset(geojson, 100, 'meters'));
+}
+
+suite
+ .on('cycle', e => console.log(String(e.target)))
+ .on('complete', () => {})
+ .run();
diff --git a/packages/turf-line-offset/index.d.ts b/packages/turf-line-offset/index.d.ts
new file mode 100644
index 0000000000..9fd6b7a973
--- /dev/null
+++ b/packages/turf-line-offset/index.d.ts
@@ -0,0 +1,14 @@
+///
+
+import {Units} from '@turf/helpers';
+
+type GeometryObject = GeoJSON.GeometryObject;
+type Feature = GeoJSON.Feature;
+type Geoms = GeoJSON.LineString | GeoJSON.MultiLineString;
+
+/**
+ * http://turfjs.org/docs/#lineoffset
+ */
+declare function lineOffset(line: Feature | Geom, distance: number, units?: Units): Feature;
+declare namespace lineOffset { }
+export = lineOffset;
diff --git a/packages/turf-line-offset/index.js b/packages/turf-line-offset/index.js
new file mode 100644
index 0000000000..a11ac08b45
--- /dev/null
+++ b/packages/turf-line-offset/index.js
@@ -0,0 +1,118 @@
+var meta = require('@turf/meta');
+var helpers = require('@turf/helpers');
+var getCoords = require('@turf/invariant').getCoords;
+var intersection = require('./intersection');
+var flattenEach = meta.flattenEach;
+var lineString = helpers.lineString;
+var multiLineString = helpers.multiLineString;
+var distanceToDegrees = helpers.distanceToDegrees;
+
+/**
+ * Takes a {@link LineString|line} and returns a {@link LineString|line} at offset by the specified distance.
+ *
+ * @name lineOffset
+ * @param {Geometry|Feature} geojson input GeoJSON
+ * @param {number} distance distance to offset the line (can be of negative value)
+ * @param {string} [units=kilometers] can be degrees, radians, miles, kilometers, inches, yards, meters
+ * @returns {Feature} Line offset from the input line
+ * @example
+ * var line = {
+ * "type": "Feature",
+ * "properties": {
+ * "stroke": "#F00"
+ * },
+ * "geometry": {
+ * "type": "LineString",
+ * "coordinates": [[-83, 30], [-84, 36], [-78, 41]]
+ * }
+ * };
+ *
+ * var offsetLine = turf.lineOffset(line, 2, "miles");
+ *
+ * //addToMap
+ * offsetLine.properties.stroke = "#00F"
+ * var addToMap = [offsetLine, line]
+ */
+module.exports = function (geojson, distance, units) {
+ if (!geojson) throw new Error('geojson is required');
+ if (distance === undefined || distance === null || isNaN(distance)) throw new Error('distance is required');
+ var type = (geojson.type === 'Feature') ? geojson.geometry.type : geojson.type;
+ var properties = geojson.properties;
+
+ switch (type) {
+ case 'LineString':
+ return lineOffset(geojson, distance, units);
+ case 'MultiLineString':
+ var coords = [];
+ flattenEach(geojson, function (feature) {
+ coords.push(lineOffset(feature, distance, units).geometry.coordinates);
+ });
+ return multiLineString(coords, properties);
+ default:
+ throw new Error('geometry ' + type + ' is not supported');
+ }
+};
+
+/**
+ * Line Offset
+ *
+ * @private
+ * @param {Geometry|Feature} line input line
+ * @param {number} distance distance to offset the line (can be of negative value)
+ * @param {string} [units=kilometers] units
+ * @returns {Feature} Line offset from the input line
+ */
+function lineOffset(line, distance, units) {
+ var segments = [];
+ var offsetDegrees = distanceToDegrees(distance, units);
+ var coords = getCoords(line);
+ var finalCoords = [];
+ coords.forEach(function (currentCoords, index) {
+ if (index !== coords.length - 1) {
+ var segment = processSegment(currentCoords, coords[index + 1], offsetDegrees);
+ segments.push(segment);
+ if (index > 0) {
+ var seg2Coords = segments[index - 1];
+ var intersects = intersection(segment, seg2Coords);
+
+ // Handling for line segments that aren't straight
+ if (intersects !== false) {
+ seg2Coords[1] = intersects;
+ segment[0] = intersects;
+ }
+
+ finalCoords.push(seg2Coords[0]);
+ if (index === coords.length - 2) {
+ finalCoords.push(segment[0]);
+ finalCoords.push(segment[1]);
+ }
+ }
+ // Handling for lines that only have 1 segment
+ if (coords.length === 2) {
+ finalCoords.push(segment[0]);
+ finalCoords.push(segment[1]);
+ }
+ }
+ });
+ return lineString(finalCoords, line.properties);
+}
+
+/**
+ * Process Segment
+ * Inspiration taken from http://stackoverflow.com/questions/2825412/draw-a-parallel-line
+ *
+ * @private
+ * @param {Array} point1 Point coordinates
+ * @param {Array} point2 Point coordinates
+ * @param {number} offset Offset
+ * @returns {Array>} offset points
+ */
+function processSegment(point1, point2, offset) {
+ var L = Math.sqrt((point1[0] - point2[0]) * (point1[0] - point2[0]) + (point1[1] - point2[1]) * (point1[1] - point2[1]));
+
+ var out1x = point1[0] + offset * (point2[1] - point1[1]) / L;
+ var out2x = point2[0] + offset * (point2[1] - point1[1]) / L;
+ var out1y = point1[1] + offset * (point1[0] - point2[0]) / L;
+ var out2y = point2[1] + offset * (point1[0] - point2[0]) / L;
+ return [[out1x, out1y], [out2x, out2y]];
+}
diff --git a/packages/turf-line-offset/intersection.js b/packages/turf-line-offset/intersection.js
new file mode 100644
index 0000000000..c84e4a89a0
--- /dev/null
+++ b/packages/turf-line-offset/intersection.js
@@ -0,0 +1,117 @@
+/**
+ * https://github.com/rook2pawn/node-intersection
+ *
+ * Author @rook2pawn
+ */
+
+/**
+ * AB
+ *
+ * @private
+ * @param {Array>} segment - 2 vertex line segment
+ * @returns {Array} coordinates [x, y]
+ */
+function ab(segment) {
+ var start = segment[0];
+ var end = segment[1];
+ return [end[0] - start[0], end[1] - start[1]];
+}
+
+/**
+ * Cross Product
+ *
+ * @private
+ * @param {Array} v1 coordinates [x, y]
+ * @param {Array} v2 coordinates [x, y]
+ * @returns {Array} Cross Product
+ */
+function crossProduct(v1, v2) {
+ return (v1[0] * v2[1]) - (v2[0] * v1[1]);
+}
+
+/**
+ * Add
+ *
+ * @private
+ * @param {Array} v1 coordinates [x, y]
+ * @param {Array} v2 coordinates [x, y]
+ * @returns {Array} Add
+ */
+function add(v1, v2) {
+ return [v1[0] + v2[0], v1[1] + v2[1]];
+}
+
+/**
+ * Sub
+ *
+ * @private
+ * @param {Array} v1 coordinates [x, y]
+ * @param {Array} v2 coordinates [x, y]
+ * @returns {Array} Sub
+ */
+function sub(v1, v2) {
+ return [v1[0] - v2[0], v1[1] - v2[1]];
+}
+
+/**
+ * scalarMult
+ *
+ * @private
+ * @param {number} s scalar
+ * @param {Array} v coordinates [x, y]
+ * @returns {Array} scalarMult
+ */
+function scalarMult(s, v) {
+ return [s * v[0], s * v[1]];
+}
+
+/**
+ * Intersect Segments
+ *
+ * @private
+ * @param {Array} a coordinates [x, y]
+ * @param {Array} b coordinates [x, y]
+ * @returns {Array} intersection
+ */
+function intersectSegments(a, b) {
+ var p = a[0];
+ var r = ab(a);
+ var q = b[0];
+ var s = ab(b);
+
+ var cross = crossProduct(r, s);
+ var qmp = sub(q, p);
+ var numerator = crossProduct(qmp, s);
+ var t = numerator / cross;
+ var intersection = add(p, scalarMult(t, r));
+ return intersection;
+}
+
+/**
+ * Is Parallel
+ *
+ * @private
+ * @param {Array} a coordinates [x, y]
+ * @param {Array} b coordinates [x, y]
+ * @returns {boolean} true if a and b are parallel (or co-linear)
+ */
+function isParallel(a, b) {
+ var r = ab(a);
+ var s = ab(b);
+ return (crossProduct(r, s) === 0);
+}
+
+/**
+ * Intersection
+ *
+ * @private
+ * @param {Array} a coordinates [x, y]
+ * @param {Array} b coordinates [x, y]
+ * @returns {Array|boolean} true if a and b are parallel (or co-linear)
+ */
+function intersection(a, b) {
+ if (isParallel(a, b)) return false;
+ return intersectSegments(a, b);
+}
+
+module.exports = intersection;
diff --git a/packages/turf-line-offset/package.json b/packages/turf-line-offset/package.json
new file mode 100644
index 0000000000..6fbd2eddc2
--- /dev/null
+++ b/packages/turf-line-offset/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "@turf/line-offset",
+ "version": "4.0.0",
+ "description": "turf line-offset module",
+ "main": "index.js",
+ "types": "index.d.ts",
+ "files": [
+ "index.js",
+ "index.d.ts",
+ "intersection.js"
+ ],
+ "scripts": {
+ "test": "node test.js",
+ "bench": "node bench.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/Turfjs/turf.git"
+ },
+ "keywords": [
+ "line",
+ "linestring",
+ "turf",
+ "offset"
+ ],
+ "author": "Turf Authors",
+ "contributors": [
+ "David Wee <@rook2pawn>",
+ "Rowan Winsemius <@rowanwins>",
+ "Denis Carriere <@DenisCarriere>"
+ ],
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/Turfjs/turf/issues"
+ },
+ "homepage": "https://github.com/Turfjs/turf",
+ "devDependencies": {
+ "@turf/truncate": "^4.3.0",
+ "benchmark": "^2.1.4",
+ "load-json-file": "^2.0.0",
+ "tape": "^4.6.3",
+ "write-json-file": "^2.0.0"
+ },
+ "dependencies": {
+ "@turf/helpers": "^4.3.0",
+ "@turf/invariant": "^4.3.0",
+ "@turf/meta": "^4.2.0"
+ }
+}
diff --git a/packages/turf-line-offset/test.js b/packages/turf-line-offset/test.js
new file mode 100644
index 0000000000..96741c2c1c
--- /dev/null
+++ b/packages/turf-line-offset/test.js
@@ -0,0 +1,58 @@
+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 truncate = require('@turf/truncate');
+const {featureCollection, lineString} = require('@turf/helpers');
+const lineOffset = require('./');
+
+const directories = {
+ in: path.join(__dirname, 'test', 'in') + path.sep,
+ out: path.join(__dirname, 'test', 'out') + path.sep
+};
+
+let fixtures = fs.readdirSync(directories.in).map(filename => {
+ return {
+ filename,
+ name: path.parse(filename).name,
+ geojson: load.sync(directories.in + filename)
+ };
+});
+// fixtures = fixtures.filter(fixture => fixture.name === 'polygon');
+
+test('turf-line-offset', t => {
+ for (const {name, geojson} of fixtures) {
+ let {distance, units} = geojson.properties || {};
+ distance = distance || 50;
+ const output = truncate(lineOffset(geojson, distance, units), 4);
+ output.properties.stroke = '#00F';
+ const results = featureCollection([output, geojson]);
+
+ if (process.env.REGEN) write.sync(directories.out + name + '.geojson', results);
+ t.deepEqual(results, load.sync(directories.out + name + '.geojson'), name);
+ }
+ t.end();
+});
+
+test('turf-line-offset - Throws Errors', t => {
+ const line = lineString([[10, 10], [0, 0]]);
+ t.throws(() => lineOffset(), /geojson is required/);
+ t.throws(() => lineOffset(line, /offset is required/));
+ t.end();
+});
+
+test('turf-line-offset - Support Geometry Objects', t => {
+ const line = lineString([[10, 10], [0, 0]]);
+ t.ok(lineOffset(line.geometry, 10), 'Geometry Object');
+ t.end();
+});
+
+test('turf-line-offset - Prevent Input Mutation', t => {
+ const line = lineString([[10, 10], [0, 0]]);
+ const before = JSON.parse(JSON.stringify(line));
+ lineOffset(line.geometry, 10);
+
+ t.deepEqual(line, before, 'input does not mutate');
+ t.end();
+});
diff --git a/packages/turf-line-offset/test/in/line-concave.geojson b/packages/turf-line-offset/test/in/line-concave.geojson
new file mode 100644
index 0000000000..6de65e3ac3
--- /dev/null
+++ b/packages/turf-line-offset/test/in/line-concave.geojson
@@ -0,0 +1,38 @@
+{
+ "type": "Feature",
+ "properties": {
+ "stroke-width": 5,
+ "stroke": "red",
+ "distance": 150,
+ "units": "miles"
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 0.9667968749999999,
+ 21.69826549685252
+ ],
+ [
+ -2.5927734375,
+ 24.44714958973082
+ ],
+ [
+ 0.3955078125,
+ 29.726222319395504
+ ],
+ [
+ 7.207031249999999,
+ 29.916852233070173
+ ],
+ [
+ 11.337890625,
+ 23.079731762449878
+ ],
+ [
+ 3.2080078125,
+ 21.04349121680354
+ ]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/turf-line-offset/test/in/line-horizontal.geojson b/packages/turf-line-offset/test/in/line-horizontal.geojson
new file mode 100644
index 0000000000..97ae5bef0b
--- /dev/null
+++ b/packages/turf-line-offset/test/in/line-horizontal.geojson
@@ -0,0 +1,20 @@
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 10,
+ 0
+ ],
+ [
+ 20,
+ 0
+ ]
+ ]
+ }
+ }
\ No newline at end of file
diff --git a/packages/turf-line-offset/test/in/linestring-long.geojson b/packages/turf-line-offset/test/in/linestring-long.geojson
new file mode 100644
index 0000000000..e9f886e7cb
--- /dev/null
+++ b/packages/turf-line-offset/test/in/linestring-long.geojson
@@ -0,0 +1,172 @@
+{
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ -24.2578125,
+ 41.244772343082076
+ ],
+ [
+ -6.328125,
+ 45.336701909968134
+ ],
+ [
+ 0.703125,
+ 49.83798245308484
+ ],
+ [
+ 2.8125,
+ 44.33956524809713
+ ],
+ [
+ -6.328125,
+ 39.095962936305476
+ ],
+ [
+ -5.44921875,
+ 36.4566360115962
+ ],
+ [
+ -0.17578125,
+ 37.3002752813443
+ ],
+ [
+ 1.58203125,
+ 39.36827914916014
+ ],
+ [
+ 3.33984375,
+ 40.58058466412761
+ ],
+ [
+ 6.15234375,
+ 41.244772343082076
+ ],
+ [
+ 7.3828125,
+ 39.50404070558415
+ ],
+ [
+ 4.5703125,
+ 37.020098201368114
+ ],
+ [
+ 3.69140625,
+ 35.31736632923788
+ ],
+ [
+ 4.921875,
+ 31.653381399664
+ ],
+ [
+ 8.61328125,
+ 32.54681317351514
+ ],
+ [
+ 10.72265625,
+ 34.016241889667015
+ ],
+ [
+ 10.546875,
+ 36.03133177633187
+ ],
+ [
+ 10.37109375,
+ 38.272688535980976
+ ],
+ [
+ 10.01953125,
+ 39.232253141714885
+ ],
+ [
+ 9.4921875,
+ 40.58058466412761
+ ],
+ [
+ 9.31640625,
+ 41.902277040963696
+ ],
+ [
+ 11.42578125,
+ 44.08758502824516
+ ],
+ [
+ 13.53515625,
+ 43.96119063892024
+ ],
+ [
+ 14.765625,
+ 42.8115217450979
+ ],
+ [
+ 15.8203125,
+ 37.71859032558816
+ ],
+ [
+ 15.644531250000002,
+ 36.1733569352216
+ ],
+ [
+ 14.94140625,
+ 34.59704151614417
+ ],
+ [
+ 13.359375,
+ 31.952162238024975
+ ],
+ [
+ 11.77734375,
+ 22.755920681486405
+ ],
+ [
+ 15.8203125,
+ 29.22889003019423
+ ],
+ [
+ 16.875,
+ 31.50362930577303
+ ],
+ [
+ 17.2265625,
+ 33.43144133557529
+ ],
+ [
+ 17.402343749999996,
+ 35.02999636902566
+ ],
+ [
+ 19.51171875,
+ 41.244772343082076
+ ],
+ [
+ 21.4453125,
+ 41.11246878918088
+ ],
+ [
+ 20.7421875,
+ 38.8225909761771
+ ],
+ [
+ 21.09375,
+ 36.1733569352216
+ ],
+ [
+ 21.26953125,
+ 34.016241889667015
+ ],
+ [
+ 21.26953125,
+ 31.353636941500987
+ ],
+ [
+ 26.015625,
+ 33.284619968887675
+ ]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/turf-line-offset/test/in/linestring-same-start-end.geojson b/packages/turf-line-offset/test/in/linestring-same-start-end.geojson
new file mode 100644
index 0000000000..f7b5f7b1af
--- /dev/null
+++ b/packages/turf-line-offset/test/in/linestring-same-start-end.geojson
@@ -0,0 +1,36 @@
+{
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 122.431640625,
+ -19.559790136497398
+ ],
+ [
+ 122.51953124999999,
+ -26.902476886279807
+ ],
+ [
+ 134.82421875,
+ -27.215556209029675
+ ],
+ [
+ 139.306640625,
+ -22.998851594142913
+ ],
+ [
+ 133.9453125,
+ -14.434680215297268
+ ],
+ [
+ 122.431640625,
+ -19.559790136497398
+ ]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/turf-line-offset/test/in/linestring-single-segment-only.geojson b/packages/turf-line-offset/test/in/linestring-single-segment-only.geojson
new file mode 100644
index 0000000000..66f8cf2d08
--- /dev/null
+++ b/packages/turf-line-offset/test/in/linestring-single-segment-only.geojson
@@ -0,0 +1,20 @@
+{
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 10
+ ]
+ ]
+ }
+}
diff --git a/packages/turf-line-offset/test/in/linestring-straight.geojson b/packages/turf-line-offset/test/in/linestring-straight.geojson
new file mode 100644
index 0000000000..03a9c2f33f
--- /dev/null
+++ b/packages/turf-line-offset/test/in/linestring-straight.geojson
@@ -0,0 +1,24 @@
+{
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 1,
+ -10
+ ],
+ [
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 10
+ ]
+ ]
+ }
+}
diff --git a/packages/turf-line-offset/test/in/multi-linestring.geojson b/packages/turf-line-offset/test/in/multi-linestring.geojson
new file mode 100644
index 0000000000..dc64a6014f
--- /dev/null
+++ b/packages/turf-line-offset/test/in/multi-linestring.geojson
@@ -0,0 +1,14 @@
+{
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "MultiLineString",
+ "coordinates": [
+ [[10, 10], [2, 2], [0, 0]],
+ [[25, 5], [20, 10], [15, 5]]
+ ]
+ }
+}
diff --git a/packages/turf-line-offset/test/in/northern-line.geojson b/packages/turf-line-offset/test/in/northern-line.geojson
new file mode 100644
index 0000000000..ecb97bfaa1
--- /dev/null
+++ b/packages/turf-line-offset/test/in/northern-line.geojson
@@ -0,0 +1,60 @@
+{
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ -93.66943359374999,
+ 75.2782047773442
+ ],
+ [
+ -94.15283203125,
+ 75.52189820596192
+ ],
+ [
+ -94.68017578125,
+ 75.60133818586581
+ ],
+ [
+ -95.64697265625,
+ 75.55208098028335
+ ],
+ [
+ -95.8447265625,
+ 75.37837872661018
+ ],
+ [
+ -96.339111328125,
+ 75.10975495781904
+ ],
+ [
+ -96.251220703125,
+ 74.89940428652537
+ ],
+ [
+ -95.20751953125,
+ 74.73829331009176
+ ],
+ [
+ -94.50439453125,
+ 74.63965846462719
+ ],
+ [
+ -93.878173828125,
+ 74.65129477919845
+ ],
+ [
+ -93.526611328125,
+ 74.73250841433554
+ ],
+ [
+ -93.50463867187499,
+ 75.09845822124332
+ ]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/turf-line-offset/test/out/line-concave.geojson b/packages/turf-line-offset/test/out/line-concave.geojson
new file mode 100644
index 0000000000..8885f2bdb2
--- /dev/null
+++ b/packages/turf-line-offset/test/out/line-concave.geojson
@@ -0,0 +1,81 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke-width": 5,
+ "stroke": "#00F",
+ "distance": 150,
+ "units": "miles"
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 2.2933,
+ 23.416
+ ],
+ [
+ 0.2226,
+ 25.0151
+ ],
+ [
+ 1.6807,
+ 27.591
+ ],
+ [
+ 6.0035,
+ 27.712
+ ],
+ [
+ 7.9614,
+ 24.4714
+ ],
+ [
+ 2.6807,
+ 23.1488
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke-width": 5,
+ "stroke": "red",
+ "distance": 150,
+ "units": "miles"
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 0.9667968749999999,
+ 21.69826549685252
+ ],
+ [
+ -2.5927734375,
+ 24.44714958973082
+ ],
+ [
+ 0.3955078125,
+ 29.726222319395504
+ ],
+ [
+ 7.207031249999999,
+ 29.916852233070173
+ ],
+ [
+ 11.337890625,
+ 23.079731762449878
+ ],
+ [
+ 3.2080078125,
+ 21.04349121680354
+ ]
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/turf-line-offset/test/out/line-horizontal.geojson b/packages/turf-line-offset/test/out/line-horizontal.geojson
new file mode 100644
index 0000000000..e9696f37d8
--- /dev/null
+++ b/packages/turf-line-offset/test/out/line-horizontal.geojson
@@ -0,0 +1,45 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#00F",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 10,
+ -0.4495
+ ],
+ [
+ 20,
+ -0.4495
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 10,
+ 0
+ ],
+ [
+ 20,
+ 0
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/packages/turf-line-offset/test/out/linestring-long.geojson b/packages/turf-line-offset/test/out/linestring-long.geojson
new file mode 100644
index 0000000000..7febfe18a8
--- /dev/null
+++ b/packages/turf-line-offset/test/out/linestring-long.geojson
@@ -0,0 +1,349 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#00F",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ -24.1578,
+ 40.8065
+ ],
+ [
+ -6.1517,
+ 44.9159
+ ],
+ [
+ 0.481,
+ 49.162
+ ],
+ [
+ 2.2549,
+ 44.5379
+ ],
+ [
+ -6.8708,
+ 39.3029
+ ],
+ [
+ -5.7551,
+ 35.9525
+ ],
+ [
+ 0.0592,
+ 36.8826
+ ],
+ [
+ 1.8861,
+ 39.0319
+ ],
+ [
+ 3.5254,
+ 40.1625
+ ],
+ [
+ 5.9604,
+ 40.7376
+ ],
+ [
+ 6.7829,
+ 39.574
+ ],
+ [
+ 4.2095,
+ 37.3012
+ ],
+ [
+ 3.2047,
+ 35.3545
+ ],
+ [
+ 4.627,
+ 31.1195
+ ],
+ [
+ 8.801,
+ 32.1298
+ ],
+ [
+ 11.1931,
+ 33.7961
+ ],
+ [
+ 10.9949,
+ 36.0684
+ ],
+ [
+ 10.8144,
+ 38.3694
+ ],
+ [
+ 10.4399,
+ 39.3915
+ ],
+ [
+ 9.9306,
+ 40.6937
+ ],
+ [
+ 9.7906,
+ 41.7463
+ ],
+ [
+ 11.6055,
+ 43.6265
+ ],
+ [
+ 13.3466,
+ 43.5222
+ ],
+ [
+ 14.3544,
+ 42.5805
+ ],
+ [
+ 15.3655,
+ 37.6979
+ ],
+ [
+ 15.2057,
+ 36.2931
+ ],
+ [
+ 14.5418,
+ 34.8047
+ ],
+ [
+ 12.9306,
+ 32.111
+ ],
+ [
+ 10.9464,
+ 20.5769
+ ],
+ [
+ 16.2164,
+ 29.0145
+ ],
+ [
+ 17.3069,
+ 31.3666
+ ],
+ [
+ 17.6716,
+ 33.3665
+ ],
+ [
+ 17.8438,
+ 34.932
+ ],
+ [
+ 19.8262,
+ 40.7727
+ ],
+ [
+ 20.8493,
+ 40.7027
+ ],
+ [
+ 20.2837,
+ 38.8607
+ ],
+ [
+ 20.6466,
+ 36.1255
+ ],
+ [
+ 20.82,
+ 33.998
+ ],
+ [
+ 20.82,
+ 30.6854
+ ],
+ [
+ 26.185,
+ 32.8682
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ -24.2578125,
+ 41.244772343082076
+ ],
+ [
+ -6.328125,
+ 45.336701909968134
+ ],
+ [
+ 0.703125,
+ 49.83798245308484
+ ],
+ [
+ 2.8125,
+ 44.33956524809713
+ ],
+ [
+ -6.328125,
+ 39.095962936305476
+ ],
+ [
+ -5.44921875,
+ 36.4566360115962
+ ],
+ [
+ -0.17578125,
+ 37.3002752813443
+ ],
+ [
+ 1.58203125,
+ 39.36827914916014
+ ],
+ [
+ 3.33984375,
+ 40.58058466412761
+ ],
+ [
+ 6.15234375,
+ 41.244772343082076
+ ],
+ [
+ 7.3828125,
+ 39.50404070558415
+ ],
+ [
+ 4.5703125,
+ 37.020098201368114
+ ],
+ [
+ 3.69140625,
+ 35.31736632923788
+ ],
+ [
+ 4.921875,
+ 31.653381399664
+ ],
+ [
+ 8.61328125,
+ 32.54681317351514
+ ],
+ [
+ 10.72265625,
+ 34.016241889667015
+ ],
+ [
+ 10.546875,
+ 36.03133177633187
+ ],
+ [
+ 10.37109375,
+ 38.272688535980976
+ ],
+ [
+ 10.01953125,
+ 39.232253141714885
+ ],
+ [
+ 9.4921875,
+ 40.58058466412761
+ ],
+ [
+ 9.31640625,
+ 41.902277040963696
+ ],
+ [
+ 11.42578125,
+ 44.08758502824516
+ ],
+ [
+ 13.53515625,
+ 43.96119063892024
+ ],
+ [
+ 14.765625,
+ 42.8115217450979
+ ],
+ [
+ 15.8203125,
+ 37.71859032558816
+ ],
+ [
+ 15.644531250000002,
+ 36.1733569352216
+ ],
+ [
+ 14.94140625,
+ 34.59704151614417
+ ],
+ [
+ 13.359375,
+ 31.952162238024975
+ ],
+ [
+ 11.77734375,
+ 22.755920681486405
+ ],
+ [
+ 15.8203125,
+ 29.22889003019423
+ ],
+ [
+ 16.875,
+ 31.50362930577303
+ ],
+ [
+ 17.2265625,
+ 33.43144133557529
+ ],
+ [
+ 17.402343749999996,
+ 35.02999636902566
+ ],
+ [
+ 19.51171875,
+ 41.244772343082076
+ ],
+ [
+ 21.4453125,
+ 41.11246878918088
+ ],
+ [
+ 20.7421875,
+ 38.8225909761771
+ ],
+ [
+ 21.09375,
+ 36.1733569352216
+ ],
+ [
+ 21.26953125,
+ 34.016241889667015
+ ],
+ [
+ 21.26953125,
+ 31.353636941500987
+ ],
+ [
+ 26.015625,
+ 33.284619968887675
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/packages/turf-line-offset/test/out/linestring-same-start-end.geojson b/packages/turf-line-offset/test/out/linestring-same-start-end.geojson
new file mode 100644
index 0000000000..9d60ae863c
--- /dev/null
+++ b/packages/turf-line-offset/test/out/linestring-same-start-end.geojson
@@ -0,0 +1,77 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#00F",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 121.9822,
+ -19.5652
+ ],
+ [
+ 122.0752,
+ -27.3408
+ ],
+ [
+ 134.9976,
+ -27.6696
+ ],
+ [
+ 139.8836,
+ -23.0733
+ ],
+ [
+ 134.1192,
+ -13.8652
+ ],
+ [
+ 122.2488,
+ -19.1491
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 122.431640625,
+ -19.559790136497398
+ ],
+ [
+ 122.51953124999999,
+ -26.902476886279807
+ ],
+ [
+ 134.82421875,
+ -27.215556209029675
+ ],
+ [
+ 139.306640625,
+ -22.998851594142913
+ ],
+ [
+ 133.9453125,
+ -14.434680215297268
+ ],
+ [
+ 122.431640625,
+ -19.559790136497398
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/packages/turf-line-offset/test/out/linestring-single-segment-only.geojson b/packages/turf-line-offset/test/out/linestring-single-segment-only.geojson
new file mode 100644
index 0000000000..5d2d9553ec
--- /dev/null
+++ b/packages/turf-line-offset/test/out/linestring-single-segment-only.geojson
@@ -0,0 +1,45 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#00F",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 1.4495,
+ 1
+ ],
+ [
+ 1.4495,
+ 10
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 10
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/packages/turf-line-offset/test/out/linestring-straight.geojson b/packages/turf-line-offset/test/out/linestring-straight.geojson
new file mode 100644
index 0000000000..825e7a7d5e
--- /dev/null
+++ b/packages/turf-line-offset/test/out/linestring-straight.geojson
@@ -0,0 +1,53 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#00F",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 1.4495,
+ -10
+ ],
+ [
+ 1.4495,
+ 1
+ ],
+ [
+ 1.4495,
+ 10
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 1,
+ -10
+ ],
+ [
+ 1,
+ 1
+ ],
+ [
+ 1,
+ 10
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/packages/turf-line-offset/test/out/multi-linestring.geojson b/packages/turf-line-offset/test/out/multi-linestring.geojson
new file mode 100644
index 0000000000..cc7c5d6340
--- /dev/null
+++ b/packages/turf-line-offset/test/out/multi-linestring.geojson
@@ -0,0 +1,85 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#00F",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "MultiLineString",
+ "coordinates": [
+ [
+ [
+ 9.6821,
+ 10.3179
+ ],
+ [
+ 1.6821,
+ 2.3179
+ ],
+ [
+ -0.3179,
+ 0.3179
+ ]
+ ],
+ [
+ [
+ 25.3179,
+ 5.3179
+ ],
+ [
+ 20,
+ 10.6357
+ ],
+ [
+ 14.6821,
+ 5.3179
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "MultiLineString",
+ "coordinates": [
+ [
+ [
+ 10,
+ 10
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ [
+ [
+ 25,
+ 5
+ ],
+ [
+ 20,
+ 10
+ ],
+ [
+ 15,
+ 5
+ ]
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/packages/turf-line-offset/test/out/northern-line.geojson b/packages/turf-line-offset/test/out/northern-line.geojson
new file mode 100644
index 0000000000..bbc616bbdb
--- /dev/null
+++ b/packages/turf-line-offset/test/out/northern-line.geojson
@@ -0,0 +1,125 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#00F",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ -93.4671,
+ 75.6796
+ ],
+ [
+ -94.0147,
+ 75.9557
+ ],
+ [
+ -94.6579,
+ 76.0526
+ ],
+ [
+ -95.8261,
+ 75.9931
+ ],
+ [
+ -96.1036,
+ 75.7493
+ ],
+ [
+ -96.9104,
+ 75.311
+ ],
+ [
+ -96.5688,
+ 74.4936
+ ],
+ [
+ -95.273,
+ 74.2936
+ ],
+ [
+ -94.5316,
+ 74.1896
+ ],
+ [
+ -93.8228,
+ 74.2027
+ ],
+ [
+ -93.098,
+ 74.3702
+ ],
+ [
+ -93.0559,
+ 75.0715
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "stroke": "#F00",
+ "stroke-width": 6
+ },
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ -93.66943359374999,
+ 75.2782047773442
+ ],
+ [
+ -94.15283203125,
+ 75.52189820596192
+ ],
+ [
+ -94.68017578125,
+ 75.60133818586581
+ ],
+ [
+ -95.64697265625,
+ 75.55208098028335
+ ],
+ [
+ -95.8447265625,
+ 75.37837872661018
+ ],
+ [
+ -96.339111328125,
+ 75.10975495781904
+ ],
+ [
+ -96.251220703125,
+ 74.89940428652537
+ ],
+ [
+ -95.20751953125,
+ 74.73829331009176
+ ],
+ [
+ -94.50439453125,
+ 74.63965846462719
+ ],
+ [
+ -93.878173828125,
+ 74.65129477919845
+ ],
+ [
+ -93.526611328125,
+ 74.73250841433554
+ ],
+ [
+ -93.50463867187499,
+ 75.09845822124332
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/packages/turf-line-offset/test/types.ts b/packages/turf-line-offset/test/types.ts
new file mode 100644
index 0000000000..358953f71c
--- /dev/null
+++ b/packages/turf-line-offset/test/types.ts
@@ -0,0 +1,11 @@
+import * as lineOffset from '../'
+import {lineString, multiLineString} from '@turf/helpers'
+
+const line = lineString([[0, 0], [10, 10]])
+const multiLine = multiLineString([[[0, 0], [10, 10]], [[5, 5], [15, 15]]])
+
+lineOffset(line, 50)
+lineOffset(line.geometry, 50)
+lineOffset(multiLine, 50)
+lineOffset(multiLine.geometry, 50)
+lineOffset(line, 50, 'miles')