rgeo 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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