├── README.md ├── LICENSE ├── main.rs └── simplify.rs /README.md: -------------------------------------------------------------------------------- 1 | Port of [simplify-js](https://github.com/mourner/simplify-js) to [rust](https://github.com/mozilla/rust), compile with: 2 | 3 | ```bash 4 | rustc --opt-level=3 -o simplify main.rs 5 | ``` 6 | 7 | run with `./simplify points.json lessPoints.json 0.8` 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Vladimir Agafonkin 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are 5 | permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of 8 | conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | of conditions and the following disclaimer in the documentation and/or other materials 12 | provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 15 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 17 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 21 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | extern mod extra; 2 | use simplify::{Point,simplify}; 3 | use extra::json; 4 | use extra::json::ToJson; 5 | use extra::time::precise_time_s; 6 | use std::path; 7 | use std::os::args; 8 | use std::io::{buffered_file_writer,read_whole_file_str}; 9 | 10 | mod simplify; 11 | fn dealList(l:~[json::Json])->~[Point]{ 12 | println(fmt!("from %?",l.len())); 13 | l.map(|b|{ 14 | match *b{ 15 | json::List([json::Number(x),json::Number(y)])=>Point{x:x,y:y}, 16 | _=>Point{x:0.0,y:0.0} 17 | } 18 | }) 19 | } 20 | fn dealJson (s:~str)->~[Point]{ 21 | match json::from_str(s){ 22 | Ok(j)=> match j{ 23 | json::List(l)=>dealList(l), 24 | _=>~[Point{x:0.0,y:0.0}] 25 | }, 26 | _=>~[Point{x:0.0,y:0.0}] 27 | } 28 | } 29 | fn writeOut ( j:~[Point] , outPath:~path::Path) { 30 | println(fmt!("to %?",j.len())); 31 | match buffered_file_writer(outPath) { 32 | Ok(writer)=>j.to_json().to_writer(writer), 33 | Err(e)=>println(fmt!("%?",e)) 34 | } 35 | true; 36 | } 37 | fn main() { 38 | let args : ~[~str] = args(); 39 | let reader = read_whole_file_str(~path::Path(args[1])); 40 | let outPath = ~path::Path(args[2]); 41 | let simp =from_str::(args[3]).unwrap_or(1.0f); 42 | match reader{ 43 | Ok(points)=> { 44 | let p :~[Point] = dealJson(points); 45 | let startT :float = precise_time_s(); 46 | let out = simplify(p,simp,false); 47 | let endT : float = precise_time_s(); 48 | println!("time {} ms",(endT-startT)*1000f); 49 | writeOut(out,outPath) 50 | } 51 | Err(e)=>println(fmt!("%?",e)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /simplify.rs: -------------------------------------------------------------------------------- 1 | #[link(name = "simplify", vers = "0.0.6")]; 2 | extern mod extra; 3 | use extra::json::Json; 4 | use extra::json::List; 5 | use extra::json::ToJson; 6 | use extra::treemap::TreeSet; 7 | use std::vec; 8 | #[deriving(Clone, Eq)] 9 | pub struct Point { 10 | x: float, 11 | y: float 12 | } 13 | 14 | impl ToJson for Point { 15 | fn to_json(&self) -> Json { List(~[self.x.to_json(),self.y.to_json()]) } 16 | } 17 | impl Sub for Point { 18 | #[inline] 19 | fn sub(&self, other: &Point) -> Point { Point {x:self.x-other.x,y:self.y-other.y} } 20 | } 21 | impl Add for Point { 22 | #[inline] 23 | fn add(&self, other: &Point) -> Point { Point { x:self.x+other.x,y:self.y+other.y }} 24 | } 25 | impl Mul for Point { 26 | #[inline] 27 | fn mul(&self, other: &Point) -> Point { Point { x:self.x*other.x,y:self.y*other.y }} 28 | } 29 | 30 | impl Point { 31 | fn sum(self) -> float { self.x+self.y } 32 | fn sqsum(self) -> float { self.x * self.x + self.y * self.y} 33 | fn sub(self, other: float) -> Point { Point { x:self.x - other , y:self.y - other }} 34 | fn mul(self, other: float) -> Point { Point { x:self.x * other, y:self.y * other }} 35 | fn add(self, other: float) -> Point { Point {x:self.x + other, y:self.y + other }} 36 | } 37 | type Pair = (uint, uint); 38 | 39 | fn calcStuff(p:Point,p1:Point,d1:Point)->float { 40 | let top = ((p - p1) * d1).sum(); 41 | let bottom = d1.sqsum(); 42 | if bottom == 0.0 { 43 | 0.0 44 | }else{ 45 | top/bottom 46 | } 47 | } 48 | fn getSquareSegmentDistance(p: Point, p1: Point, p2: Point) -> float { 49 | let d1 = p2-p1; 50 | let d2 = match d1{ 51 | Point {x:0.0,_} | Point {y:0.0,_}=> {p1} 52 | _=>{ 53 | let t = calcStuff(p,p1,d1); 54 | if t>1.0 { 55 | p2 56 | }else if t>0.0{ 57 | d1.mul(t)+p1 58 | }else{ 59 | p1 60 | } 61 | } 62 | }; 63 | (p-d2).sqsum() 64 | } 65 | 66 | fn simplifyRadialDistance(points:~[Point], sqTolerance:float) -> ~[Point]{ 67 | let mut it = points.iter(); 68 | it.next(); 69 | let mut prevPoint : Point = points[0u]; 70 | let mut newPoints : ~[Point] = ~[prevPoint]; 71 | let &last = points.last(); 72 | for &point in it{ 73 | if (point - prevPoint).sqsum() > sqTolerance { 74 | newPoints.push(point); 75 | prevPoint = point; 76 | } 77 | } 78 | if (prevPoint!= last) { 79 | newPoints.push(last); 80 | } 81 | newPoints 82 | } 83 | fn simplifyDouglasPeucker(points : ~[Point], tolerance : float) -> ~[Point]{ 84 | let len = points.len(); 85 | let mut markers = TreeSet::new(); 86 | let mut stack : ~[Pair] = ~[]; 87 | markers.insert(0u); 88 | markers.insert(len-1u); 89 | let mut pair:Pair = (0u,len-1u); 90 | loop { 91 | let first = pair.first(); 92 | let second = pair.second(); 93 | let (first_pt, second_pt) = (points[first], points[second]); 94 | let mut index = 0u; 95 | let mut max_sq_dist = 0.0f; 96 | let i = first + 1u; 97 | for (i, &point_i) in points.slice_from(i) 98 | .iter() 99 | .enumerate() 100 | .map(|(new_i, point)| (i + new_i, point)) 101 | .take_while(|&(i, _)| i < second) { 102 | let sq_dist = getSquareSegmentDistance(point_i, first_pt, second_pt); 103 | if (sq_dist > max_sq_dist) { 104 | index = i; 105 | max_sq_dist = sq_dist; 106 | } 107 | } 108 | if max_sq_dist > tolerance { 109 | markers.insert(index); 110 | stack.push((first,index)); 111 | stack.push((index,second)); 112 | } 113 | match stack.pop_opt() { 114 | Some(p)=>pair=p, 115 | None=>break 116 | }; 117 | } 118 | vec::from_fn(markers.len(),|k| points[k]) 119 | } 120 | 121 | pub fn simplify(points : ~[Point], sqTolerance : float, hq:bool) -> ~[Point]{ 122 | let tolerance = sqTolerance*sqTolerance; 123 | let pts:~[Point] = if hq { 124 | points 125 | } else { 126 | simplifyRadialDistance(points,tolerance) 127 | }; 128 | simplifyDouglasPeucker(pts,tolerance) 129 | } 130 | --------------------------------------------------------------------------------