File Coverage

lib/Geo/LibProj/FFI.pm
Criterion Covered Total %
statement 137 137 100.0
branch 30 30 100.0
condition 12 12 100.0
subroutine 67 67 100.0
pod n/a
total 246 246 100.0


line stmt bran cond sub pod time code
1 12     12   1154671 use 5.012;
  12         83  
2 12     12   52 use warnings;
  12         16  
  12         473  
3              
4             # ABSTRACT: Foreign function interface to PROJ coordinate transformation software
5             package Geo::LibProj::FFI 0.04;
6              
7              
8 12     12   4948 use Alien::proj 1.07;
  12         965269  
  12         114  
9 12     12   8084 use FFI::Platypus 1.00;
  12         78177  
  12         386  
10 12     12   5327 use FFI::C 0.08;
  12         35080  
  12         365  
11 12     12   10209 use Convert::Binary::C 0.04;
  12         12325  
  12         1174  
12              
13 12         186 use Exporter::Easy (TAGS => [
14             context => [qw(
15             proj_context_create
16             proj_context_destroy
17             proj_context_use_proj4_init_rules
18             )],
19             setup => [qw(
20             proj_create
21             proj_create_argv
22             proj_create_crs_to_crs
23             proj_create_crs_to_crs_from_pj
24             proj_normalize_for_visualization
25             proj_destroy
26             )],
27             area => [qw(
28             proj_area_create
29             proj_area_set_bbox
30             proj_area_destroy
31             )],
32             transform => [qw(
33             proj_trans
34             )],
35             error => [qw(
36             proj_context_errno
37             proj_errno
38             proj_errno_set
39             proj_errno_reset
40             proj_errno_restore
41             proj_errno_string
42             proj_context_errno_string
43             )],
44             logging => [qw(
45             proj_log_level
46             proj_log_func
47             )],
48             info => [qw(
49             proj_info
50             proj_pj_info
51             proj_grid_info
52             proj_init_info
53             )],
54             lists => [qw(
55             proj_list_operations
56             proj_list_ellps
57             proj_list_units
58             proj_list_angular_units
59             proj_list_prime_meridians
60             )],
61             distance => [qw(
62             proj_lp_dist
63             proj_lpz_dist
64             proj_xy_dist
65             proj_xyz_dist
66             proj_geod
67             )],
68             misc => [qw(
69             proj_coord
70             )],
71             const => [qw(
72             PJ_DEFAULT_CTX
73             PJ_LOG_NONE PJ_LOG_ERROR PJ_LOG_DEBUG PJ_LOG_TRACE PJ_LOG_TELL
74             PJ_FWD PJ_IDENT PJ_INV
75             )],
76             all => [qw(
77             :context
78             :setup
79             :area
80             :transform
81             :error
82             :logging
83             :info
84             :lists
85             :distance
86             :misc
87             :const
88             proj_cleanup
89             )],
90 12     12   5814 ]);
  12         16078  
91              
92             my $ffi = FFI::Platypus->new(
93             api => 1,
94             lang => 'C',
95             lib => [Alien::proj->dynamic_libs],
96             );
97             FFI::C->ffi($ffi);
98              
99             my $c = Convert::Binary::C->new;
100              
101             $ffi->load_custom_type('::StringPointer' => 'string_pointer');
102             # string* should also work, but doesn't in $ffi->cast
103             $ffi->load_custom_type('::StringArray' => 'string_array');
104             # string[] should also work, but causes strlen in proj_create_crs_to_crs_from_pj to segfault
105              
106              
107              
108             # based on proj.h version 8.0.0
109              
110             # ***************************************************************************
111             # Copyright (c) 2016, 2017, Thomas Knudsen / SDFE
112             # Copyright (c) 2018, Even Rouault
113             #
114             # Permission is hereby granted, free of charge, to any person obtaining a
115             # copy of this software and associated documentation files (the "Software"),
116             # to deal in the Software without restriction, including without limitation
117             # the rights to use, copy, modify, merge, publish, distribute, sublicense,
118             # and/or sell copies of the Software, and to permit persons to whom the
119             # Software is furnished to do so, subject to the following conditions:
120             #
121             # The above copyright notice and this permission notice shall be included
122             # in all copies or substantial portions of the Software.
123             #
124             # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
125             # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
126             # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO COORD SHALL
127             # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
128             # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
129             # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
130             # DEALINGS IN THE SOFTWARE.
131             # ***************************************************************************
132              
133             # C API new generation
134              
135             $ffi->type('opaque' => 'PJ_AREA');
136              
137             # Data type for projection/transformation information
138             $ffi->type('opaque' => 'PJ'); # the PJ object herself
139              
140             # Data types for list of operations, ellipsoids, datums and units used in PROJ.4
141             $c->parse(<
142             struct PJ_LIST {
143             const char *id; /* projection keyword */
144             void *(*proj)(void *); /* projection entry point */
145             const char * const *descr; /* description text */
146             };
147             ENDC
148             $ffi->custom_type( 'PJ_OPERATIONS' => {
149             native_to_perl => sub {
150             my ($ptr) = @_;
151             my $size = $c->sizeof('PJ_LIST');
152             my @list;
153             while () {
154             $ptr += $size;
155             my $item = $c->unpack('PJ_LIST', $ffi->cast( 'opaque' => "record($size)*", $ptr ));
156             last unless $item->{id};
157             $item->{id} = $ffi->cast( 'opaque' => 'string', $item->{id} );
158             $item->{descr} = $ffi->cast( 'opaque' => 'string_pointer', $item->{descr} );
159             push @list, $item;
160             }
161             return \@list;
162             },
163             });
164              
165             sub _unpack_list {
166 4     4   14 my ($type, $ptr) = @_;
167 4         37 my $size = $c->sizeof($type);
168 4         10 my @list;
169 4         6 while () {
170 88         237 my $item = $c->unpack($type, $ffi->cast( 'opaque' => "record($size)*", $ptr ));
171 88 100       6743 last unless $item->{id};
172             $item->{$_} = $ffi->cast( 'opaque' => 'string', $item->{$_} )
173 84         217 for grep { $c->typeof("$type.$_") eq 'char *' } keys %$item;
  308         1096  
174 84         23788 push @list, $item;
175 84         99 $ptr += $size;
176             }
177 4         267 return \@list;
178             }
179              
180             $c->parse(<
181             struct PJ_ELLPS {
182             const char *id; /* ellipse keyword name */
183             const char *major; /* a= value */
184             const char *ell; /* elliptical parameter */
185             const char *name; /* comments */
186             };
187             ENDC
188             $ffi->custom_type( 'PJ_ELLPS' => {
189             native_to_perl => sub { _unpack_list(PJ_ELLPS => @_) },
190             });
191              
192             $c->parse(<
193             struct PJ_UNITS {
194             const char *id; /* units keyword */
195             const char *to_meter; /* multiply by value to get meters */
196             const char *name; /* comments */
197             double factor; /* to_meter factor in actual numbers */
198             };
199             ENDC
200             $ffi->custom_type( 'PJ_UNITS' => {
201             native_to_perl => sub { _unpack_list(PJ_UNITS => @_) },
202             });
203              
204             $c->parse(<
205             struct PJ_PRIME_MERIDIANS {
206             const char *id; /* prime meridian keyword */
207             const char *defn; /* offset from greenwich in DMS format. */
208             };
209             ENDC
210             $ffi->custom_type( 'PJ_PRIME_MERIDIANS' => {
211             native_to_perl => sub { _unpack_list(PJ_PRIME_MERIDIANS => @_) },
212             });
213              
214              
215             # Geodetic, mostly spatiotemporal coordinate types
216             {
217             package Geo::LibProj::FFI::PJ_XYZT 0.04;
218 2     2   6676 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ x y z t }) }
219             package Geo::LibProj::FFI::PJ_UVWT 0.04;
220 2     2   4372 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ u v w t })->uvwt }
221             package Geo::LibProj::FFI::PJ_LPZT 0.04;
222 2     2   5541 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ lam phi z t }) }
223             package Geo::LibProj::FFI::PJ_OPK 0.04;
224 2     2   4253 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ o p k 0 }) }
225             # Rotations: omega, phi, kappa
226             package Geo::LibProj::FFI::PJ_ENU 0.04;
227 2     2   4659 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ e n u 0 }) }
228             # East, North, Up
229             package Geo::LibProj::FFI::PJ_GEOD 0.04;
230 2     2   3697 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ s a1 a2 0 }) }
231             # Geodesic length, fwd azi, rev azi
232             }
233              
234             # Classic proj.4 pair/triplet types - moved into the PJ_ name space
235             {
236             package Geo::LibProj::FFI::PJ_UV 0.04;
237 2     2   4137 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ u v 0 0 })->uv }
238             package Geo::LibProj::FFI::PJ_XY 0.04;
239 2     2   5880 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ x y 0 0 }) }
240             package Geo::LibProj::FFI::PJ_LP 0.04;
241 4     4   3562 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ lam phi 0 0 }) }
242            
243             package Geo::LibProj::FFI::PJ_XYZ 0.04;
244 2     2   5935 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ x y z 0 }) }
245             package Geo::LibProj::FFI::PJ_UVW 0.04;
246 2     2   4204 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ u v w 0 })->uvw }
247             package Geo::LibProj::FFI::PJ_LPZ 0.04;
248 2     2   4659 sub new { Geo::LibProj::FFI::PJ_COORD->_new($_[1], qw{ lam phi z 0 }) }
249             }
250              
251              
252             # Data type for generic geodetic 3D data plus epoch information
253             # Avoid preprocessor renaming and implicit type-punning: Use a union to make it explicit
254             {
255             # FFI::C::Union can't be passed by value due to limitations within
256             # FFI::Platypus. Workaround: Use a Record with some additional Perl
257             # glue. The performance may not be perfect, but seems satisfactory.
258            
259             package Geo::LibProj::FFI::PJ_COORD 0.04;
260 12     12   26180 use FFI::Platypus::Record;
  12         17270  
  12         13019  
261             record_layout_1(qw{ double x double y double z double t });
262             sub _new {
263 26     26   73 my ($class, $values, @params) = @_;
264 26   100     83 $values //= {};
265 26   100     33 @params = map { $values->{$_} // 0 } @params;
  104         233  
266 26         142 return $class->new({ 'x' => $params[0], 'y' => $params[1], 'z' => $params[2], 't' => $params[3] });
267             }
268             sub _set {
269 24     24   63 my ($self, $values, @params) = @_;
270 24 100       49 if (ref $values eq 'HASH') {
271 12         26 @params = map { $values->{$_} } @params;
  48         73  
272             }
273             else {
274 12         64 @params = map { eval "\$values->$_" } grep !/^0$/, @params; ## no critic (ProhibitStringyEval)
  36         1363  
275             }
276 24         55 $self->v(\@params);
277             }
278            
279             # union members:
280             sub v { # First and foremost, it really is "just 4 numbers in a vector"
281 58     58   17917 my ($self, $vector) = @_;
282 58 100       328 return [ $self->x(), $self->y(), $self->z(), $self->t() ] unless $vector;
283 27   100     103 $self->x($vector->[0] // 0);
284 27   100     57 $self->y($vector->[1] // 0);
285 27   100     70 $self->z($vector->[2] // 0);
286 27   100     124 $self->t($vector->[3] // 0);
287             }
288 3 100   3   4919 sub xyzt { $_[1] ? $_[0]->_set($_[1], qw{ x y z t }) : shift }
289 5 100   5   8760 sub uvwt { $_[1] ? $_[0]->_set($_[1], qw{ u v w t }) : Geo::LibProj::FFI::PJ_UVWT->_new(shift) }
290 3 100   3   4321 sub lpzt { $_[1] ? $_[0]->_set($_[1], qw{ lam phi z t }) : shift }
291 3 100   3   2522 sub geod { $_[1] ? $_[0]->_set($_[1], qw{ s a1 a2 0 }) : shift }
292 3 100   3   2853 sub opk { $_[1] ? $_[0]->_set($_[1], qw{ o p k 0 }) : shift }
293 5 100   5   45295 sub enu { $_[1] ? $_[0]->_set($_[1], qw{ e n u 0 }) : shift }
294 3 100   3   4732 sub xyz { $_[1] ? $_[0]->_set($_[1], qw{ x y z 0 }) : shift }
295 5 100   5   7629 sub uvw { $_[1] ? $_[0]->_set($_[1], qw{ u v w 0 }) : Geo::LibProj::FFI::PJ_UVWT->_new(shift) }
296 3 100   3   3525 sub lpz { $_[1] ? $_[0]->_set($_[1], qw{ lam phi z 0 }) : shift }
297 3 100   3   4347 sub xy { $_[1] ? $_[0]->_set($_[1], qw{ x y 0 0 }) : shift }
298 5 100   5   6453 sub uv { $_[1] ? $_[0]->_set($_[1], qw{ u v 0 0 }) : Geo::LibProj::FFI::PJ_UVWT->_new(shift) }
299 3 100   3   2906 sub lp { $_[1] ? $_[0]->_set($_[1], qw{ lam phi 0 0 }) : shift }
300            
301             # struct members:
302             # PJ_UV* need their own package due to name collisions.
303             # The other types are implemented by the PJ_COORD package.
304            
305 15     15   11823 sub lam { shift->x( @_ ) }
306 5     5   3245 sub o { shift->x( @_ ) }
307 6     6   3222 sub e { shift->x( @_ ) }
308 5     5   2340 sub s { shift->x( @_ ) }
309            
310 15     15   7261 sub phi { shift->y( @_ ) }
311 5     5   2328 sub p { shift->y( @_ ) }
312 6     6   2445 sub n { shift->y( @_ ) }
313 5     5   127 sub a1 { shift->y( @_ ) }
314            
315 5     5   2347 sub k { shift->z( @_ ) }
316 5     5   2327 sub u { shift->z( @_ ) }
317 5     5   130 sub a2 { shift->z( @_ ) }
318            
319             package Geo::LibProj::FFI::PJ_UVWT;
320 9     9   43 sub _new { bless \$_[1], $_[0] }
321 15     15   9420 sub u { ${shift()}->x( @_ ) }
  15         91  
322 15     15   7121 sub v { ${shift()}->y( @_ ) }
  15         79  
323 10     10   4582 sub w { ${shift()}->z( @_ ) }
  10         56  
324 5     5   2300 sub t { ${shift()}->t( @_ ) }
  5         27  
325            
326             }
327             $ffi->type('record(Geo::LibProj::FFI::PJ_COORD)' => 'PJ_COORD');
328              
329              
330             {
331             package Geo::LibProj::FFI::PJ_INFO 0.04;
332 12     12   106 use FFI::Platypus::Record;
  12         24  
  12         1338  
333             record_layout_1(
334             int => 'major', # Major release number
335             int => 'minor', # Minor release number
336             int => 'patch', # Patch level
337             string => 'release', # Release info. Version + date
338             string => 'version', # Full version number
339             string => 'searchpath', # Paths where init and grid files are
340             # looked for. Paths are separated by
341             # semi-colons on Windows, and colons
342             # on non-Windows platforms.
343             opaque => 'paths',
344             size_t => 'path_count',
345             );
346             }
347             $ffi->type('record(Geo::LibProj::FFI::PJ_INFO)' => 'PJ_INFO');
348              
349             {
350             package Geo::LibProj::FFI::PJ_PROJ_INFO 0.04;
351 12     12   75 use FFI::Platypus::Record;
  12         26  
  12         937  
352             record_layout_1(
353             string => 'id', # Name of the projection in question
354             string => 'description', # Description of the projection
355             string => 'definition', # Projection definition
356             int => 'has_inverse', # 1 if an inverse mapping exists, 0 otherwise
357             double => 'accuracy', # Expected accuracy of the transformation. -1 if unknown.
358             );
359             }
360             $ffi->type('record(Geo::LibProj::FFI::PJ_PROJ_INFO)' => 'PJ_PROJ_INFO');
361              
362             {
363             package Geo::LibProj::FFI::PJ_GRID_INFO 0.04;
364 12     12   120 use FFI::Platypus::Record;
  12         42  
  12         3560  
365             record_layout_1(
366             'string(32)' => 'gridname_NUL', # name of grid
367             'string(260)' => 'filename_NUL', # full path to grid
368             'string(8)' => 'format_NUL', # file format of grid
369             double => 'left', double => 'lower', # Coordinates of lower left corner
370             double => 'right', double => 'upper', # Coordinates of upper right corner
371             int => 'n_lon', int => 'n_lat', # Grid size
372             double => 'cs_lon', double => 'cs_lat', # Cell size of grid
373             );
374 1     1   21459 sub gridname { my $s = shift->gridname_NUL; $s =~ s/\0+$//; $s }
  1         6  
  1         4  
375 1     1   584 sub filename { my $s = shift->filename_NUL; $s =~ s/\0+$//; $s }
  1         30  
  1         18  
376 1     1   531 sub format { my $s = shift->format_NUL; $s =~ s/\0+$//; $s }
  1         5  
  1         4  
377 1     1   572 sub lowerleft { Geo::LibProj::FFI::PJ_LP->new({ lam => $_[0]->left, phi => $_[0]->lower }) }
378 1     1   592 sub upperright { Geo::LibProj::FFI::PJ_LP->new({ lam => $_[0]->right, phi => $_[0]->upper }) }
379             }
380             $ffi->type('record(Geo::LibProj::FFI::PJ_GRID_INFO)' => 'PJ_GRID_INFO');
381              
382             {
383             package Geo::LibProj::FFI::PJ_INIT_INFO 0.04;
384 12     12   85 use FFI::Platypus::Record;
  12         23  
  12         3413  
385             record_layout_1(
386             'string(32)' => 'name_NUL', # name of init file
387             'string(260)' => 'filename_NUL', # full path to the init file.
388             'string(32)' => 'version_NUL', # version of the init file
389             'string(32)' => 'origin_NUL', # origin of the file, e.g. EPSG
390             'string(16)' => 'lastupdate_NUL', # Date of last update in YYYY-MM-DD format
391             );
392 1     1   2176 sub name { my $s = shift->name_NUL; $s =~ s/\0+$//; $s }
  1         6  
  1         5  
393 1     1   567 sub filename { my $s = shift->filename_NUL; $s =~ s/\0+$//; $s }
  1         6  
  1         17  
394 1     1   532 sub version { my $s = shift->version_NUL; $s =~ s/\0+$//; $s }
  1         6  
  1         3  
395 1     1   287 sub origin { my $s = shift->origin_NUL; $s =~ s/\0+$//; $s }
  1         6  
  1         3  
396 1     1   289 sub lastupdate { my $s = shift->lastupdate_NUL; $s =~ s/\0+$//; $s }
  1         6  
  1         6  
397             }
398             $ffi->type('record(Geo::LibProj::FFI::PJ_INIT_INFO)' => 'PJ_INIT_INFO');
399              
400             FFI::C->enum('PJ_LOG_LEVEL', [
401             [PJ_LOG_NONE => 0],
402             [PJ_LOG_ERROR => 1],
403             [PJ_LOG_DEBUG => 2],
404             [PJ_LOG_TRACE => 3],
405             [PJ_LOG_TELL => 4],
406             [PJ_LOG_DEBUG_MAJOR => 2], # for proj_api.h compatibility
407             [PJ_LOG_DEBUG_MINOR => 3], # for proj_api.h compatibility
408             ], {rev => 'int'});
409              
410             # The context type - properly namespaced synonym for pj_ctx
411             $ffi->type('opaque' => 'PJ_CONTEXT');
412              
413             # A P I
414              
415             # The objects returned by the functions defined in this section have minimal
416             # interaction with the functions of the
417             # iso19111_functions section, and vice versa. See its introduction
418             # paragraph for more details.
419              
420             # Functionality for handling thread contexts
421 12     12   81 use constant PJ_DEFAULT_CTX => 0;
  12         19  
  12         7931  
422             $ffi->attach( proj_context_create => [] => 'PJ_CONTEXT');
423             $ffi->attach( proj_context_destroy => ['PJ_CONTEXT'] => 'void');
424              
425             $ffi->attach( proj_context_use_proj4_init_rules => [qw( PJ_CONTEXT int )] => 'void' );
426              
427             # Manage the transformation definition object PJ
428             $ffi->attach( proj_create => [qw( PJ_CONTEXT string )] => 'PJ' );
429             $ffi->attach( proj_create_argv => [qw( PJ_CONTEXT int string_array )] => 'PJ');
430             $ffi->attach( proj_create_crs_to_crs => [qw( PJ_CONTEXT string string PJ_AREA )] => 'PJ');
431             $ffi->attach( proj_create_crs_to_crs_from_pj => [qw( PJ_CONTEXT PJ PJ PJ_AREA string_array )] => 'PJ', sub{
432             $_[0]->( @_[1..4], $_[5] || [] ); # StringArray won't accept NULL
433             });
434             $ffi->attach( proj_normalize_for_visualization => ['PJ_CONTEXT', 'PJ'] => 'PJ');
435             $ffi->attach( proj_destroy => ['PJ'] => 'void');
436              
437              
438             $ffi->attach( proj_area_create => [] => 'PJ_AREA');
439             $ffi->attach( proj_area_set_bbox => [qw( PJ_AREA double double double double )] => 'void');
440             $ffi->attach( proj_area_destroy => [qw( PJ_AREA )] => 'void');
441              
442             # Apply transformation to observation - in forward or inverse direction
443             FFI::C->enum('PJ_DIRECTION', [
444             [PJ_FWD => 1], # Forward
445             [PJ_IDENT => 0], # Do nothing
446             [PJ_INV => -1], # Inverse
447             ]);
448              
449              
450             $ffi->attach( proj_trans => ['PJ', 'PJ_DIRECTION', 'PJ_COORD'] => 'PJ_COORD');
451              
452             # non-standard method (now discouraged; originally used by Perl cs2cs)
453             # (expects and returns a single point as array ref)
454             $ffi->attach( [proj_trans => '_trans'] => ['PJ', 'PJ_DIRECTION', 'PJ_COORD'] => 'PJ_COORD', sub {
455             my ($sub, $pj, $dir, $coord) = @_;
456             $sub->( $pj, $dir, proj_coord($coord->[0] // 0, $coord->[1] // 0, $coord->[2] // 0, $coord->[3] // 0) )->v;
457             });
458              
459              
460             # Initializers
461             $ffi->attach( proj_coord => [qw( double double double double )] => 'PJ_COORD');
462              
463             # Geodesic distance between two points with angular 2D coordinates
464             $ffi->attach( proj_lp_dist => [qw( PJ PJ_COORD PJ_COORD )] => 'double');
465              
466             # The geodesic distance AND the vertical offset
467             $ffi->attach( proj_lpz_dist => [qw( PJ PJ_COORD PJ_COORD )] => 'double');
468              
469             # Euclidean distance between two points with linear 2D coordinates
470             $ffi->attach( proj_xy_dist => [qw( PJ_COORD PJ_COORD )] => 'double');
471              
472             # Euclidean distance between two points with linear 3D coordinates
473             $ffi->attach( proj_xyz_dist => [qw( PJ_COORD PJ_COORD )] => 'double');
474              
475             # Geodesic distance (in meter) + fwd and rev azimuth between two points on the ellipsoid
476             $ffi->attach( proj_geod => [qw( PJ PJ_COORD PJ_COORD )] => 'PJ_COORD');
477              
478             # Set or read error level
479             $ffi->attach( proj_context_errno => ['PJ_CONTEXT'] => 'int');
480             $ffi->attach( proj_errno => ['PJ_CONTEXT'] => 'int');
481             $ffi->attach( proj_errno_set => ['PJ_CONTEXT', 'int'] => 'int');
482             $ffi->attach( proj_errno_reset => ['PJ_CONTEXT'] => 'int');
483             $ffi->attach( proj_errno_restore => ['PJ_CONTEXT', 'int'] => 'int');
484             $ffi->attach( proj_errno_string => ['int'] => 'string'); # deprecated. use proj_context_errno_string()
485             eval { $ffi->attach( proj_context_errno_string => ['PJ_CONTEXT', 'int'] => 'string'); 1 }
486             or do { *proj_context_errno_string = sub { proj_errno_string($_[1]); } };
487              
488             $ffi->attach( proj_log_level => ['PJ_CONTEXT', 'PJ_LOG_LEVEL'] => 'PJ_LOG_LEVEL');
489             $ffi->attach( proj_log_func => ['PJ_CONTEXT', 'opaque', '(opaque,int,string)->void'] => 'void', sub {
490             my ($sub, $ctx, $app_data, $logf) = @_;
491             my $closure = $ffi->closure( $app_data ? sub {
492             my (undef, $level, $msg) = @_;
493             $logf->($app_data, $level, $msg);
494             } : $logf );
495             $closure->sticky;
496             $sub->($ctx, 0, $closure);
497             });
498              
499             # Info functions - get information about various PROJ.4 entities
500             $ffi->attach( proj_info => [] => 'PJ_INFO');
501             $ffi->attach( proj_pj_info => ['PJ'] => 'PJ_PROJ_INFO');
502             $ffi->attach( proj_grid_info => ['string'] => 'PJ_GRID_INFO');
503             $ffi->attach( proj_init_info => ['string'] => 'PJ_INIT_INFO');
504              
505             # List functions:
506             # Get lists of operations, ellipsoids, units and prime meridians.
507             $ffi->attach( proj_list_operations => [] => 'PJ_OPERATIONS');
508             $ffi->attach( proj_list_ellps => [] => 'PJ_ELLPS');
509             $ffi->attach( proj_list_units => [] => 'PJ_UNITS');
510             $ffi->attach( proj_list_angular_units => [] => 'PJ_UNITS');
511             $ffi->attach( proj_list_prime_meridians => [] => 'PJ_PRIME_MERIDIANS');
512              
513             $ffi->attach( proj_cleanup => [] => 'void');
514              
515             1;
516              
517             __END__