File Coverage

lib/Geo/LibProj/FFI.pm
Criterion Covered Total %
statement 29 29 100.0
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 40 40 100.0


line stmt bran cond sub pod time code
1 9     9   1055663 use 5.012;
  9         75  
2 9     9   50 use warnings;
  9         13  
  9         464  
3              
4             # ABSTRACT: Foreign function interface to PROJ coordinate transformation software
5             package Geo::LibProj::FFI 0.02;
6              
7              
8 9     9   4649 use Alien::proj 1.07;
  9         873747  
  9         92  
9 9     9   7262 use FFI::Platypus 1.00;
  9         69190  
  9         363  
10 9     9   4854 use FFI::C 0.08;
  9         31165  
  9         736  
11              
12 9         140 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 9     9   4972 ]);
  9         14075  
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.02;
109             FFI::C->struct('PJ_XYZT' => [ 'x' => 'double', 'y' => 'double', 'z' => 'double', 't' => 'double' ]);
110             package Geo::LibProj::FFI::PJ_UVWT 0.02;
111             FFI::C->struct('PJ_UVWT' => [ u => 'double', v => 'double', w => 'double', t => 'double' ]);
112             package Geo::LibProj::FFI::PJ_LPZT 0.02;
113             FFI::C->struct('PJ_LPZT' => [ lam => 'double', phi => 'double', z => 'double', t => 'double' ]);
114             package Geo::LibProj::FFI::PJ_OPK 0.02;
115             FFI::C->struct('PJ_OPK' => [ o => 'double', p => 'double', k => 'double' ]);
116             # Rotations: omega, phi, kappa
117             package Geo::LibProj::FFI::PJ_ENU 0.02;
118             FFI::C->struct('PJ_ENU' => [ e => 'double', n => 'double', u => 'double' ]);
119             # East, North, Up
120             package Geo::LibProj::FFI::PJ_GEOD 0.02;
121             FFI::C->struct('PJ_GEOD' => [ 's' => 'double', 'a1' => 'double', 'a2' => 'double' ]);
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.02;
128             FFI::C->struct('PJ_UV' => [ u => 'double', v => 'double' ]);
129             package Geo::LibProj::FFI::PJ_XY 0.02;
130             FFI::C->struct('PJ_XY' => [ 'x' => 'double', 'y' => 'double' ]);
131             package Geo::LibProj::FFI::PJ_LP 0.02;
132             FFI::C->struct('PJ_LP' => [ lam => 'double', phi => 'double' ]);
133            
134             package Geo::LibProj::FFI::PJ_XYZ 0.02;
135             FFI::C->struct('PJ_XYZ' => [ 'x' => 'double', 'y' => 'double', 'z' => 'double' ]);
136             package Geo::LibProj::FFI::PJ_UVW 0.02;
137             FFI::C->struct('PJ_UVW' => [ u => 'double', v => 'double', w => 'double' ]);
138             package Geo::LibProj::FFI::PJ_LPZ 0.02;
139             FFI::C->struct('PJ_LPZ' => [ lam => 'double', phi => 'double', z => 'double' ]);
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             package Geo::LibProj::FFI::PJ_COORD::Union 0.02;
147             FFI::C->union('PJ_COORD_union' => [
148             v => 'double[4]', # First and foremost, it really is "just 4 numbers in a vector"
149             xyzt => 'PJ_XYZT',
150             uvwt => 'PJ_UVWT',
151             lpzt => 'PJ_LPZT',
152             geod => 'PJ_GEOD',
153             opk => 'PJ_OPK',
154             enu => 'PJ_ENU',
155             xyz => 'PJ_XYZ',
156             uvw => 'PJ_UVW',
157             lpz => 'PJ_LPZ',
158             xy => 'PJ_XY',
159             uv => 'PJ_UV',
160             lp => 'PJ_LP',
161            
162             ]);
163            
164             # FFI::C::Union can't be passed by value due to limitations within
165             # FFI::Platypus. Workaround: Convert the Union to a Record with the
166             # same data structure as the union, then the inverse on return.
167             # Unsurprisingly, this is kinda slow in Perl ...
168             # Ideas to maybe make it faster:
169             # - refactor to use different functions from PROJ (where possible)
170             # - FFI::Platypus::Bundle
171             # - XS
172             sub as_record {
173 1     1   3 Geo::LibProj::FFI::PJ_COORD::Record->new( v => [@{shift->v}] );
  1         5  
174             }
175            
176             package Geo::LibProj::FFI::PJ_COORD::Record 0.02;
177 9     9   15526 use FFI::Platypus::Record;
  9         14903  
  9         1152  
178             record_layout_1(qw{ double[4] v });
179             sub as_union {
180 3     3   245 Geo::LibProj::FFI::PJ_COORD::Union->new({ v => shift->v });
181             }
182             }
183             $ffi->type('record(Geo::LibProj::FFI::PJ_COORD::Record)' => 'PJ_COORD');
184              
185              
186             {
187             package Geo::LibProj::FFI::PJ_INFO 0.02;
188 9     9   76 use FFI::Platypus::Record;
  9         18  
  9         1089  
189             record_layout_1(
190             int => 'major', # Major release number
191             int => 'minor', # Minor release number
192             int => 'patch', # Patch level
193             string => 'release', # Release info. Version + date
194             string => 'version', # Full version number
195             string => 'searchpath', # Paths where init and grid files are
196             # looked for. Paths are separated by
197             # semi-colons on Windows, and colons
198             # on non-Windows platforms.
199             opaque => 'paths',
200             size_t => 'path_count',
201             );
202             }
203             $ffi->type('record(Geo::LibProj::FFI::PJ_INFO)' => 'PJ_INFO');
204              
205             FFI::C->enum('PJ_LOG_LEVEL', [
206             [PJ_LOG_NONE => 0],
207             [PJ_LOG_ERROR => 1],
208             [PJ_LOG_DEBUG => 2],
209             [PJ_LOG_TRACE => 3],
210             [PJ_LOG_TELL => 4],
211             [PJ_LOG_DEBUG_MAJOR => 2], # for proj_api.h compatibility
212             [PJ_LOG_DEBUG_MINOR => 3], # for proj_api.h compatibility
213             ], {rev => 'int'});
214              
215             # The context type - properly namespaced synonym for pj_ctx
216             $ffi->type('opaque' => 'PJ_CONTEXT');
217              
218             # A P I
219              
220             # The objects returned by the functions defined in this section have minimal
221             # interaction with the functions of the
222             # iso19111_functions section, and vice versa. See its introduction
223             # paragraph for more details.
224              
225             # Functionality for handling thread contexts
226 9     9   68 use constant PJ_DEFAULT_CTX => 0;
  9         17  
  9         4846  
227             $ffi->attach( proj_context_create => [] => 'PJ_CONTEXT');
228             $ffi->attach( proj_context_destroy => ['PJ_CONTEXT'] => 'void');
229              
230             $ffi->attach( proj_context_use_proj4_init_rules => [qw( PJ_CONTEXT int )] => 'void' );
231              
232             # Manage the transformation definition object PJ
233             $ffi->attach( proj_create => [qw( PJ_CONTEXT string )] => 'PJ' );
234             $ffi->attach( proj_create_argv => [qw( PJ_CONTEXT int string_array )] => 'PJ');
235             $ffi->attach( proj_create_crs_to_crs => [qw( PJ_CONTEXT string string PJ_AREA )] => 'PJ');
236             $ffi->attach( proj_create_crs_to_crs_from_pj => [qw( PJ_CONTEXT PJ PJ PJ_AREA string_array )] => 'PJ', sub{
237             $_[0]->( @_[1..4], $_[5] || [] ); # StringArray won't accept NULL
238             });
239             $ffi->attach( proj_normalize_for_visualization => ['PJ_CONTEXT', 'PJ'] => 'PJ');
240             $ffi->attach( proj_destroy => ['PJ'] => 'void');
241              
242              
243             # Apply transformation to observation - in forward or inverse direction
244             FFI::C->enum('PJ_DIRECTION', [
245             [PJ_FWD => 1], # Forward
246             [PJ_IDENT => 0], # Do nothing
247             [PJ_INV => -1], # Inverse
248             ]);
249              
250              
251             $ffi->attach( proj_trans => ['PJ', 'PJ_DIRECTION', 'PJ_COORD'] => 'PJ_COORD', sub {
252             my ($sub, $pj, $dir, $coord) = @_;
253             $sub->( $pj, $dir, $coord->as_record )->as_union;
254             });
255              
256             # non-standard fast method that avoids PJ_COORD unions entirely
257             # (expects and returns a single point as array ref)
258             $ffi->attach( [proj_trans => '_trans'] => ['PJ', 'PJ_DIRECTION', 'PJ_COORD'] => 'PJ_COORD', sub {
259             my ($sub, $pj, $dir, $coord) = @_;
260             $coord = Geo::LibProj::FFI::PJ_COORD::Record->new( v => $coord );
261             $sub->( $pj, $dir, $coord )->v;
262             });
263              
264              
265             # Initializers
266             $ffi->attach( proj_coord => [qw( double double double double )] => 'PJ_COORD', sub {
267             my $sub = shift;
268             $sub->(@_)->as_union;
269             });
270              
271             # Set or read error level
272             $ffi->attach( proj_context_errno => ['PJ_CONTEXT'] => 'int');
273             $ffi->attach( proj_errno_string => ['int'] => 'string'); # deprecated. use proj_context_errno_string()
274             eval { $ffi->attach( proj_context_errno_string => ['PJ_CONTEXT', 'int'] => 'string'); 1 }
275             or do { *proj_context_errno_string = sub { proj_errno_string($_[1]); } };
276              
277             $ffi->attach( proj_log_level => ['PJ_CONTEXT', 'PJ_LOG_LEVEL'] => 'PJ_LOG_LEVEL');
278              
279             # Info functions - get information about various PROJ.4 entities
280             $ffi->attach( proj_info => [] => 'PJ_INFO');
281              
282             $ffi->attach( proj_cleanup => [] => 'void');
283              
284             1;
285              
286             __END__