georuby-ext 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/Guardfile +13 -0
- data/README.rdoc +37 -0
- data/Rakefile +10 -0
- data/georuby-ext.gemspec +31 -0
- data/lib/georuby-ext.rb +17 -0
- data/lib/georuby-ext/geokit.rb +25 -0
- data/lib/georuby-ext/georuby/envelope.rb +10 -0
- data/lib/georuby-ext/georuby/geometry.rb +9 -0
- data/lib/georuby-ext/georuby/line_string.rb +41 -0
- data/lib/georuby-ext/georuby/locators.rb +225 -0
- data/lib/georuby-ext/georuby/multi_polygon.rb +9 -0
- data/lib/georuby-ext/georuby/point.rb +73 -0
- data/lib/georuby-ext/georuby/polygon.rb +68 -0
- data/lib/georuby-ext/proj4.rb +11 -0
- data/lib/georuby-ext/rgeo.rb +23 -0
- data/lib/georuby-ext/rspec_helper.rb +61 -0
- data/spec/georuby/line_string_spec.rb +120 -0
- data/spec/georuby/linear_ring_spec.rb +33 -0
- data/spec/georuby/locators_spec.rb +120 -0
- data/spec/georuby/multi_polygon_spec.rb +29 -0
- data/spec/georuby/point_spec.rb +44 -0
- data/spec/georuby/polygon_spec.rb +134 -0
- data/spec/rgeo_spec.rb +81 -0
- data/spec/spec_helper.rb +23 -0
- metadata +227 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
class GeoRuby::SimpleFeatures::Point
|
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
|
10
|
+
end
|
11
|
+
|
12
|
+
def eql?(other)
|
13
|
+
[x,y,srid] == [other.x, other.y, other.srid]
|
14
|
+
end
|
15
|
+
|
16
|
+
def hash
|
17
|
+
[x,y,srid].hash
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"#{y},#{x}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.centroid(points)
|
25
|
+
case points.size
|
26
|
+
when 0
|
27
|
+
nil
|
28
|
+
when 1
|
29
|
+
points.first
|
30
|
+
when 2
|
31
|
+
return GeoRuby::SimpleFeatures::Point.from_x_y points.map(&:x).sum / 2, points.map(&:y).sum / 2, points.first.srid
|
32
|
+
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
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_lat_lng
|
41
|
+
Geokit::LatLng.new y, x
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
to_lat_lng.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_wgs84
|
49
|
+
self.class.from_lat_lng to_lat_lng.google_to_wgs84, 4326
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_google
|
53
|
+
self.class.from_lat_lng to_lat_lng.wgs84_to_google, 900913
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_rgeo
|
57
|
+
factory = RGeo::Geos::Factory.create
|
58
|
+
factory.point(self.x, self.y)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_openlayers
|
62
|
+
OpenLayers::LonLat.new x, y
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.bounds(points)
|
66
|
+
return nil if points.blank?
|
67
|
+
|
68
|
+
points.inject(points.first.envelope) do |envelope, point|
|
69
|
+
envelope.extend!(point.envelope)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class GeoRuby::SimpleFeatures::Polygon
|
2
|
+
|
3
|
+
def self.circle(center, radius, sides_number = 24)
|
4
|
+
coordinates = (0...sides_number).collect do |side|
|
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]
|
9
|
+
end
|
10
|
+
# Close the circle
|
11
|
+
coordinates << coordinates.first
|
12
|
+
from_coordinates [coordinates]
|
13
|
+
end
|
14
|
+
|
15
|
+
def side_count
|
16
|
+
# Reduce by one because polygon is closed
|
17
|
+
(rings.collect(&:size).sum) - 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def points
|
21
|
+
rings.collect(&:points).flatten
|
22
|
+
end
|
23
|
+
|
24
|
+
def centroid
|
25
|
+
if rgeo_polygon = to_rgeo
|
26
|
+
rgeo_polygon.centroid.to_georuby
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
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
|
36
|
+
|
37
|
+
georuby_polygons.each do |polygon|
|
38
|
+
polygon_union = polygon_union.union(polygon.to_rgeo)
|
39
|
+
end
|
40
|
+
|
41
|
+
polygon_union.to_georuby
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.intersection(georuby_polygons)
|
45
|
+
factory = RGeo::Geos::Factory.create
|
46
|
+
if !georuby_polygons.empty?
|
47
|
+
polygon_intersection = georuby_polygons.first.to_rgeo
|
48
|
+
georuby_polygons.shift
|
49
|
+
end
|
50
|
+
|
51
|
+
georuby_polygons.each do |polygon|
|
52
|
+
polygon_intersection = polygon_intersection.intersection(polygon.to_rgeo)
|
53
|
+
end
|
54
|
+
|
55
|
+
polygon_intersection.to_georuby
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_wgs84
|
59
|
+
self.class.from_points([self.points.collect(&:to_wgs84)], 4326)
|
60
|
+
end
|
61
|
+
|
62
|
+
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)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Proj4::Projection
|
2
|
+
|
3
|
+
def self.wgs84
|
4
|
+
Proj4::Projection.new '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.google
|
8
|
+
Proj4::Projection.new '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs'
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class RGeo::Geos::PointImpl
|
2
|
+
def to_georuby
|
3
|
+
GeoRuby::SimpleFeatures::Point.from_x_y x, y, srid
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class RGeo::Geos::LineStringImpl
|
8
|
+
def to_georuby
|
9
|
+
GeoRuby::SimpleFeatures::LineString.from_points points.collect(&:to_georuby), srid
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class RGeo::Geos::PolygonImpl
|
14
|
+
def to_georuby
|
15
|
+
GeoRuby::SimpleFeatures::Polygon.from_linear_rings [exterior_ring.to_georuby], srid
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class RGeo::Geos::MultiPolygonImpl
|
20
|
+
def to_georuby
|
21
|
+
GeoRuby::SimpleFeatures::MultiPolygon.from_polygons collect(&:to_georuby), srid
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
RSpec::Matchers.define :be_same_polygon do |reference|
|
2
|
+
match do |actual|
|
3
|
+
reference =
|
4
|
+
case reference
|
5
|
+
when Array
|
6
|
+
GeoRuby::SimpleFeatures::Polygon.from_coordinates [reference]
|
7
|
+
when String
|
8
|
+
geometry reference
|
9
|
+
else
|
10
|
+
reference
|
11
|
+
end
|
12
|
+
|
13
|
+
[actual.points, reference.points].transpose.all? do |actual_point, reference_point|
|
14
|
+
actual_point.euclidian_distance(reference_point) < 0.01
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def geometry(text, srid = 4326)
|
20
|
+
GeoRuby::SimpleFeatures::Geometry.from_ewkt "SRID=#{srid};#{text}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def point(x=0.0, y=0.0, srid = 4326)
|
24
|
+
GeoRuby::SimpleFeatures::Point.from_x_y(x,y,srid)
|
25
|
+
end
|
26
|
+
|
27
|
+
def points(text)
|
28
|
+
text.split(",").collect { |definition| geometry "POINT(#{definition})" }
|
29
|
+
end
|
30
|
+
|
31
|
+
def line_string(*points)
|
32
|
+
if points.one? and String === points.first
|
33
|
+
geometry("LINESTRING(#{points})")
|
34
|
+
else
|
35
|
+
GeoRuby::SimpleFeatures::LineString.from_points(points)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def linear_ring(*points)
|
40
|
+
GeoRuby::SimpleFeatures::LinearRing.from_points(points)
|
41
|
+
end
|
42
|
+
|
43
|
+
def polygon(*points)
|
44
|
+
GeoRuby::SimpleFeatures::Polygon.from_points([points])
|
45
|
+
end
|
46
|
+
|
47
|
+
def multi_polygon(*polygons)
|
48
|
+
GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([polygons])
|
49
|
+
end
|
50
|
+
|
51
|
+
def rgeo_point(x = 0, y = 0, srid = 4326)
|
52
|
+
RGeo::Geos.factory(:srid => srid).point(x, y)
|
53
|
+
end
|
54
|
+
|
55
|
+
def rgeometry(text, srid = 4326)
|
56
|
+
RGeo::Geos.factory(:srid => srid).parse_wkt text
|
57
|
+
end
|
58
|
+
|
59
|
+
def rgeo_line_string(text, srid = 4326)
|
60
|
+
rgeometry "LINESTRING(#{text})"
|
61
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GeoRuby::SimpleFeatures::LineString do
|
4
|
+
|
5
|
+
subject { line_string "0 0,1 1,0 0" }
|
6
|
+
|
7
|
+
describe "#to_rgeo" do
|
8
|
+
it "should create a RGeo Geos geometry" do
|
9
|
+
subject.to_rgeo.should be_kind_of(RGeo::Geos::GeometryImpl)
|
10
|
+
end
|
11
|
+
|
12
|
+
context "returned RGeo LineString" do
|
13
|
+
it "should have the same srid" do
|
14
|
+
subject.to_rgeo.srid.should == subject.srid
|
15
|
+
end
|
16
|
+
it "should have the points" do
|
17
|
+
line_string("0 0,1 1,0 0").to_rgeo.should == rgeo_line_string("0 0,1 1,0 0")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#==" do
|
23
|
+
|
24
|
+
it "should be true when points are same" do
|
25
|
+
line_string("0 0,1 1").should == line_string("0 0,1 1")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be true when the other is LineRing with the same points" do
|
29
|
+
line_string("0 0,1 1,0 0").should == line_string("0 0,1 1,0 0").to_ring
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#to_ring" do
|
35
|
+
|
36
|
+
it "should be a GeoRuby::SimpleFeatures::LinearRing" do
|
37
|
+
subject.to_ring.should be_instance_of(GeoRuby::SimpleFeatures::LinearRing)
|
38
|
+
end
|
39
|
+
|
40
|
+
context "returned LinearRing" do
|
41
|
+
|
42
|
+
it "should have the same srid" do
|
43
|
+
subject.to_ring.should have_same(:srid).than(subject)
|
44
|
+
# subject.to_ring.srid.should == subject.srid
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when line is closed" do
|
48
|
+
subject { line_string("0 0,1 1,0 0") }
|
49
|
+
|
50
|
+
it "should have the same points" do
|
51
|
+
subject.to_ring.points.should == subject.points
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when line isn't closed" do
|
56
|
+
subject { line_string("0 0,1 1") }
|
57
|
+
|
58
|
+
it "should have the line points and the first one" do
|
59
|
+
subject.to_ring.points.should == (subject.points << subject.first)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#change" do
|
68
|
+
|
69
|
+
let(:other_points) { subject.points + points("3 3") }
|
70
|
+
|
71
|
+
it "should change the points if specified" do
|
72
|
+
subject.change(:points => other_points).points.should == other_points
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should change the points if specified" do
|
76
|
+
subject.change(:srid => 1).srid.should == 1
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should not change unspecified attributes" do
|
80
|
+
subject.change(:points => other_points).should have_same(:srid, :with_z, :with_m).than(subject)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#reverse" do
|
86
|
+
|
87
|
+
it "should return a LineString with reversed points" do
|
88
|
+
subject.reverse.points.should == subject.points.reverse
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should not change other attributes" do
|
92
|
+
subject.reverse.should have_same(:srid, :with_z, :with_m).than(subject)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#to_wgs84" do
|
98
|
+
|
99
|
+
it "should project all points into wgs84" do
|
100
|
+
subject.to_wgs84.points.each_with_index do |wgs84_point, index|
|
101
|
+
wgs84_point.should == subject[index].to_wgs84
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should have the srid 4326" do
|
106
|
+
subject.to_wgs84.srid.should == 4326
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should not change other attributes" do
|
110
|
+
subject.reverse.should have_same(:with_z, :with_m).than(subject)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should be closed when is_closed is true" do
|
116
|
+
subject.stub :is_closed => true
|
117
|
+
subject.should be_closed
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GeoRuby::SimpleFeatures::LinearRing do
|
4
|
+
let(:result) {factory = RGeo::Geos::Factory.create
|
5
|
+
factory.linear_ring([factory.point(0, 0), factory.point(2, 1), factory.point(3, 2), factory.point(0, 0)])}
|
6
|
+
|
7
|
+
describe "to_rgeo" do
|
8
|
+
it "should create a RGeo::Feature::LinearRing" do
|
9
|
+
line = linear_ring(point(0,0), point(2,1), point(3,2), point(0,0))
|
10
|
+
line.to_rgeo.should == result
|
11
|
+
end
|
12
|
+
|
13
|
+
# it "should create a RGeo::Feature::LinearRing if the georuby linear ring is not closed" do
|
14
|
+
# line = linear_ring(point(0,0), point(2,1), point(3,2))
|
15
|
+
# line.to_rgeo.should == result
|
16
|
+
# end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#to_wgs84" do
|
21
|
+
let(:line) {linear_ring(point(0,0), point(1,1), point(0,0))}
|
22
|
+
let(:line_wgs84) { GeoRuby::SimpleFeatures::LinearRing.from_points([point(0,0,4326), point(0.000008983152841195214, 0.000008983152840993819,4326), point(0,0,4326)], 4326)}
|
23
|
+
|
24
|
+
it "should return true when we compare line coordinates" do
|
25
|
+
line.to_wgs84.should == line_wgs84
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return same srid" do
|
29
|
+
line.to_wgs84.srid.should == line_wgs84.srid
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GeoRuby::SimpleFeatures::MultiLineString do
|
4
|
+
|
5
|
+
def point(x, y)
|
6
|
+
GeoRuby::SimpleFeatures::Point.from_x_y x,y
|
7
|
+
end
|
8
|
+
def line(*xy)
|
9
|
+
GeoRuby::SimpleFeatures::LineString.from_coordinates [*xy]
|
10
|
+
end
|
11
|
+
def lines(*lines)
|
12
|
+
GeoRuby::SimpleFeatures::MultiLineString.from_line_strings lines
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should create a valid ewkb" do
|
16
|
+
GeoRuby::SimpleFeatures::Geometry.from_ewkb(lines(line([0,0],[4,0]),line([0,2],[4,2])).as_ewkb)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "locate_point" do
|
20
|
+
|
21
|
+
context "examples" do
|
22
|
+
|
23
|
+
it "should find 2,1 at 0.5 between 0,0 and 4,0" do
|
24
|
+
lines(line([0,0],[4,0])).locate_point(point(2,1)).should == 0.5
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should find 2,1 at 1.5 for (0,4)...(0,1) and (0,0)...(4,0)" do
|
28
|
+
lines(line([0,4],[0,1]), line([0,0],[4,0])).locate_point(point(2,1)).should == 1.5
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "interpolate_point" do
|
36
|
+
|
37
|
+
context "examples" do
|
38
|
+
|
39
|
+
it "should find 0.5 at (2,0) between 0,0 and 4,0" do
|
40
|
+
lines(line([0,0],[4,0])).interpolate_point(0.5).should == point(2,0)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should find 1.5 at (2,0) for (0,4)...(0,1) and (0,0)...(4,0)" do
|
44
|
+
lines(line([0,4],[0,1]), line([0,0],[4,0])).interpolate_point(1.5).should == point(2,0)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe GeoRuby::SimpleFeatures::LineString do
|
54
|
+
|
55
|
+
def point(x, y)
|
56
|
+
GeoRuby::SimpleFeatures::Point.from_x_y x,y
|
57
|
+
end
|
58
|
+
def line(*xy)
|
59
|
+
GeoRuby::SimpleFeatures::LineString.from_coordinates [*xy]
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "locate_point" do
|
63
|
+
|
64
|
+
context "examples" do
|
65
|
+
|
66
|
+
it "should find 2,1 at 0.5 between 0,0 and 4,0" do
|
67
|
+
line([0,0],[4,0]).locate_point(point(2,1)).should == 0.5
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should find 2,1 at 0.5 between 0,0 and 4,0 (with several segments)" do
|
71
|
+
line([0,0],[2,0],[4,0]).locate_point(point(2,1)).should == 0.5
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe GeoRuby::SimpleFeatures::LineString::PointLocator do
|
82
|
+
|
83
|
+
def point(x, y)
|
84
|
+
GeoRuby::SimpleFeatures::Point.from_x_y x,y
|
85
|
+
end
|
86
|
+
alias_method :p, :point
|
87
|
+
|
88
|
+
def locator(target, departure, arrival)
|
89
|
+
GeoRuby::SimpleFeatures::LineString::PointLocator.new target, departure, arrival
|
90
|
+
end
|
91
|
+
|
92
|
+
context "examples" do
|
93
|
+
|
94
|
+
it "should locate 0,y at 0 between 0,0 and 4,0" do
|
95
|
+
-10.upto(10) do |y|
|
96
|
+
locator(p(0,y), p(0,0), p(4,0)).locate_point.should be_zero
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should locate 2,y at 0.5 between 0,0 and 4,0" do
|
101
|
+
-10.upto(10) do |y|
|
102
|
+
locator(p(2,y), p(0,0), p(4,0)).locate_point.should == 0.5
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should locate 4,y at 1 between 0,0 and 4,0" do
|
107
|
+
-10.upto(10) do |y|
|
108
|
+
locator(p(4,y), p(0,0), p(4,0)).locate_point.should == 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should find 2,y at a distance of y from 0,0 and 4,0" do
|
113
|
+
-10.upto(10) do |y|
|
114
|
+
locator(p(2,y), p(0,0), p(4,0)).distance_from_segment.should be_within(0.001).of(y.abs)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|