├── .gitignore ├── .jshintrc ├── .npmignore ├── LICENSE ├── README.md ├── optimize-paths.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | *.log -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion" : 6 3 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Evelios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Optimize Path 2 | 3 | This is an algorithm to optimize a series of line paths. This algorithm is used 4 | to sort the paths in an order that will speed up the time it will take to print 5 | them with a pen-plotter. This algorithm can also take into account the pen 6 | thickness so that if a line ends near where a different line starts, then their 7 | paths are merged together so that the pen plotter doesn't have to lift up the 8 | pen to continue with the stroke. 9 | 10 | # Usage 11 | 12 | `optimizePath(input_paths, [pen_thickness])` 13 | + `input_paths` : a list of path lines `[ line1, line2, line3, ... ]` 14 | + Input lines are in list form, `[ [x1, y1], [x2, y2], ... ]` 15 | + `pen_thickness` : the thickness of the pen which determines the maximum 16 | distnace to combine paths tohether 17 | 18 | ## Example 19 | ```js 20 | const optimizePath = require('optimize-paths'); 21 | 22 | const input = [ 23 | [[7, 8], [5, 3]], // 1 24 | [[2, 3], [4, 4]], // 2 25 | [[1, 1], [2, 2]], // 3 26 | ]; 27 | 28 | const output = optimizePaths(input); 29 | // [ 30 | // [[1, 1], [2, 2]], // 3 31 | // [[2, 3], [4, 4]], // 2 32 | // [[5, 3], [7, 8]], // 1 33 | // ]; 34 | 35 | // Calling with the pen thickness input 36 | const pen_width = 1; 37 | const joined_output = optimizePaths(input, pen_width); 38 | // [ 39 | // [[1, 1], [2, 2], [2, 3], [4, 4]], // 3 & 2 40 | // [[5, 3], [7, 8]], // 1 41 | // ]; 42 | ``` 43 | 44 | # Change Log 45 | 46 | ### Version 1.1.0 47 | + Added support for combining lines based on the pen thickness -------------------------------------------------------------------------------- /optimize-paths.js: -------------------------------------------------------------------------------- 1 | const deepCopy = require('deep-copy'); 2 | 3 | /** 4 | * Return a new list contining the optimized path from lines to speed up 5 | * print times for the pen plotter. It also includes an optimization to combine 6 | * path together with a maximum distance which should be set by the pen 7 | * thickness. 8 | * 9 | * @param {Line[]} lines A list of lines represented in vector list form 10 | * @param {number} pen_width Combine lines together smaller than the pen width 11 | */ 12 | module.exports = function optimizePath(lines, pen_width=0) { 13 | if (lines.length == 0) { 14 | return []; 15 | } 16 | 17 | const dist2 = (v1, v2) => Math.pow((v1[0] - v2[0]), 2) + Math.pow((v1[1] - v2[1]), 2); 18 | const pen_width_squared = Math.pow(pen_width, 2); // Square this because distance is in squared units 19 | let frontier = deepCopy(lines); 20 | let current_node = frontier.pop(); 21 | let explored = [current_node]; 22 | 23 | while (frontier.length !== 0) { 24 | let reversed = false; 25 | let path_index = -1; 26 | let closest_dist = Infinity; 27 | let dist = Infinity; 28 | let path; 29 | let current_endpoint = current_node[current_node.length - 1]; 30 | 31 | // Get the path that is closest to the current_node 32 | for (let index = 0; index < frontier.length; index ++) { 33 | path = frontier[index]; 34 | // Regular Orientation 35 | dist = dist2(current_endpoint, path[0]); 36 | if (dist < closest_dist) { 37 | reversed = false; 38 | path_index = index; 39 | closest_dist = dist; 40 | } 41 | // Reversed Orientation 42 | dist = dist2(current_endpoint, path[path.length-1]); 43 | if (dist < closest_dist) { 44 | reversed = true; 45 | path_index = index; 46 | closest_dist = dist; 47 | } 48 | } 49 | 50 | // Add the closest path to the explored list and remove it from the frontier 51 | current_node = frontier[path_index]; 52 | frontier.splice(path_index, 1); 53 | if (reversed) { 54 | current_node = current_node.reverse(); 55 | } 56 | 57 | // If the paths are closer than the pen width, them combine them 58 | if (closest_dist < pen_width_squared) { 59 | explored[explored.length - 1] = explored[explored.length - 1].concat(current_node); 60 | } else { 61 | explored.push(current_node); 62 | } 63 | 64 | } 65 | 66 | return explored; 67 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optimize-paths", 3 | "version": "1.1.0", 4 | "description": "This is an optimization algorithm to optimize a list of paths to minimize the amount of time needed to draw the paths with a pen-plotter", 5 | "main": "optimize-paths.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Evelios/optimize-path.git" 12 | }, 13 | "keywords": [ 14 | "svg", 15 | "paths", 16 | "line", 17 | "plotter", 18 | "vector" 19 | ], 20 | "author": "Thomas Waters", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/Evelios/optimize-path/issues" 24 | }, 25 | "homepage": "https://github.com/Evelios/optimize-path#readme", 26 | "dependencies": { 27 | "deep-copy": "^1.4.2" 28 | }, 29 | "devDependencies": { 30 | "tape": "^4.9.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const optimizePath = require('./optimize-paths'); 2 | const test = require('tape'); 3 | 4 | test('Optimize Path', t => { 5 | const input = [ 6 | [[7, 8], [5, 3]], 7 | [[2, 3], [4, 4]], 8 | [[1, 1], [2, 2]], 9 | ]; 10 | 11 | const output = [ 12 | [[1, 1], [2, 2]], 13 | [[2, 3], [4, 4]], 14 | [[5, 3], [7, 8]], 15 | ]; 16 | 17 | t.deepEqual(optimizePath(input), output); 18 | t.end(); 19 | }); 20 | 21 | test('Optimize Path - Connected Lines', t => { 22 | const pen_thickness = 0.25; 23 | const input = [ 24 | [[7, 8], [5, 3]], 25 | [[2, 2.1], [4, 4]], 26 | [[1, 1], [2, 2]], 27 | ]; 28 | 29 | const output = [ 30 | [[1, 1], [2, 2], [2, 2.1], [4, 4]], 31 | [[5, 3], [7, 8]], 32 | ]; 33 | 34 | t.deepEqual(optimizePath(input, pen_thickness), output); 35 | t.end(); 36 | }); 37 | 38 | test('Optimize Path - Don\'t Connect Lines', t => { 39 | const pen_thickness = 0.05; 40 | const input = [ 41 | [[7, 8], [5, 3]], 42 | [[2, 2.1], [4, 4]], 43 | [[1, 1], [2, 2]], 44 | ]; 45 | 46 | const output = [ 47 | [[1, 1], [2, 2]], 48 | [[2, 2.1], [4, 4]], 49 | [[5, 3], [7, 8]], 50 | ]; 51 | 52 | t.deepEqual(optimizePath(input, pen_thickness), output); 53 | t.end(); 54 | }); --------------------------------------------------------------------------------