├── .circleci └── config.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── example └── example.rb ├── lib ├── simplify_rb.rb └── simplify_rb │ ├── douglas_peucker_simplifier.rb │ ├── point.rb │ ├── radial_distance_simplifier.rb │ └── version.rb ├── simplify_rb.gemspec └── spec ├── fixtures ├── all-points.yml ├── result-fast.yml └── result-high-quality.yml ├── simplify_rb └── point_spec.rb ├── simplify_rb_spec.rb └── spec_helper.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | 4 | orbs: 5 | ruby: circleci/ruby@1.1 6 | 7 | executors: 8 | ruby-docker: 9 | parameters: 10 | ruby-version: 11 | type: string 12 | docker: 13 | - image: circleci/ruby:<> 14 | 15 | #### 16 | 17 | jobs: 18 | test: 19 | parameters: 20 | ruby-version: 21 | type: string 22 | executor: 23 | name: ruby-docker 24 | ruby-version: <> 25 | steps: 26 | - checkout 27 | - ruby/install-deps: 28 | with-cache: true 29 | - run: 30 | name: run tests 31 | command: | 32 | bundle exec rspec 33 | 34 | #### 35 | 36 | workflows: 37 | all-tests: 38 | jobs: 39 | - test: 40 | matrix: 41 | parameters: 42 | ruby-version: ["2.5", "2.6", "2.7", "3.0"] 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | InstalledFiles 7 | _yardoc 8 | coverage 9 | doc/ 10 | lib/bundler/man 11 | pkg 12 | rdoc 13 | spec/reports 14 | test/tmp 15 | test/version_tmp 16 | tmp 17 | .ruby-version 18 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'spec/fixtures/*' 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in simplify_rb.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | simplify_rb (0.4.0) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | diff-lcs (1.4.4) 10 | rake (13.0.1) 11 | rspec (3.9.0) 12 | rspec-core (~> 3.9.0) 13 | rspec-expectations (~> 3.9.0) 14 | rspec-mocks (~> 3.9.0) 15 | rspec-core (3.9.2) 16 | rspec-support (~> 3.9.3) 17 | rspec-expectations (3.9.2) 18 | diff-lcs (>= 1.2.0, < 2.0) 19 | rspec-support (~> 3.9.0) 20 | rspec-mocks (3.9.1) 21 | diff-lcs (>= 1.2.0, < 2.0) 22 | rspec-support (~> 3.9.0) 23 | rspec-support (3.9.3) 24 | 25 | PLATFORMS 26 | ruby 27 | 28 | DEPENDENCIES 29 | rake 30 | rspec 31 | simplify_rb! 32 | 33 | BUNDLED WITH 34 | 2.1.4 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 odlp 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimplifyRb - Polyline simplification 2 | 3 | [![Build Status](https://circleci.com/gh/odlp/simplify_rb.svg?style=shield)](https://circleci.com/gh/odlp/simplify_rb) [![Gem Version](https://badge.fury.io/rb/simplify_rb.svg)](https://badge.fury.io/rb/simplify_rb) 4 | 5 | SimplifyRb is a Ruby port of [simplify.js](https://github.com/mourner/simplify-js) by Vladimir Agafonkin. 6 | 7 | You can use this gem to reduce the number of points in a complex polyline / polygon, making use of an optimized Douglas-Peucker algorithm. 8 | 9 | ## Usage 10 | 11 | ```ruby 12 | require 'simplify_rb' 13 | 14 | points = [ 15 | { x: 51.5256, y: -0.0875 }, 16 | { x: 51.7823, y: -0.0912 } 17 | ] 18 | tolerance = 1 19 | high_quality = true 20 | 21 | SimplifyRb::Simplifier.new.process(points, tolerance, high_quality) 22 | ``` 23 | 24 | ```points```: An array of hashes, containing x,y coordinates. 25 | 26 | ```tolerance```: (optional, 1 by default): Affects the amount of simplification that occurs (the smaller, the less simplification). 27 | 28 | ```high_quality```: (optional, False by default): Flag to exclude the distance pre-processing. Produces higher quality results when true is passed, but runs slower. 29 | 30 | ### Custom points 31 | 32 | You can also use custom points, such as a struct or object which responds to `:x` and `:y`, rather than hashes: 33 | 34 | ```ruby 35 | CustomPointStruct = Struct.new(:x, :y) 36 | 37 | custom_points = [ 38 | CustomPointStruct.new(51.5256, -0.0875), 39 | CustomPointStruct.new(51.7823, -0.0912) 40 | ] 41 | 42 | tolerance = 1 43 | high_quality = true 44 | 45 | SimplifyRb::Simplifier.new.process(custom_points, tolerance, high_quality) 46 | ``` 47 | 48 | ## Installation 49 | 50 | Add this line to your application's Gemfile: 51 | 52 | gem 'simplify_rb' 53 | 54 | And then execute: 55 | 56 | $ bundle 57 | 58 | Or install it yourself as: 59 | 60 | $ gem install simplify_rb 61 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | task default: [:spec] 6 | -------------------------------------------------------------------------------- /example/example.rb: -------------------------------------------------------------------------------- 1 | require 'simplify_rb' 2 | 3 | points = [ 4 | { x: 51.5256, y: -0.0875 }, 5 | { x: 51.7823, y: -0.0912 } 6 | ] 7 | tolerance = 1 8 | high_quality = true 9 | 10 | SimplifyRb::Simplifier.new.process(points, tolerance, high_quality) 11 | -------------------------------------------------------------------------------- /lib/simplify_rb.rb: -------------------------------------------------------------------------------- 1 | require 'simplify_rb/version' 2 | require 'simplify_rb/point' 3 | require 'simplify_rb/radial_distance_simplifier' 4 | require 'simplify_rb/douglas_peucker_simplifier' 5 | 6 | module SimplifyRb 7 | class Simplifier 8 | def process(raw_points, tolerance = 1, highest_quality = false) 9 | raise ArgumentError.new('raw_points must be enumerable') unless raw_points.is_a? Enumerable 10 | 11 | return raw_points if raw_points.length <= 1 12 | 13 | sq_tolerance = tolerance * tolerance 14 | 15 | points = raw_points.map { |p| Point.new(p) } 16 | 17 | unless highest_quality 18 | points = RadialDistanceSimplifier.new.process(points, sq_tolerance) 19 | end 20 | 21 | DouglasPeuckerSimplifier.new 22 | .process(points, sq_tolerance) 23 | .map(&:original_entity) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/simplify_rb/douglas_peucker_simplifier.rb: -------------------------------------------------------------------------------- 1 | # Optimized Douglas-Peucker algorithm 2 | 3 | module SimplifyRb 4 | class DouglasPeuckerSimplifier 5 | def process(points, sq_tolerance) 6 | points.first.keep = true 7 | points.last.keep = true 8 | 9 | simplify_douglas_peucker(points, sq_tolerance) 10 | .select(&:keep) 11 | end 12 | 13 | private 14 | 15 | MaxSqDist = Struct.new(:max_sq_dist, :index) 16 | 17 | def simplify_douglas_peucker(points, sq_tolerance) 18 | first_i = 0 19 | last_i = points.length - 1 20 | index = nil 21 | stack = [] 22 | 23 | while last_i 24 | result = calc_max_sq_dist(first_i, last_i, points) 25 | index = result.index 26 | 27 | if result.max_sq_dist > sq_tolerance 28 | points[index].keep = true 29 | 30 | stack.push(first_i, index, index, last_i) 31 | end 32 | 33 | first_i, last_i = stack.pop(2) 34 | end 35 | 36 | points 37 | end 38 | 39 | def calc_max_sq_dist(first_i, last_i, points) 40 | index = nil 41 | max_sq_dist = 0 42 | range = (first_i + 1)...last_i 43 | 44 | range.each do |i| 45 | sq_dist = get_sq_seg_dist(points[i], points[first_i], points[last_i]) 46 | 47 | if sq_dist > max_sq_dist 48 | index = i 49 | max_sq_dist = sq_dist 50 | end 51 | end 52 | 53 | MaxSqDist.new(max_sq_dist, index) 54 | end 55 | 56 | # Square distance from a point to a segment 57 | def get_sq_seg_dist(point, point_1, point_2) 58 | x = point_1.x 59 | y = point_1.y 60 | dx = point_2.x - x 61 | dy = point_2.y - y 62 | 63 | if dx != 0 || dy != 0 64 | t = ((point.x - x) * dx + (point.y - y) * dy) / (dx * dx + dy * dy) 65 | 66 | if t > 1 67 | x = point_2.x 68 | y = point_2.y 69 | 70 | elsif t > 0 71 | x += dx * t 72 | y += dy * t 73 | end 74 | end 75 | 76 | dx = point.x - x 77 | dy = point.y - y 78 | 79 | dx * dx + dy * dy 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/simplify_rb/point.rb: -------------------------------------------------------------------------------- 1 | module SimplifyRb 2 | class Point 3 | attr_reader :x, :y, :original_entity 4 | attr_accessor :keep 5 | 6 | def initialize(raw_point) 7 | @original_entity = raw_point 8 | @x, @y = parse_x_y(raw_point) 9 | end 10 | 11 | def get_sq_dist_to(other_point) 12 | dx = x - other_point.x 13 | dy = y - other_point.y 14 | 15 | dx * dx + dy * dy 16 | end 17 | 18 | private 19 | 20 | def parse_x_y(raw_point) 21 | x = nil 22 | y = nil 23 | 24 | if raw_point.kind_of? Hash 25 | x = raw_point[:x] || raw_point['x'] 26 | y = raw_point[:y] || raw_point['y'] 27 | elsif raw_point.respond_to?(:x) && raw_point.respond_to?(:y) 28 | x = raw_point.x 29 | y = raw_point.y 30 | end 31 | 32 | if x.nil? || y.nil? 33 | raise ArgumentError.new('Points must have :x and :y values') 34 | end 35 | 36 | [x, y] 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/simplify_rb/radial_distance_simplifier.rb: -------------------------------------------------------------------------------- 1 | # Basic distance-based simplification 2 | 3 | module SimplifyRb 4 | class RadialDistanceSimplifier 5 | def process(points, sq_tolerance) 6 | new_points = [points.first] 7 | 8 | points.each do |point| 9 | sq_dist = point.get_sq_dist_to(new_points.last) 10 | new_points << point if sq_dist > sq_tolerance 11 | end 12 | 13 | new_points << points.last unless new_points.last == points.last 14 | 15 | new_points 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/simplify_rb/version.rb: -------------------------------------------------------------------------------- 1 | module SimplifyRb 2 | VERSION = "0.4.0" 3 | end 4 | -------------------------------------------------------------------------------- /simplify_rb.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'simplify_rb/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'simplify_rb' 8 | spec.version = SimplifyRb::VERSION 9 | spec.authors = ['odlp'] 10 | spec.description = 'You can use this gem to reduce the number of points in a complex polyline / polygon, making use of an optimized Douglas-Peucker algorithm. Ruby port of Simplify.js.' 11 | spec.summary = 'Polyline simplification library. Ruby port of Simplify.js.' 12 | spec.homepage = 'https://github.com/odlp/simplify_rb' 13 | spec.license = 'MIT' 14 | 15 | spec.files = `git ls-files`.split($/) 16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ['lib'] 19 | 20 | spec.add_development_dependency 'rake' 21 | spec.add_development_dependency 'rspec' 22 | end 23 | -------------------------------------------------------------------------------- /spec/fixtures/all-points.yml: -------------------------------------------------------------------------------- 1 | - :x: 224.55 2 | :y: 250.15 3 | - :x: 226.91 4 | :y: 244.19 5 | - :x: 233.31 6 | :y: 241.45 7 | - :x: 234.98 8 | :y: 236.06 9 | - :x: 244.21 10 | :y: 232.76 11 | - :x: 262.59 12 | :y: 215.31 13 | - :x: 267.76 14 | :y: 213.81 15 | - :x: 273.57 16 | :y: 201.84 17 | - :x: 273.12 18 | :y: 192.16 19 | - :x: 277.62 20 | :y: 189.03 21 | - :x: 280.36 22 | :y: 181.41 23 | - :x: 286.51 24 | :y: 177.74 25 | - :x: 292.41 26 | :y: 159.37 27 | - :x: 296.91 28 | :y: 155.64 29 | - :x: 314.95 30 | :y: 151.37 31 | - :x: 319.75 32 | :y: 145.16 33 | - :x: 330.33 34 | :y: 137.57 35 | - :x: 341.48 36 | :y: 139.96 37 | - :x: 369.98 38 | :y: 137.89 39 | - :x: 387.39 40 | :y: 142.51 41 | - :x: 391.28 42 | :y: 139.39 43 | - :x: 409.52 44 | :y: 141.14 45 | - :x: 414.82 46 | :y: 139.75 47 | - :x: 427.72 48 | :y: 127.30 49 | - :x: 439.60 50 | :y: 119.74 51 | - :x: 474.93 52 | :y: 107.87 53 | - :x: 486.51 54 | :y: 106.75 55 | - :x: 489.20 56 | :y: 109.45 57 | - :x: 493.79 58 | :y: 108.63 59 | - :x: 504.74 60 | :y: 119.66 61 | - :x: 512.96 62 | :y: 122.35 63 | - :x: 518.63 64 | :y: 120.89 65 | - :x: 524.09 66 | :y: 126.88 67 | - :x: 529.57 68 | :y: 127.86 69 | - :x: 534.21 70 | :y: 140.93 71 | - :x: 539.27 72 | :y: 147.24 73 | - :x: 567.69 74 | :y: 148.91 75 | - :x: 575.25 76 | :y: 157.26 77 | - :x: 580.62 78 | :y: 158.15 79 | - :x: 601.53 80 | :y: 156.85 81 | - :x: 617.74 82 | :y: 159.86 83 | - :x: 622.00 84 | :y: 167.04 85 | - :x: 629.55 86 | :y: 194.60 87 | - :x: 638.90 88 | :y: 195.61 89 | - :x: 641.26 90 | :y: 200.81 91 | - :x: 651.77 92 | :y: 204.56 93 | - :x: 671.55 94 | :y: 222.55 95 | - :x: 683.68 96 | :y: 217.45 97 | - :x: 695.25 98 | :y: 219.15 99 | - :x: 700.64 100 | :y: 217.98 101 | - :x: 703.12 102 | :y: 214.36 103 | - :x: 712.26 104 | :y: 215.87 105 | - :x: 721.49 106 | :y: 212.81 107 | - :x: 727.81 108 | :y: 213.36 109 | - :x: 729.98 110 | :y: 208.73 111 | - :x: 735.32 112 | :y: 208.20 113 | - :x: 739.94 114 | :y: 204.77 115 | - :x: 769.98 116 | :y: 208.42 117 | - :x: 779.60 118 | :y: 216.87 119 | - :x: 784.20 120 | :y: 218.16 121 | - :x: 800.24 122 | :y: 214.62 123 | - :x: 810.53 124 | :y: 219.73 125 | - :x: 817.19 126 | :y: 226.82 127 | - :x: 820.77 128 | :y: 236.17 129 | - :x: 827.23 130 | :y: 236.16 131 | - :x: 829.89 132 | :y: 239.89 133 | - :x: 851.00 134 | :y: 248.94 135 | - :x: 859.88 136 | :y: 255.49 137 | - :x: 865.21 138 | :y: 268.53 139 | - :x: 857.95 140 | :y: 280.30 141 | - :x: 865.48 142 | :y: 291.45 143 | - :x: 866.81 144 | :y: 298.66 145 | - :x: 864.68 146 | :y: 302.71 147 | - :x: 867.79 148 | :y: 306.17 149 | - :x: 859.87 150 | :y: 311.37 151 | - :x: 860.08 152 | :y: 314.35 153 | - :x: 858.29 154 | :y: 314.94 155 | - :x: 858.10 156 | :y: 327.60 157 | - :x: 854.54 158 | :y: 335.40 159 | - :x: 860.92 160 | :y: 343.00 161 | - :x: 856.43 162 | :y: 350.15 163 | - :x: 851.42 164 | :y: 352.96 165 | - :x: 849.84 166 | :y: 359.59 167 | - :x: 854.56 168 | :y: 365.53 169 | - :x: 849.74 170 | :y: 370.38 171 | - :x: 844.09 172 | :y: 371.89 173 | - :x: 844.75 174 | :y: 380.44 175 | - :x: 841.52 176 | :y: 383.67 177 | - :x: 839.57 178 | :y: 390.40 179 | - :x: 845.59 180 | :y: 399.05 181 | - :x: 848.40 182 | :y: 407.55 183 | - :x: 843.71 184 | :y: 411.30 185 | - :x: 844.09 186 | :y: 419.88 187 | - :x: 839.51 188 | :y: 432.76 189 | - :x: 841.33 190 | :y: 441.04 191 | - :x: 847.62 192 | :y: 449.22 193 | - :x: 847.16 194 | :y: 458.44 195 | - :x: 851.38 196 | :y: 462.79 197 | - :x: 853.97 198 | :y: 471.15 199 | - :x: 866.36 200 | :y: 480.77 201 | -------------------------------------------------------------------------------- /spec/fixtures/result-fast.yml: -------------------------------------------------------------------------------- 1 | - :x: 224.55 2 | :y: 250.15 3 | - :x: 267.76 4 | :y: 213.81 5 | - :x: 296.91 6 | :y: 155.64 7 | - :x: 330.33 8 | :y: 137.57 9 | - :x: 409.52 10 | :y: 141.14 11 | - :x: 439.6 12 | :y: 119.74 13 | - :x: 486.51 14 | :y: 106.75 15 | - :x: 529.57 16 | :y: 127.86 17 | - :x: 539.27 18 | :y: 147.24 19 | - :x: 617.74 20 | :y: 159.86 21 | - :x: 629.55 22 | :y: 194.6 23 | - :x: 671.55 24 | :y: 222.55 25 | - :x: 727.81 26 | :y: 213.36 27 | - :x: 739.94 28 | :y: 204.77 29 | - :x: 769.98 30 | :y: 208.42 31 | - :x: 779.6 32 | :y: 216.87 33 | - :x: 800.24 34 | :y: 214.62 35 | - :x: 820.77 36 | :y: 236.17 37 | - :x: 859.88 38 | :y: 255.49 39 | - :x: 865.21 40 | :y: 268.53 41 | - :x: 857.95 42 | :y: 280.3 43 | - :x: 867.79 44 | :y: 306.17 45 | - :x: 859.87 46 | :y: 311.37 47 | - :x: 854.54 48 | :y: 335.4 49 | - :x: 860.92 50 | :y: 343 51 | - :x: 849.84 52 | :y: 359.59 53 | - :x: 854.56 54 | :y: 365.53 55 | - :x: 844.09 56 | :y: 371.89 57 | - :x: 839.57 58 | :y: 390.4 59 | - :x: 848.4 60 | :y: 407.55 61 | - :x: 839.51 62 | :y: 432.76 63 | - :x: 853.97 64 | :y: 471.15 65 | - :x: 866.36 66 | :y: 480.77 67 | -------------------------------------------------------------------------------- /spec/fixtures/result-high-quality.yml: -------------------------------------------------------------------------------- 1 | - :x: 224.55 2 | :y: 250.15 3 | - :x: 267.76 4 | :y: 213.81 5 | - :x: 296.91 6 | :y: 155.64 7 | - :x: 330.33 8 | :y: 137.57 9 | - :x: 409.52 10 | :y: 141.14 11 | - :x: 439.6 12 | :y: 119.74 13 | - :x: 486.51 14 | :y: 106.75 15 | - :x: 529.57 16 | :y: 127.86 17 | - :x: 539.27 18 | :y: 147.24 19 | - :x: 617.74 20 | :y: 159.86 21 | - :x: 629.55 22 | :y: 194.6 23 | - :x: 671.55 24 | :y: 222.55 25 | - :x: 727.81 26 | :y: 213.36 27 | - :x: 739.94 28 | :y: 204.77 29 | - :x: 769.98 30 | :y: 208.42 31 | - :x: 784.2 32 | :y: 218.16 33 | - :x: 800.24 34 | :y: 214.62 35 | - :x: 820.77 36 | :y: 236.17 37 | - :x: 859.88 38 | :y: 255.49 39 | - :x: 865.21 40 | :y: 268.53 41 | - :x: 857.95 42 | :y: 280.3 43 | - :x: 867.79 44 | :y: 306.17 45 | - :x: 858.29 46 | :y: 314.94 47 | - :x: 854.54 48 | :y: 335.4 49 | - :x: 860.92 50 | :y: 343 51 | - :x: 849.84 52 | :y: 359.59 53 | - :x: 854.56 54 | :y: 365.53 55 | - :x: 844.09 56 | :y: 371.89 57 | - :x: 839.57 58 | :y: 390.4 59 | - :x: 848.4 60 | :y: 407.55 61 | - :x: 839.51 62 | :y: 432.76 63 | - :x: 853.97 64 | :y: 471.15 65 | - :x: 866.36 66 | :y: 480.77 67 | -------------------------------------------------------------------------------- /spec/simplify_rb/point_spec.rb: -------------------------------------------------------------------------------- 1 | require 'simplify_rb/point' 2 | 3 | RSpec.describe SimplifyRb::Point do 4 | describe 'parsing hashes with string keys' do 5 | it 'determines the :x, :y value' do 6 | raw_point = { "x" => 51.5256, "y" => -0.0875 } 7 | point = described_class.new(raw_point) 8 | 9 | expect(point.x).to eq(51.5256) 10 | expect(point.y).to eq(-0.0875) 11 | end 12 | end 13 | 14 | describe 'parsing structs' do 15 | it 'determines the :x, :y value' do 16 | CustomPointStruct = Struct.new(:x, :y) 17 | raw_point = CustomPointStruct.new(51.5256, -0.0875) 18 | 19 | point = described_class.new(raw_point) 20 | 21 | expect(point.x).to eq(51.5256) 22 | expect(point.y).to eq(-0.0875) 23 | end 24 | end 25 | 26 | describe 'handling raw points which are objects' do 27 | it 'determines the :x, :y value' do 28 | class MyCustomPoint 29 | attr_reader :x, :y 30 | 31 | def initialize(x, y) 32 | @x = x 33 | @y = y 34 | end 35 | end 36 | 37 | raw_point = MyCustomPoint.new(51.5256, -0.0875) 38 | 39 | point = described_class.new(raw_point) 40 | 41 | expect(point.x).to eq(51.5256) 42 | expect(point.y).to eq(-0.0875) 43 | end 44 | end 45 | 46 | describe 'missing x/y values' do 47 | it 'raises an error if the points are missing keys' do 48 | invalid_point = { Z: 51.5256, y: -0.0875 } 49 | expect { described_class.new(invalid_point) }.to raise_error(ArgumentError, 'Points must have :x and :y values') 50 | 51 | invalid_point = { x: 51.5256, Z: -0.0875 } 52 | expect { described_class.new(invalid_point) }.to raise_error(ArgumentError, 'Points must have :x and :y values') 53 | end 54 | 55 | it 'raises an error if points don\'t respond to x / y' do 56 | class UnconventialPoint 57 | attr_reader :a, :b 58 | 59 | def initialize(a, b) 60 | @a = a 61 | @b = b 62 | end 63 | end 64 | 65 | invalid_point = UnconventialPoint.new(51.5256, -0.0875) 66 | expect { described_class.new(invalid_point) }.to raise_error(ArgumentError, 'Points must have :x and :y values') 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/simplify_rb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'simplify_rb' 2 | require 'yaml' 3 | 4 | RSpec.describe SimplifyRb::Simplifier do 5 | describe '#process' do 6 | context 'simplifies points correctly with the given tolerance' do 7 | let(:test_data) { fixture_file('all-points.yml') } 8 | let(:expected_result_fast) { fixture_file('result-fast.yml') } 9 | let(:expected_result_high_quality) { fixture_file('result-high-quality.yml') } 10 | 11 | it 'uses the fast strategy by default', focus: true do 12 | result = subject.process(test_data, 5) 13 | expect(result).to eq(expected_result_fast) 14 | end 15 | 16 | it 'uses the high quality strategy when the flag is passed' do 17 | result = subject.process(test_data, 5, true) 18 | expect(result).to eq(expected_result_high_quality) 19 | end 20 | end 21 | 22 | describe 'extra properties on the data' do 23 | it 'preserves the extra properties' do 24 | richer_data = [ 25 | { x: 51.5256, y: -0.0875, note: 'Foo bar' }, 26 | { x: 51.7823, y: -0.0912, attr: 123 } 27 | ] 28 | 29 | result = subject.process(richer_data, 5, true) 30 | 31 | expect(result.length).to eq 2 32 | 33 | expect(result.first[:note]).to eq 'Foo bar' 34 | expect(result.last[:attr]).to eq 123 35 | end 36 | end 37 | 38 | context 'only one point' do 39 | it 'returns a list with one point' do 40 | data = [{ x: 1, y: 2 }] 41 | expect(subject.process(data)).to eq(data) 42 | end 43 | end 44 | 45 | context 'no points' do 46 | it 'returns an empty list of points' do 47 | expect(subject.process([])).to be_empty 48 | end 49 | end 50 | 51 | describe 'unexpected arguments' do 52 | context 'when raw_points is not enumerable' do 53 | it 'raises an ArgumentError' do 54 | data = Object.new 55 | expect { subject.process(data) }.to raise_error(ArgumentError, 'raw_points must be enumerable') 56 | end 57 | end 58 | 59 | context "when raw_points is enumerable, but doesn't respond to x/y" do 60 | it 'raises an ArgumentError' do 61 | data = [{ foo: :bar }, { foo: :bar }] 62 | expect { subject.process(data) }.to raise_error(ArgumentError, 'Points must have :x and :y values') 63 | end 64 | end 65 | end 66 | end 67 | 68 | private 69 | 70 | def fixture_file(name) 71 | path = File.expand_path("fixtures/#{name}", __dir__) 72 | YAML.load_file(path) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.mock_with :rspec do |mocks| 3 | mocks.verify_partial_doubles = true 4 | end 5 | 6 | config.disable_monkey_patching! 7 | config.warnings = true 8 | 9 | config.order = :random 10 | Kernel.srand config.seed 11 | end 12 | --------------------------------------------------------------------------------