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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +18 -0
- data/Guardfile +17 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +29 -0
- data/Rakefile +27 -0
- data/data/FL +0 -0
- data/data/GL27 +22 -0
- data/data/IGNF +487 -0
- data/data/MD +0 -0
- data/data/TN +0 -0
- data/data/WI +0 -0
- data/data/WO +0 -0
- data/data/alaska +0 -0
- data/data/conus +0 -0
- data/data/epsg +8464 -0
- data/data/esri +5937 -0
- data/data/esri.extra +948 -0
- data/data/hawaii +0 -0
- data/data/nad.lst +142 -0
- data/data/nad27 +809 -0
- data/data/nad83 +744 -0
- data/data/ntf_r93.gsb +0 -0
- data/data/ntv1_can.dat +0 -0
- data/data/null +0 -0
- data/data/nzgd2kgrid0005.gsb +0 -0
- data/data/other.extra +53 -0
- data/data/proj_def.dat +17 -0
- data/data/prvi +0 -0
- data/data/stgeorge +0 -0
- data/data/stlrnc +0 -0
- data/data/stpaul +0 -0
- data/data/world +212 -0
- data/ffi-proj4.gemspec +23 -0
- data/lib/ffi-proj4.rb +164 -0
- data/lib/ffi-proj4/error.rb +92 -0
- data/lib/ffi-proj4/point.rb +39 -0
- data/lib/ffi-proj4/projection.rb +310 -0
- data/lib/ffi-proj4/projxy.rb +64 -0
- data/lib/ffi-proj4/tools.rb +57 -0
- data/lib/ffi-proj4/version.rb +5 -0
- data/test/point_tests.rb +69 -0
- data/test/projection_tests.rb +197 -0
- data/test/simple_transformation_tests.rb +82 -0
- data/test/test_helper.rb +87 -0
- data/test/transformation_tests.rb +108 -0
- metadata +108 -0
@@ -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
|