line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Geo::Distance; |
2
|
4
|
|
|
4
|
|
869358
|
use 5.008001; |
|
4
|
|
|
|
|
30
|
|
3
|
4
|
|
|
4
|
|
23
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
78
|
|
4
|
4
|
|
|
4
|
|
17
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
190
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.23'; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=encoding utf8 |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 NAME |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
Geo::Distance - Calculate distances and closest locations. (DEPRECATED) |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 SYNOPSIS |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
use Geo::Distance; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
my $geo = new Geo::Distance; |
18
|
|
|
|
|
|
|
$geo->formula('hsin'); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
$geo->reg_unit( 'toad_hop', 200120 ); |
21
|
|
|
|
|
|
|
$geo->reg_unit( 'frog_hop' => 6 => 'toad_hop' ); |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
my $distance = $geo->distance( 'unit_type', $lon1,$lat1 => $lon2,$lat2 ); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
my $locations = $geo->closest( |
26
|
|
|
|
|
|
|
dbh => $dbh, |
27
|
|
|
|
|
|
|
table => $table, |
28
|
|
|
|
|
|
|
lon => $lon, |
29
|
|
|
|
|
|
|
lat => $lat, |
30
|
|
|
|
|
|
|
unit => $unit_type, |
31
|
|
|
|
|
|
|
distance => $dist_in_unit |
32
|
|
|
|
|
|
|
); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 DESCRIPTION |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
This perl library aims to provide as many tools to make it as simple as possible to calculate |
37
|
|
|
|
|
|
|
distances between geographic points, and anything that can be derived from that. Currently |
38
|
|
|
|
|
|
|
there is support for finding the closest locations within a specified distance, to find the |
39
|
|
|
|
|
|
|
closest number of points to a specified point, and to do basic point-to-point distance |
40
|
|
|
|
|
|
|
calculations. |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 DEPRECATED |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
This module has been gutted and is now a wrapper around L, please |
45
|
|
|
|
|
|
|
use that module instead. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
When switching from this module to L make sure you reverse the |
48
|
|
|
|
|
|
|
coordinates when passing them to L. GIS::Distance takes |
49
|
|
|
|
|
|
|
lat/lon pairs while Geo::Distance takes lon/lat pairs. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head1 STABILITY |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
The interface to Geo::Distance is fairly stable nowadays. If this changes it |
54
|
|
|
|
|
|
|
will be noted here. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
C<0.21> - All distance calculations are now handled by L. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
C<0.10> - The closest() method has a changed argument syntax and no longer supports array searches. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
C<0.09> - Changed the behavior of the reg_unit function. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
C<0.07> - OO only, and other changes all over. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=cut |
65
|
|
|
|
|
|
|
|
66
|
4
|
|
|
4
|
|
1835
|
use GIS::Distance; |
|
4
|
|
|
|
|
144091
|
|
|
4
|
|
|
|
|
149
|
|
67
|
4
|
|
|
4
|
|
1727
|
use GIS::Distance::Constants qw( :all ); |
|
4
|
|
|
|
|
9417
|
|
|
4
|
|
|
|
|
560
|
|
68
|
4
|
|
|
4
|
|
30
|
use Carp qw( croak ); |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
154
|
|
69
|
4
|
|
|
4
|
|
24
|
use Const::Fast; |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
21
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head1 FORMULAS |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
C - See L. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
C - See L. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
C - See L. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
C - See L. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
C - See L. |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
C - See L. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
C - See L. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=cut |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
const our %GEO_TO_GIS_FORMULA_MAP => (qw( |
90
|
|
|
|
|
|
|
cos Cosine |
91
|
|
|
|
|
|
|
gcd GreatCircle |
92
|
|
|
|
|
|
|
hsin Haversine |
93
|
|
|
|
|
|
|
mt MathTrig |
94
|
|
|
|
|
|
|
null Null |
95
|
|
|
|
|
|
|
polar Polar |
96
|
|
|
|
|
|
|
tv Vincenty |
97
|
|
|
|
|
|
|
)); |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
const our @FORMULAS => (keys %GEO_TO_GIS_FORMULA_MAP); |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=head1 PROPERTIES |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=head2 UNITS |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
All functions accept a unit type to do the computations of distance with. By default no units |
106
|
|
|
|
|
|
|
are defined in a Geo::Distance object. You can add units with reg_unit() or create some default |
107
|
|
|
|
|
|
|
units with default_units(). |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head2 LATITUDE AND LONGITUDE |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
When a function needs a longitude and latitude, they must always be in decimal degree format. |
112
|
|
|
|
|
|
|
Here is some sample code for converting from other formats to decimal: |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# DMS to Decimal |
115
|
|
|
|
|
|
|
my $decimal = $degrees + ($minutes/60) + ($seconds/3600); |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# Precision Six Integer to Decimal |
118
|
|
|
|
|
|
|
my $decimal = $integer * .000001; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
If you want to convert from decimal radians to degrees you can use Math::Trig's rad2deg function. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head1 METHODS |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head2 new |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
my $geo = new Geo::Distance; |
127
|
|
|
|
|
|
|
my $geo = new Geo::Distance( no_units=>1 ); |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
Returns a blessed Geo::Distance object. The new constructor accepts one optional |
130
|
|
|
|
|
|
|
argument. |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
no_units - Whether or not to load the default units. Defaults to 0 (false). |
133
|
|
|
|
|
|
|
kilometer, kilometre, meter, metre, centimeter, centimetre, millimeter, |
134
|
|
|
|
|
|
|
millimetre, yard, foot, inch, light second, mile, nautical mile, |
135
|
|
|
|
|
|
|
poppy seed, barleycorn, rod, pole, perch, chain, furlong, league, |
136
|
|
|
|
|
|
|
fathom |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=cut |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub new { |
141
|
4
|
|
|
4
|
1
|
378
|
my $class = shift; |
142
|
4
|
|
|
|
|
13
|
my $self = bless {}, $class; |
143
|
4
|
|
|
|
|
13
|
my %args = @_; |
144
|
|
|
|
|
|
|
|
145
|
4
|
|
|
|
|
26
|
$self->{formula} = 'hsin'; |
146
|
4
|
|
|
|
|
42
|
$self->{units} = {}; |
147
|
4
|
50
|
|
|
|
17
|
if(!$args{no_units}){ |
148
|
4
|
|
|
|
|
20
|
$self->reg_unit( $KILOMETER_RHO, 'kilometer' ); |
149
|
4
|
|
|
|
|
17
|
$self->reg_unit( 1000, 'meter', => 'kilometer' ); |
150
|
4
|
|
|
|
|
13
|
$self->reg_unit( 100, 'centimeter' => 'meter' ); |
151
|
4
|
|
|
|
|
19
|
$self->reg_unit( 10, 'millimeter' => 'centimeter' ); |
152
|
|
|
|
|
|
|
|
153
|
4
|
|
|
|
|
20
|
$self->reg_unit( 'kilometre' => 'kilometer' ); |
154
|
4
|
|
|
|
|
16
|
$self->reg_unit( 'metre' => 'meter' ); |
155
|
4
|
|
|
|
|
12
|
$self->reg_unit( 'centimetre' => 'centimeter' ); |
156
|
4
|
|
|
|
|
12
|
$self->reg_unit( 'millimetre' => 'millimeter' ); |
157
|
|
|
|
|
|
|
|
158
|
4
|
|
|
|
|
12
|
$self->reg_unit( 'mile' => 1609.344, 'meter' ); |
159
|
4
|
|
|
|
|
14
|
$self->reg_unit( 'nautical mile' => 1852, 'meter' ); |
160
|
4
|
|
|
|
|
15
|
$self->reg_unit( 'yard' => 0.9144, 'meter' ); |
161
|
4
|
|
|
|
|
14
|
$self->reg_unit( 3, 'foot' => 'yard' ); |
162
|
4
|
|
|
|
|
14
|
$self->reg_unit( 12, 'inch' => 'foot' ); |
163
|
4
|
|
|
|
|
13
|
$self->reg_unit( 'light second' => 299792458, 'meter' ); |
164
|
|
|
|
|
|
|
|
165
|
4
|
|
|
|
|
15
|
$self->reg_unit( 'poppy seed' => 2.11, 'millimeter' ); |
166
|
4
|
|
|
|
|
19
|
$self->reg_unit( 'barleycorn' => 8.467, 'millimeter' ); |
167
|
4
|
|
|
|
|
18
|
$self->reg_unit( 'rod' => 5.0292, 'meter' ); |
168
|
4
|
|
|
|
|
18
|
$self->reg_unit( 'pole' => 'rod' ); |
169
|
4
|
|
|
|
|
12
|
$self->reg_unit( 'perch' => 'rod' ); |
170
|
4
|
|
|
|
|
15
|
$self->reg_unit( 'chain' => 20.1168, 'meter' ); |
171
|
4
|
|
|
|
|
14
|
$self->reg_unit( 'furlong' => 201.168, 'meter' ); |
172
|
4
|
|
|
|
|
11
|
$self->reg_unit( 'league' => 4.828032, 'kilometer' ); |
173
|
4
|
|
|
|
|
8
|
$self->reg_unit( 1.8288, 'fathom' => 'meter' ); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
4
|
|
|
|
|
15
|
return $self; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=head2 formula |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
if($geo->formula eq 'hsin'){ ... } |
182
|
|
|
|
|
|
|
$geo->formula('cos'); |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Allows you to retrieve and set the formula that is currently being used to |
185
|
|
|
|
|
|
|
calculate distances. See the available L. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
C is the default. Both C and C are inferior in speed |
188
|
|
|
|
|
|
|
and accuracy to C. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=cut |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub formula { |
193
|
2
|
|
|
2
|
1
|
482
|
my $self = shift; |
194
|
|
|
|
|
|
|
|
195
|
2
|
50
|
|
|
|
7
|
return $self->{formula} if !$_[0]; |
196
|
2
|
|
|
|
|
5
|
my $formula = shift; |
197
|
|
|
|
|
|
|
|
198
|
2
|
|
|
|
|
6
|
my $gis_formula = $GEO_TO_GIS_FORMULA_MAP{ $formula }; |
199
|
|
|
|
|
|
|
|
200
|
2
|
50
|
|
|
|
5
|
croak( |
201
|
|
|
|
|
|
|
'Unknown formula (available formulas are ', |
202
|
|
|
|
|
|
|
join(', ', sort @FORMULAS), |
203
|
|
|
|
|
|
|
')', |
204
|
|
|
|
|
|
|
) if !$gis_formula; |
205
|
|
|
|
|
|
|
|
206
|
2
|
|
|
|
|
3
|
$self->{formula} = $formula; |
207
|
2
|
|
|
|
|
4
|
$self->{gis_formula} = $gis_formula; |
208
|
|
|
|
|
|
|
|
209
|
2
|
|
|
|
|
3
|
return $formula; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head2 reg_unit |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
$geo->reg_unit( $radius, $key ); |
215
|
|
|
|
|
|
|
$geo->reg_unit( $key1 => $key2 ); |
216
|
|
|
|
|
|
|
$geo->reg_unit( $count1, $key1 => $key2 ); |
217
|
|
|
|
|
|
|
$geo->reg_unit( $key1 => $count2, $key2 ); |
218
|
|
|
|
|
|
|
$geo->reg_unit( $count1, $key1 => $count2, $key2 ); |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
This method is used to create custom unit types. There are several ways of calling it, |
221
|
|
|
|
|
|
|
depending on if you are defining the unit from scratch, or if you are basing it off |
222
|
|
|
|
|
|
|
of an existing unit (such as saying 12 inches = 1 foot ). When defining a unit from |
223
|
|
|
|
|
|
|
scratch you pass the name and rho (radius of the earth in that unit) value. |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
So, if you wanted to do your calculations in human adult steps you would have to have an |
226
|
|
|
|
|
|
|
average human adult walk from the crust of the earth to the core (ignore the fact that |
227
|
|
|
|
|
|
|
this is impossible). So, assuming we did this and we came up with 43,200 steps, you'd |
228
|
|
|
|
|
|
|
do something like the following. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# Define adult step unit. |
231
|
|
|
|
|
|
|
$geo->reg_unit( 43200, 'adult step' ); |
232
|
|
|
|
|
|
|
# This can be read as "It takes 43,200 adult_steps to walk the radius of the earth". |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
Now, if you also wanted to do distances in baby steps you might think "well, now I |
235
|
|
|
|
|
|
|
gotta get a baby to walk to the center of the earth". But, you don't have to! If you do some |
236
|
|
|
|
|
|
|
research you'll find (no research was actually conducted) that there are, on average, |
237
|
|
|
|
|
|
|
4.7 baby steps in each adult step. |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# Define baby step unit. |
240
|
|
|
|
|
|
|
$geo->reg_unit( 4.7, 'baby step' => 'adult step' ); |
241
|
|
|
|
|
|
|
# This can be read as "4.7 baby steps is the same as one adult step". |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
And if we were doing this in reverse and already had the baby step unit but not |
244
|
|
|
|
|
|
|
the adult step, you would still use the exact same syntax as above. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=cut |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub reg_unit { |
249
|
92
|
|
|
92
|
1
|
123
|
my $self = shift; |
250
|
92
|
|
|
|
|
118
|
my $units = $self->{units}; |
251
|
92
|
|
|
|
|
142
|
my($count1,$key1,$count2,$key2); |
252
|
92
|
|
|
|
|
114
|
$count1 = shift; |
253
|
92
|
100
|
66
|
|
|
317
|
if($count1=~/[^\.0-9]/ or !@_){ $key1=$count1; $count1=1; } |
|
64
|
|
|
|
|
92
|
|
|
64
|
|
|
|
|
81
|
|
254
|
28
|
|
|
|
|
52
|
else{ $key1 = shift; } |
255
|
92
|
100
|
|
|
|
156
|
if(!@_){ |
256
|
4
|
|
|
|
|
15
|
$units->{$key1} = $count1; |
257
|
|
|
|
|
|
|
}else{ |
258
|
88
|
|
|
|
|
120
|
$count2 = shift; |
259
|
88
|
100
|
66
|
|
|
302
|
if($count2=~/[^\.0-9]/ or !@_){ $key2=$count2; $count2=1; } |
|
48
|
|
|
|
|
71
|
|
|
48
|
|
|
|
|
65
|
|
260
|
40
|
|
|
|
|
65
|
else{ $key2 = shift; } |
261
|
88
|
50
|
|
|
|
165
|
($key1,$key2) = ($key2,$key1) if( defined $units->{$key1} ); |
262
|
88
|
|
|
|
|
244
|
$units->{$key1} = ($units->{$key2}*$count1) / $count2; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=head2 distance |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
my $distance = $geo->distance( 'unit_type', $lon1,$lat1 => $lon2,$lat2 ); |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
Calculates the distance between two lon/lat points. |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=cut |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub distance { |
275
|
39
|
|
|
39
|
1
|
117
|
my ($self, $unit, $lon1, $lat1, $lon2, $lat2) = @_; |
276
|
|
|
|
|
|
|
|
277
|
39
|
|
|
|
|
106
|
my $unit_rho = $self->{units}->{$unit}; |
278
|
39
|
50
|
|
|
|
84
|
croak('Unkown unit type "' . $unit . '"') if !$unit_rho; |
279
|
|
|
|
|
|
|
|
280
|
39
|
|
|
|
|
147
|
my $gis = GIS::Distance->new( $self->{gis_formula} ); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# Reverse lon/lat to lat/lon, the way GIS::Distance wants it. |
283
|
39
|
|
|
|
|
10145
|
my $km = $gis->{code}->( $lat1, $lon1, $lat2, $lon2 ); |
284
|
|
|
|
|
|
|
|
285
|
39
|
|
|
|
|
1469
|
return $km * ($unit_rho / $KILOMETER_RHO); |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
4
|
|
|
4
|
|
5146
|
use Math::Trig qw( acos asin atan deg2rad great_circle_distance pi tan ); |
|
4
|
|
|
|
|
53646
|
|
|
4
|
|
|
|
|
5210
|
|
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub old_distance { |
291
|
0
|
|
|
0
|
0
|
0
|
my($self,$unit,$lon1,$lat1,$lon2,$lat2) = @_; |
292
|
0
|
0
|
|
|
|
0
|
croak('Unkown unit type "'.$unit.'"') unless($unit = $self->{units}->{$unit}); |
293
|
|
|
|
|
|
|
|
294
|
0
|
0
|
|
|
|
0
|
return 0 if $self->{formula} eq 'null'; |
295
|
|
|
|
|
|
|
|
296
|
0
|
0
|
|
|
|
0
|
if($self->{formula} eq 'mt'){ |
297
|
0
|
|
|
|
|
0
|
return great_circle_distance( |
298
|
|
|
|
|
|
|
deg2rad($lon1), |
299
|
|
|
|
|
|
|
deg2rad(90 - $lat1), |
300
|
|
|
|
|
|
|
deg2rad($lon2), |
301
|
|
|
|
|
|
|
deg2rad(90 - $lat2), |
302
|
|
|
|
|
|
|
$unit |
303
|
|
|
|
|
|
|
); |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
0
|
|
|
|
|
0
|
$lon1 = deg2rad($lon1); $lat1 = deg2rad($lat1); |
|
0
|
|
|
|
|
0
|
|
307
|
0
|
|
|
|
|
0
|
$lon2 = deg2rad($lon2); $lat2 = deg2rad($lat2); |
|
0
|
|
|
|
|
0
|
|
308
|
0
|
|
|
|
|
0
|
my $c; |
309
|
0
|
0
|
|
|
|
0
|
if($self->{formula} eq 'cos'){ |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
310
|
0
|
|
|
|
|
0
|
my $a = sin($lat1) * sin($lat2); |
311
|
0
|
|
|
|
|
0
|
my $b = cos($lat1) * cos($lat2) * cos($lon2 - $lon1); |
312
|
0
|
|
|
|
|
0
|
$c = acos($a + $b); |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
elsif($self->{formula} eq 'hsin'){ |
315
|
0
|
|
|
|
|
0
|
my $dlon = $lon2 - $lon1; |
316
|
0
|
|
|
|
|
0
|
my $dlat = $lat2 - $lat1; |
317
|
0
|
|
|
|
|
0
|
my $a = (sin($dlat/2)) ** 2 + cos($lat1) * cos($lat2) * (sin($dlon/2)) ** 2; |
318
|
0
|
|
|
|
|
0
|
$c = 2 * atan2(sqrt($a), sqrt(abs(1-$a))); |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
elsif($self->{formula} eq 'polar'){ |
321
|
0
|
|
|
|
|
0
|
my $a = pi/2 - $lat1; |
322
|
0
|
|
|
|
|
0
|
my $b = pi/2 - $lat2; |
323
|
0
|
|
|
|
|
0
|
$c = sqrt( $a ** 2 + $b ** 2 - 2 * $a * $b * cos($lon2 - $lon1) ); |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
elsif($self->{formula} eq 'gcd'){ |
326
|
0
|
|
|
|
|
0
|
$c = 2*asin( sqrt( |
327
|
|
|
|
|
|
|
( sin(($lat1-$lat2)/2) )**2 + |
328
|
|
|
|
|
|
|
cos($lat1) * cos($lat2) * |
329
|
|
|
|
|
|
|
( sin(($lon1-$lon2)/2) )**2 |
330
|
|
|
|
|
|
|
) ); |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# Eric Samuelson recommended this formula. |
333
|
|
|
|
|
|
|
# http://forums.devshed.com/t54655/sc3d021a264676b9b440ea7cbe1f775a1.html |
334
|
|
|
|
|
|
|
# http://williams.best.vwh.net/avform.htm |
335
|
|
|
|
|
|
|
# It seems to produce the same results at the hsin formula, so... |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
#my $dlon = $lon2 - $lon1; |
338
|
|
|
|
|
|
|
#my $dlat = $lat2 - $lat1; |
339
|
|
|
|
|
|
|
#my $a = (sin($dlat / 2)) ** 2 |
340
|
|
|
|
|
|
|
# + cos($lat1) * cos($lat2) * (sin($dlon / 2)) ** 2; |
341
|
|
|
|
|
|
|
#$c = 2 * atan2(sqrt($a), sqrt(1 - $a)); |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
elsif($self->{formula} eq 'tv'){ |
344
|
0
|
|
|
|
|
0
|
my($a,$b,$f) = (6378137,6356752.3142,1/298.257223563); |
345
|
0
|
|
|
|
|
0
|
my $l = $lon2 - $lon1; |
346
|
0
|
|
|
|
|
0
|
my $u1 = atan((1-$f) * tan($lat1)); |
347
|
0
|
|
|
|
|
0
|
my $u2 = atan((1-$f) * tan($lat2)); |
348
|
0
|
|
|
|
|
0
|
my $sin_u1 = sin($u1); my $cos_u1 = cos($u1); |
|
0
|
|
|
|
|
0
|
|
349
|
0
|
|
|
|
|
0
|
my $sin_u2 = sin($u2); my $cos_u2 = cos($u2); |
|
0
|
|
|
|
|
0
|
|
350
|
0
|
|
|
|
|
0
|
my $lambda = $l; |
351
|
0
|
|
|
|
|
0
|
my $lambda_pi = 2 * pi; |
352
|
0
|
|
|
|
|
0
|
my $iter_limit = 20; |
353
|
0
|
|
|
|
|
0
|
my($cos_sq_alpha,$sin_sigma,$cos2sigma_m,$cos_sigma,$sigma); |
354
|
0
|
|
0
|
|
|
0
|
while( abs($lambda-$lambda_pi) > 1e-12 && --$iter_limit>0 ){ |
355
|
0
|
|
|
|
|
0
|
my $sin_lambda = sin($lambda); my $cos_lambda = cos($lambda); |
|
0
|
|
|
|
|
0
|
|
356
|
0
|
|
|
|
|
0
|
$sin_sigma = sqrt(($cos_u2*$sin_lambda) * ($cos_u2*$sin_lambda) + |
357
|
|
|
|
|
|
|
($cos_u1*$sin_u2-$sin_u1*$cos_u2*$cos_lambda) * ($cos_u1*$sin_u2-$sin_u1*$cos_u2*$cos_lambda)); |
358
|
0
|
|
|
|
|
0
|
$cos_sigma = $sin_u1*$sin_u2 + $cos_u1*$cos_u2*$cos_lambda; |
359
|
0
|
|
|
|
|
0
|
$sigma = atan2($sin_sigma, $cos_sigma); |
360
|
0
|
|
|
|
|
0
|
my $alpha = asin($cos_u1 * $cos_u2 * $sin_lambda / $sin_sigma); |
361
|
0
|
|
|
|
|
0
|
$cos_sq_alpha = cos($alpha) * cos($alpha); |
362
|
0
|
|
|
|
|
0
|
$cos2sigma_m = $cos_sigma - 2*$sin_u1*$sin_u2/$cos_sq_alpha; |
363
|
0
|
|
|
|
|
0
|
my $cc = $f/16*$cos_sq_alpha*(4+$f*(4-3*$cos_sq_alpha)); |
364
|
0
|
|
|
|
|
0
|
$lambda_pi = $lambda; |
365
|
0
|
|
|
|
|
0
|
$lambda = $l + (1-$cc) * $f * sin($alpha) * |
366
|
|
|
|
|
|
|
($sigma + $cc*$sin_sigma*($cos2sigma_m+$cc*$cos_sigma*(-1+2*$cos2sigma_m*$cos2sigma_m))); |
367
|
|
|
|
|
|
|
} |
368
|
0
|
0
|
|
|
|
0
|
undef if( $iter_limit==0 ); |
369
|
0
|
|
|
|
|
0
|
my $usq = $cos_sq_alpha*($a*$a-$b*$b)/($b*$b); |
370
|
0
|
|
|
|
|
0
|
my $aa = 1 + $usq/16384*(4096+$usq*(-768+$usq*(320-175*$usq))); |
371
|
0
|
|
|
|
|
0
|
my $bb = $usq/1024 * (256+$usq*(-128+$usq*(74-47*$usq))); |
372
|
0
|
|
|
|
|
0
|
my $delta_sigma = $bb*$sin_sigma*($cos2sigma_m+$bb/4*($cos_sigma*(-1+2*$cos2sigma_m*$cos2sigma_m)- |
373
|
|
|
|
|
|
|
$bb/6*$cos2sigma_m*(-3+4*$sin_sigma*$sin_sigma)*(-3+4*$cos2sigma_m*$cos2sigma_m))); |
374
|
0
|
|
|
|
|
0
|
$c = ( $b*$aa*($sigma-$delta_sigma) ) / $self->{units}->{meter}; |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
else{ |
377
|
0
|
|
|
|
|
0
|
croak('Unkown distance formula "'.$self->{formula}.'"'); |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
|
380
|
0
|
|
|
|
|
0
|
return $unit * $c; |
381
|
|
|
|
|
|
|
} |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head2 closest |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
my $locations = $geo->closest( |
386
|
|
|
|
|
|
|
dbh => $dbh, |
387
|
|
|
|
|
|
|
table => $table, |
388
|
|
|
|
|
|
|
lon => $lon, |
389
|
|
|
|
|
|
|
lat => $lat, |
390
|
|
|
|
|
|
|
unit => $unit_type, |
391
|
|
|
|
|
|
|
distance => $dist_in_unit |
392
|
|
|
|
|
|
|
); |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
This method finds the closest locations within a certain distance and returns an |
395
|
|
|
|
|
|
|
array reference with a hash for each location matched. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
The closest method requires the following arguments: |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
dbh - a DBI database handle |
400
|
|
|
|
|
|
|
table - a table within dbh that contains the locations to search |
401
|
|
|
|
|
|
|
lon - the longitude of the center point |
402
|
|
|
|
|
|
|
lat - the latitude of the center point |
403
|
|
|
|
|
|
|
unit - the unit of measurement to use, such as "meter" |
404
|
|
|
|
|
|
|
distance - the distance, in units, from the center point to find locations |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
The following arguments are optional: |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
lon_field - the name of the field in the table that contains the longitude, defaults to "lon" |
409
|
|
|
|
|
|
|
lat_field - the name of the field in the table that contains the latitude, defaults to "lat" |
410
|
|
|
|
|
|
|
fields - an array reference of extra field names that you would like returned with each location |
411
|
|
|
|
|
|
|
where - additional rules for the where clause of the sql |
412
|
|
|
|
|
|
|
bind - an array reference of bind variables to go with the placeholders in where |
413
|
|
|
|
|
|
|
sort - whether to sort the locations by their distance, making the closest location the first returned |
414
|
|
|
|
|
|
|
count - return at most these number of locations (implies sort => 1) |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
This method uses some very simplistic calculations to SQL select out of the dbh. This |
417
|
|
|
|
|
|
|
means that the SQL should work fine on almost any database (only tested on MySQL and SQLite so far) and |
418
|
|
|
|
|
|
|
this also means that it is fast. Once this sub set of locations has been retrieved |
419
|
|
|
|
|
|
|
then more precise calculations are made to narrow down the result set. Remember, though, that |
420
|
|
|
|
|
|
|
the farther out your distance is, and the more locations in the table, the slower your searches will be. |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=cut |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub closest { |
425
|
4
|
|
|
4
|
1
|
9560
|
my $self = shift; |
426
|
4
|
|
|
|
|
26
|
my %args = @_; |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# Set defaults and prepare. |
429
|
4
|
|
33
|
|
|
15
|
my $dbh = $args{dbh} || croak('You must supply a database handle'); |
430
|
4
|
50
|
|
|
|
19
|
$dbh->isa('DBI::db') || croak('The dbh must be a DBI database handle'); |
431
|
4
|
|
33
|
|
|
13
|
my $table = $args{table} || croak('You must supply a table name'); |
432
|
4
|
|
33
|
|
|
10
|
my $lon = $args{lon} || croak('You must supply a longitude'); |
433
|
4
|
|
33
|
|
|
9
|
my $lat = $args{lat} || croak('You must supply a latitude'); |
434
|
4
|
|
33
|
|
|
9
|
my $distance = $args{distance} || croak('You must supply a distance'); |
435
|
4
|
|
33
|
|
|
9
|
my $unit = $args{unit} || croak('You must specify a unit type'); |
436
|
4
|
|
33
|
|
|
12
|
my $unit_size = $self->{units}->{$unit} || croak('This unit type is not known'); |
437
|
4
|
|
|
|
|
16
|
my $degrees = $distance / ( $DEG_RATIO * $unit_size ); |
438
|
4
|
|
50
|
|
|
15
|
my $lon_field = $args{lon_field} || 'lon'; |
439
|
4
|
|
50
|
|
|
12
|
my $lat_field = $args{lat_field} || 'lat'; |
440
|
4
|
|
50
|
|
|
13
|
my $fields = $args{fields} || []; |
441
|
|
|
|
|
|
|
|
442
|
4
|
|
|
|
|
10
|
unshift @$fields, $lon_field, $lat_field; |
443
|
4
|
|
|
|
|
12
|
$fields = join( ',', @$fields ); |
444
|
4
|
|
100
|
|
|
16
|
my $count = $args{count} || 0; |
445
|
4
|
|
66
|
|
|
15
|
my $sort = $args{sort} || ( $count ? 1 : 0 ); |
446
|
4
|
|
|
|
|
13
|
my $where = qq{$lon_field >= ? AND $lat_field >= ? AND $lon_field <= ? AND $lat_field <= ?}; |
447
|
4
|
50
|
|
|
|
10
|
$where .= ( $args{where} ? " AND ($args{where})" : '' ); |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
my @bind = ( |
450
|
|
|
|
|
|
|
$lon-$degrees, $lat-$degrees, |
451
|
|
|
|
|
|
|
$lon+$degrees, $lat+$degrees, |
452
|
4
|
50
|
|
|
|
21
|
( $args{bind} ? @{$args{bind}} : () ) |
|
0
|
|
|
|
|
0
|
|
453
|
|
|
|
|
|
|
); |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
# Retrieve locations. |
456
|
4
|
|
|
|
|
32
|
my $sth = $dbh->prepare(qq{ |
457
|
|
|
|
|
|
|
SELECT $fields |
458
|
|
|
|
|
|
|
FROM $table |
459
|
|
|
|
|
|
|
WHERE $where |
460
|
|
|
|
|
|
|
}); |
461
|
4
|
|
|
|
|
426
|
$sth->execute( @bind ); |
462
|
4
|
|
|
|
|
14
|
my $locations = []; |
463
|
4
|
|
|
|
|
152
|
while(my $location = $sth->fetchrow_hashref){ |
464
|
35
|
|
|
|
|
398
|
push @$locations, $location; |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
# Calculate distances. |
468
|
4
|
|
|
|
|
11
|
my $closest = []; |
469
|
4
|
|
|
|
|
10
|
foreach my $location (@$locations){ |
470
|
|
|
|
|
|
|
$location->{distance} = $self->distance( |
471
|
|
|
|
|
|
|
$unit, $lon, $lat, |
472
|
|
|
|
|
|
|
$location->{$lon_field}, |
473
|
35
|
|
|
|
|
81
|
$location->{$lat_field} |
474
|
|
|
|
|
|
|
); |
475
|
35
|
100
|
|
|
|
101
|
if( $location->{distance} <= $distance ){ |
476
|
32
|
|
|
|
|
68
|
push @$closest, $location; |
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
} |
479
|
4
|
|
|
|
|
11
|
$locations = $closest; |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
# Sort. |
482
|
4
|
100
|
|
|
|
10
|
if( $sort ){ |
483
|
1
|
|
|
|
|
8
|
@$locations = sort { $a->{distance} <=> $b->{distance} } @$locations; |
|
17
|
|
|
|
|
26
|
|
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
# Split for count. |
487
|
4
|
100
|
66
|
|
|
17
|
if( $count and $count < @$locations ){ |
488
|
1
|
|
|
|
|
6
|
splice @$locations, $count; |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
4
|
|
|
|
|
64
|
return $locations; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
1; |
495
|
|
|
|
|
|
|
__END__ |