hexagonly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YzNjMDVmZmQwZTZkNzQzODUzMjczNWNlZjBiYjkyYjZlOTRkZTUyMg==
5
+ data.tar.gz: !binary |-
6
+ NTYxM2RlOTQ2M2FiYTQ1N2QzYjBiNDdkZWI4MjY3MDY0ZWIxNWVkYQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDY1NzlkNDgyZDhkZTFmYzkyMTE1M2UxODI5YzA0NjU0YmU4YTQ3MzA1YjNl
10
+ ZGE2MTYwMjk4NTE1ODdjNGE2NWEwZTM2ZGExYmJlOGFjODg2YzE0YjgwNTFk
11
+ MDNhYWU0NTJhMmVjZGRiMDc3ODI2MjZhMTBlZTk3ZmMzMDQyMjA=
12
+ data.tar.gz: !binary |-
13
+ YjM3MzAyMTVlNWNiODNiNzc0ZjUyZmUyZmQ5YmE5MTI0ODVlOWM2NGFmMDE0
14
+ NDZmMTYwNzdhNGRkYjQ3M2E1ZTY2YjlmNTgwNGIzZTU0M2ZkYTA0MTY0NTYw
15
+ OTI3YjhiYjIyYTcxNzUzZjRmYzAyNjMwODYzMzAxNDQ5NzEyMzk=
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+ # Ruby version
20
+ .ruby-gemset
21
+ .ruby-version
22
+
23
+ # Roadmap
24
+ roadmap.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ hexagonly
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p448
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hexagonly.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 lipanski
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.
data/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # Hexagonly
2
+
3
+ Provides helper classes for performing flat-topped hexagonal tiling and other polygon-related operations.
4
+
5
+ ## Motivation
6
+
7
+ A personal project required me to **group geographical coordinates** on a map, compute different stats for individual groups
8
+ and display those stats in such a way that made them visually identify their groups.
9
+ Squares and rectangles didn't really work for me, because different points on the boundries aren't equally distanced to the center.
10
+ Circles would have been a good option for grouping objects in a 2-dimensional space, but then again
11
+ circles are not really *tileable*.
12
+ **Hexagons**, on the other hand, combine properties of the two shapes: they are easily tileable and have (sort of) a radius.
13
+
14
+ I've tested this on my map project, but I guess it should work with **2D games** as well, or anything else that requires tiling or polygon-related operations.
15
+
16
+ ## Features
17
+
18
+ - Currently supported shapes: Point, Polygon, Hexagon.
19
+ - Define a **Polygon** and check whether a Point lies within its boundries (*crossing count* algorithm).
20
+ - Define a **Hexagon** and determine its boundries, based on the hexagon center and size. All **Polygon** methods apply to this shape as well.
21
+ - Generate **neighbouring Hexagons** for a given Hexagon.
22
+ - **Hexagonal tiling**: generate hexagons to fill up a space, based on its boundries (two points suffice) and the hexagon size.
23
+ - **Hexagonal tiling & collecting objects on the way**: generate hexagons to match the boundries of a given collection of Points (a *space*), then store contained Points (or custom objects) for every Hexagon.
24
+ - Convert shapes (Polygon, Hexagon, Point) and mixed collections of shapes to **GeoJson**.
25
+ - For every defined shape you can either use pre-defined classes or use your own custom classes, by including the appropriate **Hexagonly shape module**.
26
+
27
+ The gem currently supports *flat-topped* hexagons only. For *pointy-topped* hexagons just place a bug request and I'll look into it.
28
+
29
+ ## Installation
30
+
31
+ Add this line to your application's Gemfile:
32
+
33
+ gem 'hexagonly'
34
+
35
+ And then execute:
36
+
37
+ $ bundle install
38
+
39
+ Or install it yourself as:
40
+
41
+ $ gem install hexagonly
42
+
43
+ ## Usage
44
+
45
+ ### Points
46
+
47
+ There are 2 ways for defining Point objects:
48
+
49
+ 1. By using the pre-defined ``Hexagonly::Point`` class:
50
+
51
+ ```ruby
52
+ point = Hexagonly::Point.new(1, 2)
53
+
54
+ puts point.x_coord # => 1
55
+ puts point.y_coord # => 2
56
+ ```
57
+
58
+ 2. By using your custom class (e.g. think ActiveRecord) and including ``Hexagonly::Point::Methods``
59
+ inside your class definition. Then you would assign your own accessors as coordinate getters and setters.
60
+ This is accomplished via the class method ``x_y_coord_methods`` which takes two arguments: the names of the x and y coordinate accessors.
61
+ The ``x_y_coord_methods`` defaults to ``:x`` and ``:y``.
62
+
63
+ ```ruby
64
+ class MyCustomPoint
65
+ include Hexagonly::Point::Methods
66
+
67
+ # Sets accessors :a and :b as coordinate accessors
68
+ x_y_coord_methods :a, :b
69
+
70
+ attr_accessor :a, :b
71
+ def initialize(a, b)
72
+ @a, @b = a, b
73
+ end
74
+ end
75
+
76
+ point = MyCustomPoint.new(1, 2)
77
+
78
+ puts point.x_coord # => 1
79
+ puts point.y_coord # => 2
80
+ ```
81
+
82
+ ### Poylgons
83
+
84
+ The same 2 ways of instanciating apply to Polygons as well:
85
+
86
+ 1. By using the pre-defined ``Hexagonly::Polygon`` class:
87
+
88
+ ```ruby
89
+ corners = [ Hexagonly::Point.new(2, 1), Hexagonly::Point.new(5, 5), Hexagonly::Point.new(6, 1), ... ]
90
+ poly = Hexagonly::Polygon(corners)
91
+
92
+ puts poly.poly_points # => corners...
93
+ puts poly.contains?(Hexagonly::Point(4, 2)) # => true
94
+ ```
95
+
96
+ 2. By using custom classes, which include ``Hexagonly::Polygon::Methods`` and assigning custom corner accessor, set via the class method ``poly_points_method``.
97
+ The ``poly_points_method`` method defaults to ``:poly_points``.
98
+
99
+ ```ruby
100
+ class MyCustomPolygon
101
+ include Hexagonly::Polygon::Methods
102
+
103
+ # Sets :corners as the polygon corners accessor
104
+ poly_points_method :corners
105
+
106
+ attr_accessor :corners
107
+ def initialize(corners)
108
+ @corners = corners
109
+ end
110
+ end
111
+
112
+ corners = [ Hexagonly::Point.new(2, 1), Hexagonly::Point.new(5, 5), Hexagonly::Point.new(6, 1), ... ]
113
+ poly = MyCustomPolygon.new(corners)
114
+
115
+ puts poly.poly_points # => corners...
116
+ puts poly.contains?(Hexagonly::Point(4, 2)) # => true
117
+ ```
118
+
119
+ ### Hexagons
120
+
121
+ Hexagons inherit all methods of Polygons. There are 2 ways of creating new Hexagons:
122
+
123
+ 1. By using the pre-defined ``Hexagonly::Hexagon`` class:
124
+
125
+ ```ruby
126
+ center = Hexagonly::Point.new(4, 4)
127
+ hexagon = Hexagonly::Hexagon.new(center, 1.5)
128
+
129
+ puts hexagon.hex_corners # => corners...
130
+ puts hexagon.contains?(center) # => true
131
+ ```
132
+
133
+ 2. By using a custom class, which includes ``Hexagonly::Hexagon::Methods``, and calling ``setup_hex`` on your instance:
134
+
135
+ ```ruby
136
+ class MyCustomHexagon
137
+ include Hexagonly::Hexagon::Methods
138
+
139
+ def initialize(center, size)
140
+ setup_hex(center, size)
141
+ end
142
+ end
143
+
144
+ center = Hexagonly::Point.new(4, 4)
145
+ hexagon = MyCustomHexagon.new(center, 1.5)
146
+
147
+ puts hexagon.hex_center # => center
148
+ puts hexagon.hex_v_size # => distance from center to the top / bottom borders
149
+ puts hexagon.hex_corners # => corners...
150
+ puts hexagon.contains?(center) # => true
151
+ ```
152
+
153
+ ### Hexagonal tiling
154
+
155
+ You start by defining your boundries. Boundries are basically a collection of 2 or more Hexagonaly::Point objects or objects including ``Hexagonly::Point::Methods``:
156
+
157
+ ```ruby
158
+ boundries = [
159
+ Hexagonly::Point.new(1, 2),
160
+ Hexagonly::Point.new(4, 5),
161
+ ...
162
+ ]
163
+
164
+ # Or you can use your custom Point class...
165
+ boundries = [
166
+ MyCustomPoint.new(1, 2),
167
+ MyCustomPoint.new(4, 5),
168
+ ...
169
+ ]
170
+ ```
171
+
172
+ Once you've defined your boundries, you can pass them to the ``Hexagonly::Hexagon.pack`` method. This takes 3 arguments:
173
+ - the *boundries* or *points* that mark your hexagon field
174
+ - half of the desired width of your hexagons (the distance from the center to the left / right boundries)
175
+ - a *Hash* of additional parameters:
176
+ - ``:hexagon_class``: the class used to instanciate new Hexagons. Defaults to ``Hexagonly::Hexagon``. If you are
177
+ using custom Hexagon classes, you should include your class here.
178
+ - ``:point_class``: the class used to instanciate Hexagon center points. Defaults to ``Hexagonly::Point``.
179
+ - ``:grab_points``: a boolean, determining whether the first argument (points / boundries) will also be used to collect
180
+ contained points for every generated hexagon (see next category).
181
+ - ``:reject_empty``: a boolean, determining whether generated hexagons with no collected points should be removed from
182
+ the result. Only works if ``:grab_points`` is enabled (see next category).
183
+
184
+ ```ruby
185
+ # Generated Hexagons will be Hexagonly::Hexagon instances
186
+ hexagons = Hexagonly::Hexagon.pack(boundries, 0.3)
187
+
188
+ # Generated Hexagons will be MyCustomHexagon instances
189
+ hexagons = Hexagonly::Hexagon.pack(boundries, 0.3, { :hexagon_class => MyCustomHexagon })
190
+ ```
191
+
192
+ ### Hexagonal tiling & collecting objects on the way
193
+
194
+ While generating your hexagons from a collection of Point objects, you might want to **store all contained points within individual hexagons**.
195
+ This is accomplished by the same ``Hexagonly::Hexagon.pack`` method, only you'll need to enable the additional ``:grab_points`` parameter.
196
+ Enabling the``:reject_empty`` parameter will remove all empty hexagons from the results Array.
197
+
198
+ ```ruby
199
+ points = [
200
+ Hexagonly::Point.new(1, 1),
201
+ Hexagonly::Point.new(2, 2),
202
+ Hexagonly::Point.new(2, 3),
203
+ Hexagonly::Point.new(7, 1),
204
+ ...
205
+ ]
206
+
207
+ hexagons = Hexagonly::Hexagon.pack(points, 0.25, { :grab_points => true, :reject_empty => true })
208
+
209
+ puts hexagons[0].collected_points # => all objects from the points variable contained within this hexagon
210
+ puts hexagons[0].collected_points[0].class # => Hexagonly::Point or your custom point class
211
+ ```
212
+
213
+ ### More examples
214
+
215
+ While tiling and grabbing objects, you can also mix classes in your ``points`` collection, as long as they are
216
+ compatible with ``Hexagonly::Point`` methods:
217
+
218
+ ```ruby
219
+ class Salami
220
+ include Hexagonly::Point::Methods
221
+
222
+ # .x_y_coord_methods defaults to :x and :y
223
+ attr_accessor :x, :y
224
+ def initialize(x, y)
225
+ @x, @y = x, y
226
+ end
227
+ end
228
+
229
+ class Cheese
230
+ include Hexagonly::Point::Methods
231
+
232
+ attr_accessor :x, :y
233
+ def initialize(x, y)
234
+ @x, @y = x, y
235
+ end
236
+ end
237
+
238
+ class Pizza
239
+ include Hexagonly::Hexagon::Methods
240
+ end
241
+
242
+ # We have a couple of ingredients layed out on the table
243
+ ingredients = [
244
+ Salami.new(1, 1),
245
+ Cheese.new(2, 2),
246
+ Cheese.new(3, 3),
247
+ Salami.new(4, 4),
248
+ ...
249
+ ]
250
+
251
+ # And we want to create hexagonal pizzas out of them, by just laying the dough on top and removing the extra dough
252
+ pizza_size = 1.0
253
+ pizzas = Hexagonly::Hexagon.pack(ingredients, pizza_size, { :hexagon_class => Pizza, :grab_points => true, :reject_empty => true })
254
+
255
+ puts pizzas[0].class # => Pizza
256
+ puts pizzas[0].collected_points[0].class # => Salami or Cheese
257
+ ```
258
+
259
+ ## Contributing
260
+
261
+ 1. Fork it
262
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
263
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
264
+ 4. Push to the branch (`git push origin my-new-feature`)
265
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
data/hexagonly.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hexagonly/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hexagonly"
8
+ spec.version = Hexagonly::VERSION
9
+ spec.authors = ["Florin Lipan"]
10
+ spec.email = ["lipanski@gmail.com"]
11
+ spec.description = %q{Hexagonal tiling in Ruby.}
12
+ spec.summary = %q{Provides helper classes for performing regular, flat-topped hexagonal tiling and other polygon-related operations.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ # General
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ # Testing
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "factory_girl"
27
+ # Docs
28
+ spec.add_development_dependency "yard"
29
+ spec.add_development_dependency "redcarpet"
30
+
31
+ end
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ module Hexagonly
4
+ class GeoJson
5
+
6
+ attr_reader :features
7
+
8
+ # @param [Array] features an array of objects that support the #to_geojson method
9
+ def initialize(features = nil)
10
+ add_features(features) unless features.nil?
11
+ end
12
+
13
+ # Adds features (Points, Spaces, Hexagons) that support the #to_geojson method.
14
+ #
15
+ # @param [Array] features an array of objects that support the #to_geojson method
16
+ def add_features(features)
17
+ @features ||= []
18
+ features.each do |feat|
19
+ @features << feat.to_geojson
20
+ end
21
+ end
22
+
23
+ # Outputs the GeoJson string.
24
+ #
25
+ # @return [String] a valid GeoJSON string
26
+ def to_json
27
+ {
28
+ :type => "FeatureCollection",
29
+ :features => @features
30
+ }.to_json
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,212 @@
1
+ module Hexagonly
2
+ class Hexagon
3
+
4
+ # Adds Hexagon methods to an object.
5
+ #
6
+ # @example
7
+ # class MyHexagon
8
+ #
9
+ # include Hexagonly::Hexagon::Methods
10
+ #
11
+ # def initialize(center, size)
12
+ # setup_hex(center, size)
13
+ # end
14
+ #
15
+ # end
16
+ #
17
+ # hex = MyHexagon.new(Hexagonly::Point.new(1, 2), 0.5)
18
+ # hex.hex_corners # => an array containing all 6 corners of the hexagon
19
+ # hex.contains?(Hexagonly::Point.new(1, 2)) # => true
20
+ # hex.contains?(Hexagonly::Point.new(3, 3)) # => false
21
+ module Methods
22
+
23
+ include Hexagonly::Polygon::Methods
24
+
25
+ def self.included(base)
26
+ base.extend(Hexagonly::Polygon::ClassMethods)
27
+ base.poly_points_method :hex_corners
28
+
29
+ base.extend(ClassMethods)
30
+ end
31
+
32
+ module ClassMethods
33
+
34
+ # Starting from the north-east corner of the given Points array and going east,
35
+ # then repeating for every other row to the south, the method generates an
36
+ # array of hexagons. If params[:grab_points] is true, every hexagon created
37
+ # on the way grabs all the points contained within.
38
+ #
39
+ # @param points [Array<Hexagonly::Point>] the boundries for the generated hexagons
40
+ # or an Array of points that will be "grabbed" by the generated hexagons
41
+ # (see @params[:grap_points])
42
+ # @param hex_size [Float] the size of the generated hexagons (width / 2)
43
+ # @param [Hash] params additional parameters
44
+ # @option params [false, true] :grab_points whether to "grab" points -
45
+ # stores included points unter hex#collected_points and excluded points
46
+ # under hex#rejected_points
47
+ # @option params [false, true] :reject_empty when :grab_points is enabled
48
+ # settings this option to true removes empty hexagons from the results
49
+ # @option params [Class] :hexagon_class the class used to instanciate new hexagons
50
+ # @option params [Class] :point_class the class used to instanciate hexagon center points
51
+ #
52
+ # @return [Array<Hexagonly::Hexagon] an array of hexagons
53
+ def pack(points, hex_size, params = {})
54
+ return [] if points.nil? || points.empty? || points.size < 2
55
+
56
+ hexagons = []
57
+ space = Hexagonly::Space.new(points)
58
+
59
+ h_hexagon_count = ((space.east.x_coord - space.west.x_coord) / (hex_size * 1.5)).abs.ceil
60
+ v_hexagon_count = ((space.north.y_coord - space.south.y_coord) / (hex_size * Math.cos(Math::PI / 6) * 2.0)).abs.ceil + 1
61
+
62
+ point_class = params.fetch(:point_class, Hexagonly::Point)
63
+ hexagon_class = params.fetch(:hexagon_class, Hexagonly::Hexagon)
64
+ grab_points = params.fetch(:grab_points, false)
65
+ reject_empty = params.fetch(:reject_empty, false)
66
+
67
+ north_west = point_class.new.tap{ |p| p.set_coords(space.west.x_coord, space.north.y_coord) }
68
+ hexagons << hex = hexagon_class.new.tap{ |h| h.setup_hex(north_west, hex_size); h.grab(points) if grab_points }
69
+ v_hexagon_count.times do |i|
70
+ first_hex = hex
71
+ h_hexagon_count.times do |j|
72
+ if j % 2 == 0
73
+ hexagons << hex = hex.north_east_hexagon(params)
74
+ else
75
+ hexagons << hex = hex.south_east_hexagon(params)
76
+ end
77
+ end
78
+
79
+ hexagons << hex = first_hex.south_hexagon(params) if i < v_hexagon_count - 1
80
+ end
81
+
82
+ if grab_points && reject_empty
83
+ hexagons.reject!{ |hex| hex.collected_points.empty? }
84
+ end
85
+
86
+ hexagons
87
+ end
88
+
89
+ end
90
+
91
+ attr_accessor :hex_center, :hex_size
92
+
93
+ # Initializes the hexagon.
94
+ #
95
+ # @param hex_center [Hexagonly::Point] the center of the hexagon
96
+ # @param hex_size [Float] the size of the hexagon (horizontal width / 2)
97
+ def setup_hex(hex_center, hex_size)
98
+ @hex_center, @hex_size = hex_center, hex_size
99
+ end
100
+
101
+ # The distance from the center of the hexagon to the top / bottom edges.
102
+ #
103
+ # @return [Float]
104
+ def hex_v_size
105
+ raise "hex_size not defined!" if @hex_size.nil?
106
+
107
+ @hex_size * Math.cos(Math::PI / 6)
108
+ end
109
+
110
+ # Checks whether the given point lies within the hexagon.
111
+ #
112
+ # @return [true|false]
113
+ def contains?(point)
114
+ loosely_contains?(point) ? super : false
115
+ end
116
+
117
+ # Checks whether the given point lies within the bounding box of the hexagon.
118
+ #
119
+ # @return [true|false]
120
+ def loosely_contains?(point)
121
+ raise "Call #setup_hex first!" if @hex_center.nil? || @hex_size.nil?
122
+
123
+ ((point.x_coord - @hex_center.x_coord).abs <= @hex_size) && ((point.y_coord - @hex_center.y_coord).abs <= hex_v_size)
124
+ end
125
+
126
+ # Returns a hexagon to the north-east with the same radius.
127
+ def north_east_hexagon(params = {})
128
+ raise "Call #setup_hex first!" if @hex_center.nil? || @hex_size.nil?
129
+
130
+ grab_points = params.fetch(:grab_points, false)
131
+
132
+ north_east_center = hex_point_class.new(@hex_center.x_coord + @hex_size * 1.5, @hex_center.y_coord + hex_v_size)
133
+ self.class.new.tap do |hex|
134
+ hex.setup_hex(north_east_center, @hex_size)
135
+ hex.grab(@rejected_points) if grab_points
136
+ end
137
+ end
138
+
139
+ # Returns a hexagon to the south-east with the same radius.
140
+ def south_east_hexagon(params = {})
141
+ raise "Call #setup_hex first!" if @hex_center.nil? || @hex_size.nil?
142
+
143
+ grab_points = params.fetch(:grab_points, false)
144
+
145
+ south_east_center = hex_point_class.new(@hex_center.x_coord + @hex_size * 1.5, @hex_center.y_coord - hex_v_size)
146
+ self.class.new.tap do |hex|
147
+ hex.setup_hex(south_east_center, @hex_size)
148
+ hex.grab(@rejected_points) if grab_points
149
+ end
150
+ end
151
+
152
+ # Returns a hexagon to the south with the same radius.
153
+ def south_hexagon(params = {})
154
+ raise "Call #setup_hex first!" if @hex_center.nil? || @hex_size.nil?
155
+
156
+ grab_points = params.fetch(:grab_points, false)
157
+
158
+ south_center = hex_point_class.new(@hex_center.x_coord, @hex_center.y_coord - hex_v_size * 2.0)
159
+ self.class.new.tap do |hex|
160
+ hex.setup_hex(south_center, @hex_size)
161
+ hex.grab(@rejected_points) if grab_points
162
+ end
163
+ end
164
+
165
+ # Returns an array of the 6 points defining the corners of the hexagon.
166
+ #
167
+ # @return [Array<HexagonTiling::Point>] an array of points, coresponding to
168
+ # the 6 corners of the hexagon
169
+ def hex_corners
170
+ raise "Call #setup_hex first!" if @hex_center.nil? || @hex_size.nil?
171
+
172
+ corners = []
173
+ (0..5).each do |i|
174
+ angle = 2 * Math::PI / 6 * i
175
+ corner_x = @hex_center.x_coord + @hex_size * Math.cos(angle)
176
+ corner_y = @hex_center.y_coord + @hex_size * Math.sin(angle)
177
+ corners << hex_point_class.new(corner_x, corner_y)
178
+ end
179
+
180
+ corners
181
+ end
182
+
183
+ def to_geojson
184
+ corner_points = hex_corners.map{ |p| [p.x_coord, p.y_coord] }
185
+ corner_points << corner_points.last
186
+ {
187
+ :type => "Feature",
188
+ :geometry => {
189
+ :type => "Polygon",
190
+ :coordinates => [corner_points]
191
+ },
192
+ :properties => nil
193
+ }
194
+ end
195
+
196
+ private
197
+
198
+ def hex_point_class
199
+ @hex_center.nil? ? Hexagonly::Point : @hex_center.class
200
+ end
201
+
202
+ end
203
+
204
+ include Methods
205
+
206
+ # (see #setup_hex)
207
+ def initialize(*params)
208
+ setup_hex(*params) if params.size == 2
209
+ end
210
+
211
+ end
212
+ end