ffi-proj4 0.1.0

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,92 @@
1
+
2
+ module Proj4
3
+ class Error < StandardError
4
+ ERRORS = %w{
5
+ Unknown
6
+ NoArgsInInitList
7
+ NoOptionsInInitFile
8
+ NoColonInInitString
9
+ ProjectionNotNamed
10
+ UnknownProjectionId
11
+ EffectiveEccentricityEq1
12
+ UnknownUnitConversionId
13
+ InvalidBooleanParamArgument
14
+ UnknownEllipticalParameterName
15
+ ReciprocalFlatteningIsZero
16
+ RadiusReferenceLatitudeGt90
17
+ SquaredEccentricityLessThanZero
18
+ MajorAxisOrRadiusIsZeroOrNotGiven
19
+ LatitudeOrLongitudeExceededLimits
20
+ InvalidXOrY
21
+ ImproperlyFormedDMSValue
22
+ NonConvergentInverseMeridinalDist
23
+ NonConvergentInversePhi2
24
+ AcosOrAsinArgTooBig
25
+ ToleranceCondition
26
+ ConicLat1EqMinusLat2
27
+ Lat1GreaterThan90
28
+ Lat1IsZero
29
+ LatTsGreater90
30
+ NoDistanceBetweenControlPoints
31
+ ProjectionNotSelectedToBeRotated
32
+ WSmallerZeroOrMSmallerZero
33
+ LsatNotInRange
34
+ PathNotInRange
35
+ HSmallerZero
36
+ KSmallerZero
37
+ Lat0IsZeroOr90OrAlphaIsZero
38
+ Lat1EqLat2OrLat1IsZeroOrLat2Is90
39
+ EllipticalUsageRequired
40
+ InvalidUTMZoneNumber
41
+ ArgsOutOfRangeForTchebyEval
42
+ NoProjectionToBeRotated
43
+ FailedToLoadNAD2783CorrectionFile
44
+ BothNAndMMustBeSpecdAndGreaterZero
45
+ NSmallerZeroOrNGreaterOneOrNotSpecified
46
+ Lat1OrLat2NotSpecified
47
+ AbsoluteLat1EqLat2
48
+ Lat0IsHalfPiFromMeanLat
49
+ UnparseableCoordinateSystemDefinition
50
+ GeocentricTransformationMissingZOrEllps
51
+ UnknownPrimeMeridianConversionId
52
+ IllegalAxisOrientationCombination
53
+ PointNotWithinAvailableDatumShiftGrids
54
+ InvalidSweepAxis
55
+ }
56
+
57
+ def self.list
58
+ ERRORS
59
+ end
60
+
61
+ def self.error(errno)
62
+ ERRORS[errno.abs] || 'Unknown'
63
+ end
64
+
65
+ def self.raise_error(errno)
66
+ raise self.instantiate_error(errno), caller[0..-1]
67
+ end
68
+
69
+ def self.instantiate_error(errno)
70
+ name = self.error(errno)
71
+ Proj4.const_get("#{name}Error").new(Proj4::FFIProj4.pj_strerrno(errno))
72
+ end
73
+
74
+ def errno
75
+ self.class.errno
76
+ end
77
+ alias :errnum :errno
78
+ end
79
+
80
+ Error::ERRORS.each_with_index do |err, index|
81
+ Proj4.module_eval(<<-EOF, __FILE__, __LINE__ + 1)
82
+ class #{err}Error < Proj4::Error
83
+ class << self
84
+ def errno
85
+ #{index}
86
+ end
87
+ alias :errnum :errno
88
+ end
89
+ end
90
+ EOF
91
+ end
92
+ end
@@ -0,0 +1,39 @@
1
+
2
+ module Proj4
3
+ class Point
4
+ include Proj4::Tools
5
+
6
+ attr_accessor :x, :y, :z
7
+
8
+ def initialize(x, y, z = nil)
9
+ @x, @y, @z = x, y, z
10
+ end
11
+
12
+ alias :lon :x
13
+ alias :lon= :x=
14
+ alias :lat :y
15
+ alias :lat= :y=
16
+
17
+ def to_deg!
18
+ self.x = rad_to_deg(self.x)
19
+ self.y = rad_to_deg(self.y)
20
+ self.z = rad_to_deg(self.z) unless self.z.nil?
21
+ self
22
+ end
23
+
24
+ def to_deg
25
+ self.dup.to_deg!
26
+ end
27
+
28
+ def to_rad!
29
+ self.x = deg_to_rad(self.x)
30
+ self.y = deg_to_rad(self.y)
31
+ self.z = deg_to_rad(self.z) unless self.z.nil?
32
+ self
33
+ end
34
+
35
+ def to_rad
36
+ self.dup.to_rad!
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,310 @@
1
+
2
+ module Proj4
3
+ class ProjectionParseError < RuntimeError; end
4
+
5
+ class Projection
6
+ include Tools
7
+
8
+ attr_reader :ptr
9
+
10
+ def initialize(arg, auto_free = true)
11
+ args = case arg
12
+ when Array
13
+ arg.collect { |a| a.sub(/^\+/, '') }
14
+ when String
15
+ if arg =~ /^(epsg|esri):/i
16
+ [ "+init=#{arg}" ]
17
+ else
18
+ arg.strip.split(/ /).collect { |a| a.sub(/^\+/, '') }
19
+ end
20
+ when Hash
21
+ arg.collect { |k, v|
22
+ if v.nil?
23
+ k.to_s
24
+ else
25
+ "#{k.to_s.strip}=#{v.to_s.strip}"
26
+ end
27
+ }
28
+ when Proj4::Projection
29
+ arg.definition.strip.split(/ /).collect { |a| a.sub(/^\+/, '') }
30
+ else
31
+ raise ArgumentError.new("Unknown type #{arg.class} for projection definition")
32
+ end
33
+
34
+ params = args.collect(&:strip).collect { |a|
35
+ if !(a =~ /^\+/)
36
+ "+#{a}"
37
+ else
38
+ a
39
+ end
40
+ }.join(' ')
41
+
42
+ ptr = FFIProj4.pj_init_plus(params)
43
+
44
+ if ptr.null?
45
+ result = FFIProj4.pj_get_errno_ref.read_int
46
+ raise ProjectionParseError.new(FFIProj4.pj_strerrno(result))
47
+ else
48
+ @ptr = FFI::AutoPointer.new(
49
+ ptr,
50
+ self.class.method(:release)
51
+ )
52
+ end
53
+
54
+ self.ptr.autorelease = auto_free
55
+ end
56
+
57
+ def self.release(ptr) #:nodoc:
58
+ FFIProj4.pj_free(ptr)
59
+ end
60
+
61
+ def lat_long?
62
+ bool_result(FFIProj4.pj_is_latlong(self.ptr))
63
+ end
64
+ alias :isLatLong? :lat_long?
65
+
66
+ def geocentric?
67
+ bool_result(FFIProj4.pj_is_geocent(self.ptr))
68
+ end
69
+ alias :isGeocent? :geocentric?
70
+ alias :isGeocentric? :geocentric?
71
+
72
+ def definition
73
+ @definition ||= FFIProj4.pj_get_def(self.ptr, 0).strip
74
+ end
75
+ alias :getDef :definition
76
+
77
+ def to_hash
78
+ @hash ||= self.definition.split(/ /).inject({}) { |memo, opt|
79
+ memo.tap {
80
+ k, v = opt.split('=')
81
+ k.sub!(/^\+/, '')
82
+ v = true if v.nil?
83
+ memo[k.to_sym] = v
84
+ }
85
+ }
86
+ end
87
+ alias :definition_as_hash :to_hash
88
+
89
+ def to_s
90
+ "#<Proj4::Projection #{definition}>"
91
+ end
92
+ alias :inspect :to_s
93
+
94
+ def forward!(*args)
95
+ xy, point = xy_and_point_from_args(*args)
96
+
97
+ ret = FFIProj4.pj_fwd(xy, self.ptr)
98
+ result = FFIProj4.pj_get_errno_ref.read_int
99
+
100
+ if result == 0
101
+ point.x = ret[:x]
102
+ point.y = ret[:y]
103
+ point.z = 0 if point.respond_to?(:z=)
104
+ point
105
+ else
106
+ raise Proj4::Error.instantiate_error(result)
107
+ end
108
+ end
109
+ alias :forward_rad! :forward!
110
+ alias :forwardRad! :forward!
111
+
112
+ def forward(*args)
113
+ self.forward!(*dup_args(*args))
114
+ end
115
+ alias :forward_rad :forward
116
+ alias :forwardRad :forward
117
+
118
+ def forward_deg!(*args)
119
+ self.forward!(*args_deg_to_rad(*args))
120
+ end
121
+ alias :forwardDeg! :forward_deg!
122
+
123
+ def forward_deg(*args)
124
+ self.forward_deg!(*dup_args(*args))
125
+ end
126
+ alias :forwardDeg :forward_deg
127
+
128
+ def forward_all!(collection)
129
+ collection.each do |args|
130
+ self.forward_all!(proj, *args)
131
+ end
132
+ collection
133
+ end
134
+
135
+ def forward_all(proj, collection)
136
+ collection.collect do |args|
137
+ self.forward!(proj, *(dup_args(*args)))
138
+ end
139
+ end
140
+
141
+ def inverse!(*args)
142
+ xy, point = xy_and_point_from_args(*args)
143
+
144
+ ret = FFIProj4.pj_inv(xy, self.ptr)
145
+ result = FFIProj4.pj_get_errno_ref.read_int
146
+
147
+ if result == 0
148
+ point.x = ret[:x]
149
+ point.y = ret[:y]
150
+ point.z = 0 if point.respond_to?(:z=)
151
+ point
152
+ else
153
+ raise Proj4::Error.instantiate_error(result)
154
+ end
155
+ end
156
+ alias :inverse_rad! :inverse!
157
+ alias :inverseRad! :inverse!
158
+
159
+ def inverse(*args)
160
+ self.inverse!(*dup_args(*args))
161
+ end
162
+ alias :inverse_rad :inverse
163
+ alias :inverseRad :inverse
164
+
165
+ def inverse_deg!(*args)
166
+ self.inverse!(*args).to_deg!
167
+ end
168
+ alias :inverseDeg! :inverse_deg!
169
+
170
+ def inverse_deg(*args)
171
+ self.inverse_deg!(*dup_args(*args))
172
+ end
173
+ alias :inverseDeg :inverse_deg
174
+
175
+ def transform!(proj, *args)
176
+ perform_transform(:pj_transform, proj, *args)
177
+ end
178
+ alias :transform_rad! :transform!
179
+ alias :transformRad! :transform!
180
+
181
+ def transform(proj, *args)
182
+ self.transform!(proj, *(dup_args(*args)))
183
+ end
184
+ alias :transform_rad :transform
185
+ alias :transformRad :transform
186
+
187
+ def transform_deg!(proj, *args)
188
+ self.transform!(proj, *args).to_deg!
189
+ end
190
+
191
+ def transform_deg(proj, *args)
192
+ self.transform_deg!(proj, *(dup_args(*args)))
193
+ end
194
+
195
+ def datum_transform!(proj, *args)
196
+ perform_transform(:pj_datum_transform, proj, *args)
197
+ end
198
+ alias :datum_transform_rad! :datum_transform!
199
+
200
+ def datum_transform(proj, *args)
201
+ self.datum_transform!(proj, *(dup_args(*args)))
202
+ end
203
+
204
+ def datum_transform_deg!(proj, *args)
205
+ self.datum_transform!(proj, *args).to_deg!
206
+ end
207
+
208
+ def datum_transform_deg(proj, *args)
209
+ self.datum_transform_deg!(proj, *(dup_args(*args)))
210
+ end
211
+
212
+ def transform_all!(proj, collection)
213
+ collection.each do |args|
214
+ self.transform!(proj, *args)
215
+ end
216
+ collection
217
+ end
218
+
219
+ def transform_all(proj, collection)
220
+ collection.collect do |args|
221
+ self.transform!(proj, *(dup_args(*args)))
222
+ end
223
+ end
224
+
225
+ def projection
226
+ self.to_hash[:proj]
227
+ end
228
+
229
+ def datum
230
+ self.to_hash[:datum]
231
+ end
232
+
233
+ private
234
+ def xy_and_point_from_args(*args)
235
+ if args.length == 1
236
+ point = args.first
237
+ if point.is_a?(Proj4::ProjXY)
238
+ [ point, Proj4::Point.new(point.x, point.y) ]
239
+ elsif point.respond_to?(:x) && point.respond_to?(:y)
240
+ [ Proj4::ProjXY.alloc_in.init(point.x, point.y), point ]
241
+ else
242
+ raise ArgumentError.new("Expected a Proj4::ProjXY, a Proj4::Point or an object that responds to x and y methods.")
243
+ end
244
+ elsif args.length == 2
245
+ [ Proj4::ProjXY.alloc_in.init(args[0], args[1]), Proj4::Point.new(args[0], args[1]) ]
246
+ else
247
+ raise ArgumentError.new("Wrong number of arguments #{args.length} for 1-2")
248
+ end
249
+ end
250
+
251
+ def point_from_args(*args)
252
+ if args.length >= 2
253
+ Proj4::Point.new(*args)
254
+ elsif args.length == 1 && args.first.respond_to?(:x) && args.first.respond_to?(:y)
255
+ args.first
256
+ else
257
+ raise ArgumentError.new("Expected either coordinates, a Proj4::Point or an object that responds to x and y methods.")
258
+ end
259
+ end
260
+
261
+ def args_deg_to_rad(*args)
262
+ args.collect { |value|
263
+ deg_to_rad!(value)
264
+ }
265
+ end
266
+
267
+ def args_rad_to_deg(*args)
268
+ args.collect { |value|
269
+ rad_to_deg!(value)
270
+ }
271
+ end
272
+
273
+ def dup_args(*args)
274
+ args.collect { |value|
275
+ if !value.is_a?(Numeric) && value.respond_to?(:dup)
276
+ value.dup
277
+ else
278
+ value
279
+ end
280
+ }
281
+ end
282
+
283
+ def perform_transform(transform_method, proj, *args)
284
+ if !proj.is_a?(Proj4::Projection)
285
+ raise TypeError.new("Expected a Proj4::Projection")
286
+ end
287
+
288
+ point = point_from_args(*args)
289
+
290
+ x_ptr = FFI::MemoryPointer.new(:double)
291
+ y_ptr = FFI::MemoryPointer.new(:double)
292
+ z_ptr = FFI::MemoryPointer.new(:double)
293
+
294
+ x_ptr.write_double(point.x)
295
+ y_ptr.write_double(point.y)
296
+ z_ptr.write_double(point.z.nil? ? 0 : point.z) if point.respond_to?(:z)
297
+
298
+ result = FFIProj4.send(transform_method, self.ptr, proj.ptr, 1, 1, x_ptr, y_ptr, z_ptr)
299
+
300
+ if result >= 0 && !bool_result(result)
301
+ point.x = x_ptr.read_double
302
+ point.y = y_ptr.read_double
303
+ point.z = z_ptr.read_double if point.respond_to?(:z=)
304
+ point
305
+ else
306
+ raise Proj4::Error.instantiate_error(result)
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,64 @@
1
+
2
+ module Proj4
3
+ class ProjXY < FFI::Struct
4
+ layout(
5
+ :x, :double,
6
+ :y, :double
7
+ )
8
+
9
+ def initialize(*args)
10
+ case args.first
11
+ when FFI::Pointer, FFI::Buffer
12
+ super
13
+ else
14
+ super()
15
+ self.init(*args)
16
+ end
17
+ end
18
+
19
+ def init(*args)
20
+ if !args.empty?
21
+ self[:x] = args[0].to_f
22
+ self[:y] = args[1].to_f
23
+ end
24
+
25
+ self
26
+ end
27
+
28
+ def x=(v)
29
+ self[:x] = v
30
+ end
31
+
32
+ def y=(v)
33
+ self[:y] = v
34
+ end
35
+
36
+ def x
37
+ self[:x]
38
+ end
39
+
40
+ def y
41
+ self[:y]
42
+ end
43
+
44
+ def to_deg!
45
+ self[:x] = self[:x] * Proj4::RAD_TO_DEG
46
+ self[:y] = self[:y] * Proj4::RAD_TO_DEG
47
+ self
48
+ end
49
+
50
+ def to_deg
51
+ self.dup.to_deg!
52
+ end
53
+
54
+ def to_rad!
55
+ self[:x] = self[:x] * Proj4::DEG_TO_RAD
56
+ self[:y] = self[:y] * Proj4::DEG_TO_RAD
57
+ self
58
+ end
59
+
60
+ def to_rad
61
+ self.dup.to_rad!
62
+ end
63
+ end
64
+ end