hexagonly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+ module Hexagonly
2
+ class Point
3
+
4
+ # Adds Point methods to an object. Any Point bears x and y coordinates,
5
+ # returned by the #x and #y methods. You can either override these methods
6
+ # to output your desired coordinates or you can map coordinate readers to
7
+ # different method names by using the #x_y_coord_methods, #x_coord_method or
8
+ # #y_coord_method methods.
9
+ #
10
+ # @example
11
+ # class MyPoint
12
+ #
13
+ # include Hexagonly::Point::Methods
14
+ # # The x coordinate will be read from #a and the y coordinate will be read from #b
15
+ # x_y_coord_methods :a, :b
16
+ #
17
+ # attr_accessor :a, :b
18
+ # def initialize(a, b); @a, @b = a, b; end
19
+ #
20
+ # end
21
+ #
22
+ # p = MyPoint.new(1, 2)
23
+ # p.x_coord # => 1
24
+ # p.y_coord # => 2
25
+ module Methods
26
+
27
+ include Comparable
28
+
29
+ def self.included(base)
30
+ base.extend(ClassMethods)
31
+ end
32
+
33
+ module ClassMethods
34
+ attr_accessor :x_coord_method_name, :y_coord_method_name
35
+
36
+ def x_coord_method(x_method)
37
+ self.x_coord_method_name = x_method.to_sym
38
+ end
39
+
40
+ def y_coord_method(y_method)
41
+ self.y_coord_method_name = y_method.to_sym
42
+ end
43
+
44
+ def x_y_coord_methods(x_method, y_method)
45
+ x_coord_method(x_method)
46
+ y_coord_method(y_method)
47
+ end
48
+ end
49
+
50
+ def x_coord
51
+ send(self.class.x_coord_method_name || 'x')
52
+ end
53
+
54
+ def x_coord=(value)
55
+ send("#{self.class.x_coord_method_name || 'x'}=", value)
56
+ end
57
+
58
+ def y_coord
59
+ send(self.class.y_coord_method_name || 'y')
60
+ end
61
+
62
+ def y_coord=(value)
63
+ send("#{self.class.y_coord_method_name || 'y'}=", value)
64
+ end
65
+
66
+ # Sets the coordinates for the current Point.
67
+ #
68
+ # @param x [Float]
69
+ # @param y [Float]
70
+ def set_coords(x, y)
71
+ self.x_coord = x
72
+ self.y_coord = y
73
+ end
74
+
75
+ def <=>(another_point)
76
+ if x_coord == another_point.x_coord && y_coord == another_point.y_coord
77
+ 0
78
+ elsif x_coord > another_point.x_coord
79
+ 1
80
+ else
81
+ -1
82
+ end
83
+ end
84
+
85
+ # Enable implicit splat.
86
+ # def to_ary
87
+ # [x_coord, y_coord]
88
+ # end
89
+
90
+ def to_geojson
91
+ {
92
+ :type => "Feature",
93
+ :geometry => {
94
+ :type => "Point",
95
+ :coordinates => [x_coord, y_coord]
96
+ },
97
+ :properties => nil
98
+ }
99
+ end
100
+
101
+ end
102
+
103
+ include Methods
104
+
105
+ attr_accessor :x, :y
106
+
107
+ # (see #set_coords)
108
+ def initialize(*coords)
109
+ set_coords(*coords) if coords.size == 2
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,94 @@
1
+ module Hexagonly
2
+ class Polygon
3
+
4
+ # Adds Polygon methods to an object. The Polygon corners are read via
5
+ # the #poly_points method. You can override this method or use the
6
+ # #poly_points_method class method to set a method name for reading
7
+ # polygon corners.
8
+ #
9
+ # @example
10
+ # class MyPolygon
11
+ #
12
+ # include Hexagonly::Polygon::Methods
13
+ # poly_points_method :corners
14
+ #
15
+ # attr_reader :corners
16
+ # def initialize(corners); @corners = corners; end
17
+ #
18
+ # end
19
+ module Methods
20
+
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ end
24
+
25
+ module ClassMethods
26
+ attr_accessor :poly_points_method_name
27
+
28
+ def poly_points_method(points_method)
29
+ self.poly_points_method_name = points_method.to_sym
30
+ end
31
+ end
32
+
33
+ attr_accessor :collected_points, :rejected_points
34
+
35
+ def poly_points
36
+ raise NoMethodError if self.class.poly_points_method_name.nil?
37
+
38
+ send(self.class.poly_points_method_name)
39
+ end
40
+
41
+ # Crossing count algorithm for determining whether a point lies within a
42
+ # polygon. Ported from http://www.visibone.com/inpoly/inpoly.c.txt
43
+ # (original C code by Bob Stein & Craig Yap).
44
+ def contains?(point)
45
+ raise "Not a valid polygon!" if poly_points.nil? || poly_points.size < 3
46
+
47
+ is_inside = false
48
+ old_p = poly_points.last
49
+ poly_points.each do |new_p|
50
+ if new_p.x_coord > old_p.x_coord
51
+ first_p = old_p
52
+ second_p = new_p
53
+ else
54
+ first_p = new_p
55
+ second_p = old_p
56
+ end
57
+ if ((new_p.x_coord < point.x_coord) == (point.x_coord <= old_p.x_coord)) && ((point.y_coord - first_p.y_coord) * (second_p.x_coord - first_p.x_coord) < (second_p.y_coord - first_p.y_coord) * (point.x_coord - first_p.x_coord))
58
+ is_inside = ! is_inside
59
+ end
60
+ old_p = new_p
61
+ end
62
+
63
+ is_inside
64
+ end
65
+
66
+ # Grabs all points within the polygon boundries from an array of Points
67
+ # and appends them to @collected_points. All rejected Points are stored
68
+ # under @rejected_points (if you want to pass the to other objects).
69
+ #
70
+ # @param points [Array<Hexagonly::Point>]
71
+ #
72
+ # @return [Array<Hexagonly::Point] the grabed points
73
+ def grab(points)
74
+ parts = points.partition{ |p| contains?(p) }
75
+ @collected_points ||= []
76
+ @collected_points += parts[0]
77
+ @rejected_points = parts[1]
78
+
79
+ parts[0]
80
+ end
81
+
82
+ end
83
+
84
+ include Methods
85
+
86
+ attr_accessor :poly_points
87
+
88
+ # @param [Array<Hexagonly::Point>] poly_points the points that make up the polygon
89
+ def initialize(poly_points)
90
+ @poly_points = poly_points
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,37 @@
1
+ module Hexagonly
2
+ class Space
3
+
4
+ attr_reader :points, :north, :west, :south, :east, :height, :width, :center
5
+
6
+ # @param [Array<Hexagonly::Point>] points an array of points that make up the space
7
+ def initialize(points)
8
+ @points = points
9
+ refresh
10
+ end
11
+
12
+ private
13
+
14
+ def refresh
15
+ compute_boundries
16
+ compute_center
17
+ end
18
+
19
+ def compute_boundries
20
+ @north, @west, @south, @east = nil
21
+ @points.each do |p|
22
+ @north = p if @north.nil? || @north.y_coord < p.y_coord
23
+ @west = p if @west.nil? || @west.x_coord > p.x_coord
24
+ @south = p if @south.nil? || @south.y_coord > p.y_coord
25
+ @east = p if @east.nil? || @east.x_coord < p.x_coord
26
+ end
27
+ end
28
+
29
+ def compute_center
30
+ compute_boundries if @north.nil? || @west.nil? || @south.nil? || @east.nil?
31
+ @height = @north.y_coord - @south.y_coord
32
+ @width = @east.x_coord - @west.x_coord
33
+ @center = Hexagonly::Point.new(@width / 2 + @west.x_coord, @height / 2 + @south.y_coord)
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Hexagonly
2
+ VERSION = "0.1.0"
3
+ end
data/lib/hexagonly.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'hexagonly/version'
2
+
3
+ require 'hexagonly/point'
4
+ require 'hexagonly/space'
5
+ require 'hexagonly/polygon'
6
+ require 'hexagonly/hexagon'
7
+ require 'hexagonly/geo_json'
8
+
9
+ module Hexagonly
10
+
11
+ end
data/localities.rb ADDED
@@ -0,0 +1,17 @@
1
+ # bundle exec ruby localities.rb | xclip -selection clipboard
2
+
3
+ require 'hexagonly'
4
+ require 'csv'
5
+
6
+ localities_csv = CSV.read(File.expand_path('../spec/fixtures/localities.csv', __FILE__))
7
+
8
+ points = []
9
+ localities_csv.first(1000).each do |row|
10
+ points << Hexagonly::Point.new(row[2].to_f, row[1].to_f)
11
+ end
12
+
13
+ hexagons = Hexagonly::Hexagon.pack(points, 0.4, { grab_points: true, reject_empty: true })
14
+
15
+ geo = Hexagonly::GeoJson.new(hexagons)
16
+ # geo.add_features(points)
17
+ puts geo.to_json
data/spec/factories.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'csv'
2
+
3
+ FactoryGirl.define do
4
+ localities_csv = CSV.read(File.expand_path('../fixtures/localities.csv', __FILE__))
5
+
6
+ sequence(:x) { |n| localities_csv.size >= n ? localities_csv[n][2].to_f : nil }
7
+ sequence(:y) { |n| localities_csv.size >= n ? localities_csv[n][1].to_f : nil }
8
+
9
+ factory :point, :class => Hexagonly::Point do
10
+ initialize_with { new(generate(:x), generate(:y)) }
11
+ end
12
+ end