///|
pub(all) struct TrackPoint {
latitude_value : Double
longitude_value : Double
altitude_value : ExactDecimal?
datetime_value : UtcDateTime?
reported_speed_knots_value : ExactDecimal?
quality_value : GgaFixQuality?
satellites_value : Int?
usable_value : Bool
source_record_indexes_value : Array[Int]
} derive(Eq, Debug)
///|
pub(all) struct TrackSegment {
start_index_value : Int
end_index_value : Int
distance_meters_value : Double
bearing_degrees_value : Double
duration_millis_value : Int64?
derived_speed_mps_value : Double?
} derive(Eq, Debug)
///|
pub(all) struct BoundingBox {
minimum_latitude_value : Double
maximum_latitude_value : Double
minimum_longitude_value : Double
maximum_longitude_value : Double
} derive(Eq, Debug)
///|
pub(all) struct TrackSummary {
point_count_value : Int
segment_count_value : Int
total_distance_meters_value : Double
total_duration_millis_value : Int64?
bounds_value : BoundingBox?
} derive(Eq, Debug)
///|
pub(all) struct Track {
points_value : Array[TrackPoint]
segments_value : Array[TrackSegment]
summary_value : TrackSummary
} derive(Eq, Debug)
///|
pub fn TrackPoint::new(
latitude : Double,
longitude : Double,
altitude? : ExactDecimal? = None,
datetime? : UtcDateTime? = None,
reported_speed_knots? : ExactDecimal? = None,
quality? : GgaFixQuality? = None,
satellites? : Int? = None,
usable? : Bool = true,
source_record_indexes? : Array[Int] = [],
) -> Result[TrackPoint, NmeaError] {
match validate_position(latitude, longitude) {
Err(error) => Err(error)
Ok(_) =>
Ok({
latitude_value: latitude,
longitude_value: longitude,
altitude_value: altitude,
datetime_value: datetime,
reported_speed_knots_value: reported_speed_knots,
quality_value: quality,
satellites_value: satellites,
usable_value: usable,
source_record_indexes_value: source_record_indexes.copy(),
})
}
}
///|
pub fn TrackPoint::from_fused(fix : FusedFix) -> Result[TrackPoint, NmeaError] {
let latitude = match fix.latitude_value {
Some(value) => value
None => return Err(geographic_error("fused fix has no latitude"))
}
let longitude = match fix.longitude_value {
Some(value) => value
None => return Err(geographic_error("fused fix has no longitude"))
}
let datetime = match fix.date_value {
Some(date) => Some(UtcDateTime::new(date, fix.time_value))
None => None
}
TrackPoint::new(
latitude,
longitude,
altitude=fix.altitude_value,
datetime~,
reported_speed_knots=fix.speed_knots_value,
quality=fix.quality_value,
satellites=fix.satellites_value,
usable=fix.usable_value,
source_record_indexes=fix.source_record_indexes_value,
)
}
///|
fn build_segment(
start : TrackPoint,
end : TrackPoint,
start_index : Int,
) -> Result[TrackSegment, NmeaError] {
let distance = match
haversine_distance_meters(
start.latitude_value,
start.longitude_value,
end.latitude_value,
end.longitude_value,
) {
Ok(value) => value
Err(error) => return Err(error)
}
let bearing = match
initial_bearing_degrees(
start.latitude_value,
start.longitude_value,
end.latitude_value,
end.longitude_value,
) {
Ok(value) => value
Err(error) => return Err(error)
}
let duration = match (start.datetime_value, end.datetime_value) {
(Some(first), Some(second)) => Some(second.millis_since(first))
_ => None
}
let derived_speed = match duration {
Some(value) if value > 0L =>
match speed_meters_per_second(distance, elapsed_millis=value) {
Ok(speed) => Some(speed)
Err(_) => None
}
_ => None
}
Ok({
start_index_value: start_index,
end_index_value: start_index + 1,
distance_meters_value: distance,
bearing_degrees_value: bearing,
duration_millis_value: duration,
derived_speed_mps_value: derived_speed,
})
}
///|
fn track_bounds(points : Array[TrackPoint]) -> BoundingBox? {
if points.length() == 0 {
return None
}
let mut minimum_latitude = points[0].latitude_value
let mut maximum_latitude = points[0].latitude_value
let mut minimum_longitude = points[0].longitude_value
let mut maximum_longitude = points[0].longitude_value
for index = 1; index < points.length(); index = index + 1 {
let point = points[index]
if point.latitude_value < minimum_latitude {
minimum_latitude = point.latitude_value
}
if point.latitude_value > maximum_latitude {
maximum_latitude = point.latitude_value
}
if point.longitude_value < minimum_longitude {
minimum_longitude = point.longitude_value
}
if point.longitude_value > maximum_longitude {
maximum_longitude = point.longitude_value
}
}
Some({
minimum_latitude_value: minimum_latitude,
maximum_latitude_value: maximum_latitude,
minimum_longitude_value: minimum_longitude,
maximum_longitude_value: maximum_longitude,
})
}
///|
pub fn Track::from_points(
points : Array[TrackPoint],
) -> Result[Track, NmeaError] {
let stored_points = points.copy()
let segments : Array[TrackSegment] = []
for index = 0; index + 1 < stored_points.length(); index = index + 1 {
match build_segment(stored_points[index], stored_points[index + 1], index) {
Ok(segment) => segments.push(segment)
Err(error) => return Err(error)
}
}
let mut total_distance = 0.0
let mut total_duration : Int64 = 0L
let mut complete_duration = segments.length() > 0
for segment in segments {
total_distance = total_distance + segment.distance_meters_value
match segment.duration_millis_value {
Some(duration) => total_duration = total_duration + duration
None => complete_duration = false
}
}
let summary = {
point_count_value: stored_points.length(),
segment_count_value: segments.length(),
total_distance_meters_value: total_distance,
total_duration_millis_value: if complete_duration {
Some(total_duration)
} else {
None
},
bounds_value: track_bounds(stored_points),
}
Ok({
points_value: stored_points,
segments_value: segments,
summary_value: summary,
})
}
///|
pub fn TrackPoint::latitude(self : TrackPoint) -> Double {
self.latitude_value
}
///|
pub fn TrackPoint::longitude(self : TrackPoint) -> Double {
self.longitude_value
}
///|
pub fn TrackPoint::altitude(self : TrackPoint) -> ExactDecimal? {
self.altitude_value
}
///|
pub fn TrackPoint::datetime(self : TrackPoint) -> UtcDateTime? {
self.datetime_value
}
///|
pub fn TrackPoint::reported_speed_knots(self : TrackPoint) -> ExactDecimal? {
self.reported_speed_knots_value
}
///|
pub fn TrackPoint::quality(self : TrackPoint) -> GgaFixQuality? {
self.quality_value
}
///|
pub fn TrackPoint::satellites(self : TrackPoint) -> Int? {
self.satellites_value
}
///|
pub fn TrackPoint::usable(self : TrackPoint) -> Bool {
self.usable_value
}
///|
pub fn TrackPoint::source_record_indexes(self : TrackPoint) -> Array[Int] {
self.source_record_indexes_value.copy()
}
///|
pub fn TrackSegment::start_index(self : TrackSegment) -> Int {
self.start_index_value
}
///|
pub fn TrackSegment::end_index(self : TrackSegment) -> Int {
self.end_index_value
}
///|
pub fn TrackSegment::distance_meters(self : TrackSegment) -> Double {
self.distance_meters_value
}
///|
pub fn TrackSegment::bearing_degrees(self : TrackSegment) -> Double {
self.bearing_degrees_value
}
///|
pub fn TrackSegment::duration_millis(self : TrackSegment) -> Int64? {
self.duration_millis_value
}
///|
pub fn TrackSegment::derived_speed_mps(self : TrackSegment) -> Double? {
self.derived_speed_mps_value
}
///|
pub fn BoundingBox::minimum_latitude(self : BoundingBox) -> Double {
self.minimum_latitude_value
}
///|
pub fn BoundingBox::new(
minimum_latitude : Double,
maximum_latitude : Double,
minimum_longitude : Double,
maximum_longitude : Double,
) -> Result[BoundingBox, NmeaError] {
match validate_position(minimum_latitude, minimum_longitude) {
Err(error) => return Err(error)
Ok(_) => ()
}
match validate_position(maximum_latitude, maximum_longitude) {
Err(error) => return Err(error)
Ok(_) => ()
}
if minimum_latitude > maximum_latitude ||
minimum_longitude > maximum_longitude {
return Err(geographic_error("bounding box minimum exceeds maximum"))
}
Ok({
minimum_latitude_value: minimum_latitude,
maximum_latitude_value: maximum_latitude,
minimum_longitude_value: minimum_longitude,
maximum_longitude_value: maximum_longitude,
})
}
///|
pub fn BoundingBox::maximum_latitude(self : BoundingBox) -> Double {
self.maximum_latitude_value
}
///|
pub fn BoundingBox::minimum_longitude(self : BoundingBox) -> Double {
self.minimum_longitude_value
}
///|
pub fn BoundingBox::maximum_longitude(self : BoundingBox) -> Double {
self.maximum_longitude_value
}
///|
pub fn TrackSummary::point_count(self : TrackSummary) -> Int {
self.point_count_value
}
///|
pub fn TrackSummary::segment_count(self : TrackSummary) -> Int {
self.segment_count_value
}
///|
pub fn TrackSummary::total_distance_meters(self : TrackSummary) -> Double {
self.total_distance_meters_value
}
///|
pub fn TrackSummary::total_duration_millis(self : TrackSummary) -> Int64? {
self.total_duration_millis_value
}
///|
pub fn TrackSummary::bounds(self : TrackSummary) -> BoundingBox? {
self.bounds_value
}
///|
pub fn Track::points(self : Track) -> Array[TrackPoint] {
self.points_value.copy()
}
///|
pub fn Track::segments(self : Track) -> Array[TrackSegment] {
self.segments_value.copy()
}
///|
pub fn Track::summary(self : Track) -> TrackSummary {
self.summary_value
}