File Coverage

lib/Geo/LibProj/FFI.pm
Criterion Covered Total %
statement 91 91 100.0
branch 28 28 100.0
condition 12 12 100.0
subroutine 53 53 100.0
pod n/a
total 184 184 100.0


line stmt bran cond sub pod time code
1 10     10   1165795 use 5.012;
  10         86  
2 10     10   53 use warnings;
  10         18  
  10         466  
3              
4             # ABSTRACT: Foreign function interface to PROJ coordinate transformation software
5             package Geo::LibProj::FFI 0.03;
6              
7              
8 10     10   5055 use Alien::proj 1.07;
  10         971546  
  10         103  
9 10     10   8567 use FFI::Platypus 1.00;
  10         78189  
  10         440  
10 10     10   6039 use FFI::C 0.08;
  10         35178  
  10         823  
11              
12 10         168 use Exporter::Easy (TAGS => [
13             context => [qw(
14             proj_context_create
15             proj_context_destroy
16             proj_context_use_proj4_init_rules
17             )],
18             setup => [qw(
19             proj_create
20             proj_create_argv
21             proj_create_crs_to_crs
22             proj_create_crs_to_crs_from_pj
23             proj_normalize_for_visualization
24             proj_destroy
25             )],
26             transform => [qw(
27             proj_trans
28             )],
29             error => [qw(
30             proj_context_errno
31             proj_errno_string
32             proj_context_errno_string
33             )],
34             logging => [qw(
35             proj_log_level
36             )],
37             info => [qw(
38             proj_info
39             )],
40             misc => [qw(
41             proj_coord
42             )],
43             const => [qw(
44             PJ_DEFAULT_CTX
45             PJ_LOG_NONE PJ_LOG_ERROR PJ_LOG_DEBUG PJ_LOG_TRACE PJ_LOG_TELL
46             PJ_FWD PJ_IDENT PJ_INV
47             )],
48             all => [qw(
49             :context
50             :setup
51             :transform
52             :error
53             :logging
54             :info
55             :misc
56             :const
57             proj_cleanup
58             )],
59 10     10   5765 ]);
  10         16138  
60              
61             my $ffi = FFI::Platypus->new(
62             api => 1,
63             lang => 'C',
64             lib => [Alien::proj->dynamic_libs],
65             );
66             FFI::C->ffi($ffi);
67              
68             $ffi->load_custom_type('::StringArray' => 'string_array');
69             # string[] should also work, but causes strlen in proj_create_crs_to_crs_from_pj to segfault
70              
71              
72              
73             # based on proj.h version 8.0.0
74              
75             # ***************************************************************************
76             # Copyright (c) 2016, 2017, Thomas Knudsen / SDFE
77             # Copyright (c) 2018, Even Rouault
78             #
79             # Permission is hereby granted, free of charge, to any person obtaining a
80             # copy of this software and associated documentation files (the "Software"),
81             # to deal in the Software without restriction, including without limitation
82             # the rights to use, copy, modify, merge, publish, distribute, sublicense,
83             # and/or sell copies of the Software, and to permit persons to whom the
84             # Software is furnished to do so, subject to the following conditions:
85             #
86             # The above copyright notice and this permission notice shall be included
87             # in all copies or substantial portions of the Software.
88             #
89             # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
90             # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91             # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO COORD SHALL
92             # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93             # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
94             # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
95             # DEALINGS IN THE SOFTWARE.
96             # ***************************************************************************
97              
98             # C API new generation
99              
100             $ffi->type('opaque' => 'PJ_AREA');
101              
102             # Data type for projection/transformation information
103             $ffi->type('opaque' => 'PJ'); # the PJ object herself
104              
105              
106             # Geodetic, mostly spatiotemporal coordinate types
107             {
108             package Geo::LibProj::FFI::PJ_XYZT 0.03;
109 2     2   8133 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ x y z t }) }
110             package Geo::LibProj::FFI::PJ_UVWT 0.03;
111 2     2   6070 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ u v w t })->uvwt }
112             package Geo::LibProj::FFI::PJ_LPZT 0.03;
113 2     2   6974 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ lam phi z t }) }
114             package Geo::LibProj::FFI::PJ_OPK 0.03;
115 2     2   5897 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ o p k 0 }) }
116             # Rotations: omega, phi, kappa
117             package Geo::LibProj::FFI::PJ_ENU 0.03;
118 2     2   5717 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ e n u 0 }) }
119             # East, North, Up
120             package Geo::LibProj::FFI::PJ_GEOD 0.03;
121 2     2   4974 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ s a1 a2 0 }) }
122             # Geodesic length, fwd azi, rev azi
123             }
124              
125             # Classic proj.4 pair/triplet types - moved into the PJ_ name space
126             {
127             package Geo::LibProj::FFI::PJ_UV 0.03;
128 2     2   4916 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ u v 0 0 })->uv }
129             package Geo::LibProj::FFI::PJ_XY 0.03;
130 2     2   6463 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ x y 0 0 }) }
131             package Geo::LibProj::FFI::PJ_LP 0.03;
132 2     2   3969 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ lam phi 0 0 }) }
133            
134             package Geo::LibProj::FFI::PJ_XYZ 0.03;
135 2     2   7284 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ x y z 0 }) }
136             package Geo::LibProj::FFI::PJ_UVW 0.03;
137 2     2   5121 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ u v w 0 })->uvw }
138             package Geo::LibProj::FFI::PJ_LPZ 0.03;
139 2     2   5625 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ lam phi z 0 }) }
140             }
141              
142              
143             # Data type for generic geodetic 3D data plus epoch information
144             # Avoid preprocessor renaming and implicit type-punning: Use a union to make it explicit
145             {
146             # FFI::C::Union can't be passed by value due to limitations within
147             # FFI::Platypus. Workaround: Use a Record with some additional Perl
148             # glue. The performance may not be perfect, but seems satisfactory.
149            
150             package Geo::LibProj::FFI::PJ_COORD 0.03;
151 10     10   18547 use FFI::Platypus::Record;
  10         17248  
  10         11300  
152             record_layout_1(qw{ double x double y double z double t });
153             sub _new {
154 24     24   99 my ($class, $values, @params) = @_;
155 24   100     119 $values //= {};
156 24   100     49 @params = map { $values->{$_} // 0 } @params;
  96         274  
157 24         150 return $class->new({ 'x' => $params[0], 'y' => $params[1], 'z' => $params[2], 't' => $params[3] });
158             }
159             sub _set {
160 24     24   81 my ($self, $values, @params) = @_;
161 24 100       69 if (ref $values eq 'HASH') {
162 12         27 @params = map { $values->{$_} } @params;
  48         94  
163             }
164             else {
165 12         84 @params = map { eval "\$values->$_" } grep !/^0$/, @params; ## no critic (ProhibitStringyEval)
  36         1910  
166             }
167 24         79 $self->v(\@params);
168             }
169            
170             # union members:
171             sub v { # First and foremost, it really is "just 4 numbers in a vector"
172 58     58   23264 my ($self, $vector) = @_;
173 58 100       418 return [ $self->x(), $self->y(), $self->z(), $self->t() ] unless $vector;
174 27   100     109 $self->x($vector->[0] // 0);
175 27   100     81 $self->y($vector->[1] // 0);
176 27   100     93 $self->z($vector->[2] // 0);
177 27   100     167 $self->t($vector->[3] // 0);
178             }
179 3 100   3   6246 sub xyzt { $_[1] ? $_[0]->_set($_[1], qw{ x y z t }) : shift }
180 5 100   5   11669 sub uvwt { $_[1] ? $_[0]->_set($_[1], qw{ u v w t }) : Geo::LibProj::FFI::PJ_UVWT->_new(shift) }
181 3 100   3   5571 sub lpzt { $_[1] ? $_[0]->_set($_[1], qw{ lam phi z t }) : shift }
182 3 100   3   3388 sub geod { $_[1] ? $_[0]->_set($_[1], qw{ s a1 a2 0 }) : shift }
183 3 100   3   4017 sub opk { $_[1] ? $_[0]->_set($_[1], qw{ o p k 0 }) : shift }
184 5 100   5   61655 sub enu { $_[1] ? $_[0]->_set($_[1], qw{ e n u 0 }) : shift }
185 3 100   3   5976 sub xyz { $_[1] ? $_[0]->_set($_[1], qw{ x y z 0 }) : shift }
186 5 100   5   10085 sub uvw { $_[1] ? $_[0]->_set($_[1], qw{ u v w 0 }) : Geo::LibProj::FFI::PJ_UVWT->_new(shift) }
187 3 100   3   4768 sub lpz { $_[1] ? $_[0]->_set($_[1], qw{ lam phi z 0 }) : shift }
188 3 100   3   5573 sub xy { $_[1] ? $_[0]->_set($_[1], qw{ x y 0 0 }) : shift }
189 5 100   5   8731 sub uv { $_[1] ? $_[0]->_set($_[1], qw{ u v 0 0 }) : Geo::LibProj::FFI::PJ_UVWT->_new(shift) }
190 3 100   3   3807 sub lp { $_[1] ? $_[0]->_set($_[1], qw{ lam phi 0 0 }) : shift }
191            
192             # struct members:
193             # PJ_UV* need their own package due to name collisions.
194             # The other types are implemented by the PJ_COORD package.
195            
196 15     15   15802 sub lam { shift->x( @_ ) }
197 5     5   4306 sub o { shift->x( @_ ) }
198 6     6   4244 sub e { shift->x( @_ ) }
199 5     5   3369 sub s { shift->x( @_ ) }
200            
201 15     15   9590 sub phi { shift->y( @_ ) }
202 5     5   3446 sub p { shift->y( @_ ) }
203 6     6   3242 sub n { shift->y( @_ ) }
204 5     5   164 sub a1 { shift->y( @_ ) }
205            
206 5     5   2987 sub k { shift->z( @_ ) }
207 5     5   2985 sub u { shift->z( @_ ) }
208 5     5   161 sub a2 { shift->z( @_ ) }
209            
210             package Geo::LibProj::FFI::PJ_UVWT;
211 10     10   111 use parent -norequire => 'Geo::LibProj::FFI::PJ_COORD';
  10         18  
  10         205  
212 9     9   55 sub _new { bless \$_[1], $_[0] }
213 15     15   12317 sub u { ${shift()}->x( @_ ) }
  15         147  
214 15     15   9391 sub v { ${shift()}->y( @_ ) }
  15         100  
215 10     10   5762 sub w { ${shift()}->z( @_ ) }
  10         68  
216 5     5   2925 sub t { ${shift()}->t( @_ ) }
  5         39  
217            
218             }
219             $ffi->type('record(Geo::LibProj::FFI::PJ_COORD)' => 'PJ_COORD');
220              
221              
222             {
223             package Geo::LibProj::FFI::PJ_INFO 0.03;
224 10     10   2491 use FFI::Platypus::Record;
  10         22  
  10         1906  
225             record_layout_1(
226             int => 'major', # Major release number
227             int => 'minor', # Minor release number
228             int => 'patch', # Patch level
229             string => 'release', # Release info. Version + date
230             string => 'version', # Full version number
231             string => 'searchpath', # Paths where init and grid files are
232             # looked for. Paths are separated by
233             # semi-colons on Windows, and colons
234             # on non-Windows platforms.
235             opaque => 'paths',
236             size_t => 'path_count',
237             );
238             }
239             $ffi->type('record(Geo::LibProj::FFI::PJ_INFO)' => 'PJ_INFO');
240              
241             FFI::C->enum('PJ_LOG_LEVEL', [
242             [PJ_LOG_NONE => 0],
243             [PJ_LOG_ERROR => 1],
244             [PJ_LOG_DEBUG => 2],
245             [PJ_LOG_TRACE => 3],
246             [PJ_LOG_TELL => 4],
247             [PJ_LOG_DEBUG_MAJOR => 2], # for proj_api.h compatibility
248             [PJ_LOG_DEBUG_MINOR => 3], # for proj_api.h compatibility
249             ], {rev => 'int'});
250              
251             # The context type - properly namespaced synonym for pj_ctx
252             $ffi->type('opaque' => 'PJ_CONTEXT');
253              
254             # A P I
255              
256             # The objects returned by the functions defined in this section have minimal
257             # interaction with the functions of the
258             # iso19111_functions section, and vice versa. See its introduction
259             # paragraph for more details.
260              
261             # Functionality for handling thread contexts
262 10     10   87 use constant PJ_DEFAULT_CTX => 0;
  10         26  
  10         4678  
263             $ffi->attach( proj_context_create => [] => 'PJ_CONTEXT');
264             $ffi->attach( proj_context_destroy => ['PJ_CONTEXT'] => 'void');
265              
266             $ffi->attach( proj_context_use_proj4_init_rules => [qw( PJ_CONTEXT int )] => 'void' );
267              
268             # Manage the transformation definition object PJ
269             $ffi->attach( proj_create => [qw( PJ_CONTEXT string )] => 'PJ' );
270             $ffi->attach( proj_create_argv => [qw( PJ_CONTEXT int string_array )] => 'PJ');
271             $ffi->attach( proj_create_crs_to_crs => [qw( PJ_CONTEXT string string PJ_AREA )] => 'PJ');
272             $ffi->attach( proj_create_crs_to_crs_from_pj => [qw( PJ_CONTEXT PJ PJ PJ_AREA string_array )] => 'PJ', sub{
273             $_[0]->( @_[1..4], $_[5] || [] ); # StringArray won't accept NULL
274             });
275             $ffi->attach( proj_normalize_for_visualization => ['PJ_CONTEXT', 'PJ'] => 'PJ');
276             $ffi->attach( proj_destroy => ['PJ'] => 'void');
277              
278              
279             # Apply transformation to observation - in forward or inverse direction
280             FFI::C->enum('PJ_DIRECTION', [
281             [PJ_FWD => 1], # Forward
282             [PJ_IDENT => 0], # Do nothing
283             [PJ_INV => -1], # Inverse
284             ]);
285              
286              
287             $ffi->attach( proj_trans => ['PJ', 'PJ_DIRECTION', 'PJ_COORD'] => 'PJ_COORD');
288              
289             # non-standard method (now discouraged; originally used by Perl cs2cs)
290             # (expects and returns a single point as array ref)
291             $ffi->attach( [proj_trans => '_trans'] => ['PJ', 'PJ_DIRECTION', 'PJ_COORD'] => 'PJ_COORD', sub {
292             my ($sub, $pj, $dir, $coord) = @_;
293             $sub->( $pj, $dir, proj_coord($coord->[0] // 0, $coord->[1] // 0, $coord->[2] // 0, $coord->[3] // 0) )->v;
294             });
295              
296              
297             # Initializers
298             $ffi->attach( proj_coord => [qw( double double double double )] => 'PJ_COORD');
299              
300             # Set or read error level
301             $ffi->attach( proj_context_errno => ['PJ_CONTEXT'] => 'int');
302             $ffi->attach( proj_errno_string => ['int'] => 'string'); # deprecated. use proj_context_errno_string()
303             eval { $ffi->attach( proj_context_errno_string => ['PJ_CONTEXT', 'int'] => 'string'); 1 }
304             or do { *proj_context_errno_string = sub { proj_errno_string($_[1]); } };
305              
306             $ffi->attach( proj_log_level => ['PJ_CONTEXT', 'PJ_LOG_LEVEL'] => 'PJ_LOG_LEVEL');
307              
308             # Info functions - get information about various PROJ.4 entities
309             $ffi->attach( proj_info => [] => 'PJ_INFO');
310              
311             $ffi->attach( proj_cleanup => [] => 'void');
312              
313             1;
314              
315             __END__