georuby-ext 0.0.1
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/.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
|