Skip to content

Commit 17b93f2

Browse files
committed
initial commit
0 parents  commit 17b93f2

File tree

10 files changed

+426
-0
lines changed

10 files changed

+426
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
node_modules

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src

LICENSE.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Copyright 2018 Russell Biggs
2+
3+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4+
5+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Convert GeoJSON to ASCII STL
2+
3+
4+
### Installation
5+
```sh
6+
npm install geojson2stl
7+
```
8+
9+
### In the terminal
10+
```sh
11+
geojson2stl example.geojson
12+
```
13+
14+
15+
#### Flags
16+
17+
-o
18+
--output
19+
20+
21+
Sets the output file path and file name
22+
23+
_Default_: ./output.stl
24+
25+
26+
-e
27+
--extrude
28+
29+
30+
Sets the millimeters to extrude the shape
31+
32+
_Default_: 1
33+
34+
35+
-s
36+
--size
37+
38+
39+
Set the maximum dimension (in x or y dimensions) of the output shape in millimeters
40+
41+
_Default_:200
42+
43+
44+
example
45+
```
46+
geojson2stl -s 150 -e 10 -o ./example.stl example.geojson
47+
```
48+
returns a 150mm maximum dimension, 10mm extrude file named example.stl
49+
50+
51+
### in Node
52+
```js
53+
const geojson2stl = require('geojson2stl');
54+
const fs = require('fs');
55+
56+
fs.readFile('./myFeatures.geojson', 'utf-8', (err,data) => {
57+
let options = {};
58+
options.size = 150; //150mm maximum width or height
59+
options.extrude = 5; //extrude 5mm in z axis
60+
let stl = geojson2stl(data, options);
61+
...
62+
...
63+
});
64+
```
65+
66+
67+
## Reference
68+
69+
geojson2stl(geojson[, options]]) <>
70+
71+
Takes a required parameter of geojson and returns ASCII STL text of the input geojson. Options is an opject with the following optional attributes:
72+
* ``` output``` - filename to give name attribute in STL file _default_: 'output.stl'
73+
* ``` extrude``` - a number indicating millimeters to extrude the shape _default_: 1
74+
* ``` size``` - a number indicating the maximum dimension of the output shape _default_: 200
75+
76+
77+
78+
## Caveats
79+
expects geojson as WGS84 (as per the [specification](https://tools.ietf.org/html/rfc7946)) and projects all coordinates to mercator.

package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "geojson2stl",
3+
"version": "0.1.0",
4+
"description": "Convert a GeoJSON to STL.",
5+
"main": "build/index.js",
6+
"scripts": {
7+
"build": "babel src -d build",
8+
"pretest": "npm run build",
9+
"posttest": "rm -rf build",
10+
"test": "tape 'tests/**/*-test.js'",
11+
"prepublish": "npm run build"
12+
},
13+
"babel": {
14+
"presets": [
15+
"env"
16+
]
17+
},
18+
"keywords": [
19+
"geojson",
20+
"STL",
21+
"3D Printing"
22+
],
23+
"author": {
24+
"name": "Russ Biggs",
25+
"url": "https://russbiggs.com"
26+
},
27+
"homepage": "https://github.com/russbiggs/geojson2stl",
28+
"license": "ISC",
29+
"bin": {
30+
"geojson2stl": "build/geojson2stl.js"
31+
},
32+
"repository": {
33+
"type": "git",
34+
"url": "https://github.com/russbiggs/geojson2stl.git"
35+
},
36+
"dependencies": {
37+
"@turf/bbox": "^6.0.0",
38+
"@turf/centroid": "^6.0.0",
39+
"@turf/projection": "^6.0.0",
40+
"colors": "^1.2.1",
41+
"commander": "^2.15.0",
42+
"earcut": "^2.1.3"
43+
},
44+
"devDependencies": {
45+
"babel-cli": "^6.26.0",
46+
"babel-preset-env": "^1.6.1",
47+
"tape": "^4.9.0"
48+
}
49+
}

src/geojson2stl.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const fs = require('fs');
2+
const commander = require('commander');
3+
const geojson2stl = require('../build');
4+
const bbox = require('@turf/bbox').default;
5+
const colors = require('colors');
6+
7+
commander.version(require('../package.json').version)
8+
.usage('[options] [file]')
9+
.description('Convert a GeoJSON to STL.')
10+
.option('-o, --output <path>', 'output file name', './output.stl')
11+
.option('-e, --extrude <int>', 'extrude value in mm, default 1mm', 1)
12+
.option('-s, --size <int>', 'size in mm, default is 200mm', 200)
13+
.parse(process.argv);
14+
15+
if (commander.args.length === 0) {
16+
console.error();
17+
console.error(" error: no input file given");
18+
console.error();
19+
process.exit(1);
20+
}
21+
22+
fs.readFile(commander.args[0], 'utf-8', (err, data) => {
23+
let options = {};
24+
options.size = parseInt(commander.size)
25+
options.extrude = parseInt(commander.extrude);
26+
options.output = commander.output;
27+
let geojson = JSON.parse(data);
28+
let dims = bbox(geojson);
29+
let xDiff = dims[2] - dims[0];
30+
let yDiff = dims[3] - dims[1];
31+
let xDim, yDim;
32+
if (xDiff > yDiff) {
33+
xDim = options.size;
34+
yDim = options.size * yDiff/xDiff;
35+
} else if (yDiff > xDiff) {
36+
xDim = options.size * xDiff/yDiff;
37+
yDim = options.size;
38+
} else {
39+
xDim = options.size;
40+
yDim = options.size;
41+
}
42+
let stl = geojson2stl(geojson, options);
43+
console.log(colors.blue(`output dimensions 📏`));
44+
console.log(colors.blue(`x\t${xDim}mm`));
45+
console.log(colors.blue(`y\t${yDim}mm`));
46+
console.log(colors.blue(`z\t${options.extrude}mm`));
47+
fs.writeFile(options.output, stl, () => {
48+
console.log(colors.green(`${options.output} written.`));
49+
});
50+
});

src/index.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
const centroid = require('@turf/centroid').default;
2+
const bbox = require('@turf/bbox').default;
3+
const projection = require('@turf/projection');
4+
const earcut = require('earcut');
5+
const path = require('path');
6+
7+
module.exports = ( geojson, options )=>{
8+
let output = './output.stl';
9+
if (options && options.output != null) output = options.output;
10+
let extrude = 1;
11+
if (options && options.extrude != null) extrude = options.extrude;
12+
let size = 200;
13+
if (options && options.size != null) size = options.size;
14+
let projected = projection.toMercator(geojson);
15+
let scaled = scaleFeatures(projected, size);
16+
let facets = '';
17+
for (let i = 0; i < scaled.features.length; i++) {
18+
let feature = scaled.features[i].geometry.coordinates[0];
19+
facets += convertFeature(feature, extrude, size);
20+
}
21+
let filename = path.parse(output).base.split('.')[0];
22+
return combineStl(filename, facets);
23+
};
24+
25+
const convertFeature = ( feature, extrusion, size )=>{
26+
let stl = '';
27+
let bottom = [];
28+
let top = [];
29+
for (let i = 0; i < feature.length; i++) {
30+
bottom.push(feature[i].concat(0));
31+
top.push(feature[i].concat(extrusion));
32+
}
33+
let lines = [];
34+
for (let i = 0; i < feature.length; i++) {
35+
let line = [feature[i], feature[i + 1]];
36+
if (i + 1 != feature.length) {
37+
lines.push(line);
38+
}
39+
}
40+
for (let i = 0; i < lines.length; i++) {
41+
let facet = createFace(lines[i], extrusion);
42+
stl += facet;
43+
}
44+
stl += triangulate([top]);
45+
stl += triangulate([bottom]);
46+
return stl;
47+
}
48+
49+
const triangulate = ( face )=>{
50+
let flat = earcut.flatten(face);
51+
let tris = earcut(flat.vertices, flat.holes, flat.dimensions);
52+
let arrays = [];
53+
let size = 3;
54+
while (tris.length > 0) {
55+
arrays.push(tris.splice(0, size));
56+
}
57+
let triangles = '';
58+
for (let i = 0; i < arrays.length; i++) {
59+
let facet = stlFacet(face[0][arrays[i][0]], face[0][arrays[i][1]], face[0][arrays[i][2]]);
60+
triangles += facet;
61+
}
62+
return triangles;
63+
}
64+
65+
const scaleFeatures= ( feature, size )=> {
66+
let featureCentroid = centroid(feature).geometry.coordinates;
67+
let dims = bbox(feature);
68+
let xDiff = dims[2] - dims[0];
69+
let yDiff = dims[3] - dims[1];
70+
let xDim, yDim;
71+
if (xDiff > yDiff) {
72+
xDim = size;
73+
yDim = size * yDiff/xDiff;
74+
} else if (yDiff > xDiff) {
75+
xDim = size * xDiff/yDiff;
76+
yDim = size;
77+
} else {
78+
xDim = size;
79+
yDim = size;
80+
}
81+
let xSize = xDim/xDiff;
82+
let ySize = yDim/yDiff;
83+
let offset = [-featureCentroid[0], -featureCentroid[1]];
84+
for (let i = 0; i<feature.features.length; i++) {
85+
let coordsOffset = [];
86+
for (let j=0; j<feature.features[i].geometry.coordinates[0].length; j++) {
87+
let coords = feature.features[i].geometry.coordinates[0][j];
88+
let newCoords = [(coords[0] + offset[0]) * xSize, (coords[1] + offset[1]) * ySize];
89+
coordsOffset.push(newCoords);
90+
}
91+
feature.features[i].geometry.coordinates = [coordsOffset];
92+
}
93+
return feature;
94+
}
95+
96+
const createFace = (line, extrusion )=>{
97+
let start = line[0];
98+
let end = line[1];
99+
let triangle1 = stlFacet(start.concat(0), start.concat(extrusion), end.concat(extrusion));
100+
let triangle2 = stlFacet(start.concat(0), end.concat(0), end.concat(extrusion));
101+
let face = `${triangle1}${triangle2}`;
102+
return face;
103+
}
104+
105+
const stlFacet= ( point1, point2, point3 )=>{
106+
let loop = `facet normal 1.0 1.0 1.0
107+
outer loop
108+
vertex ${point1[0]} ${point1[1]} ${point1[2]}
109+
vertex ${point2[0]} ${point2[1]} ${point2[2]}
110+
vertex ${point3[0]} ${point3[1]} ${point3[2]}
111+
endloop
112+
endfacet\n`;
113+
return loop;
114+
}
115+
116+
const combineStl = ( name, facets )=>{
117+
let template = `solid ${name}\n${facets}`;
118+
return template;
119+
}

tests/index-test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const fs = require('fs');
2+
const geojson2stl = require('../build/');
3+
const test = require('tape');
4+
5+
const options = {
6+
"output": "./outputs/test.stl",
7+
"extrude": 10,
8+
"size": 100
9+
};
10+
11+
test("geojson2stl(test.geojson) with options", function(test) {
12+
let geojson = fs.readFileSync("tests/test.geojson");
13+
let actual = geojson2stl(JSON.parse(geojson), options);
14+
let expected = fs.readFileSync("tests/test.stl", "utf8");
15+
test.deepEqual(actual, expected);
16+
test.end();
17+
});

tests/test.geojson

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [{
4+
"type": "Feature",
5+
"properties": {},
6+
"geometry": {
7+
"type": "Polygon",
8+
"coordinates": [
9+
[
10+
[1, 1],
11+
[1, -1],
12+
[-1, -1],
13+
[-1, 1],
14+
[1, 1]
15+
]
16+
]
17+
}
18+
}]
19+
}

0 commit comments

Comments
 (0)