geo_graf 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b2efd6937e6852e7b2fc4d422629843b005acbf8
4
+ data.tar.gz: 3a832e48ff664b270f751f33fd070098189301cd
5
+ SHA512:
6
+ metadata.gz: 47a2b6cfa2e69e6a60985f654fc52f2fea73db5da92d1364500c99af7233ffea763044271009f1bb3481f351add9b69fe5745ef58920a6c54327f9dd22d3bc04
7
+ data.tar.gz: c2f0a08bf4bb9f17a7bfb8a46ff4f5a4e59eaf840762e721349b4a377c9fb9f8d2c947b4d5aed429b28980915785557aff8dd9c8990bcd0fe44603f58bb4040e
@@ -0,0 +1,2 @@
1
+ tmp/
2
+ *.gem
@@ -0,0 +1,6 @@
1
+ before_install: sudo apt-get install libgeos++-dev
2
+
3
+ language: ruby
4
+ rvm:
5
+ - 2.0.0
6
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in wimdu-geograf.gemspec
4
+ gemspec
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ geo_graf (1.0.2)
5
+ rgeo
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ celluloid (0.15.2)
11
+ timers (~> 1.1.0)
12
+ coderay (1.0.9)
13
+ diff-lcs (1.2.4)
14
+ ffi (1.9.0)
15
+ formatador (0.2.4)
16
+ guard (2.0.3)
17
+ formatador (>= 0.2.4)
18
+ listen (~> 2.0)
19
+ lumberjack (~> 1.0)
20
+ pry (>= 0.9.12)
21
+ thor (>= 0.18.1)
22
+ guard-rspec (3.1.0)
23
+ guard (>= 1.8)
24
+ rspec (~> 2.13)
25
+ listen (2.0.3)
26
+ celluloid (>= 0.15.2)
27
+ rb-fsevent (>= 0.9.3)
28
+ rb-inotify (>= 0.9)
29
+ lumberjack (1.0.4)
30
+ method_source (0.8.2)
31
+ pry (0.9.12.2)
32
+ coderay (~> 1.0.5)
33
+ method_source (~> 0.8)
34
+ slop (~> 3.4)
35
+ rake (10.1.0)
36
+ rb-fsevent (0.9.3)
37
+ rb-inotify (0.9.2)
38
+ ffi (>= 0.5.0)
39
+ rgeo (0.3.20)
40
+ rspec (2.14.1)
41
+ rspec-core (~> 2.14.0)
42
+ rspec-expectations (~> 2.14.0)
43
+ rspec-mocks (~> 2.14.0)
44
+ rspec-core (2.14.5)
45
+ rspec-expectations (2.14.3)
46
+ diff-lcs (>= 1.1.3, < 2.0)
47
+ rspec-mocks (2.14.3)
48
+ slop (3.4.6)
49
+ thor (0.18.1)
50
+ timers (1.1.0)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ bundler (~> 1.3)
57
+ geo_graf!
58
+ guard-rspec
59
+ rake
@@ -0,0 +1,4 @@
1
+ guard 'rspec', keep_failed: true, cli: '--format documentation' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Wimdu GmbH
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.
@@ -0,0 +1,90 @@
1
+ # GeoGraf
2
+
3
+ [![Build Status](https://travis-ci.org/wimdu/geo_graf.png?branch=master)](https://travis-ci.org/wimdu/geo_graf)
4
+
5
+ ## Overview
6
+
7
+ This gem computes the relation between multiple polygons (do they overlap and
8
+ by how much?), such as Wimdu's GeoLocations.
9
+
10
+ GeoGraf uses [RGeo](http://dazuma.github.io/rgeo/rdoc/) to perform the
11
+ calculations. It uses the
12
+ [Mercator projection](http://en.wikipedia.org/wiki/Mercator_projection), which
13
+ projects the surface of earth onto a flat surface. This is inaccurate, as
14
+ areas appear bigger when they are further away from the equator (have a look at
15
+ Greenland and the DR Congo on Google Maps, they both are roughly 2 million
16
+ square kilometers big). However, the assumption is that the error of areas that
17
+ relate to each other is rather small, as the overlapping area is at the same
18
+ latitude. And we're not interested in the correct area calculation, we just need
19
+ to know if they overlap, which is the bigger one and roughly how much they
20
+ overlap.
21
+
22
+ The input is expected to be an array of polygon descriptions. Each polygon
23
+ description has to be a hash with an `:id` key and `:polygon_coords`, which
24
+ itself is an array of coordinates describing the polygon. Each coordinate is
25
+ an array of it's latitude and longitude (in this order).
26
+
27
+ Example:
28
+
29
+ ```ruby
30
+ require 'geo_graf'
31
+
32
+ GeoGraf.intersections_for(
33
+ [
34
+ {
35
+ id: 1,
36
+ polygon_coords: [[-2, -2], [2, -2], [2, 2], [-2, 2], [-2, -2]]
37
+ },
38
+ {
39
+ id: 2,
40
+ polygon_coords: [[-3, -3], [-1, -3], [-1, -1], [-3, -1], [-3, -3]]
41
+ }
42
+ ]
43
+ )
44
+ ```
45
+
46
+ The output is an array of relation descriptions. Each description is a hash in
47
+ the following format:
48
+ * `:id`: The ID of the polygon. It's always the ID of the smaller polygon
49
+ (by area).
50
+ * `:contained_area_percentage`: How much of the area of the smaller polygon
51
+ is contained in the larger one.
52
+ * `:container_id`: The ID of the larger polygon.
53
+
54
+ If two polygons don't overlap, no relation exists and there will be no
55
+ description returned for these two.
56
+
57
+ For the example above, the following result will be returned:
58
+
59
+ ```ruby
60
+ [
61
+ {
62
+ id: 2,
63
+ contained_area_percentage: 25,
64
+ container_id: 1
65
+ }
66
+ ]
67
+ ```
68
+
69
+ This means that 25% of the area of polygon 2 overlap with the area of polygon
70
+ 1.
71
+
72
+ ## Installation
73
+
74
+ GeoGraf depends on RGeo with [GEOS](http://trac.osgeo.org/geos/) >= 3.3.3. You
75
+ need to install it before installing RGeo.
76
+
77
+ ### MacOs with homebrew
78
+
79
+ ```sh
80
+ brew install geos
81
+ ```
82
+
83
+ ### Ubuntu/Debian
84
+
85
+ ```sh
86
+ sudo apt-get install libgeos++-dev
87
+ ```
88
+
89
+ ## License
90
+ Copyright (c) 2013 Wimdu GmbH (MIT License). See LICENSE.txt for details.
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'bundler/setup'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.rspec_opts = ['--color', '--format doc', '--order rand']
7
+ end
8
+
9
+ task default: :spec
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = 'geo_graf'
3
+ spec.version = '1.0.2'
4
+ spec.date = Time.now.strftime('%Y-%m-%d')
5
+ spec.authors = ['Marek Nowak', 'Johannes Barre']
6
+ spec.email = ['marek.nowak@wimdu.com', 'johannes.barre@wimdu.com']
7
+ spec.description = %q{Calculates the relations between overlapping polygons}
8
+ spec.summary = %q{Calculates the relations between overlapping polygons}
9
+ spec.homepage = 'https://github.com/wimdu/geo_graf'
10
+
11
+ spec.files = `git ls-files`.split($/)
12
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
13
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
14
+ spec.require_paths = ['lib']
15
+
16
+ spec.add_dependency 'rgeo'
17
+ spec.add_development_dependency 'bundler', '~> 1.3'
18
+ spec.add_development_dependency 'guard-rspec'
19
+ spec.add_development_dependency 'rake'
20
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'geo_graf/intersection_calculator'
2
+
3
+ module GeoGraf
4
+ class << self
5
+
6
+ def intersections_for(input_geodata)
7
+ IntersectionCalculator.new(input_geodata).intersections
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,45 @@
1
+ require 'rgeo'
2
+ require 'geo_graf/polygon'
3
+
4
+ module GeoGraf
5
+ class GeosNotInstalledError < StandardError; end
6
+
7
+ class IntersectionCalculator
8
+ def initialize(input_geodata)
9
+ @geodata ||= input_geodata
10
+ .map { |geodatum| {id: geodatum[:id], polygon: polygon_from_coords(geodatum[:polygon_coords])} }
11
+ .sort_by { |geodatum| geodatum[:polygon].area } # always have smaller first
12
+ end
13
+
14
+ def intersections
15
+ intersections = []
16
+
17
+ geodata.combination(2) do |smaller, bigger|
18
+ intersection_area = smaller[:polygon].intersection_area(bigger[:polygon])
19
+
20
+ unless intersection_area.zero?
21
+ intersections << {
22
+ id: smaller[:id],
23
+ contained_area_percentage: (intersection_area.to_f / smaller[:polygon].area * 100).round,
24
+ container_id: bigger[:id]
25
+ }
26
+ end
27
+ end
28
+
29
+ intersections
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :geodata
35
+
36
+ def polygon_from_coords(coords)
37
+ Polygon.new(coords, rgeo_factory)
38
+ end
39
+
40
+ def rgeo_factory
41
+ raise(GeosNotInstalledError, 'The Geos library needs to be installed (see README.md).') unless RGeo::Geos.supported?
42
+ @factory ||= RGeo::Geographic.simple_mercator_factory
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,83 @@
1
+ module GeoGraf
2
+ class Polygon
3
+ def initialize(coordinates, factory)
4
+ @rgeo_polygons = [create_polygon(coordinates, factory)]
5
+
6
+ create_shadow_polygon_if_required(coordinates, factory)
7
+ end
8
+
9
+ def intersection_area(other)
10
+ intersection = intersection(other)
11
+
12
+ if intersection.respond_to?(:area)
13
+ intersection.area
14
+ elsif intersection.respond_to?(:map)
15
+ sum_of_areas_in(intersection)
16
+ else
17
+ 0.0
18
+ end
19
+ end
20
+
21
+ def area
22
+ rgeo_polygons.first.area
23
+ end
24
+
25
+ protected
26
+
27
+ attr_reader :rgeo_polygons
28
+
29
+ private
30
+
31
+ def intersection(other)
32
+ intersection = nil
33
+
34
+ # If you wonder why for a seemingly simple intersection of two
35
+ # polygons we are actually intersecting multiple rgeo_polygons
36
+ # with other's rgeo_polygons, check out the comment above
37
+ # #create_shadow_polygon_if_required. Peace.
38
+
39
+ rgeo_polygons.each do |polygon|
40
+ other.rgeo_polygons.each do |other_polygon|
41
+ intersection = polygon.intersection(other_polygon)
42
+ return intersection if intersection && !intersection.is_empty?
43
+ end
44
+ end
45
+ intersection
46
+ end
47
+
48
+ def create_polygon(coordinates, factory, longitude_offset: 0)
49
+ points = coordinates.map { |c| factory.point(c[1] + longitude_offset, c[0]) }
50
+ ring = factory.linear_ring(points)
51
+ factory.polygon(ring)
52
+ end
53
+
54
+ def sum_of_areas_in(collection)
55
+ collection
56
+ .select { |i| i.respond_to?(:area) }
57
+ .map(&:area)
58
+ .inject(0, :+)
59
+ end
60
+
61
+ # Ah, the shadow polygon... We use it because RGeo is kind of retarded
62
+ # and doesn't deal with situations in which shapes wrap around
63
+ # the 180th meridian correctly. Therefore we create a fake shape with
64
+ # an offset of either 360 or -360 degrees to ensure that we catch
65
+ # the intersection if there is one. And, the name is awesome, right?
66
+
67
+ def create_shadow_polygon_if_required(coordinates, factory)
68
+ if line_180(factory).intersects?(rgeo_polygons.first)
69
+ rgeo_polygons << create_polygon(coordinates, factory, longitude_offset: -360)
70
+ elsif line_minus_180(factory).intersects?(rgeo_polygons.first)
71
+ rgeo_polygons << create_polygon(coordinates, factory, longitude_offset: 360)
72
+ end
73
+ end
74
+
75
+ def line_180(factory)
76
+ factory.line(factory.point(180, 90), factory.point(180, -90))
77
+ end
78
+
79
+ def line_minus_180(factory)
80
+ factory.line(factory.point(-180, 90), factory.point(-180, -90))
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,215 @@
1
+ $: << '../lib'
2
+
3
+ require 'geo_graf/intersection_calculator'
4
+
5
+ describe GeoGraf::IntersectionCalculator do
6
+ describe "#intersections" do
7
+ subject(:intersections) { described_class.new(input).intersections }
8
+
9
+ context "given no shape" do
10
+ let(:input) { [] }
11
+
12
+ it "returns no intersections" do
13
+ expect(intersections).to be_empty
14
+ end
15
+ end
16
+
17
+ context "given only one shape" do
18
+ let(:input) {
19
+ [{
20
+ id: 1,
21
+ polygon_coords: [[-2, -2], [2, -2], [-2, 1], [-2, -2]]
22
+ }]
23
+ }
24
+
25
+ it "returns no intersections" do
26
+ expect(intersections).to be_empty
27
+ end
28
+ end
29
+
30
+ context "given two nonintersecting shapes" do
31
+ let(:input) {
32
+ [
33
+ {
34
+ id: 1,
35
+ polygon_coords: [[-2, -2], [2, -2], [-2, 1], [-2, -2]]
36
+ },
37
+ {
38
+ id: 2,
39
+ polygon_coords: [[100, 100], [102, 100], [100, 101], [100, 100]]
40
+ }
41
+ ]
42
+ }
43
+
44
+ it "returns no intersections" do
45
+ expect(intersections).to be_empty
46
+ end
47
+ end
48
+
49
+ context "given two identical shapes" do
50
+ let(:input) {
51
+ [
52
+ {
53
+ id: 1,
54
+ polygon_coords: [[-2, -2], [2, -2], [-2, 1], [-2, -2]]
55
+ },
56
+ {
57
+ id: 2,
58
+ polygon_coords: [[-2, -2], [2, -2], [-2, 1], [-2, -2]]
59
+ }
60
+ ]
61
+ }
62
+
63
+ it "returns an intersection of the second one containing the first one 100%" do
64
+ expect(intersections).to match_array([{id: 1, contained_area_percentage: 100, container_id: 2}])
65
+ end
66
+ end
67
+
68
+ context "given one square inside the other" do
69
+ let(:input) {
70
+ [
71
+ {
72
+ id: 1,
73
+ polygon_coords: [[-2, -2], [2, -2], [2, 2], [-2, 2], [-2, -2]]
74
+ },
75
+ {
76
+ id: 2,
77
+ polygon_coords: [[-2, -2], [0, -2], [0, 0], [-2, 0], [-2, -2]]
78
+ }
79
+ ]
80
+ }
81
+
82
+ it "returns an intersection of the first one containing the second one 100%" do
83
+ expect(intersections).to match_array([{id: 2, contained_area_percentage: 100, container_id: 1}])
84
+ end
85
+ end
86
+
87
+ context "given one square intersecting the other 25%" do
88
+ let(:input) {
89
+ [
90
+ {
91
+ id: 1,
92
+ polygon_coords: [[-2, -2], [2, -2], [2, 2], [-2, 2], [-2, -2]]
93
+ },
94
+ {
95
+ id: 2,
96
+ polygon_coords: [[-3, -3], [-1, -3], [-1, -1], [-3, -1], [-3, -3]]
97
+ }
98
+ ]
99
+ }
100
+
101
+ it "returns an intersection of the first one containing the second one 25%" do
102
+ expect(intersections).to match_array([{id: 2, contained_area_percentage: 25, container_id: 1}])
103
+ end
104
+ end
105
+
106
+ context "given one square intersecting the other square of the same size 50%" do
107
+ let(:input) {
108
+ [
109
+ {
110
+ id: 1,
111
+ polygon_coords: [[-2, -2], [2, -2], [2, 2], [-2, 2], [-2, -2]]
112
+ },
113
+ {
114
+ id: 2,
115
+ polygon_coords: [[-4, -2], [0, -2], [0, 2], [-4, 2], [-4, -2]]
116
+ }
117
+ ]
118
+ }
119
+
120
+ it "returns an intersection of the second one containing the first one 50%" do
121
+ expect(intersections).to match_array([{id: 1, contained_area_percentage: 50, container_id: 2}])
122
+ end
123
+ end
124
+
125
+ context "given multiple shapes of which some intersect" do
126
+ let(:input) {
127
+ [
128
+ {
129
+ id: 1,
130
+ polygon_coords: [[-2, -2], [2, -2], [2, 2], [-2, 2], [-2, -2]]
131
+ },
132
+ {
133
+ id: 2,
134
+ polygon_coords: [[-1, 1], [-4, 1], [-4, -2], [-1, -2], [-1, 1]]
135
+ },
136
+ {
137
+ id: 3,
138
+ polygon_coords: [[-3, -3], [-1, -3], [-1, -1], [-3, -1], [-3, -3]]
139
+ },
140
+ {
141
+ id: 4,
142
+ polygon_coords: [[-1, 8], [-3, 8], [-3, 6], [-1, 6], [-1, 8]]
143
+ },
144
+ {
145
+ id: 5,
146
+ polygon_coords: [[11, 6], [11, 0], [2, 6], [11, 6]]
147
+ },
148
+ {
149
+ id: 6,
150
+ polygon_coords: [[5, 4], [8, 4], [8, 2], [5, 2], [5, 4]]
151
+ },
152
+ {
153
+ id: 7,
154
+ polygon_coords: [[3, -3], [1, -3], [1, -1], [3, -1], [3, -3]]
155
+ }
156
+ ]
157
+ }
158
+
159
+ it "returns 4 intersections" do
160
+ expect(intersections).to match_array([
161
+ {id: 2, contained_area_percentage: 33, container_id: 1},
162
+ {id: 3, contained_area_percentage: 50, container_id: 2},
163
+ {id: 3, contained_area_percentage: 25, container_id: 1},
164
+ {id: 6, contained_area_percentage: 49, container_id: 5}, # 49% because of it's not a plane but a sphere
165
+ {id: 7, contained_area_percentage: 25, container_id: 1}
166
+ ])
167
+ end
168
+ end
169
+
170
+ context "given multiple shapes on the both sides of 180 degrees line of which some intersect" do
171
+ let(:input) {
172
+ [
173
+ {
174
+ id: 1,
175
+ polygon_coords: [[-2, 178], [2, 178], [2, -178], [-2, -178], [-2, 178]]
176
+ },
177
+ {
178
+ id: 2,
179
+ polygon_coords: [[-1, -179], [-4, -179], [-4, 178], [-1, 178], [-1, -179]]
180
+ },
181
+ {
182
+ id: 3,
183
+ polygon_coords: [[-3, 177], [-1, 177], [-1, 179], [-3, 179], [-3, 177]]
184
+ },
185
+ {
186
+ id: 4,
187
+ polygon_coords: [[-1, -172], [-3, -172], [-3, -174], [-1, -174], [-1, -172]]
188
+ },
189
+ {
190
+ id: 5,
191
+ polygon_coords: [[11, -174], [11, 180], [2, -174], [11, -174]]
192
+ },
193
+ {
194
+ id: 6,
195
+ polygon_coords: [[5, -176], [8, -176], [8, -178], [5, -178], [5, -176]]
196
+ },
197
+ {
198
+ id: 7,
199
+ polygon_coords: [[3, 177], [1, 177], [1, 179], [3, 179], [3, 177]]
200
+ }
201
+ ]
202
+ }
203
+
204
+ it "returns 4 intersections" do
205
+ expect(intersections).to match_array([
206
+ {id: 2, contained_area_percentage: 33, container_id: 1},
207
+ {id: 3, contained_area_percentage: 50, container_id: 2},
208
+ {id: 3, contained_area_percentage: 25, container_id: 1},
209
+ {id: 6, contained_area_percentage: 49, container_id: 5}, # 49% because of it's not a plane but a sphere
210
+ {id: 7, contained_area_percentage: 25, container_id: 1}
211
+ ])
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,146 @@
1
+ $: << '../lib'
2
+
3
+ require 'geo_graf/polygon'
4
+
5
+ describe GeoGraf::Polygon do
6
+ let(:polygon) { described_class.new(input, factory) }
7
+ let(:factory) { double("Factory", polygon: rgeo_polygon, point: rgeo_point, linear_ring: rgeo_linear_ring, line: line_180) }
8
+ let(:rgeo_polygon) { double("RGeoPolygon") }
9
+ let(:rgeo_point) { double("RGeoPoint") }
10
+ let(:rgeo_linear_ring) { double("LinearRing") }
11
+ let(:line_180) { double("Line", intersects?: false) }
12
+ let(:input) { [[-2, -2], [2, -2], [-2, 1], [-2, -2]] }
13
+
14
+ describe ".new" do
15
+ subject(:create_new_polygon) { polygon }
16
+
17
+ context "when the polygon doesn't span across 180th meridian" do
18
+ it "creates a point for each coordinate" do
19
+ factory.should_receive(:point).exactly(8).times # 4 + 4 used for creation of 180 and -180 lines
20
+
21
+ create_new_polygon
22
+ end
23
+
24
+ it "creates a linear ring out of the points" do
25
+ factory.should_receive(:linear_ring).with([rgeo_point] * 4)
26
+
27
+ create_new_polygon
28
+ end
29
+
30
+ it "creates the polygon using the linear ring" do
31
+ factory.should_receive(:polygon).with(rgeo_linear_ring)
32
+
33
+ create_new_polygon
34
+ end
35
+ end
36
+
37
+ context "when the polygon spans across 180th meridian" do
38
+ let(:line_180) { double("Line", intersects?: true) }
39
+
40
+ it "creates two polygons using the linear rings" do
41
+ factory.should_receive(:polygon).with(rgeo_linear_ring).twice
42
+
43
+ create_new_polygon
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#area" do
49
+ subject(:get_area) { polygon.area }
50
+
51
+ it "delegates to the internal RGeo polygon" do
52
+ rgeo_polygon.should_receive(:area)
53
+
54
+ get_area
55
+ end
56
+ end
57
+
58
+ describe "#intersection_area" do
59
+ let(:other_polygon) { double('OtherPolygon', rgeo_polygons: [others_rgeo_polygon_1]) }
60
+ let(:others_rgeo_polygon_1) { double('OtherRgeoPolygon1') }
61
+ let(:intersection) { double('Intersection', is_empty?: false, area: 1234.5) }
62
+ let(:no_intersection) { double('NoIntersection', is_empty?: true) }
63
+
64
+ context "when the polygon doesn't span across 180th meridian" do
65
+ context "when it intersects the other polygon" do
66
+ before do
67
+ rgeo_polygon.stub(:intersection).with(others_rgeo_polygon_1).and_return(intersection)
68
+ end
69
+
70
+ it "returns the area of the intersection" do
71
+ expect(polygon.intersection_area(other_polygon)).to eq(1234.5)
72
+ end
73
+
74
+ context "when the intersection spans over multiple polygons and points" do
75
+ let(:intersection) { [double('Point'), double('Polygon', area: 123.67), double('Polygon', area: 3.12)] }
76
+
77
+ before do
78
+ intersection.stub(:is_empty?).and_return(false)
79
+ end
80
+
81
+ it "returns the sum of areas of the intersection" do
82
+ expect(polygon.intersection_area(other_polygon)).to eq(126.79)
83
+ end
84
+ end
85
+ end
86
+
87
+ context "when it doesn't intersect the other polygon" do
88
+ before do
89
+ rgeo_polygon.stub(:intersection).with(others_rgeo_polygon_1).and_return(no_intersection)
90
+ end
91
+
92
+ it "returns 0.0" do
93
+ expect(polygon.intersection_area(other_polygon)).to eq(0.0)
94
+ end
95
+ end
96
+ end
97
+
98
+ context "when the polygon spans across 180th meridian" do
99
+ let(:other_polygon) {
100
+ double('OtherPolygon', rgeo_polygons: [others_rgeo_polygon_1, others_rgeo_polygon_2])
101
+ }
102
+ let(:others_rgeo_polygon_2) { double('OtherRgeoPolygon2') }
103
+ let(:shadow_rgeo_polygon) { double("ShadowRgeoPolygon") }
104
+ let(:line_180) { double("Line", intersects?: true) }
105
+
106
+ before do
107
+ factory.stub(:polygon).and_return(rgeo_polygon, shadow_rgeo_polygon)
108
+ rgeo_polygon.stub(:intersection).with(others_rgeo_polygon_1).and_return(no_intersection)
109
+ rgeo_polygon.stub(:intersection).with(others_rgeo_polygon_2).and_return(no_intersection)
110
+ shadow_rgeo_polygon.stub(:intersection).with(others_rgeo_polygon_2).and_return(no_intersection)
111
+ end
112
+
113
+ context "when it intersects the other polygon" do
114
+ before do
115
+ shadow_rgeo_polygon.stub(:intersection).with(others_rgeo_polygon_1).and_return(intersection)
116
+ end
117
+
118
+ it "returns the area of the intersection" do
119
+ expect(polygon.intersection_area(other_polygon)).to eq(1234.5)
120
+ end
121
+
122
+ context "when the intersection spans over multiple polygon" do
123
+ let(:intersection) { [double('Point'), double('Polygon', area: 123.67), double('Polygon', area: 3.12)] }
124
+
125
+ before do
126
+ intersection.stub(:is_empty?).and_return(false)
127
+ end
128
+
129
+ it "returns the sum of areas of the intersection" do
130
+ expect(polygon.intersection_area(other_polygon)).to eq(126.79)
131
+ end
132
+ end
133
+ end
134
+
135
+ context "when it doesn't intersect the other polygon" do
136
+ before do
137
+ shadow_rgeo_polygon.stub(:intersection).with(others_rgeo_polygon_1).and_return(no_intersection)
138
+ end
139
+
140
+ it "returns 0.0" do
141
+ expect(polygon.intersection_area(other_polygon)).to eq(0.0)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,22 @@
1
+ $: << '../lib'
2
+
3
+ require 'geo_graf'
4
+
5
+ describe GeoGraf do
6
+ describe "#intersections_for" do
7
+ subject(:intersections_for_input) { described_class.intersections_for(input) }
8
+
9
+ let(:calculator) { double('Calculator') }
10
+ let(:input) { double('Input') }
11
+ let(:intersection_calculator_output) { double('Output') }
12
+
13
+ before do
14
+ allow(GeoGraf::IntersectionCalculator).to receive(:new).with(input).and_return(calculator)
15
+ allow(calculator).to receive(:intersections).and_return(intersection_calculator_output)
16
+ end
17
+
18
+ it "runs intersections on a new instance of IntersectionCalculator" do
19
+ expect(intersections_for_input).to eq(intersection_calculator_output)
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geo_graf
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Marek Nowak
8
+ - Johannes Barre
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rgeo
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '1.3'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '1.3'
42
+ - !ruby/object:Gem::Dependency
43
+ name: guard-rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description: Calculates the relations between overlapping polygons
71
+ email:
72
+ - marek.nowak@wimdu.com
73
+ - johannes.barre@wimdu.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - .travis.yml
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - Guardfile
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - geo_graf.gemspec
87
+ - lib/geo_graf.rb
88
+ - lib/geo_graf/intersection_calculator.rb
89
+ - lib/geo_graf/polygon.rb
90
+ - spec/geo_graf/intersection_calculator_spec.rb
91
+ - spec/geo_graf/polygon_spec.rb
92
+ - spec/geo_graf_spec.rb
93
+ homepage: https://github.com/wimdu/geo_graf
94
+ licenses: []
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.0.3
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Calculates the relations between overlapping polygons
116
+ test_files:
117
+ - spec/geo_graf/intersection_calculator_spec.rb
118
+ - spec/geo_graf/polygon_spec.rb
119
+ - spec/geo_graf_spec.rb