rgeo 0.2.1 → 0.2.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.
@@ -0,0 +1,138 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # OGC CS factory for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module RGeo
38
+
39
+ module CoordSys
40
+
41
+
42
+ # This module contains an implementation of the CS (coordinate
43
+ # systems) package of the OGC Coordinate Transform spec. It contains
44
+ # classes for representing ellipsoids, datums, coordinate systems,
45
+ # and other related concepts, as well as a parser for the WKT format
46
+ # for specifying coordinate systems.
47
+ #
48
+ # Generally, the easiest way to create coordinate system objects is
49
+ # to use RGeo::CoordSys::CS.create_from_wkt, which parses the WKT
50
+ # format. This and other methods of the FactoryMethods module are all
51
+ # available as convenient module functions on the CS module.
52
+ #
53
+ # Almost the entire spec is implemented here. Currently missing are:
54
+ #
55
+ # * XML format is not implemented. We're assuming that WKT is the
56
+ # preferred format.
57
+ # * The PT and CT packages are not implemented.
58
+ # * FittedCoordinateSystem is not implemented.
59
+
60
+ module CS
61
+
62
+
63
+ module FactoryMethods
64
+
65
+ def create_compound_coordinate_system(name_, head_, tail_)
66
+ CompoundCoordinateSystem.create(name_, head_, tail_)
67
+ end
68
+
69
+ def create_ellipsoid(name_, semi_major_axis_, semi_minor_axis_, linear_unit_)
70
+ Ellipsoid.create_ellipsoid(name_, semi_major_axis_, semi_minor_axis_, linear_unit_)
71
+ end
72
+
73
+ def create_flattened_sphere(name_, semi_major_axis_, inverse_flattening_, linear_unit_)
74
+ Ellipsoid.create_flattened_sphere(name_, semi_major_axis_, inverse_flattening_, linear_unit_)
75
+ end
76
+
77
+ def create_from_wkt(str_)
78
+ WKTParser.new(str_).parse
79
+ end
80
+
81
+ def create_geographic_coordinate_system(name_, angular_unit_, horizontal_datum_, prime_meridian_, axis0_, axis1_)
82
+ GeographicCoordinateSystem.create(name_, angular_unit_, horizontal_datum_, prime_meridian_, axis0_, axis1_)
83
+ end
84
+
85
+ def create_horizontal_datum(name_, horizontal_datum_type_, ellipsoid_, to_wgs84_)
86
+ HorizontalDatum.create(name_, horizontal_datum_type_, ellipsoid_, to_wgs84_)
87
+ end
88
+
89
+ def create_local_coordinate_system(name_, datum_, unit_, axes_)
90
+ LocalCoordinateSystem.create(name_, datum_, unit_, axes_)
91
+ end
92
+
93
+ def create_local_datum(name_, local_datum_type_)
94
+ LocalDatum.create(name, local_datum_type_)
95
+ end
96
+
97
+ def create_prime_meridian(name_, angular_unit_, longitude_)
98
+ PrimeMeridian.create(name, angular_unit_, longitude_)
99
+ end
100
+
101
+ def create_projected_coordinate_system(name_, gcs_, projection_, linear_unit_, axis0_, axis1_)
102
+ ProjectedCoordinateSystem.create(name_, gcs_, projection_, linear_unit_, axis0_, axis1_)
103
+ end
104
+
105
+ def create_projection(name_, wkt_projection_class_, parameters_)
106
+ Projection.create(name_, wkt_projection_class_, parameters_)
107
+ end
108
+
109
+ def create_vertical_coordinate_system(name_, vertical_datum_, vertical_unit_, axis_)
110
+ VerticalCoordinateSystem.create(name_, vertical_datum_, vertical_unit_, axis_)
111
+ end
112
+
113
+ def create_vertical_datum(name_, vertical_datum_type_)
114
+ VerticalDatum.create(name_, vertical_datum_type_)
115
+ end
116
+
117
+ end
118
+
119
+
120
+ class CoordinateSystemFactory
121
+
122
+ include FactoryMethods
123
+
124
+ end
125
+
126
+
127
+ class << self
128
+
129
+ include FactoryMethods
130
+
131
+ end
132
+
133
+
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,307 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # OGC CS wkt parser for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module RGeo
38
+
39
+ module CoordSys
40
+
41
+
42
+ module CS
43
+
44
+
45
+ class WKTParser # :nodoc:
46
+
47
+ def initialize(str_)
48
+ @scanner = ::StringScanner.new(str_)
49
+ next_token
50
+ end
51
+
52
+
53
+ def parse(containing_type_=nil)
54
+ if @cur_token.kind_of?(QuotedString) ||
55
+ @cur_token.kind_of?(::Numeric) ||
56
+ (containing_type_ == 'AXIS' && @cur_token.kind_of?(TypeString))
57
+ value_ = @cur_token
58
+ next_token
59
+ return value_
60
+ end
61
+ unless @cur_token.kind_of?(TypeString)
62
+ raise Error::ParseError("Found token #{@cur_token} when we expected a value")
63
+ end
64
+ type_ = @cur_token
65
+ next_token
66
+ consume_token_type(:begin)
67
+ args_ = ArgumentList.new
68
+ args_ << parse(type_)
69
+ loop do
70
+ break unless @cur_token == :comma
71
+ next_token
72
+ args_ << parse(type_)
73
+ end
74
+ consume_token_type(:end)
75
+ obj_ = nil
76
+ case type_
77
+ when 'AUTHORITY'
78
+ obj_ = AuthorityClause.new(args_.shift(QuotedString), args_.shift(QuotedString))
79
+ when 'AXIS'
80
+ obj_ = AxisInfo.create(args_.shift(QuotedString), args_.shift(TypeString))
81
+ when 'TOWGS84'
82
+ bursa_wolf_params_ = args_.find_all(::Numeric)
83
+ unless bursa_wolf_params_.size == 7
84
+ raise Error::ParseError("Expected 7 Bursa Wolf parameters but found #{bursa_wolf_params_.size}")
85
+ end
86
+ obj_ = WGS84ConversionInfo.create(*bursa_wolf_params_)
87
+ when 'UNIT'
88
+ case containing_type_
89
+ when 'GEOCCS', 'VERT_CS', 'PROJCS', 'SPHEROID'
90
+ klass_ = LinearUnit
91
+ when 'GEOGCS'
92
+ klass_ = AngularUnit
93
+ else
94
+ klass_ = Unit
95
+ end
96
+ obj_ = klass_.create(args_.shift(QuotedString), args_.shift(::Numeric), *args_.find_first(AuthorityClause).to_a)
97
+ when 'PARAMETER'
98
+ obj_ = ProjectionParameter.create(args_.shift(QuotedString), args_.shift(::Numeric))
99
+ when 'PRIMEM'
100
+ obj_ = PrimeMeridian.create(args_.shift(QuotedString), nil, args_.shift(::Numeric), *args_.find_first(AuthorityClause).to_a)
101
+ when 'SPHEROID'
102
+ obj_ = Ellipsoid.create_flattened_sphere(args_.shift(QuotedString), args_.shift(::Numeric), args_.shift(::Numeric), args_.find_first(LinearUnit), *args_.find_first(AuthorityClause).to_a)
103
+ when 'PROJECTION'
104
+ name_ = args_.shift(QuotedString)
105
+ obj_ = Projection.create(name_, name_, args_.find_all(ProjectionParameter), *args_.find_first(AuthorityClause).to_a)
106
+ when 'DATUM'
107
+ name_ = args_.shift(QuotedString)
108
+ ellipsoid_ = args_.find_first(Ellipsoid)
109
+ to_wgs84_ = args_.find_first(WGS84ConversionInfo)
110
+ obj_ = HorizontalDatum.create(name_, HD_GEOCENTRIC, ellipsoid_, to_wgs84_, *args_.find_first(AuthorityClause).to_a)
111
+ when 'VERT_DATUM'
112
+ obj_ = VerticalDatum.create(args_.shift(QuotedString), args_.shift(::Numeric), *args_.find_first(AuthorityClause).to_a)
113
+ when 'LOCAL_DATUM'
114
+ obj_ = LocalDatum.create(args_.shift(QuotedString), args_.shift(::Numeric), *args_.find_first(AuthorityClause).to_a)
115
+ when 'COMPD_CS'
116
+ obj_ = CompoundCoordinateSystem.create(args_.shift(QuotedString), args_.shift(CoordinateSystem), args_.shift(CoordinateSystem), *args_.find_first(AuthorityClause).to_a)
117
+ when 'LOCAL_CS'
118
+ name_ = args_.shift(QuotedString)
119
+ local_datum_ = args_.find_first(LocalDatum)
120
+ unit_ = args_.find_first(Unit)
121
+ axes_ = args_.find_all(AxisInfo)
122
+ unless axes_.size > 0
123
+ raise Error::ParseError("Expected at least one AXIS in a LOCAL_CS")
124
+ end
125
+ obj_ = LocalCoordinateSystem.create(name_, local_datum_, unit_, axes_, *args_.find_first(AuthorityClause).to_a)
126
+ when 'GEOCCS'
127
+ name_ = args_.shift(QuotedString)
128
+ horizontal_datum_ = args_.find_first(HorizontalDatum)
129
+ prime_meridian_ = args_.find_first(PrimeMeridian)
130
+ linear_unit_ = args_.find_first(LinearUnit)
131
+ axes_ = args_.find_all(AxisInfo)
132
+ unless axes_.size == 0 || axes_.size == 3
133
+ raise Error::ParseError("GEOCCS must contain either 0 or 3 AXIS parameters")
134
+ end
135
+ obj_ = GeocentricCoordinateSystem.create(name_, horizontal_datum_, prime_meridian_, linear_unit_, axes_[0], axes_[1], axes_[2], *args_.find_first(AuthorityClause).to_a)
136
+ when 'VERT_CS'
137
+ name_ = args_.shift(QuotedString)
138
+ vertical_datum_ = args_.find_first(VerticalDatum)
139
+ linear_unit_ = args_.find_first(LinearUnit)
140
+ axis_ = args_.find_first(AxisInfo)
141
+ obj_ = VerticalCoordinateSystem.create(name_, vertical_datum_, linear_unit_, axis_, *args_.find_first(AuthorityClause).to_a)
142
+ when 'GEOGCS'
143
+ name_ = args_.shift(QuotedString)
144
+ horizontal_datum_ = args_.find_first(HorizontalDatum)
145
+ prime_meridian_ = args_.find_first(PrimeMeridian)
146
+ angular_unit_ = args_.find_first(AngularUnit)
147
+ axes_ = args_.find_all(AxisInfo)
148
+ unless axes_.size == 0 || axes_.size == 2
149
+ raise Error::ParseError("GEOGCS must contain either 0 or 2 AXIS parameters")
150
+ end
151
+ obj_ = GeographicCoordinateSystem.create(name_, angular_unit_, horizontal_datum_, prime_meridian_, axes_[0], axes_[1], *args_.find_first(AuthorityClause).to_a)
152
+ when 'PROJCS'
153
+ name_ = args_.shift(QuotedString)
154
+ geographic_coordinate_system_ = args_.find_first(GeographicCoordinateSystem)
155
+ projection_ = args_.find_first(Projection)
156
+ parameters_ = args_.find_all(ProjectionParameter)
157
+ projection_.instance_variable_get(:@parameters).concat(parameters_)
158
+ linear_unit_ = args_.find_first(LinearUnit)
159
+ axes_ = args_.find_all(AxisInfo)
160
+ unless axes_.size == 0 || axes_.size == 2
161
+ raise Error::ParseError("PROJCS must contain either 0 or 2 AXIS parameters")
162
+ end
163
+ obj_ = ProjectedCoordinateSystem.create(name_, geographic_coordinate_system_, projection_, linear_unit_, axes_[0], axes_[1], *args_.find_first(AuthorityClause).to_a)
164
+ else
165
+ raise Error::ParseError, "Unrecognized type: #{type_}"
166
+ end
167
+ args_.assert_empty
168
+ obj_
169
+ end
170
+
171
+
172
+ def consume_token_type(type_) # :nodoc:
173
+ expect_token_type(type_)
174
+ tok_ = @cur_token
175
+ next_token
176
+ tok_
177
+ end
178
+
179
+ def expect_token_type(type_) # :nodoc:
180
+ unless type_ === @cur_token
181
+ raise Error::ParseError, "#{type_.inspect} expected but #{@cur_token.inspect} found."
182
+ end
183
+ end
184
+
185
+ def next_token # :nodoc:
186
+ @scanner.skip(/\s+/)
187
+ case @scanner.peek(1)
188
+ when '"'
189
+ @scanner.getch
190
+ @cur_token = QuotedString.new(@scanner.scan(/[^"]*/))
191
+ @scanner.getch
192
+ when ','
193
+ @scanner.getch
194
+ @cur_token = :comma
195
+ when '(','['
196
+ @scanner.getch
197
+ @cur_token = :begin
198
+ when ']',')'
199
+ @scanner.getch
200
+ @cur_token = :end
201
+ when /[a-zA-Z]/
202
+ @cur_token = TypeString.new(@scanner.scan(/[a-zA-Z]\w*/))
203
+ when '', nil
204
+ @cur_token = nil
205
+ else
206
+ @scanner.scan_until(/[^\s\(\)\[\],"]+/)
207
+ token_ = @scanner.matched
208
+ if token_ =~ /^[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?$/
209
+ @cur_token = token_.to_f
210
+ else
211
+ raise Error::ParseError, "Bad token: #{token_.inspect}"
212
+ end
213
+ end
214
+ @cur_token
215
+ end
216
+
217
+ def cur_token # :nodoc:
218
+ @cur_token
219
+ end
220
+
221
+
222
+ class QuotedString < ::String # :nodoc:
223
+ end
224
+
225
+ class TypeString < ::String # :nodoc:
226
+ end
227
+
228
+ class AuthorityClause # :nodoc:
229
+
230
+ def initialize(name_, code_)
231
+ @name = name_
232
+ @code = code_
233
+ end
234
+
235
+ def to_a
236
+ [@name, @code]
237
+ end
238
+
239
+ end
240
+
241
+
242
+ class ArgumentList # :nodoc:
243
+
244
+ def initialize
245
+ @values = []
246
+ end
247
+
248
+ def <<(value_)
249
+ @values << value_
250
+ end
251
+
252
+ def assert_empty
253
+ if @values.size > 0
254
+ names_ = @values.map do |val_|
255
+ val_.kind_of?(Base) ? val_._wkt_typename : val_.inspect
256
+ end
257
+ raise Error::ParseError, "#{@remaining} unexpected arguments: #{names_.join(', ')}"
258
+ end
259
+ end
260
+
261
+ def find_first(klass_)
262
+ @values.each_with_index do |val_, index_|
263
+ if val_.kind_of?(klass_)
264
+ @values.slice!(index_)
265
+ return val_
266
+ end
267
+ end
268
+ nil
269
+ end
270
+
271
+ def find_all(klass_)
272
+ results_ = []
273
+ nvalues_ = []
274
+ @values.each do |val_|
275
+ if val_.kind_of?(klass_)
276
+ results_ << val_
277
+ else
278
+ nvalues_ << val_
279
+ end
280
+ end
281
+ @values = nvalues_
282
+ results_
283
+ end
284
+
285
+ def shift(klass_=nil)
286
+ val_ = @values.shift
287
+ unless val_
288
+ raise Error::ParseError, "No arguments left... expected #{klass_}"
289
+ end
290
+ if klass_ && !val_.kind_of?(klass_)
291
+ raise Error::ParseError, "Expected #{klass_} but got #{val_.class}"
292
+ end
293
+ val_
294
+ end
295
+
296
+ end
297
+
298
+
299
+ end
300
+
301
+
302
+ end
303
+
304
+
305
+ end
306
+
307
+ end
@@ -168,6 +168,9 @@ module RGeo
168
168
  if defn_.kind_of?(::Hash)
169
169
  defn_ = defn_.map{ |k_, v_| v_ ? "+#{k_}=#{v_}" : "+#{k_}" }.join(' ')
170
170
  end
171
+ unless defn_ =~ /^\s*\+/
172
+ defn_ = defn_.sub(/^(\s*)/, '\1+').gsub(/(\s+)([^+\s])/, '\1+\2')
173
+ end
171
174
  result_ = _create(defn_)
172
175
  result_ = nil unless result_._valid?
173
176
  end