File Coverage

Index.xs
Criterion Covered Total %
statement 158 170 92.9
branch 62 96 64.5
condition n/a
subroutine n/a
pod n/a
total 220 266 82.7


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4             #include "INLINE.h"
5             /* Start of C code */
6              
7             // This code requires at least a C99 compiler (for uint_* types and // comments)
8              
9             #define MAJOR_VERSION 0
10             #define MINOR_VERSION 0
11             #define SUB_VERSION 7
12              
13             /* Do not put void in the parameters below, it'll break XS */
14 2           unsigned int GetCCodeVersion( ) {
15 2           return ( MAJOR_VERSION << 20 ) | ( MINOR_VERSION << 10 ) | SUB_VERSION;
16             }
17              
18             /*
19             All functions below come in two versions, one using single-precision (float)
20             math and variables and the other using double-precision (double) math and
21             variables. Functionally, the two versions of each function are identical.
22             */
23              
24 100           unsigned int fast_log2_double(double n) {
25 100           return ceil( log2(n) );
26             }
27              
28 29           unsigned int fast_log2_float(float n) {
29 29           return ceilf( log2f(n) );
30             }
31              
32              
33             #define PI 3.14159265358979
34             #define DEG2RAD (PI / 180.0) *
35             #define RAD2DEG (180.0 / PI) *
36              
37             /*
38             This code accelerates the set-up code in the Search(...) method. Functionally it is identical to the Perl version.
39             Called as ( $grid_level, $grid_size, $max_grid_idx, $lat_0_idx, $lat_1_idx, $lon_0_idx, $lon_1_idx ) = ComputeAreaExtrema( $tile_adjust, $max_size, $max_level, $p_lat, $p_lat_rad, $p_lon, $self->{polar_circumference}, $search_radius );
40             */
41              
42 29           void ComputeAreaExtrema_float( int tile_adjust, unsigned long max_size, unsigned int max_level, float p_lat, float p_lat_rad, float p_lon, float polar_circumference, float search_radius ) {
43 29           Inline_Stack_Vars; /* For return values */
44            
45 29           uint_fast8_t grid_level = 0;
46 29           uint_fast32_t grid_size = 0;
47 29           uint_fast32_t max_grid_idx = 0;
48            
49             // Determine grid level to search at
50            
51             // Size of most detailed grid tile at this point (in meters)
52 29           float ns_indexed_meters = ( polar_circumference / 2.0 ) / max_size; // Dividing by two to get pole-to-pole distance
53            
54 29           uint_fast8_t shift = fast_log2_float( search_radius / ns_indexed_meters );
55            
56 29           shift += tile_adjust;
57            
58             // Make sure the shift we computed lies within the index levels
59             if (shift < 0) {
60             shift = 0;
61 29 100         } else if (shift >= max_level) {
62 7           shift = max_level - 1;
63             }
64            
65             // Shift is relative to the highest-resolution zoom level
66             // Determine grid level to use
67 29           grid_level = max_level - shift;
68            
69 29           grid_size = 1 << (grid_level+1);
70 29           max_grid_idx = grid_size - 1;
71            
72             // Determine which grid tiles need to be checked
73            
74             // Get search point's grid indices
75            
76 29           float lat_meter_in_degrees = 360.0 / polar_circumference;
77            
78 29           float lat_radius = search_radius * lat_meter_in_degrees;
79 29           float lat_radius_rad = DEG2RAD( lat_radius );
80 29           float lon_radius = RAD2DEG( atan2f( sinf(lat_radius_rad), cosf(lat_radius_rad) * cosf(p_lat_rad) ) );
81            
82 29           float lat_0 = p_lat - lat_radius;
83 29           float lat_1 = p_lat + lat_radius;
84            
85 29           float lon_0 = p_lon - lon_radius;
86 29           float lon_1 = p_lon + lon_radius;
87            
88            
89 29 100         if (lat_0 <= -90) {
90 7           lat_0 = -90;
91             }
92            
93 29 100         if (lat_1 >= 90) {
94 5           lat_1 = 90;
95             }
96            
97 29 100         if ( lon_0 < -180.0 ) { lon_0 += 360.0; }
98 27 50         else if ( lon_0 > 180.0 ) { lon_0 -= 360.0; }
99            
100 29 50         if ( lon_1 < -180.0 ) { lon_1 += 360.0; }
101 29 100         else if ( lon_1 > 180.0 ) { lon_1 -= 360.0; }
102            
103 29 50         if ( lat_0 < -90.0 ) { lat_0 = -90.0; }
104 29 50         else if ( lat_0 > 90.0 ) { lat_0 = 90.0; }
105            
106 29 50         if ( lat_1 < -90.0 ) { lat_1 = -90.0; }
107 29 50         else if ( lat_1 > 90.0 ) { lat_1 = 90.0; }
108            
109 29           uint_fast32_t lat_0_idx = (uint_fast32_t)( ( lat_0 + 90.0 ) * max_size / 180.0 );
110 29 100         if (lat_0_idx >= max_size) lat_0_idx = max_size - 1;
111 29           lat_0_idx >>= shift;
112            
113 29           uint_fast32_t lat_1_idx = (uint_fast32_t)( ( lat_1 + 90.0 ) * max_size / 180.0 );
114 29 100         if (lat_1_idx >= max_size) lat_1_idx = max_size - 1;
115 29           lat_1_idx >>= shift;
116            
117 29           uint_fast32_t lon_0_idx = ( (uint_fast32_t)( ( lon_0 + 180.0 ) * max_size / 360.0 ) % max_size ) >> shift;
118            
119 29           uint_fast32_t lon_1_idx = ( (uint_fast32_t)( ( lon_1 + 180.0 ) * max_size / 360.0 ) % max_size ) >> shift;
120            
121 29 50         if (lat_0_idx > lat_1_idx) {
122 0           uint_fast32_t tmp = lat_1_idx;
123 0           lat_1_idx = lat_0_idx;
124 0           lat_0_idx = tmp;
125             }
126            
127             /* Populate return values (all unsigned integers) */
128            
129 29           Inline_Stack_Reset;
130 29 50         Inline_Stack_Push(sv_2mortal(newSVuv( grid_level )));
131 29 50         Inline_Stack_Push(sv_2mortal(newSVuv( grid_size )));
132 29 50         Inline_Stack_Push(sv_2mortal(newSVuv( max_grid_idx )));
133 29 50         Inline_Stack_Push(sv_2mortal(newSVuv( lat_0_idx )));
134 29 50         Inline_Stack_Push(sv_2mortal(newSVuv( lat_1_idx )));
135 29 50         Inline_Stack_Push(sv_2mortal(newSVuv( lon_0_idx )));
136 29 50         Inline_Stack_Push(sv_2mortal(newSVuv( lon_1_idx )));
137 29           Inline_Stack_Done;
138 29           }
139              
140              
141 62           void ComputeAreaExtrema_double( int tile_adjust, unsigned long max_size, unsigned int max_level, double p_lat, double p_lat_rad, double p_lon, double polar_circumference, double search_radius ) {
142 62           Inline_Stack_Vars; /* For return values */
143            
144 62           uint_fast8_t grid_level = 0;
145 62           uint_fast32_t grid_size = 0;
146 62           uint_fast32_t max_grid_idx = 0;
147            
148             // Determine grid level to search at
149            
150             // Size of most detailed grid tile at this point (in meters)
151 62           double ns_indexed_meters = ( polar_circumference / 2.0 ) / max_size; // Dividing by two to get pole-to-pole distance
152            
153 62           uint_fast8_t shift = fast_log2_double( search_radius / ns_indexed_meters );
154            
155 62           shift += tile_adjust;
156            
157             // Make sure the shift we computed lies within the index levels
158             if (shift < 0) {
159             shift = 0;
160 62 100         } else if (shift >= max_level) {
161 14           shift = max_level - 1;
162             }
163            
164             // Shift is relative to the highest-resolution zoom level
165             // Determine grid level to use
166 62           grid_level = max_level - shift;
167            
168 62           grid_size = 1 << (grid_level+1);
169 62           max_grid_idx = grid_size - 1;
170            
171             // Determine which grid tiles need to be checked
172            
173             // Get search point's grid indices
174            
175 62           double lat_meter_in_degrees = 360.0 / polar_circumference;
176            
177 62           double lat_radius = search_radius * lat_meter_in_degrees;
178 62           double lat_radius_rad = DEG2RAD( lat_radius );
179 62           double lon_radius = RAD2DEG( atan2( sin(lat_radius_rad), cos(lat_radius_rad) * cos(p_lat_rad) ) );
180            
181 62           double lat_0 = p_lat - lat_radius;
182 62           double lat_1 = p_lat + lat_radius;
183            
184 62           double lon_0 = p_lon - lon_radius;
185 62           double lon_1 = p_lon + lon_radius;
186            
187            
188 62 100         if (lat_0 <= -90) {
189 15           lat_0 = -90;
190             }
191            
192 62 100         if (lat_1 >= 90) {
193 10           lat_1 = 90;
194             }
195            
196 62 100         if ( lon_0 < -180.0 ) { lon_0 += 360.0; }
197 58 50         else if ( lon_0 > 180.0 ) { lon_0 -= 360.0; }
198            
199 62 50         if ( lon_1 < -180.0 ) { lon_1 += 360.0; }
200 62 100         else if ( lon_1 > 180.0 ) { lon_1 -= 360.0; }
201            
202 62 50         if ( lat_0 < -90.0 ) { lat_0 = -90.0; }
203 62 50         else if ( lat_0 > 90.0 ) { lat_0 = 90.0; }
204            
205 62 50         if ( lat_1 < -90.0 ) { lat_1 = -90.0; }
206 62 50         else if ( lat_1 > 90.0 ) { lat_1 = 90.0; }
207            
208 62           uint_fast32_t lat_0_idx = (uint_fast32_t)( ( lat_0 + 90.0 ) * max_size / 180.0 );
209 62 100         if (lat_0_idx >= max_size) lat_0_idx = max_size - 1;
210 62           lat_0_idx >>= shift;
211            
212 62           uint_fast32_t lat_1_idx = (uint_fast32_t)( ( lat_1 + 90.0 ) * max_size / 180.0 );
213 62 100         if (lat_1_idx >= max_size) lat_1_idx = max_size - 1;
214 62           lat_1_idx >>= shift;
215            
216 62           uint_fast32_t lon_0_idx = ( (uint_fast32_t)( ( lon_0 + 180.0 ) * max_size / 360.0 ) % max_size ) >> shift;
217            
218 62           uint_fast32_t lon_1_idx = ( (uint_fast32_t)( ( lon_1 + 180.0 ) * max_size / 360.0 ) % max_size ) >> shift;
219            
220 62 50         if (lat_0_idx > lat_1_idx) {
221 0           uint_fast32_t tmp = lat_1_idx;
222 0           lat_1_idx = lat_0_idx;
223 0           lat_0_idx = tmp;
224             }
225            
226             /* Populate return values (all unsigned integers) */
227            
228 62           Inline_Stack_Reset;
229 62 50         Inline_Stack_Push(sv_2mortal(newSVuv( grid_level )));
230 62 50         Inline_Stack_Push(sv_2mortal(newSVuv( grid_size )));
231 62 50         Inline_Stack_Push(sv_2mortal(newSVuv( max_grid_idx )));
232 62 50         Inline_Stack_Push(sv_2mortal(newSVuv( lat_0_idx )));
233 62 50         Inline_Stack_Push(sv_2mortal(newSVuv( lat_1_idx )));
234 62 50         Inline_Stack_Push(sv_2mortal(newSVuv( lon_0_idx )));
235 62 50         Inline_Stack_Push(sv_2mortal(newSVuv( lon_1_idx )));
236 62           Inline_Stack_Done;
237 62           }
238              
239              
240              
241              
242             /*
243             Compute distance between two points on a sphere
244            
245             diameter is in meters
246             lat_0, lon_0, lat_1, and lon_1 are in radians
247             Distance returned is in meters
248            
249             To compute a distance first call SetUpDistance(...) with the first point
250             then call HaversineDistance(...) to get the distance to a second point.
251            
252             The C version can use either floats or doubles whereas Perl uses doubles.
253             When using floats instead of doubles the loss of precision is typically less
254             than a meter (about 2 meters in the worst case). On modern hardware there
255             should be little noticable difference between using floats and doubles. On
256             older hardware or in embedded systems floats may give better performance.
257             */
258              
259              
260             /* Functions using floats */
261              
262             float f_diameter, f_lat_1, f_lon_1;
263             float f_cos_lat_1;
264              
265 51           void SetUpDistance_float(float new_diameter, float new_lat_1, float new_lon_1) {
266 51           f_diameter = new_diameter;
267 51           f_lat_1 = new_lat_1;
268 51           f_lon_1 = new_lon_1;
269 51           f_cos_lat_1 = cosf( new_lat_1 );
270 51           }
271              
272 170           float HaversineDistance_float(float lat_0, float lon_0) {
273 170           float sin_lat_diff_over_2 = sinf( ( lat_0 - f_lat_1 ) / 2.0 );
274 170           float sin_lon_diff_over_2 = sinf( ( lon_0 - f_lon_1 ) / 2.0 );
275            
276 340           float n = ( sin_lat_diff_over_2 * sin_lat_diff_over_2 )
277             + (
278 170           ( sin_lon_diff_over_2 * sin_lon_diff_over_2 )
279 170           * f_cos_lat_1
280 170           * cosf( lat_0 )
281             );
282            
283             /* The haversine formula may get messy around antipodal points so clip to the largest sane value. */
284 170 50         if ( n < 0.0 ) { n = 0.0; }
285            
286 170           return f_diameter * asinf( sqrtf(n) );
287             }
288              
289              
290              
291             /* Functions using doubles */
292              
293             double d_diameter, d_lat_1, d_lon_1;
294             double d_cos_lat_1;
295              
296 140           void SetUpDistance_double(double new_diameter, double new_lat_1, double new_lon_1) {
297 140           d_diameter = new_diameter;
298 140           d_lat_1 = new_lat_1;
299 140           d_lon_1 = new_lon_1;
300 140           d_cos_lat_1 = cos( new_lat_1 );
301 140           }
302              
303 1218           double HaversineDistance_double(double lat_0, double lon_0) {
304 1218           double sin_lat_diff_over_2 = sin( ( lat_0 - d_lat_1 ) / 2.0 );
305 1218           double sin_lon_diff_over_2 = sin( ( lon_0 - d_lon_1 ) / 2.0 );
306            
307 2436           double n = ( sin_lat_diff_over_2 * sin_lat_diff_over_2 )
308             + (
309 1218           ( sin_lon_diff_over_2 * sin_lon_diff_over_2 )
310 1218           * d_cos_lat_1
311 1218           * cos( lat_0 )
312             );
313            
314             /* The haversine formula may get messy around antipodal points so clip to the largest sane value. */
315 1218 50         if ( n < 0.0 ) { n = 0.0; }
316            
317 1218           return d_diameter * asin( sqrt(n) );
318             }
319              
320             /* End of C code */
321              
322             MODULE = Geo::Index PACKAGE = Geo::Index
323              
324             PROTOTYPES: DISABLE
325              
326              
327             unsigned int
328             GetCCodeVersion ()
329              
330             unsigned int
331             fast_log2_double (n)
332             double n
333              
334             unsigned int
335             fast_log2_float (n)
336             float n
337              
338             void
339             ComputeAreaExtrema_float (tile_adjust, max_size, max_level, p_lat, p_lat_rad, p_lon, polar_circumference, search_radius)
340             int tile_adjust
341             unsigned long max_size
342             unsigned int max_level
343             float p_lat
344             float p_lat_rad
345             float p_lon
346             float polar_circumference
347             float search_radius
348             PREINIT:
349             I32* temp;
350             PPCODE:
351 29           temp = PL_markstack_ptr++;
352 29           ComputeAreaExtrema_float(tile_adjust, max_size, max_level, p_lat, p_lat_rad, p_lon, polar_circumference, search_radius);
353 29 50         if (PL_markstack_ptr != temp) {
354             /* truly void, because dXSARGS not invoked */
355 0           PL_markstack_ptr = temp;
356 0           XSRETURN_EMPTY; /* return empty stack */
357             }
358             /* must have used dXSARGS; list context implied */
359 29           return; /* assume stack size is correct */
360              
361             void
362             ComputeAreaExtrema_double (tile_adjust, max_size, max_level, p_lat, p_lat_rad, p_lon, polar_circumference, search_radius)
363             int tile_adjust
364             unsigned long max_size
365             unsigned int max_level
366             double p_lat
367             double p_lat_rad
368             double p_lon
369             double polar_circumference
370             double search_radius
371             PREINIT:
372             I32* temp;
373             PPCODE:
374 62           temp = PL_markstack_ptr++;
375 62           ComputeAreaExtrema_double(tile_adjust, max_size, max_level, p_lat, p_lat_rad, p_lon, polar_circumference, search_radius);
376 62 50         if (PL_markstack_ptr != temp) {
377             /* truly void, because dXSARGS not invoked */
378 0           PL_markstack_ptr = temp;
379 0           XSRETURN_EMPTY; /* return empty stack */
380             }
381             /* must have used dXSARGS; list context implied */
382 62           return; /* assume stack size is correct */
383              
384             void
385             SetUpDistance_float (new_diameter, new_lat_1, new_lon_1)
386             float new_diameter
387             float new_lat_1
388             float new_lon_1
389             PREINIT:
390             I32* temp;
391             PPCODE:
392 51           temp = PL_markstack_ptr++;
393 51           SetUpDistance_float(new_diameter, new_lat_1, new_lon_1);
394 51 50         if (PL_markstack_ptr != temp) {
395             /* truly void, because dXSARGS not invoked */
396 51           PL_markstack_ptr = temp;
397 51           XSRETURN_EMPTY; /* return empty stack */
398             }
399             /* must have used dXSARGS; list context implied */
400 0           return; /* assume stack size is correct */
401              
402             float
403             HaversineDistance_float (lat_0, lon_0)
404             float lat_0
405             float lon_0
406              
407             void
408             SetUpDistance_double (new_diameter, new_lat_1, new_lon_1)
409             double new_diameter
410             double new_lat_1
411             double new_lon_1
412             PREINIT:
413             I32* temp;
414             PPCODE:
415 140           temp = PL_markstack_ptr++;
416 140           SetUpDistance_double(new_diameter, new_lat_1, new_lon_1);
417 140 50         if (PL_markstack_ptr != temp) {
418             /* truly void, because dXSARGS not invoked */
419 140           PL_markstack_ptr = temp;
420 140           XSRETURN_EMPTY; /* return empty stack */
421             }
422             /* must have used dXSARGS; list context implied */
423 0           return; /* assume stack size is correct */
424              
425             double
426             HaversineDistance_double (lat_0, lon_0)
427             double lat_0
428             double lon_0
429