georuby-ext 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.jrubyrc +1 -0
  2. data/.travis.yml +23 -0
  3. data/Gemfile +6 -0
  4. data/Guardfile +12 -2
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +2 -28
  7. data/georuby-ext.gemspec +14 -11
  8. data/lib/georuby-ext.rb +17 -2
  9. data/lib/georuby-ext/core_ext.rb +11 -0
  10. data/lib/georuby-ext/geokit.rb +10 -3
  11. data/lib/georuby-ext/georuby/envelope.rb +36 -1
  12. data/lib/georuby-ext/georuby/ewkb_parser.rb +11 -0
  13. data/lib/georuby-ext/georuby/ewkt_parser.rb +11 -0
  14. data/lib/georuby-ext/georuby/geometry.rb +46 -2
  15. data/lib/georuby-ext/georuby/line_string.rb +19 -7
  16. data/lib/georuby-ext/georuby/linear_ring.rb +15 -0
  17. data/lib/georuby-ext/georuby/locators.rb +30 -17
  18. data/lib/georuby-ext/georuby/multi_polygon.rb +15 -1
  19. data/lib/georuby-ext/georuby/point.rb +148 -24
  20. data/lib/georuby-ext/georuby/polygon.rb +38 -27
  21. data/lib/georuby-ext/georuby/rtree.rb +133 -0
  22. data/lib/georuby-ext/georuby/srid.rb +17 -0
  23. data/lib/georuby-ext/proj4.rb +15 -2
  24. data/lib/georuby-ext/rgeo/cartesian/feature_methods.rb +58 -0
  25. data/lib/georuby-ext/rgeo/feature/geometry.rb +11 -0
  26. data/lib/georuby-ext/rgeo/feature/geometry_collection.rb +11 -0
  27. data/lib/georuby-ext/rgeo/feature/rgeo.rb +157 -0
  28. data/lib/georuby-ext/rgeo/geos/ffi_feature_methods.rb +265 -0
  29. data/lib/georuby-ext/rspec_helper.rb +47 -8
  30. data/spec/lib/geokit_spec.rb +44 -0
  31. data/spec/lib/georuby/envelope_spec.rb +46 -0
  32. data/spec/lib/georuby/geometry_spec.rb +81 -0
  33. data/spec/{georuby → lib/georuby}/line_string_spec.rb +29 -14
  34. data/spec/lib/georuby/linear_ring_spec.rb +52 -0
  35. data/spec/lib/georuby/locators_spec.rb +123 -0
  36. data/spec/lib/georuby/multi_polygon_spec.rb +69 -0
  37. data/spec/lib/georuby/point_spec.rb +248 -0
  38. data/spec/lib/georuby/polygon_spec.rb +175 -0
  39. data/spec/lib/georuby/rtree_spec.rb +132 -0
  40. data/spec/lib/proj4_spec.rb +24 -0
  41. data/spec/lib/rgeo/cartesian/feature_methods_spec.rb +110 -0
  42. data/spec/lib/rgeo/geos/ffi_feature_methods_spec.rb +234 -0
  43. data/spec/spec_helper.rb +12 -8
  44. metadata +224 -189
  45. data/lib/georuby-ext/rgeo.rb +0 -23
  46. data/spec/georuby/linear_ring_spec.rb +0 -33
  47. data/spec/georuby/locators_spec.rb +0 -120
  48. data/spec/georuby/multi_polygon_spec.rb +0 -29
  49. data/spec/georuby/point_spec.rb +0 -44
  50. data/spec/georuby/polygon_spec.rb +0 -134
  51. data/spec/rgeo_spec.rb +0 -81
@@ -3,7 +3,21 @@ class GeoRuby::SimpleFeatures::MultiPolygon
3
3
  self.class.from_polygons(self.polygons.collect(&:to_wgs84), 4326)
4
4
  end
5
5
 
6
+ def to_google
7
+ self.class.from_polygons(self.polygons.collect(&:to_google), 900913)
8
+ end
9
+
6
10
  def polygons
7
- self.geometries.flatten
11
+ self.geometries
12
+ end
13
+
14
+ def difference(georuby_multi_polygon)
15
+ multi_polygon_difference = georuby_multi_polygon.present? ? self.to_rgeo.difference(georuby_multi_polygon.to_rgeo) : self.to_rgeo
16
+ multi_polygon_difference.to_georuby
8
17
  end
18
+
19
+ def to_rgeo
20
+ rgeo_factory.multi_polygon(polygons.collect(&:to_rgeo))
21
+ end
22
+
9
23
  end
@@ -1,20 +1,115 @@
1
1
  class GeoRuby::SimpleFeatures::Point
2
2
 
3
- def self.from_lat_lng(object, srid = GeoRuby::SimpleFeatures::DEFAULT_SRID)
4
- if object.respond_to?(:to_lat_lng)
5
- lat_lng = object.to_lat_lng
6
- else
7
- lat_lng = Geokit::LatLng.normalize object
8
- end
9
- from_x_y lat_lng.lng, lat_lng.lat, srid
3
+ # Earth radius in kms
4
+ #
5
+ # GeoRuby Point#spherical_distance uses 6370997.0 m
6
+ # Geokit::LatLng uses 6376.77271 km
7
+ # ...
8
+ @@earth_radius = 6370997.0
9
+ def self.earth_radius
10
+ @@earth_radius
11
+ end
12
+ def earth_radius
13
+ self.class.earth_radius
14
+ end
15
+
16
+ # Length of a latitude degree in meters
17
+ @@latitude_degree_distance = @@earth_radius * 2 * Math::PI / 360
18
+ def self.latitude_degree_distance
19
+ @@latitude_degree_distance
20
+ end
21
+ def latitude_degree_distance
22
+ self.class.latitude_degree_distance
23
+ end
24
+
25
+ def change(options)
26
+ # TODO support z
27
+ self.class.from_x_y(options[:x] || x,
28
+ options[:y] || y,
29
+ options[:srid] || srid)
30
+ # or instead of || requires parenthesis
31
+ end
32
+
33
+ def ==(other)
34
+ other and
35
+ other.respond_to?(:lat) and other.respond_to?(:lng) and
36
+ (other.respond_to?(:srid) and srid == other.srid) ? (lat == other.lat and lng == other.lng) : (spherical_distance(other) < 10e-3)
37
+ end
38
+
39
+ def spherical_distance_with_srid_support(other)
40
+ to_wgs84.spherical_distance_without_srid_support(other.to_wgs84)
41
+ end
42
+ alias_method_chain :spherical_distance, :srid_support
43
+
44
+ def endpoint(heading, distance, options={})
45
+ Endpointer.new(self, heading, distance, options).arrival
46
+ end
47
+
48
+ class Endpointer
49
+
50
+ attr_accessor :origin, :heading, :distance, :unit
51
+
52
+ def initialize(origin, heading, distance, options = {})
53
+ @origin, @heading, @distance = origin, heading.deg2rad, distance
54
+ end
55
+
56
+ def radius
57
+ GeoRuby::SimpleFeatures::Point.earth_radius
58
+ end
59
+
60
+ def distance_per_radius
61
+ @distance_per_radius ||= distance / radius
62
+ end
63
+
64
+ def cos_distance_per_radius
65
+ @cos_distance_per_radius ||= Math.cos(distance_per_radius)
66
+ end
67
+
68
+ def sin_distance_per_radius
69
+ @sin_distance_per_radius ||= Math.sin(distance_per_radius)
70
+ end
71
+
72
+ def latitude
73
+ @latitude ||= origin.lat.deg2rad
74
+ end
75
+
76
+ def cos_latitude
77
+ @cos_latitude ||= Math.cos(latitude)
78
+ end
79
+
80
+ def sin_latitude
81
+ @sin_latitude ||= Math.sin(latitude)
82
+ end
83
+
84
+ def longitude
85
+ @longitude ||= origin.lng.deg2rad
86
+ end
87
+
88
+ def arrival_latitude
89
+ Math.asin(sin_latitude * cos_distance_per_radius +
90
+ cos_latitude * sin_distance_per_radius * Math.cos(heading))
91
+ end
92
+
93
+ def arrival_longitude
94
+ longitude + Math.atan2(Math.sin(heading) * sin_distance_per_radius * cos_latitude,
95
+ cos_distance_per_radius - sin_latitude * Math.sin(arrival_latitude))
96
+ end
97
+
98
+ def arrival
99
+ origin.change :x => arrival_longitude.rad2deg, :y => arrival_latitude.rad2deg end
100
+
10
101
  end
11
102
 
12
103
  def eql?(other)
13
- [x,y,srid] == [other.x, other.y, other.srid]
104
+ [x,y,z,srid] == [other.x, other.y, other.z, other.srid]
105
+ end
106
+
107
+ def close_to?(other)
108
+ spherical_distance(other) < 10e-3
14
109
  end
15
110
 
16
111
  def hash
17
- [x,y,srid].hash
112
+ [x,y,z,srid].hash
18
113
  end
19
114
 
20
115
  def to_s
@@ -28,40 +123,62 @@ class GeoRuby::SimpleFeatures::Point
28
123
  when 1
29
124
  points.first
30
125
  when 2
31
- return GeoRuby::SimpleFeatures::Point.from_x_y points.map(&:x).sum / 2, points.map(&:y).sum / 2, points.first.srid
126
+ from_x_y points.sum(&:x) / 2, points.sum(&:y) / 2, srid!(points)
32
127
  else
33
- points = [points.last, *points]
34
- GeoRuby::SimpleFeatures::Polygon.from_points([points]).centroid.tap do |centroid|
35
- centroid.srid = points.first.srid if centroid
36
- end
128
+ points = [points.last, *points] # polygon must be closed for rgeo
129
+ GeoRuby::SimpleFeatures::Polygon.from_points([points], srid!(points)).centroid
37
130
  end
38
131
  end
39
132
 
133
+ def self.from_lat_lng(object, srid = 4326)
134
+ ActiveSupport::Deprecation.warn "Don't use Geokit::LatLng to represent no wgs84 point" unless srid == 4326
135
+
136
+ if object.respond_to?(:to_lat_lng)
137
+ lat_lng = object.to_lat_lng
138
+ else
139
+ lat_lng = Geokit::LatLng.normalize object
140
+ end
141
+ from_x_y lat_lng.lng, lat_lng.lat, srid
142
+ end
143
+
40
144
  def to_lat_lng
41
145
  Geokit::LatLng.new y, x
42
146
  end
43
147
 
44
- def to_s
45
- to_lat_lng.to_s
148
+ def projection
149
+ Proj4::Projection.for_srid srid
46
150
  end
151
+
152
+ def project_to(target_srid)
153
+ return self if srid == target_srid
47
154
 
48
- def to_wgs84
49
- self.class.from_lat_lng to_lat_lng.google_to_wgs84, 4326
155
+ self.class.from_pro4j projection.transform(Proj4::Projection.for_srid(target_srid), to_proj4.x, to_proj4.y), target_srid
50
156
  end
51
157
 
52
- def to_google
53
- self.class.from_lat_lng to_lat_lng.wgs84_to_google, 900913
158
+ def to_proj4(ratio = nil)
159
+ # Proj4 use radian instead of degres
160
+ ratio ||= (wgs84? ? Proj4::DEG_TO_RAD : 1.0)
161
+ Proj4::Point.new x * ratio, y * ratio
54
162
  end
55
163
 
56
- def to_rgeo
57
- factory = RGeo::Geos::Factory.create
58
- factory.point(self.x, self.y)
59
- end
164
+ def self.from_pro4j(point, srid, ratio = nil)
165
+ ratio ||= (srid == 4326 ? Proj4::RAD_TO_DEG : 1.0)
166
+ from_x_y point.x * ratio, point.y * ratio, srid
167
+ end
60
168
 
169
+ def to_rgeo
170
+ rgeo_factory.point x, y
171
+ end
172
+
61
173
  def to_openlayers
62
174
  OpenLayers::LonLat.new x, y
63
175
  end
64
176
 
177
+ # Fixes original bounding_box which creates points without srid
178
+ def bounding_box
179
+ Array.new(with_z ? 3 : 2) { dup }
180
+ end
181
+
65
182
  def self.bounds(points)
66
183
  return nil if points.blank?
67
184
 
@@ -70,4 +187,11 @@ class GeoRuby::SimpleFeatures::Point
70
187
  end
71
188
  end
72
189
 
190
+ def metric_delta(other)
191
+ longitude_degree_distance =
192
+ (latitude_degree_distance * Math.cos(lat.deg2rad)).abs
193
+ [ latitude_degree_distance * (other.lat - lat),
194
+ longitude_degree_distance * (other.lng - lng) ]
195
+ end
196
+
73
197
  end
@@ -1,48 +1,45 @@
1
1
  class GeoRuby::SimpleFeatures::Polygon
2
2
 
3
3
  def self.circle(center, radius, sides_number = 24)
4
- coordinates = (0...sides_number).collect do |side|
4
+ points = sides_number.times.map do |side|
5
5
  2 * 180 / sides_number * side
6
- end.collect do |angle|
7
- point = GeoRuby::SimpleFeatures::Point.from_lat_lng(center.to_lat_lng.endpoint(angle, radius, {:units => :kms}))
8
- [point.x, point.y]
6
+ end.map! do |angle|
7
+ center.endpoint angle, radius
9
8
  end
10
- # Close the circle
11
- coordinates << coordinates.first
12
- from_coordinates [coordinates]
9
+
10
+ from_points [points], center.srid
13
11
  end
14
12
 
15
13
  def side_count
16
- # Reduce by one because polygon is closed
17
- (rings.collect(&:size).sum) - 1
14
+ rings.sum(&:side_count)
18
15
  end
19
16
 
20
17
  def points
21
18
  rings.collect(&:points).flatten
22
19
  end
23
20
 
21
+ def perimeter
22
+ rings.sum(&:spherical_distance)
23
+ end
24
+
24
25
  def centroid
25
- if rgeo_polygon = to_rgeo
26
- rgeo_polygon.centroid.to_georuby
27
- end
26
+ to_rgeo.centroid.to_georuby
28
27
  end
29
28
 
30
29
  def self.union(georuby_polygons)
31
- factory = RGeo::Geos::Factory.create
32
- if !georuby_polygons.empty?
33
- polygon_union = georuby_polygons.first.to_rgeo
34
- georuby_polygons.shift
35
- end
30
+ return nil if georuby_polygons.empty?
31
+
32
+ rgeo_polygons = georuby_polygons.collect(&:to_rgeo)
33
+ rgeo_polygon_union = rgeo_polygons.first
36
34
 
37
- georuby_polygons.each do |polygon|
38
- polygon_union = polygon_union.union(polygon.to_rgeo)
35
+ rgeo_polygons[1..(rgeo_polygons.size - 1)].each do |rgeo_polygon|
36
+ rgeo_polygon_union = rgeo_polygon_union.union(rgeo_polygon)
39
37
  end
40
38
 
41
- polygon_union.to_georuby
42
- end
39
+ rgeo_polygon_union.to_georuby
40
+ end
43
41
 
44
42
  def self.intersection(georuby_polygons)
45
- factory = RGeo::Geos::Factory.create
46
43
  if !georuby_polygons.empty?
47
44
  polygon_intersection = georuby_polygons.first.to_rgeo
48
45
  georuby_polygons.shift
@@ -55,14 +52,28 @@ class GeoRuby::SimpleFeatures::Polygon
55
52
  polygon_intersection.to_georuby
56
53
  end
57
54
 
58
- def to_wgs84
59
- self.class.from_points([self.points.collect(&:to_wgs84)], 4326)
55
+ def difference(georuby_polygon)
56
+ polygon_difference = self.to_rgeo.difference(georuby_polygon.to_rgeo)
57
+ polygon_difference.to_georuby
60
58
  end
61
59
 
62
60
  def to_rgeo
63
- # take only the first ring for the outer ring of RGeo::Feature::Polygon
64
- factory = RGeo::Geos::Factory.create #(:srid => srid)
65
- polygon = factory.polygon(self.rings.collect(&:to_rgeo).first)
61
+ outer_ring = rings.first.to_rgeo
62
+ rings.size > 1 ? inner_rings = rings[1..(rings.size - 1)].collect(&:to_rgeo) : inner_rings = nil
63
+ rgeo_factory.polygon(outer_ring, inner_rings)
66
64
  end
67
65
 
66
+ def change(options)
67
+ self.class.from_linear_rings(options[:rings] || rings,
68
+ options[:srid] || srid,
69
+ options[:with_z] || with_z,
70
+ options[:with_m] || with_m)
71
+ # or instead of || requires parenthesis
72
+ end
73
+
74
+ def project_to(target_srid)
75
+ return self if srid == target_srid
76
+ change :rings => rings.map { |ring| ring.project_to(target_srid) }, :srid => target_srid
77
+ end
78
+
68
79
  end
@@ -0,0 +1,133 @@
1
+ module GeoRuby
2
+ class Rtree
3
+ attr_accessor :root
4
+
5
+ def initialize(root)
6
+ @root = root
7
+ end
8
+
9
+ def self.bulk_loading(elements, options = {})
10
+ options = { :node_size => 2 }.merge(options)
11
+ STRBuilder.new(elements, options[:node_size]).to_rtree
12
+ end
13
+
14
+ delegate :containing, :to => :root
15
+
16
+ class STRBuilder
17
+ attr_accessor :elements, :node_size
18
+
19
+ def initialize(elements, node_size = 2)
20
+ @elements = elements
21
+ @node_size = node_size
22
+ end
23
+
24
+ def to_rtree
25
+ Rtree.new root_node
26
+ end
27
+
28
+ def leaf_nodes
29
+ slices.collect(&:nodes).flatten
30
+ end
31
+
32
+ def root_node
33
+ nodes = leaf_nodes
34
+
35
+ while nodes.many?
36
+ nodes = [].tap do |parent_nodes|
37
+ nodes.each_slice(node_size) do |children|
38
+ parent_nodes << Node.new(children)
39
+ end
40
+ end
41
+ end
42
+
43
+ nodes.first
44
+ end
45
+
46
+ def slices
47
+ [].tap do |slices|
48
+ sort_x.each_slice(slice_size) do |slice_elements|
49
+ slices << Slice.new(slice_elements, node_size)
50
+ end
51
+ end
52
+ end
53
+
54
+ def sort_x
55
+ elements.sort_by do |element|
56
+ element.bounds.center.x
57
+ end
58
+ end
59
+
60
+ def leaf_nodes_count
61
+ (elements.count / node_size.to_f).ceil
62
+ end
63
+
64
+ def slice_size
65
+ Math.sqrt(leaf_nodes_count).ceil
66
+ end
67
+
68
+ class Slice
69
+
70
+ attr_accessor :elements, :node_size
71
+
72
+ def initialize(elements, node_size = 2)
73
+ @elements, @node_size = elements, node_size
74
+ end
75
+
76
+ delegate :size, :to => :elements
77
+
78
+ def sort_y
79
+ elements.sort_by do |element|
80
+ element.bounds.center.y
81
+ end
82
+ end
83
+
84
+ def nodes
85
+ [].tap do |nodes|
86
+ sort_y.each_slice(node_size) do |node_elements|
87
+ nodes << Node.new(node_elements)
88
+ end
89
+ end
90
+ end
91
+
92
+ def ==(other)
93
+ other.respond_to?(:elements) and elements == other.elements
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
100
+ class Node
101
+ attr_accessor :children
102
+
103
+ def initialize(*children)
104
+ @children = children.flatten
105
+ end
106
+
107
+ def bounds
108
+ @bounds ||= GeoRuby::SimpleFeatures::Envelope.bounds(children)
109
+ end
110
+ alias_method :envelope, :bounds
111
+
112
+ delegate :size, :to => :children
113
+
114
+ def ==(other)
115
+ other.respond_to?(:children) and children == other.children
116
+ end
117
+
118
+ def containing(bounds)
119
+ children.select do |child|
120
+ child.bounds.overlaps?(bounds)
121
+ end.collect do |child|
122
+ if child.respond_to?(:containing)
123
+ child.containing(bounds)
124
+ else
125
+ child
126
+ end
127
+ end.flatten
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+ end