geo_graf 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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