sgslib 0.3.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sgs/location.rb CHANGED
@@ -1,34 +1,41 @@
1
1
  #
2
- # Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
3
- # software; you can redistribute it and/or modify it under the terms of the
4
- # GNU General Public License as published by the Free Software Foundation;
5
- # either version 2, or (at your option) any later version.
2
+ # Copyright (c) 2013-2023, Kalopa Robotics Limited. All rights
3
+ # reserved.
6
4
  #
7
- # It is distributed in the hope that it will be useful, but WITHOUT
8
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
9
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
10
- # for more details.
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License as
7
+ # published by the Free Software Foundation; either version 2 of
8
+ # the License, or (at your option) any later version.
11
9
  #
12
- # You should have received a copy of the GNU General Public License along
13
- # with this product; see the file COPYING. If not, write to the Free
14
- # Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
15
14
  #
16
- # THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
17
- # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18
- # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
- # IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
20
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
- # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
22
- # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
- # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
- # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
- # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
+ # 02110-1301, USA.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY KALOPA ROBOTICS LIMITED "AS IS" AND
21
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KALOPA
24
+ # ROBOTICS LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27
+ # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+ #
33
+ # ABSTRACT
26
34
  #
27
35
 
28
36
  ##
29
- # Routines for handling sailboat location and bearings.
37
+ # Routines for handling sailboat location.
30
38
  #
31
- require 'date'
32
39
  require 'json'
33
40
 
34
41
  module SGS
@@ -97,35 +104,19 @@ module SGS
97
104
  end
98
105
 
99
106
  #
100
- # Create a new location from a string.
101
- # Uses the instance method to parse.
102
- def self.parse_str(str)
103
- loc = new
104
- loc.parse_str(str)
105
- loc
106
- end
107
-
108
- #
109
- # Create a new location from a lat/long string pair
107
+ # Create a new location from a lat/long hash.
110
108
  # Uses the instance method to parse.
111
- def self.parse(latstr, longstr)
109
+ def self.parse(data)
112
110
  loc = new
113
- loc.parse(latstr, longstr)
111
+ loc.parse(data)
114
112
  loc
115
113
  end
116
114
 
117
115
  #
118
- # Parse a lat/long value (in degrees)
119
- def parse_str(str)
120
- latstr, longstr = str.split(',')
121
- parse(latstr, longstr)
122
- end
123
-
124
- #
125
- # Parse a lat/long value pair (in degrees)
126
- def parse(latstr, longstr)
127
- @latitude = ll_parse(latstr.split, "NS")
128
- @longitude = ll_parse(longstr.split, "EW")
116
+ # Set the lat/long from a hash.
117
+ def parse(data)
118
+ @latitude = ll_parse(data["latitude"], "NS")
119
+ @longitude = ll_parse(data["longitude"], "EW")
129
120
  end
130
121
 
131
122
  #
@@ -134,6 +125,13 @@ module SGS
134
125
  @latitude and @longitude
135
126
  end
136
127
 
128
+ #
129
+ # Convert the lat/long to a hash.
130
+ def to_hash
131
+ {"latitude" => ll_to_s(latitude, "NS"),
132
+ "longitude" => ll_to_s(longitude, "EW")}
133
+ end
134
+
137
135
  #
138
136
  # Display the lat/long as a useful string (in degrees).
139
137
  def to_s
@@ -186,8 +184,11 @@ module SGS
186
184
 
187
185
  private
188
186
  #
189
- # Parse a string into a lat or long.
190
- def ll_parse(args, nsew)
187
+ # Parse a string into a lat or long. The latitude or longitude string
188
+ # should be of the format dd mm ss.sssssC (where C is NS or EW). Note
189
+ # that latitude and longitude are saved internally in radians.
190
+ def ll_parse(value, nsew)
191
+ args = value.split
191
192
  dir = args[-1].gsub(/[\d\. ]+/, '').upcase
192
193
  args.map! {|val| val.to_f}
193
194
  val = args.shift
@@ -197,15 +198,22 @@ module SGS
197
198
  end
198
199
 
199
200
  #
200
- #
201
+ # Convert a latitude or longitude into an ASCII string of the form:
202
+ # dd mm ss.ssssssC (where C is NS or EW). The value should be in
203
+ # radians.
201
204
  def ll_to_s(val, str)
205
+ val = Bearing.rtod val
202
206
  if val < 0.0
203
207
  chr = str[1]
204
208
  val = -val
205
209
  else
206
210
  chr = str[0]
207
211
  end
208
- "%8.6f%c" % [Bearing.rtod(val), chr]
212
+ deg = val.to_i
213
+ val = (val - deg.to_f) * 60.0
214
+ min = val.to_i
215
+ sec = (val - min.to_f) * 60.0
216
+ "%d %d %8.6f%c" % [deg, min, sec, chr]
209
217
  end
210
218
 
211
219
  #
@@ -223,113 +231,4 @@ module SGS
223
231
  [fmt % [deg, val], ne.chr]
224
232
  end
225
233
  end
226
-
227
- ##
228
- # Class for dealing with the angle/distance vector.
229
- #
230
- # Note that for convenience, we retain the angle in Radians. The
231
- # distance is in nautical miles.
232
- class Bearing
233
- attr_accessor :distance
234
-
235
- #
236
- # Create the Bearing instance.
237
- def initialize(angle = 0.0, distance = 0.0)
238
- self.angle = angle.to_f
239
- self.distance = distance.to_f
240
- end
241
-
242
- #
243
- # Create a bearing from an angle in degrees.
244
- def self.degrees(angle, distance)
245
- new(Bearing.dtor(angle), distance)
246
- end
247
-
248
- #
249
- # Handy function to translate degrees to radians
250
- def self.dtor(deg)
251
- deg.to_f * Math::PI / 180.0
252
- end
253
-
254
- #
255
- # Handy function to translate radians to degrees
256
- def self.rtod(rad)
257
- rad.to_f * 180.0 / Math::PI
258
- end
259
-
260
- #
261
- # Handy function to re-adjust an angle away from negative
262
- def self.absolute(angle)
263
- (angle + 2.0 * Math::PI) % (2.0 * Math::PI)
264
- end
265
-
266
- #
267
- # Another handy function to re-adjust an angle (in degrees) away from
268
- # negative.
269
- def self.absolute_d(angle)
270
- (angle + 360) % 360
271
- end
272
-
273
- #
274
- # Haversine formula for calculating distance and angle, given two
275
- # locations.
276
- #
277
- # To calculate an angle and distance from two positions:
278
- #
279
- # This code was derived from formulae on the Movable Type site:
280
- # http://www.movable-type.co.uk/scripts/latlong.html
281
- #
282
- # var d = Math.acos(Math.sin(lat1)*Math.sin(lat2) +
283
- # Math.cos(lat1)*Math.cos(lat2) *
284
- # Math.cos(lon2-lon1)) * R;
285
- # var y = Math.sin(dLon) * Math.cos(lat2);
286
- # var x = Math.cos(lat1)*Math.sin(lat2) -
287
- # Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
288
- # var angle = Math.atan2(y, x).toDeg();
289
- def self.compute(loc1, loc2)
290
- bearing = new
291
- sin_lat1 = Math.sin(loc1.latitude)
292
- sin_lat2 = Math.sin(loc2.latitude)
293
- cos_lat1 = Math.cos(loc1.latitude)
294
- cos_lat2 = Math.cos(loc2.latitude)
295
- sin_dlon = Math.sin(loc2.longitude - loc1.longitude)
296
- cos_dlon = Math.cos(loc2.longitude - loc1.longitude)
297
- bearing.distance = Math.acos(sin_lat1*sin_lat2 + cos_lat1*cos_lat2*cos_dlon) *
298
- SGS::EARTH_RADIUS
299
- y = sin_dlon * cos_lat2
300
- x = cos_lat1 * sin_lat2 - sin_lat1 * cos_lat2 * cos_dlon
301
- bearing.angle = Math.atan2(y, x)
302
- bearing
303
- end
304
-
305
- #
306
- # Set the angle
307
- def angle=(angle)
308
- @angle = Bearing.absolute(angle)
309
- end
310
-
311
- #
312
- # Get the angle
313
- def angle
314
- @angle
315
- end
316
-
317
- #
318
- # Return the angle (in degrees)
319
- def angle_d
320
- Bearing.rtod(@angle).to_i
321
- end
322
-
323
- #
324
- # Get the back-angle (the angle viewed from the opposite end of the line)
325
- def back_angle
326
- Bearing.absolute(@angle - Math::PI)
327
- end
328
-
329
- #
330
- # Convert to a string
331
- def to_s
332
- "BRNG %03dd,%.3fnm" % [angle_d, @distance]
333
- end
334
- end
335
234
  end
data/lib/sgs/logger.rb CHANGED
@@ -1,96 +1,49 @@
1
1
  #
2
- # Copyright (c) 2013, Kalopa Research. All rights reserved. This is free
3
- # software; you can redistribute it and/or modify it under the terms of the
4
- # GNU General Public License as published by the Free Software Foundation;
5
- # either version 2, or (at your option) any later version.
2
+ # Copyright (c) 2013-2023, Kalopa Robotics Limited. All rights
3
+ # reserved.
6
4
  #
7
- # It is distributed in the hope that it will be useful, but WITHOUT
8
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
9
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
10
- # for more details.
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License as
7
+ # published by the Free Software Foundation; either version 2 of
8
+ # the License, or (at your option) any later version.
11
9
  #
12
- # You should have received a copy of the GNU General Public License along
13
- # with this product; see the file COPYING. If not, write to the Free
14
- # Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
15
14
  #
16
- # THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH "AS IS" AND ANY EXPRESS OR
17
- # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18
- # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
- # IN NO EVENT SHALL KALOPA RESEARCH BE LIABLE FOR ANY DIRECT, INDIRECT,
20
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
- # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
22
- # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
- # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
- # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
- # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
+ # 02110-1301, USA.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY KALOPA ROBOTICS LIMITED "AS IS" AND
21
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KALOPA
24
+ # ROBOTICS LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27
+ # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+ #
33
+ # ABSTRACT
26
34
  #
27
35
 
28
36
  ##
29
- # Routines for handling sailboat logging.
37
+ # Routines for handling sailboat navigation and route planning.
30
38
  #
31
- require 'date'
32
-
33
39
  module SGS
34
- #
35
- # Waypoint, Attractor, and Repellor definitions
36
40
  class Logger < RedisBase
37
- attr_accessor :watch
38
-
39
- #
40
- # Watch names and definitions. The first watch is from 8PM until
41
- # midnight (local time), the middle watch is from midnight until
42
- # 4AM. The morning watch is from 4AM until 8AM. The forenoon watch
43
- # runs from 8AM until noon and the afternoon watch runs from noon
44
- # until 4PM. The dog watches run from 4PM until 8PM and around we
45
- # go again.
46
- #
47
- # Logs are sent back to base every four hours (6 per day) starting
48
- # at midnight UTC. However, some of the reporting is based on
49
- # the watch system rather than UTC. For example, reporting battery
50
- # voltage is most useful at the start of the forenoon watch, because
51
- # this represents the lowest voltage point after driving the boat all
52
- # night. As a result, the watch ID is computed based on the longitude,
53
- # which is a rough approximation of the timezone.
54
- FIRST_WATCH = 0
55
- MIDDLE_WATCH = 1
56
- MORNING_WATCH = 2
57
- FORENOON_WATCH = 3
58
- AFTERNOON_WATCH = 4
59
- DOG_WATCH = 5
60
- ALARM_REPORT = 7
61
-
62
- WATCH_NAMES = [
63
- "First Watch", "Middle Watch", "Morning Watch",
64
- "Forenoon Watch", "Afternoon Watch", "Dog Watch",
65
- "", "** ALARM REPORT **"
66
- ].freeze
67
-
68
- def initialize()
69
- @watch = FIRST_WATCH
70
- end
71
-
72
- #
73
- # Convert the watch ID to a name
74
- def watch_name
75
- WATCH_NAMES[@watch]
76
- end
77
-
78
41
  #
79
- # Determine the watch report. This takes account our actual latitude
80
- # and the current time. It does a rudimentary timezone conversion and
81
- # calculates the watch. Note that what we're calculating here is what
82
- # was the previous watch. So any time after 23:45 (local), we'll report
83
- # from the First Watch. Right up until 03:45 (local) when we'll start
84
- # reporting from the Middle Watch.
85
- def determine_watch(longitude)
86
- utc = Time.now
87
- local_hour = utc.hour + longitude * 12.0 / Math::PI
88
- p utc
89
- p local_hour
90
- local_hour += 24.0 if local_hour < 0.0
91
- local_hour -= 24.0 if local_hour >= 24.0
92
- @watch = ((local_hour / 4.0) + 0.25).to_i
93
- @watch = FIRST_WATCH if @watch == 6
42
+ # Main daemon function (called from executable)
43
+ def self.daemon
44
+ loop do
45
+ sleep 300
46
+ end
94
47
  end
95
48
  end
96
49
  end