georuby-ext 0.0.1 → 0.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.
- data/.jrubyrc +1 -0
- data/.travis.yml +23 -0
- data/Gemfile +6 -0
- data/Guardfile +12 -2
- data/MIT-LICENSE +20 -0
- data/README.rdoc +2 -28
- data/georuby-ext.gemspec +14 -11
- data/lib/georuby-ext.rb +17 -2
- data/lib/georuby-ext/core_ext.rb +11 -0
- data/lib/georuby-ext/geokit.rb +10 -3
- data/lib/georuby-ext/georuby/envelope.rb +36 -1
- data/lib/georuby-ext/georuby/ewkb_parser.rb +11 -0
- data/lib/georuby-ext/georuby/ewkt_parser.rb +11 -0
- data/lib/georuby-ext/georuby/geometry.rb +46 -2
- data/lib/georuby-ext/georuby/line_string.rb +19 -7
- data/lib/georuby-ext/georuby/linear_ring.rb +15 -0
- data/lib/georuby-ext/georuby/locators.rb +30 -17
- data/lib/georuby-ext/georuby/multi_polygon.rb +15 -1
- data/lib/georuby-ext/georuby/point.rb +148 -24
- data/lib/georuby-ext/georuby/polygon.rb +38 -27
- data/lib/georuby-ext/georuby/rtree.rb +133 -0
- data/lib/georuby-ext/georuby/srid.rb +17 -0
- data/lib/georuby-ext/proj4.rb +15 -2
- data/lib/georuby-ext/rgeo/cartesian/feature_methods.rb +58 -0
- data/lib/georuby-ext/rgeo/feature/geometry.rb +11 -0
- data/lib/georuby-ext/rgeo/feature/geometry_collection.rb +11 -0
- data/lib/georuby-ext/rgeo/feature/rgeo.rb +157 -0
- data/lib/georuby-ext/rgeo/geos/ffi_feature_methods.rb +265 -0
- data/lib/georuby-ext/rspec_helper.rb +47 -8
- data/spec/lib/geokit_spec.rb +44 -0
- data/spec/lib/georuby/envelope_spec.rb +46 -0
- data/spec/lib/georuby/geometry_spec.rb +81 -0
- data/spec/{georuby → lib/georuby}/line_string_spec.rb +29 -14
- data/spec/lib/georuby/linear_ring_spec.rb +52 -0
- data/spec/lib/georuby/locators_spec.rb +123 -0
- data/spec/lib/georuby/multi_polygon_spec.rb +69 -0
- data/spec/lib/georuby/point_spec.rb +248 -0
- data/spec/lib/georuby/polygon_spec.rb +175 -0
- data/spec/lib/georuby/rtree_spec.rb +132 -0
- data/spec/lib/proj4_spec.rb +24 -0
- data/spec/lib/rgeo/cartesian/feature_methods_spec.rb +110 -0
- data/spec/lib/rgeo/geos/ffi_feature_methods_spec.rb +234 -0
- data/spec/spec_helper.rb +12 -8
- metadata +224 -189
- data/lib/georuby-ext/rgeo.rb +0 -23
- data/spec/georuby/linear_ring_spec.rb +0 -33
- data/spec/georuby/locators_spec.rb +0 -120
- data/spec/georuby/multi_polygon_spec.rb +0 -29
- data/spec/georuby/point_spec.rb +0 -44
- data/spec/georuby/polygon_spec.rb +0 -134
- 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
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
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
|
45
|
-
|
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
|
-
|
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
|
53
|
-
|
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
|
57
|
-
|
58
|
-
|
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
|
-
|
4
|
+
points = sides_number.times.map do |side|
|
5
5
|
2 * 180 / sides_number * side
|
6
|
-
end.
|
7
|
-
|
8
|
-
[point.x, point.y]
|
6
|
+
end.map! do |angle|
|
7
|
+
center.endpoint angle, radius
|
9
8
|
end
|
10
|
-
|
11
|
-
|
12
|
-
from_coordinates [coordinates]
|
9
|
+
|
10
|
+
from_points [points], center.srid
|
13
11
|
end
|
14
12
|
|
15
13
|
def side_count
|
16
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
38
|
-
|
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
|
-
|
42
|
-
|
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
|
59
|
-
self.
|
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
|
-
|
64
|
-
|
65
|
-
|
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
|