| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# ABSTRACT: Geo::CheapRuler Fast Geodesic (Lat,Lon) approximations for city scale / not near the poles. Port of mapbox/cheap-ruler. |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package Geo::CheapRuler; |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
# |
|
6
|
|
|
|
|
|
|
# how to update README.md |
|
7
|
|
|
|
|
|
|
# pod2markdown CheapRuler.pm > ../../README.md |
|
8
|
|
|
|
|
|
|
# |
|
9
|
|
|
|
|
|
|
# $VERSION appears twice here, and once on GITHUB |
|
10
|
|
|
|
|
|
|
# |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = 'v0.1.1'; |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 NAME |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
Geo::CheapRuler - Fast GPS Geodesic functions, port of mapbox/cheap-ruler |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 VERSION |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
v0.1.1 |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
A collection of very fast approximations to common geodesic measurements. Useful for performance-sensitive code that measures things on a city scale (less than 500km, not near the poles). |
|
25
|
|
|
|
|
|
|
Can be an order of magnitude faster than Haversine based methods. |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
A Perl port of Mapbox's cheap-ruler v4.0.0 https://github.com/mapbox/cheap-ruler |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Very fast as they use just 1 trig function per call. |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 MATHS MODEL |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
The Maths model is based upon an approximation to Vincenty's formulae, which uses the Earth's actual shape, an oblate ellipsoid (squashed sphere). For 'city' scale work, it is still more accurate than |
|
34
|
|
|
|
|
|
|
the Haversine formulae (which uses several trig calls based upon a spherical Earth). For an explanation, see |
|
35
|
|
|
|
|
|
|
https://blog.mapbox.com/fast-geodesic-approximations-with-cheap-ruler-106f229ad016 |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=head1 EXPORT |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
Nothing |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 WEBSITE |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
https://github.com/aavmurphy/CheapRuler |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 USAGE |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
This module uses "geojson" style GPS geometery. Points are [lon, lat]. Polygons are a series of rings. The first ring is exterior and clockwise. Subsequent rings are interior (holes) and anticlockwise. |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
The latitude (lat) parameter passed to the constructor should be the 'median' of the lat's used, i.e. (max-lat + min-lat)/2. |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
Some methods have units, e.g. "expand a bounding box by 10 meters/miles/kilometers". The default 'units' are 'kilometers', you may which to use 'meters'. |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Data is passed / retured as arrayrefs, e.g. $p = [ 0.1, 54.1 ]; |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=head1 EXAMPLES |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
In the examples below, $p is a point, $a and $b are a line segment. |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
$p = [ -1, 57 ]; |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
$a = [0.1, 54.1]; |
|
62
|
|
|
|
|
|
|
$b = [0.2, 54.2]; |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
$ruler = Cheap::Ruler->new( ( 54.1 + 54.2 )/2, 'meters' ); |
|
65
|
|
|
|
|
|
|
# so the 'units' below are meters |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
$distance = $ruler->distance( $a, $b ); |
|
68
|
|
|
|
|
|
|
# return meters |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
$bearing = $ruler->bearing( $a, $b ); |
|
71
|
|
|
|
|
|
|
# returns degrees |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
$point = $ruler->destination( $a, 1000, 90); |
|
74
|
|
|
|
|
|
|
# returns a new point, 1000 units away at 90 degrees |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
$point = $ruler->offset( $p, 100, 200 ); |
|
77
|
|
|
|
|
|
|
# returns a point 100 units east, 200 units north |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
$distance = $ruler->lineDistance( [ $p, $a, $b ] ); |
|
80
|
|
|
|
|
|
|
# length of the line |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
$area = $ruler->area( [ |
|
83
|
|
|
|
|
|
|
[-67.031, 50.458], [-67.031, 50.534], [-66.929, 50.534], [-66.929, 50.458], [-67.031, 50.458] |
|
84
|
|
|
|
|
|
|
] ); # area of a polygon |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
$point = $ruler->along( [ [-67.031, 50.458], [-67.031, 50.534], [-66.929, 50.534] ], 2.5); |
|
87
|
|
|
|
|
|
|
# returns a point 2.5 units along the line |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
$distance = $ruler->pointToSegmentDistance( $p, $a, $b ); |
|
90
|
|
|
|
|
|
|
# distance from point to a 2 point line segment |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=cut |
|
93
|
|
|
|
|
|
|
|
|
94
|
1
|
|
|
1
|
|
84574
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
31
|
|
|
95
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
39
|
|
|
96
|
1
|
|
|
1
|
|
11
|
use v5.20; # min version for experimental signatures |
|
|
1
|
|
|
|
|
2
|
|
|
97
|
1
|
|
|
1
|
|
429
|
use experimental 'signatures'; |
|
|
1
|
|
|
|
|
2999
|
|
|
|
1
|
|
|
|
|
5
|
|
|
98
|
1
|
|
|
1
|
|
811
|
use Math::Trig; |
|
|
1
|
|
|
|
|
12264
|
|
|
|
1
|
|
|
|
|
2576
|
|
|
99
|
|
|
|
|
|
|
#use Data::Dumper; |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
our %FACTORS = ( |
|
102
|
|
|
|
|
|
|
kilometers => 1, |
|
103
|
|
|
|
|
|
|
miles => 1000 / 1609.344, |
|
104
|
|
|
|
|
|
|
nauticalmiles => 1000 / 1852, |
|
105
|
|
|
|
|
|
|
meters => 1000, |
|
106
|
|
|
|
|
|
|
metres => 1000, |
|
107
|
|
|
|
|
|
|
yards => 1000 / 0.9144, |
|
108
|
|
|
|
|
|
|
feet => 1000 / 0.3048, |
|
109
|
|
|
|
|
|
|
inches => 1000 / 0.0254 |
|
110
|
|
|
|
|
|
|
); |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Values that define WGS84 ellipsoid model of the Earth |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
our $RE = 6378.137; # equatorial radius |
|
115
|
|
|
|
|
|
|
our $FE = 1 / 298.257223563; # flattening |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
our $E2 = $FE * (2 - $FE); |
|
118
|
|
|
|
|
|
|
our $RAD = pi / 180; # pi from Math::Trig |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# |
|
121
|
|
|
|
|
|
|
# A collection of very fast approximations to common geodesic measurements. Useful for performance-sensitive code that measures things on a city scale. |
|
122
|
|
|
|
|
|
|
# |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head1 API |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=head2 CheapRuler::fromTile( $y, $z, $units='kilometers' ) |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
Creates a ruler object from Google web mercator tile coordinates (y and z). That's correct, y and z, not x. |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
See 'new' below for available units. |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
Example |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
$ruler = CheapRuler::fromTile( 11041, 15, 'meters'); |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=cut |
|
137
|
|
|
|
|
|
|
|
|
138
|
0
|
|
|
0
|
1
|
|
sub fromTile( $y, $z, $units='kilometers') { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
139
|
0
|
|
|
|
|
|
my $n = pi * (1 - 2 * ($y + 0.5) / ( 2**$z )); |
|
140
|
0
|
|
|
|
|
|
my $lat = atan(0.5 * ( exp($n) - exp(-$n) )) / $RAD; |
|
141
|
|
|
|
|
|
|
|
|
142
|
0
|
|
|
|
|
|
return CheapRuler->new($lat, $units); |
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head2 units() |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
Multipliers for converting between units. |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
See 'new' below for available units. |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
Example : convert 50 meters to yards |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
$units = CheapRuler::units(); |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
$yards = 50 * $units->{yards} / $units->{meters}; |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=cut |
|
158
|
|
|
|
|
|
|
|
|
159
|
0
|
|
|
0
|
|
|
sub CheapRuler::units() { |
|
|
0
|
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
|
return { %FACTORS }; |
|
161
|
|
|
|
|
|
|
} |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 CheapRuler->new( $lat, $units='kilometers' ) |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Create a ruler instance for very fast approximations to common geodesic measurements around a certain latitude. |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
param latitude, e.g. 54.31 |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
param units (optional), one of: kilometers miles nauticalmiles meters metres yards feet inches |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Example |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
$ruler = CheapRuler->new(35.05, 'miles'); |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=cut |
|
176
|
|
|
|
|
|
|
|
|
177
|
0
|
|
|
0
|
1
|
|
sub new ( $class, $lat, $units='kilometers' ) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
178
|
0
|
0
|
|
|
|
|
croak( 'No latitude given.') if ! defined $lat; |
|
179
|
0
|
0
|
0
|
|
|
|
croak( "Unknown unit $units. Use one of: " + join( ', ', keys(%FACTORS) ) ) if $units && ! $FACTORS{ $units }; |
|
180
|
|
|
|
|
|
|
|
|
181
|
0
|
|
|
|
|
|
my $self = bless {}; |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional |
|
184
|
0
|
0
|
|
|
|
|
my $m = $RAD * $RE * ( $units ? $FACTORS{ $units } : 1 ); |
|
185
|
0
|
|
|
|
|
|
my $coslat = cos( $lat * $RAD ); |
|
186
|
0
|
|
|
|
|
|
my $w2 = 1 / (1 - $E2 * (1 - $coslat**2) ); |
|
187
|
0
|
|
|
|
|
|
my $w = sqrt($w2); |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# multipliers for converting longitude and latitude degrees into distance |
|
190
|
0
|
|
|
|
|
|
$self->{kx} = $m * $w * $coslat; # based on normal radius of curvature |
|
191
|
0
|
|
|
|
|
|
$self->{ky} = $m * $w * $w2 * (1 - $E2); # based on meridonal radius of curvature |
|
192
|
|
|
|
|
|
|
|
|
193
|
0
|
|
|
|
|
|
return $self; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head2 distance( $a, $b ) |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
Given two points of the form [longitude, latitude], returns the distance in 'ruler' units. |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
param $a, point [longitude, latitude] |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
param $b, point [longitude, latitude] |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
returns distance (in chosen units) |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Example |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
$distance = $ruler->distance([30.5, 50.5], [30.51, 50.49]); |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=cut |
|
211
|
|
|
|
|
|
|
|
|
212
|
0
|
|
|
0
|
1
|
|
sub distance( $self, $a, $b) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
|
my $dx = &wrap( $a->[0] - $b->[0]) * $self->{kx}; |
|
215
|
0
|
|
|
|
|
|
my $dy = ( $a->[1] - $b->[1]) * $self->{ky}; |
|
216
|
0
|
|
|
|
|
|
return sqrt( $dx * $dx + $dy * $dy); |
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head2 bearing( $a, $b ) |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
Returns the bearing between two points in degrees |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
param $a, point [longitude, latitude] |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
param $b, point [longitude, latitude] |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
returns $bearing (degrees) |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Example |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
$bearing = $ruler->bearing([30.5, 50.5], [30.51, 50.49]); |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
=cut |
|
234
|
|
|
|
|
|
|
|
|
235
|
0
|
|
|
0
|
1
|
|
sub bearing($self, $a, $b) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
my $dx = &wrap($b->[0] - $a->[0]) * $self->{kx}; |
|
237
|
0
|
|
|
|
|
|
my $dy = ($b->[1] - $a->[1]) * $self->{ky}; |
|
238
|
0
|
|
|
|
|
|
return atan2($dx, $dy) / $RAD; |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=head2 destination( $point, $distance, $bearing) |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
Returns a new point given distance and bearing from the starting point. |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
param $p point [longitude, latitude] |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
param $dist distance in chosen units |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
param $bearing (degrees) |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
returns $point [longitude, latitude] |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
Example |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
$point = ruler->destination([30.5, 50.5], 0.1, 90); |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=cut |
|
258
|
|
|
|
|
|
|
|
|
259
|
0
|
|
|
0
|
1
|
|
sub destination( $self, $p, $dist, $bearing) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
260
|
0
|
|
|
|
|
|
my $a = $bearing * $RAD; |
|
261
|
0
|
|
|
|
|
|
return $self->offset( |
|
262
|
|
|
|
|
|
|
$p, |
|
263
|
|
|
|
|
|
|
sin($a) * $dist, |
|
264
|
|
|
|
|
|
|
cos($a) * $dist, |
|
265
|
|
|
|
|
|
|
); |
|
266
|
|
|
|
|
|
|
} |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=head2 offset( $point, dx, dy ) |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
Returns a new point given easting and northing offsets (in ruler units) from the starting point. |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
param $point, [longitude, latitude] |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
param $dx, easting, in ruler units |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
param $dy, northing, in ruler units |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
returns $point [longitude, latitude] |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
Example |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
$point = ruler.offset([30.5, 50.5], 10, 10); |
|
283
|
|
|
|
|
|
|
=cut |
|
284
|
|
|
|
|
|
|
|
|
285
|
0
|
|
|
0
|
1
|
|
sub offset( $self, $p, $dx, $dy) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
return [ |
|
287
|
|
|
|
|
|
|
$p->[0] + $dx / $self->{kx}, |
|
288
|
|
|
|
|
|
|
$p->[1] + $dy / $self->{ky}, |
|
289
|
0
|
|
|
|
|
|
]; |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=head2 lineDistance ( $points ) |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
Given a line (an array of points), returns the total line distance. |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
param $points, listref of points, where a point is [longitude, latitude] |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
returns $number, total line distance in 'ruler' units |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
Example |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
$length = ruler->lineDistance([ |
|
303
|
|
|
|
|
|
|
[-67.031, 50.458], [-67.031, 50.534], |
|
304
|
|
|
|
|
|
|
[-66.929, 50.534], [-66.929, 50.458] |
|
305
|
|
|
|
|
|
|
]); |
|
306
|
|
|
|
|
|
|
=cut |
|
307
|
|
|
|
|
|
|
|
|
308
|
0
|
|
|
0
|
1
|
|
sub lineDistance( $self, $points ) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
309
|
0
|
|
|
|
|
|
my $total = 0; |
|
310
|
0
|
|
|
|
|
|
foreach my $i ( 0 .. $#{ $points } - 1 ) { |
|
|
0
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
|
$total += $self->distance( $points->[ $i ], $points->[ $i + 1 ] ); |
|
312
|
|
|
|
|
|
|
} |
|
313
|
0
|
|
|
|
|
|
return $total; |
|
314
|
|
|
|
|
|
|
} |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=head2 area( $polygon ) |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
Given a polygon (an array of rings, where each ring is an array of points), returns the area. |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
param $polygon, a list-ref of rings, where a ring is a list of points [lon,lat], 1st ring is outer, 2nd+ rings are inner (holes) |
|
321
|
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
returns $number, area value in the specified 'ruler' units (square kilometers by default) |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
Example |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
$area = $ruler->area([[ |
|
327
|
|
|
|
|
|
|
[-67.031, 50.458], [-67.031, 50.534], [-66.929, 50.534], [-66.929, 50.458], [-67.031, 50.458] |
|
328
|
|
|
|
|
|
|
]]); |
|
329
|
|
|
|
|
|
|
=cut |
|
330
|
|
|
|
|
|
|
|
|
331
|
0
|
|
|
0
|
1
|
|
sub area( $self, $polygon ) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
|
my $sum = 0; |
|
333
|
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
|
foreach my $i ( 0 .. $#{ $polygon } ) { |
|
|
0
|
|
|
|
|
|
|
|
335
|
0
|
|
|
|
|
|
my @ring = @{ $polygon->[ $i ] }; |
|
|
0
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
|
|
337
|
0
|
|
|
|
|
|
for ( my ( $j, $len, $k ) = ( 0, scalar( @ring ), $#ring ); $j < $len; $k = $j++) { |
|
338
|
0
|
0
|
|
|
|
|
$sum += &wrap( $ring[$j]->[0] - $ring[$k]->[0]) * ( $ring[$j]->[1] + $ring[$k]->[1]) * ( $i ? -1 : 1 ); |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
} |
|
341
|
|
|
|
|
|
|
|
|
342
|
0
|
|
|
|
|
|
return ( abs( $sum ) / 2 ) * $self->{kx} * $self->{ky}; |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=head2 along( $line, $distance) |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
Returns the point at a specified distance along the line. |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
param $line, a list-ref of points of [lon, lat] |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
param $dist, distance in ruler units |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
returns $point, a list-ref [lon, lat] |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
Example |
|
356
|
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
$point = $ruler->along( |
|
358
|
|
|
|
|
|
|
[ [-67.031, 50.458], [-67.031, 50.534], [-66.929, 50.534] ], |
|
359
|
|
|
|
|
|
|
2.5); |
|
360
|
|
|
|
|
|
|
=cut |
|
361
|
|
|
|
|
|
|
|
|
362
|
0
|
|
|
0
|
1
|
|
sub along( $self, $line, $dist ) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
363
|
0
|
|
|
|
|
|
my $sum = 0; |
|
364
|
|
|
|
|
|
|
|
|
365
|
0
|
0
|
|
|
|
|
return $line->[0] if $dist <= 0 ; |
|
366
|
|
|
|
|
|
|
|
|
367
|
0
|
|
|
|
|
|
for (my $i = 0; $i < $#{ $line }; $i++) { |
|
|
0
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
|
my $p0 = $line->[$i]; |
|
369
|
0
|
|
|
|
|
|
my $p1 = $line->[$i + 1]; |
|
370
|
0
|
|
|
|
|
|
my $d = $self->distance($p0, $p1); |
|
371
|
0
|
|
|
|
|
|
$sum += $d; |
|
372
|
0
|
0
|
|
|
|
|
return &interpolate( $p0, $p1, ( $dist - ($sum - $d)) / $d ) if $sum > $dist; |
|
373
|
|
|
|
|
|
|
} |
|
374
|
|
|
|
|
|
|
|
|
375
|
0
|
|
|
|
|
|
return $line->[ $#{ $line } ]; |
|
|
0
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
} |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=head2 pointToSegmentDistance( $p, $a, $b ) |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
Returns the distance from a point `p` to a line segment `a` to `b`. |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
param $p, point, [longitude, latitude] |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
param $a, segment point 1, [longitude, latitude] |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
param $b, segment point 2, [longitude, latitude] |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
returns $distance (in ruler units) |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
Example |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
$distance = $ruler->pointToSegmentDistance([-67.04, 50.5], [-67.05, 50.57], [-67.03, 50.54]); |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=cut |
|
395
|
|
|
|
|
|
|
|
|
396
|
0
|
|
|
0
|
1
|
|
sub pointToSegmentDistance( $self, $p, $a, $b) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
397
|
0
|
|
|
|
|
|
my $x = $a->[0]; |
|
398
|
0
|
|
|
|
|
|
my $y = $a->[1]; |
|
399
|
0
|
|
|
|
|
|
my $dx = &wrap( $b->[0] - $x) * $self->{kx}; |
|
400
|
0
|
|
|
|
|
|
my $dy = ($b->[1] - $y) * $self->{ky}; |
|
401
|
|
|
|
|
|
|
|
|
402
|
0
|
0
|
0
|
|
|
|
if ( $dx != 0 || $dy != 0) { |
|
403
|
0
|
|
|
|
|
|
my $t = ( &wrap( $p->[0] - $x) * $self->{kx} * $dx + ( $p->[1] - $y) * $self->{ky} * $dy) / ($dx**2 + $dy**2); |
|
404
|
|
|
|
|
|
|
|
|
405
|
0
|
0
|
|
|
|
|
if ($t > 1) { |
|
|
|
0
|
|
|
|
|
|
|
406
|
0
|
|
|
|
|
|
$x = $b->[0]; |
|
407
|
0
|
|
|
|
|
|
$y = $b->[1]; |
|
408
|
|
|
|
|
|
|
} |
|
409
|
|
|
|
|
|
|
elsif ( $t > 0) { |
|
410
|
0
|
|
|
|
|
|
$x += ($dx / $self->{kx}) * $t; |
|
411
|
0
|
|
|
|
|
|
$y += ($dy / $self->{ky}) * $t; |
|
412
|
|
|
|
|
|
|
} |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
|
|
415
|
0
|
|
|
|
|
|
$dx = &wrap($p->[0] - $x) * $self->{kx}; |
|
416
|
0
|
|
|
|
|
|
$dy = ($p->[1] - $y) * $self->{ky}; |
|
417
|
|
|
|
|
|
|
|
|
418
|
0
|
|
|
|
|
|
return sqrt($dx**2 + $dy**2); |
|
419
|
|
|
|
|
|
|
} |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
=head2 pointOnLine( $line, $p ) |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
Returns an object of the form {point, index, t}, where |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
* point is closest point on the line from the given point, |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
* index is the start index of the segment with the closest point, |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
* t is a parameter from 0 to 1 that indicates where the closest point is on that segment. |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
param $line, listref of points of [lon, lat] |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
param $p, point of [longitude, latitude] |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
returns { point => [lon, lat], index => number, t => number } |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
Example |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
$info = $ruler->pointOnLine( $line, [-67.04, 50.5]) |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=cut |
|
443
|
|
|
|
|
|
|
|
|
444
|
0
|
|
|
0
|
1
|
|
sub pointOnLine( $self, $line, $p) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
445
|
0
|
|
|
|
|
|
my $minDist = "infinity"; |
|
446
|
0
|
|
|
|
|
|
my $minX = $line->[0][0]; |
|
447
|
0
|
|
|
|
|
|
my $minY = $line->[0][1]; |
|
448
|
0
|
|
|
|
|
|
my $minI = 0; |
|
449
|
0
|
|
|
|
|
|
my $minT = 0; |
|
450
|
|
|
|
|
|
|
|
|
451
|
0
|
|
|
|
|
|
for ( my $i = 0; $i < $#{ $line }; $i++) { |
|
|
0
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
|
|
453
|
0
|
|
|
|
|
|
my $x = $line->[$i][0]; |
|
454
|
0
|
|
|
|
|
|
my $y = $line->[$i][1]; |
|
455
|
0
|
|
|
|
|
|
my $dx = &wrap( $line->[$i + 1][0] - $x) * $self->{kx}; |
|
456
|
0
|
|
|
|
|
|
my $dy = ($line->[$i + 1][1] - $y) * $self->{ky}; |
|
457
|
0
|
|
|
|
|
|
my $t = 0; |
|
458
|
|
|
|
|
|
|
|
|
459
|
0
|
0
|
0
|
|
|
|
if ($dx != 0 || $dy != 0) { |
|
460
|
0
|
|
|
|
|
|
$t = ( &wrap($p->[0] - $x) * $self->{kx} * $dx + ($p->[1] - $y) * $self->{ky} * $dy) / ($dx**2 + $dy**2); |
|
461
|
|
|
|
|
|
|
|
|
462
|
0
|
0
|
|
|
|
|
if ($t > 1) { |
|
|
|
0
|
|
|
|
|
|
|
463
|
0
|
|
|
|
|
|
$x = $line->[$i + 1][0]; |
|
464
|
0
|
|
|
|
|
|
$y = $line->[$i + 1][1]; |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
} elsif ($t > 0) { |
|
467
|
0
|
|
|
|
|
|
$x += ($dx / $self->{kx}) * $t; |
|
468
|
0
|
|
|
|
|
|
$y += ($dy / $self->{ky}) * $t; |
|
469
|
|
|
|
|
|
|
} |
|
470
|
|
|
|
|
|
|
} |
|
471
|
|
|
|
|
|
|
|
|
472
|
0
|
|
|
|
|
|
$dx = wrap($p->[0] - $x) * $self->{kx}; |
|
473
|
0
|
|
|
|
|
|
$dy = ($p->[1] - $y) * $self->{ky}; |
|
474
|
|
|
|
|
|
|
|
|
475
|
0
|
|
|
|
|
|
my $sqDist = $dx**2 + $dy**2; |
|
476
|
0
|
0
|
0
|
|
|
|
if ( $minDist eq 'infinity' || $sqDist < $minDist) { |
|
477
|
0
|
|
|
|
|
|
$minDist = $sqDist; |
|
478
|
0
|
|
|
|
|
|
$minX = $x; |
|
479
|
0
|
|
|
|
|
|
$minY = $y; |
|
480
|
0
|
|
|
|
|
|
$minI = $i; |
|
481
|
0
|
|
|
|
|
|
$minT = $t; |
|
482
|
|
|
|
|
|
|
} |
|
483
|
|
|
|
|
|
|
} |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# Math::max(0, Math::min(1, $minT)); |
|
486
|
0
|
0
|
|
|
|
|
my $min = 1 < $minT ? 1 : $minT; |
|
487
|
0
|
0
|
|
|
|
|
my $max = 0 > $min ? 0 : $min; |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
return { |
|
490
|
0
|
|
|
|
|
|
point => [$minX,$minY], |
|
491
|
|
|
|
|
|
|
index => $minI, |
|
492
|
|
|
|
|
|
|
t => $max, |
|
493
|
|
|
|
|
|
|
}; |
|
494
|
|
|
|
|
|
|
} |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=head2 lineSlice( $start, $stop, $line ) |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
Returns a part of the given line between the start and the stop points (or their closest points on the line). |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
param $start, point [longitude, latitude] |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
param $stop, point [longitude, latitude] |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
param $line, arrayref of points of [lon,lat] |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
returns $linea_slice (a listref) part of the line |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
Example |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
$line_slice = $ruler->lineSlice([-67.04, 50.5], [-67.05, 50.56], $line); |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=cut |
|
513
|
|
|
|
|
|
|
|
|
514
|
0
|
|
|
0
|
1
|
|
sub lineSlice($self, $start, $stop, $line) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
515
|
0
|
|
|
|
|
|
my $p1 = $self->pointOnLine($line,$start); |
|
516
|
0
|
|
|
|
|
|
my $p2 = $self->pointOnLine($line,$stop); |
|
517
|
|
|
|
|
|
|
|
|
518
|
0
|
0
|
0
|
|
|
|
if ( $p1->{index} > $p2->{index} || ( $p1->{index} == $p2->{index} && $p1->{t} > $p2->{t} )) { |
|
|
|
|
0
|
|
|
|
|
|
519
|
0
|
|
|
|
|
|
my $tmp = $p1; |
|
520
|
0
|
|
|
|
|
|
$p1 = $p2; |
|
521
|
0
|
|
|
|
|
|
$p2 = $tmp; |
|
522
|
|
|
|
|
|
|
} |
|
523
|
|
|
|
|
|
|
|
|
524
|
0
|
|
|
|
|
|
my @slice = ( $p1->{point} ); |
|
525
|
|
|
|
|
|
|
|
|
526
|
0
|
|
|
|
|
|
my $l = $p1->{index} + 1; |
|
527
|
0
|
|
|
|
|
|
my $r = $p2->{index}; |
|
528
|
|
|
|
|
|
|
|
|
529
|
0
|
0
|
0
|
|
|
|
if ( ( ! &equals( $line->[1], $slice[0]) ) && ( $l <= $r ) ) { |
|
530
|
0
|
|
|
|
|
|
push @slice, $line->[$l]; |
|
531
|
|
|
|
|
|
|
} |
|
532
|
|
|
|
|
|
|
|
|
533
|
0
|
|
|
|
|
|
for (my $i = $l + 1; $i <= $r; $i++) { |
|
534
|
0
|
|
|
|
|
|
push @slice, $line->[$i]; |
|
535
|
|
|
|
|
|
|
} |
|
536
|
|
|
|
|
|
|
|
|
537
|
0
|
0
|
|
|
|
|
if ( ! &equals( $line->[$r], $p2->{point} )) { |
|
538
|
0
|
|
|
|
|
|
push @slice, $p2->{point}; |
|
539
|
|
|
|
|
|
|
} |
|
540
|
|
|
|
|
|
|
|
|
541
|
0
|
|
|
|
|
|
return [ @slice ]; |
|
542
|
|
|
|
|
|
|
} |
|
543
|
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
=head2 lineSliceAlong( $start, $stop, $line ) |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
Returns a part of the given line between the start and the stop points indicated by distance (in 'units') along the line. |
|
547
|
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
param $start, distance, in ruler units |
|
549
|
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
param $stop stop, distance, in ruler units |
|
551
|
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
param $line, listref of points |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
returns $line_slice, listref of points, part of a line |
|
555
|
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
Example |
|
557
|
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
$line_slice = $ruler->lineSliceAlong(10, 20, $line); |
|
559
|
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
=cut |
|
561
|
|
|
|
|
|
|
|
|
562
|
0
|
|
|
0
|
1
|
|
sub lineSliceAlong( $self, $start, $stop, $line) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
563
|
0
|
|
|
|
|
|
my $sum = 0; |
|
564
|
0
|
|
|
|
|
|
my @slice = (); |
|
565
|
|
|
|
|
|
|
|
|
566
|
0
|
|
|
|
|
|
for (my $i = 0; $i < $#{ $line }; $i++) { |
|
|
0
|
|
|
|
|
|
|
|
567
|
0
|
|
|
|
|
|
my $p0 = $line->[$i]; |
|
568
|
0
|
|
|
|
|
|
my $p1 = $line->[$i + 1]; |
|
569
|
0
|
|
|
|
|
|
my $d = $self->distance($p0, $p1); |
|
570
|
|
|
|
|
|
|
|
|
571
|
0
|
|
|
|
|
|
$sum += $d; |
|
572
|
|
|
|
|
|
|
|
|
573
|
0
|
0
|
0
|
|
|
|
if ($sum > $start && ( scalar @slice ) == 0) { |
|
574
|
0
|
|
|
|
|
|
push @slice, &interpolate($p0, $p1, ($start - ($sum - $d)) / $d); |
|
575
|
|
|
|
|
|
|
} |
|
576
|
|
|
|
|
|
|
|
|
577
|
0
|
0
|
|
|
|
|
if ($sum >= $stop) { |
|
578
|
0
|
|
|
|
|
|
push @slice, &interpolate($p0, $p1, ($stop - ($sum - $d)) / $d); |
|
579
|
0
|
|
|
|
|
|
return [ @slice ]; |
|
580
|
|
|
|
|
|
|
} |
|
581
|
|
|
|
|
|
|
|
|
582
|
0
|
0
|
|
|
|
|
if ($sum > $start) { push @slice, $p1 }; |
|
|
0
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
} |
|
584
|
|
|
|
|
|
|
|
|
585
|
0
|
|
|
|
|
|
return [ @slice ]; |
|
586
|
|
|
|
|
|
|
} |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
=head2 bufferPoint( point, buffer_distance ) |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
Given a point, returns a bounding box object ([w, s, e, n]) created from the given point buffered by a given distance. |
|
591
|
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
param $p point [longitude, latitude] |
|
593
|
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
param $buffer, a distance in ruler units |
|
595
|
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
returns $bbox, listref, [w, s, e, n] |
|
597
|
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
Example |
|
599
|
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
$bbox = $ruler->bufferPoint([30.5, 50.5], 0.01); |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=cut |
|
603
|
|
|
|
|
|
|
|
|
604
|
0
|
|
|
0
|
1
|
|
sub bufferPoint( $self, $p, $buffer) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
605
|
0
|
|
|
|
|
|
my $v = $buffer / $self->{ky}; |
|
606
|
0
|
|
|
|
|
|
my $h = $buffer / $self->{kx}; |
|
607
|
|
|
|
|
|
|
return [ |
|
608
|
0
|
|
|
|
|
|
$p->[0] - $h, |
|
609
|
|
|
|
|
|
|
$p->[1] - $v, |
|
610
|
|
|
|
|
|
|
$p->[0] + $h, |
|
611
|
|
|
|
|
|
|
$p->[1] + $v |
|
612
|
|
|
|
|
|
|
]; |
|
613
|
|
|
|
|
|
|
} |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
=head2 bufferBBox( $bbox, $buffer ) |
|
616
|
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
Given a bounding box, returns the box buffered by a given distance. |
|
618
|
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
param $bbox, listref of [w, s, e, n] |
|
620
|
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
param $buffer, distance in ruler units |
|
622
|
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
returns $bbox, a listref, [w, s, e, n] |
|
624
|
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
Example |
|
626
|
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
$bbox = $ruler->bufferBBox([30.5, 50.5, 31, 51], 0.2); |
|
628
|
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
=cut |
|
630
|
|
|
|
|
|
|
|
|
631
|
0
|
|
|
0
|
1
|
|
sub bufferBBox( $self, $bbox, $buffer) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
632
|
0
|
|
|
|
|
|
my $v = $buffer / $self->{ky}; |
|
633
|
0
|
|
|
|
|
|
my $h = $buffer / $self->{kx}; |
|
634
|
|
|
|
|
|
|
return [ |
|
635
|
0
|
|
|
|
|
|
$bbox->[0] - $h, |
|
636
|
|
|
|
|
|
|
$bbox->[1] - $v, |
|
637
|
|
|
|
|
|
|
$bbox->[2] + $h, |
|
638
|
|
|
|
|
|
|
$bbox->[3] + $v, |
|
639
|
|
|
|
|
|
|
]; |
|
640
|
|
|
|
|
|
|
} |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=head2 insideBBox( $point, $bbox ) |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
Returns true (1) if the given point is inside in the given bounding box, otherwise false (0). |
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
param $p point [longitude, latitude] |
|
647
|
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
param $bbox, listref [w, s, e, n] |
|
649
|
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
returns 0 or 1 (boolean) |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
Example |
|
653
|
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
$is_inside = $ruler->insideBBox([30.5, 50.5], [30, 50, 31, 51]); |
|
655
|
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
=cut |
|
657
|
|
|
|
|
|
|
|
|
658
|
0
|
|
|
0
|
1
|
|
sub insideBBox( $self, $p, $bbox) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
659
|
0
|
|
0
|
|
|
|
return &wrap( $p->[0] - $bbox->[0]) >= 0 && |
|
660
|
|
|
|
|
|
|
&wrap( $p->[0] - $bbox->[2]) <= 0 && |
|
661
|
|
|
|
|
|
|
$p->[1] >= $bbox->[1] && |
|
662
|
|
|
|
|
|
|
$p->[1] <= $bbox->[3]; |
|
663
|
|
|
|
|
|
|
} |
|
664
|
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=head2 CheapRuler::equals( $a, $b) |
|
666
|
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
Tests if 2 points are equal. |
|
668
|
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
a function not a method! |
|
670
|
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
param $a, point, [ lon, lat ] |
|
672
|
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
param $b, point, [ lon, lat ] |
|
674
|
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
=cut |
|
676
|
|
|
|
|
|
|
|
|
677
|
0
|
|
|
0
|
1
|
|
sub equals($a, $b) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
678
|
0
|
0
|
0
|
|
|
|
return ( $a->[0] == $b->[0] && $a->[1] == $b->[1] ) ? 1 : 0; |
|
679
|
|
|
|
|
|
|
} |
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
=head2 CheapRuler::interpolate( $a, $b, $t ) |
|
682
|
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
Returns a point along a line segment from $a to $b |
|
684
|
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
a function not a method! |
|
686
|
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
param $a, point, [lon, lat] |
|
688
|
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
param $b, point, [lon, lat] |
|
690
|
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
param $t, ratio (0 <= $t < 1 ), of the way along the line segment |
|
692
|
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
returns $p, point [ lon, lat] |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=cut |
|
696
|
|
|
|
|
|
|
|
|
697
|
0
|
|
|
0
|
1
|
|
sub interpolate($a, $b, $t) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
698
|
0
|
|
|
|
|
|
my $dx = &wrap($b->[0] - $a->[0]); |
|
699
|
0
|
|
|
|
|
|
my $dy = $b->[1] - $a->[1]; |
|
700
|
|
|
|
|
|
|
return [ |
|
701
|
0
|
|
|
|
|
|
$a->[0] + $dx * $t, |
|
702
|
|
|
|
|
|
|
$a->[1] + $dy * $t |
|
703
|
|
|
|
|
|
|
]; |
|
704
|
|
|
|
|
|
|
} |
|
705
|
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
=head2 CheapRuler::normalize( $degrees ) |
|
707
|
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
Normalize a lon degree value into [-180..180] range |
|
709
|
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
a function not a method! |
|
711
|
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
param $degrees |
|
713
|
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
return $degrees |
|
715
|
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=cut |
|
717
|
|
|
|
|
|
|
|
|
718
|
0
|
|
|
0
|
0
|
|
sub wrap( $deg) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
|
|
720
|
0
|
|
|
|
|
|
while ( $deg < -180) { $deg += 360; } |
|
|
0
|
|
|
|
|
|
|
|
721
|
0
|
|
|
|
|
|
while ( $deg > 180) { $deg -= 360; } |
|
|
0
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
|
|
723
|
0
|
|
|
|
|
|
return $deg; |
|
724
|
|
|
|
|
|
|
} |
|
725
|
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
728
|
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
GIS::Distance |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
https://metacpan.org/pod/GIS::Distance |
|
732
|
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
Group of modules with XS versions which implement Haversine and Vincenty distance formulas |
|
734
|
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
Consider for longer than 'city scale' distances, or near the Poles. |
|
736
|
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
GIS::Distance::Vincenty |
|
738
|
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
The more accurate formulae which this module approximates. There is an XS version of it. |
|
740
|
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
https://metacpan.org/pod/GIS::Distance::Vincenty |
|
742
|
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
=head1 SUPPORT |
|
744
|
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
|
746
|
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
perldoc Geo::CheapRuler |
|
748
|
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
Or at |
|
750
|
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
https://github.com/aavmurphy/CheapRuler |
|
752
|
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
=head1 BUGS |
|
754
|
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
Please report any bugs or feature requests of this port to |
|
756
|
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
https://github.com/aavmurphy/CheapRuler |
|
758
|
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
For the original, please see |
|
760
|
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
https://github.com/mapbox/cheap-ruler |
|
762
|
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
For testing, see |
|
764
|
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
https://github.com/aavmurphy/CheapRuler/blob/main/test/test.pl |
|
766
|
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
=head1 AUTHOR |
|
768
|
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
Andrew Murphy, C<< >> |
|
770
|
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
|
772
|
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
The original, mapbox/cheap-ruler, is (c) Mapbox. |
|
774
|
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
This port is Copyright (c) 2025 by Andrew Murphy. |
|
776
|
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
This is free software, licensed under: |
|
778
|
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
The Artistic License 2.0 (GPL Compatible) |
|
780
|
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
|
782
|
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
This module is a direct port of mapbox/cheap-ruler |
|
784
|
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
=head1 DEVELOPER NOTES |
|
786
|
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
README.md is auto-generated from Perl POD |
|
788
|
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
The original's test were also ported. See the Github ./tests directory. |
|
790
|
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
=cut |
|
792
|
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
1; # End of Geo::CheapRuler |