20240422
This commit is contained in:
commit
e0afef912e
1285 files changed
+83219
No files matched your search
@@ -0,0 +1,52 @@
|
||||
|
||||
namespace Mapbox.ProbeExtractorCs
|
||||
{
|
||||
|
||||
using Mapbox.Unity.Location;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a point of a GPS trace
|
||||
/// </summary>
|
||||
public struct TracePoint
|
||||
{
|
||||
public long Timestamp;
|
||||
public double Latitude;
|
||||
public double Longitude;
|
||||
public double Bearing;
|
||||
public float? Elevation;
|
||||
/// <summary> Horizontal dilution of precision </summary>
|
||||
public float? HDop;
|
||||
/// <summary> Vertical dilution of precision</summary>
|
||||
public float? VDop;
|
||||
|
||||
public static TracePoint FromLocation(Location location)
|
||||
{
|
||||
return new TracePoint()
|
||||
{
|
||||
Timestamp = (long)location.Timestamp,
|
||||
Latitude = location.LatitudeLongitude.x,
|
||||
Longitude = location.LatitudeLongitude.y,
|
||||
Bearing = location.UserHeading,
|
||||
HDop = location.Accuracy
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a probe extracted by ProbeExtractor
|
||||
/// </summary>
|
||||
public struct Probe
|
||||
{
|
||||
public double Latitude;
|
||||
public double Longitude;
|
||||
public long StartTime;
|
||||
public long Duration;
|
||||
public double Speed;
|
||||
public double Bearing;
|
||||
public double Distance;
|
||||
public bool IsGood;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ee37be5546122440bc3a9abd7318aea
|
||||
timeCreated: 1510828497
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,354 @@
|
||||
namespace Mapbox.ProbeExtractorCs
|
||||
{
|
||||
|
||||
|
||||
using Mapbox.CheapRulerCs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
public class ProbeExtractorOptions
|
||||
{
|
||||
/// <summary>Seconds</summary>
|
||||
public double MinTimeBetweenProbes = 0;
|
||||
/// <summary>Do not include probes when the distance is X times bigger than the previous one</summary>
|
||||
public double MaxDistanceRatioJump = double.MaxValue;
|
||||
/// <summary>Do not include probes when the duration is X times bigger than the previous one</summary>
|
||||
public double MaxDurationRatioJump = double.MaxValue;
|
||||
/// <summary>Meters per second per second</summary>
|
||||
public double MaxAcceleration = double.MaxValue;
|
||||
/// <summary>Meters per second per second</summary>
|
||||
public double MaxDeceleration = double.MaxValue;
|
||||
/// <summary>Minimum probes extracted data should contain</summary>
|
||||
public int MinProbes = 2;
|
||||
/// <summary>Also return probes deemed not good</summary>
|
||||
public bool OutputBadProbes = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>This module allows to pass a list of trace points and extract its probes and their properties.</para>
|
||||
/// <para>It can also act as a filter for those probes.</para>
|
||||
/// </summary>
|
||||
public class ProbeExtractor
|
||||
{
|
||||
|
||||
private CheapRuler _ruler;
|
||||
private ProbeExtractorOptions _options;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ruler">A CheapRuler instance, expected in kilometers.</param>
|
||||
/// <param name="options"></param>
|
||||
public ProbeExtractor(CheapRuler ruler, ProbeExtractorOptions options)
|
||||
{
|
||||
_ruler = ruler;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Extract probes according to ProbeExtractorOptions.
|
||||
/// </summary>
|
||||
/// <param name="trace">List of trace points</param>
|
||||
/// <returns>List of probes. Empty list if no trace point matched the options.</returns>
|
||||
public List<Probe> ExtractProbes(List<TracePoint> trace)
|
||||
{
|
||||
int tracePntCnt = trace.Count;
|
||||
long[] durations = new long[tracePntCnt - 1];
|
||||
double[] distances = new double[tracePntCnt - 1];
|
||||
double[] speeds = new double[tracePntCnt - 1];
|
||||
double[] bearings = new double[tracePntCnt - 1];
|
||||
|
||||
for (int i = 1; i < tracePntCnt; i++)
|
||||
{
|
||||
TracePoint current = trace[i];
|
||||
TracePoint previous = trace[i - 1];
|
||||
int insertIdx = i - 1;
|
||||
|
||||
durations[insertIdx] = (current.Timestamp - previous.Timestamp) / 1000; //seconds
|
||||
|
||||
double[] currLocation = new double[] { current.Longitude, current.Latitude };
|
||||
double[] prevLocation = new double[] { previous.Longitude, previous.Latitude };
|
||||
distances[insertIdx] = _ruler.Distance(currLocation, prevLocation);
|
||||
speeds[insertIdx] = distances[insertIdx] / durations[insertIdx] * 3600; //kph
|
||||
|
||||
double bearing = _ruler.Bearing(prevLocation, currLocation);
|
||||
bearings[insertIdx] = bearing < 0 ? 360 + bearing : bearing;
|
||||
}
|
||||
|
||||
List<Probe> probes = new List<Probe>();
|
||||
|
||||
// 1st pass: iterate trace points and determine if they are good
|
||||
// bail early if !_options.OutputBadProbes
|
||||
bool negativeDuration = false;
|
||||
for (int i = 1; i < speeds.Length; i++)
|
||||
{
|
||||
//assume tracpoint is good
|
||||
bool isGood = true;
|
||||
if (negativeDuration)
|
||||
{
|
||||
// if trace already has a negative duration, then all probes are bad
|
||||
isGood = false;
|
||||
}
|
||||
else if (durations[i] < 0)
|
||||
{
|
||||
// if a trace has negative duration, the trace is likely noisy
|
||||
// bail, if we don't want bad probes
|
||||
if (!_options.OutputBadProbes) { return new List<Probe>(); }
|
||||
|
||||
negativeDuration = true;
|
||||
isGood = false;
|
||||
}
|
||||
else if (durations[i] < _options.MinTimeBetweenProbes)
|
||||
{
|
||||
// if shorter than the minTimeBetweenProbes, filter.
|
||||
isGood = false;
|
||||
}
|
||||
else if (durations[i] > _options.MaxDurationRatioJump * durations[i - 1])
|
||||
{
|
||||
// if not a gradual decrease in sampling frequency, it's most likely a signal jump
|
||||
isGood = false;
|
||||
}
|
||||
else if (speeds[i] - speeds[i - 1] > _options.MaxAcceleration * durations[i])
|
||||
{
|
||||
// if accelerating faster than maxAcceleration, it's most likely a glitch
|
||||
isGood = false;
|
||||
}
|
||||
else if (speeds[i - 1] - speeds[i] > _options.MaxDeceleration * durations[i])
|
||||
{
|
||||
// if decelerating faster than maxDeceleration, it's most likely a glitch
|
||||
isGood = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isForwardDirection = compareBearing(bearings[i - 1], bearings[i], 89, false);
|
||||
if (!isForwardDirection)
|
||||
{
|
||||
isGood = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isGood || _options.OutputBadProbes)
|
||||
{
|
||||
double[] coords = pointAtDistanceAndBearing(
|
||||
trace[i - 1]
|
||||
, distances[i] / 2
|
||||
, bearings[i]
|
||||
);
|
||||
|
||||
probes.Add(new Probe()
|
||||
{
|
||||
Latitude = coords[1],
|
||||
Longitude = coords[0],
|
||||
StartTime = trace[i].Timestamp,
|
||||
Duration = durations[i],
|
||||
Distance = distances[i],
|
||||
Speed = speeds[i],
|
||||
Bearing = bearings[i],
|
||||
IsGood = isGood
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if too few good probes, drop entire trace
|
||||
if (!_options.OutputBadProbes && probes.Count < _options.MinProbes)
|
||||
{
|
||||
return new List<Probe>();
|
||||
}
|
||||
|
||||
// MinProbes can be 0, return
|
||||
if (probes.Count == 0 && _options.MinProbes == 0)
|
||||
{
|
||||
return new List<Probe>();
|
||||
}
|
||||
|
||||
|
||||
// 2nd pass
|
||||
|
||||
// require at least two probes
|
||||
if (probes.Count > 1)
|
||||
{
|
||||
// check first probe in a trace against the average of first two good probes
|
||||
var avgSpeed = (probes[0].Speed + probes[1].Speed) / 2;
|
||||
var avgDistance = (probes[0].Distance + probes[1].Distance) / 2;
|
||||
var avgDuration = (probes[0].Duration + probes[1].Duration) / 2;
|
||||
var avgBearing = averageAngle(probes[0].Bearing, probes[1].Bearing);
|
||||
|
||||
bool good = true;
|
||||
|
||||
if (negativeDuration)
|
||||
{
|
||||
if (!_options.OutputBadProbes)
|
||||
{
|
||||
return new List<Probe>();
|
||||
}
|
||||
|
||||
negativeDuration = true;
|
||||
good = false;
|
||||
|
||||
// if a trace has negative duration, the trace is likely noisy
|
||||
}
|
||||
else if (durations[0] < 0)
|
||||
{
|
||||
good = false;
|
||||
}
|
||||
else if (durations[0] < _options.MinTimeBetweenProbes)
|
||||
{
|
||||
// if shorter than the minTimeBetweenProbes, filter.
|
||||
good = false;
|
||||
}
|
||||
else if (distances[0] > _options.MaxDistanceRatioJump * avgDistance)
|
||||
{
|
||||
// if not a gradual increase in distance, it's most likely a signal jump
|
||||
good = false;
|
||||
}
|
||||
else if (durations[0] > _options.MaxDurationRatioJump * avgDuration)
|
||||
{
|
||||
// if not a gradual decrease in sampling frequency, it's most likely a signal jump
|
||||
good = false;
|
||||
}
|
||||
else if (avgSpeed - speeds[0] > _options.MaxAcceleration * durations[0])
|
||||
{
|
||||
// if accelerating faster than maxAcceleration, it's most likely a glitch
|
||||
good = false;
|
||||
}
|
||||
else if (speeds[0] - avgSpeed > _options.MaxDeceleration * durations[0])
|
||||
{
|
||||
// if decelerating faster than maxDeceleration, it's most likely a glitch
|
||||
good = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if in reverse direction, it's most likely signal jump
|
||||
bool isForwardDirection = compareBearing(bearings[0], avgBearing, 89, false);
|
||||
if (!isForwardDirection)
|
||||
{
|
||||
good = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (good || _options.OutputBadProbes)
|
||||
{
|
||||
|
||||
double[] coords = pointAtDistanceAndBearing(
|
||||
trace[0]
|
||||
, distances[0]
|
||||
, bearings[0]
|
||||
);
|
||||
|
||||
probes.Insert(
|
||||
0,
|
||||
new Probe()
|
||||
{
|
||||
Latitude = coords[1],
|
||||
Longitude = coords[0],
|
||||
StartTime = trace[0].Timestamp,
|
||||
Duration = durations[0],
|
||||
Distance = distances[0],
|
||||
Speed = speeds[0],
|
||||
Bearing = bearings[0],
|
||||
IsGood = good
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return probes;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes the average of two angles.
|
||||
/// </summary>
|
||||
/// <param name="a">First angle.</param>
|
||||
/// <param name="b">Second angle</param>
|
||||
/// <returns>Angle midway between a and b.</returns>
|
||||
private double averageAngle(double a, double b)
|
||||
{
|
||||
var anorm = normalizeAngle(a);
|
||||
var bnorm = normalizeAngle(b);
|
||||
|
||||
var minAngle = Math.Min(anorm, bnorm);
|
||||
var maxAngle = Math.Max(anorm, bnorm);
|
||||
|
||||
var dist1 = Math.Abs(a - b);
|
||||
var dist2 = (minAngle + (360 - maxAngle));
|
||||
|
||||
if (dist1 <= dist2) { return normalizeAngle(minAngle + dist1 / 2); }
|
||||
else
|
||||
{
|
||||
return normalizeAngle(maxAngle + dist2 / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Map angle to positive modulo 360 space.
|
||||
/// </summary>
|
||||
/// <param name="angle">An angle in degrees</param>
|
||||
/// <returns>Equivalent angle in [0-360] space.</returns>
|
||||
private double normalizeAngle(double angle)
|
||||
{
|
||||
return (angle < 0) ? (angle % 360) + 360 : (angle % 360);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Compare bearing `baseBearing` to `bearing`, to determine if they are close enough to each other to be considered matching.
|
||||
/// </summary>
|
||||
/// <param name="baseBearing">Base bearing</param>
|
||||
/// <param name="bearing">Number of degrees difference that is allowed between the bearings.</param>
|
||||
/// <param name="range"></param>
|
||||
/// <param name="allowReverse">allows bearings that are 180 degrees +/- `range` to be considered matching</param>
|
||||
/// <returns></returns>
|
||||
private bool compareBearing(double baseBearing, double bearing, double range, bool allowReverse)
|
||||
{
|
||||
|
||||
// map base and bearing into positive modulo 360 space
|
||||
var normalizedBase = normalizeAngle(baseBearing);
|
||||
var normalizedBearing = normalizeAngle(bearing);
|
||||
|
||||
var min = normalizeAngle(normalizedBase - range);
|
||||
var max = normalizeAngle(normalizedBase + range);
|
||||
|
||||
if (min < max)
|
||||
{
|
||||
if (min <= normalizedBearing && normalizedBearing <= max)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (min <= normalizedBearing || normalizedBearing <= max)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowReverse)
|
||||
{
|
||||
return compareBearing(normalizedBase + 180, bearing, range, false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates coordinate in between two trace points to smooth line
|
||||
/// </summary>
|
||||
/// <returns>double array containing lng/lat</returns>
|
||||
private double[] pointAtDistanceAndBearing(TracePoint tracePoint, double distance, double bearing)
|
||||
{
|
||||
return _ruler.Destination(
|
||||
new double[] { tracePoint.Longitude, tracePoint.Latitude }
|
||||
, distance
|
||||
, bearing
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5372119c5f67a74b940346a8aff4fc6
|
||||
timeCreated: 1510757089
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da2de0b67d574e64481954f696b7b58f
|
||||
folderAsset: yes
|
||||
timeCreated: 1511187101
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0c613d8a2e6422439f0ee0a0e418427
|
||||
folderAsset: yes
|
||||
timeCreated: 1511187112
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,222 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="FileSourceTest.cs" company="Mapbox">
|
||||
// Copyright (c) 2016 Mapbox. All rights reserved.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
// TODO: figure out how run tests outside of Unity with .NET framework, something like '#if !UNITY'
|
||||
|
||||
namespace Mapbox.ProbeExtractorCs.UnitTest
|
||||
{
|
||||
|
||||
|
||||
using Mapbox.Platform;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Mapbox.CheapRulerCs;
|
||||
|
||||
[TestFixture]
|
||||
internal class ProbeExtractorCsTest
|
||||
{
|
||||
|
||||
private List<TracePoint> _trace;
|
||||
private List<TracePoint> _footTrace;
|
||||
private List<Probe> _probes;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_trace = loadTraceFixture("ProbeExtractorCs_fixture_trace");
|
||||
_footTrace = loadTraceFixture("ProbeExtractorCs_fixture_trace-foot");
|
||||
_probes = loadProbeFixture("ProbeExtractorCs_fixture_probes");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Test, Order(1)]
|
||||
public void FixturesLoaded()
|
||||
{
|
||||
Assert.AreEqual(14, _trace.Count);
|
||||
Assert.AreEqual(12, _probes.Count);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void ExtractProbes()
|
||||
{
|
||||
CheapRuler ruler = CheapRuler.FromTile(49, 7);
|
||||
|
||||
ProbeExtractorOptions options = new ProbeExtractorOptions()
|
||||
{
|
||||
MinTimeBetweenProbes = 1, // seconds
|
||||
MaxDistanceRatioJump = 3, // do not include probes when the distance is 3 times bigger than the previous one
|
||||
MaxDurationRatioJump = 3, // do not include probes when the duration is 3 times bigger than the previous one
|
||||
MaxAcceleration = 15, // meters per second per second
|
||||
MaxDeceleration = 18 // meters per second per second
|
||||
};
|
||||
|
||||
ProbeExtractor extractor = new ProbeExtractor(ruler, options);
|
||||
List<Probe> extractedProbes = extractor.ExtractProbes(_trace);
|
||||
|
||||
Assert.AreEqual(12, extractedProbes.Count, "12 probes were expected to be extracted");
|
||||
|
||||
for (int i = 0; i < extractedProbes.Count; i++)
|
||||
{
|
||||
Probe fp = _probes[i]; // fixture probe
|
||||
Probe ep = extractedProbes[i]; // extracted probe
|
||||
|
||||
Assert.AreEqual(fp.Longitude, ep.Longitude, 0.001, "probe[" + i.ToString() + "]: longitude doesn't match");
|
||||
Assert.AreEqual(fp.Latitude, ep.Latitude, 0.001, "probe[" + i.ToString() + "]: latitude doesn't match");
|
||||
Assert.AreEqual(fp.StartTime, ep.StartTime, "probe[" + i.ToString() + "]: start time doesn't match");
|
||||
Assert.AreEqual(fp.Duration, ep.Duration, "probe[" + i.ToString() + "]: duration doesn't match");
|
||||
Assert.AreEqual(fp.Speed, ep.Speed, 0.001, "probe[" + i.ToString() + "]: speed doesn't match");
|
||||
Assert.AreEqual(fp.Bearing, ep.Bearing, 0.001, "probe[" + i.ToString() + "]: bearing doesn't match");
|
||||
Assert.AreEqual(fp.Distance, ep.Distance, 0.001, "probe[" + i.ToString() + "]: distance doesn't match");
|
||||
Assert.AreEqual(fp.IsGood, ep.IsGood, "probe[" + i.ToString() + "]: longitude doesn't match");
|
||||
}
|
||||
|
||||
|
||||
options.MinTimeBetweenProbes = 2;
|
||||
extractor = new ProbeExtractor(ruler, options);
|
||||
extractedProbes = extractor.ExtractProbes(_trace);
|
||||
|
||||
Assert.AreEqual(5, extractedProbes.Count, "5 probes were expected to be extracted");
|
||||
|
||||
|
||||
options.OutputBadProbes = true;
|
||||
extractor = new ProbeExtractor(ruler, options);
|
||||
extractedProbes = extractor.ExtractProbes(_trace);
|
||||
|
||||
Assert.AreEqual(13, extractedProbes.Count, "13 probes were expected to be extracted");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void ExtractFootTrace()
|
||||
{
|
||||
CheapRuler ruler = new CheapRuler(_footTrace[0].Latitude);
|
||||
ProbeExtractorOptions options = new ProbeExtractorOptions();
|
||||
|
||||
ProbeExtractor extractor = new ProbeExtractor(ruler, options);
|
||||
List<Probe> extractedProbes = extractor.ExtractProbes(_footTrace);
|
||||
|
||||
Assert.AreEqual(40, extractedProbes.Count);
|
||||
}
|
||||
|
||||
|
||||
private List<TracePoint> loadTraceFixture(string fixtureName)
|
||||
{
|
||||
TextAsset fixtureAsset = Resources.Load<TextAsset>(fixtureName);
|
||||
List<TracePoint> trace = new List<TracePoint>();
|
||||
using (StringReader sr = new StringReader(fixtureAsset.text))
|
||||
{
|
||||
// skip header
|
||||
sr.ReadLine();
|
||||
string line;
|
||||
while (null != (line = sr.ReadLine()))
|
||||
{
|
||||
string[] tokens = line.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
if (tokens.Length != 4)
|
||||
{
|
||||
Debug.LogWarning("trace.csv has wrong number of columns");
|
||||
continue;
|
||||
}
|
||||
|
||||
double lng;
|
||||
double lat;
|
||||
double bearing;
|
||||
long timestamp;
|
||||
|
||||
if (!double.TryParse(tokens[0], NumberStyles.Any, CultureInfo.InvariantCulture, out lng)) { Debug.LogWarning("could not parse longitude"); continue; }
|
||||
if (!double.TryParse(tokens[1], NumberStyles.Any, CultureInfo.InvariantCulture, out lat)) { Debug.LogWarning("could not parse latitude"); continue; }
|
||||
if (!double.TryParse(tokens[2], NumberStyles.Any, CultureInfo.InvariantCulture, out bearing)) { Debug.LogWarning("could not parse bearing"); continue; }
|
||||
if (!long.TryParse(tokens[3], NumberStyles.Any, CultureInfo.InvariantCulture, out timestamp)) { Debug.LogWarning("could not parse timestamp"); continue; }
|
||||
|
||||
trace.Add(new TracePoint()
|
||||
{
|
||||
Longitude = lng,
|
||||
Latitude = lat,
|
||||
Bearing = bearing,
|
||||
Timestamp = timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
|
||||
|
||||
private List<Probe> loadProbeFixture(string fixtureName)
|
||||
{
|
||||
TextAsset fixtureAsset = Resources.Load<TextAsset>(fixtureName);
|
||||
List<Probe> probes = new List<Probe>();
|
||||
using (StringReader sr = new StringReader(fixtureAsset.text))
|
||||
{
|
||||
// skip header
|
||||
sr.ReadLine();
|
||||
string line;
|
||||
while (null != (line = sr.ReadLine()))
|
||||
{
|
||||
string[] tokens = line.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
if (tokens.Length != 8)
|
||||
{
|
||||
Debug.LogWarning("probes.csv has wrong number of columns");
|
||||
continue;
|
||||
}
|
||||
|
||||
double lng;
|
||||
double lat;
|
||||
long startTime;
|
||||
long duration;
|
||||
double speed;
|
||||
double bearing;
|
||||
double distance;
|
||||
bool isGood;
|
||||
|
||||
if (!double.TryParse(tokens[0], NumberStyles.Any, CultureInfo.InvariantCulture, out lng)) { Debug.LogWarning("could not parse longitude"); continue; }
|
||||
if (!double.TryParse(tokens[1], NumberStyles.Any, CultureInfo.InvariantCulture, out lat)) { Debug.LogWarning("could not parse latitude"); continue; }
|
||||
if (!long.TryParse(tokens[2], NumberStyles.Any, CultureInfo.InvariantCulture, out startTime)) { Debug.LogWarning("could not parse timestamp"); continue; }
|
||||
if (!long.TryParse(tokens[3], NumberStyles.Any, CultureInfo.InvariantCulture, out duration)) { Debug.LogWarning("could not parse duration"); continue; }
|
||||
if (!double.TryParse(tokens[4], NumberStyles.Any, CultureInfo.InvariantCulture, out speed)) { Debug.LogWarning("could not parse speed"); continue; }
|
||||
if (!double.TryParse(tokens[5], NumberStyles.Any, CultureInfo.InvariantCulture, out bearing)) { Debug.LogWarning("could not parse bearing"); continue; }
|
||||
if (!double.TryParse(tokens[6], NumberStyles.Any, CultureInfo.InvariantCulture, out distance)) { Debug.LogWarning("could not parse distance"); continue; }
|
||||
if (!bool.TryParse(tokens[7], out isGood)) { Debug.LogWarning("could not parse good"); continue; }
|
||||
|
||||
probes.Add(new Probe()
|
||||
{
|
||||
Longitude = lng,
|
||||
Latitude = lat,
|
||||
StartTime = startTime,
|
||||
Duration = duration,
|
||||
Speed = speed,
|
||||
Bearing = bearing,
|
||||
Distance = distance,
|
||||
IsGood = isGood
|
||||
});
|
||||
}
|
||||
}
|
||||
return probes;
|
||||
}
|
||||
|
||||
|
||||
// quick hack for visualizing output of ProbeExtractor on http://geojson.io
|
||||
private string probesToGeojson(List<Probe> probes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// line
|
||||
sb.Append("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[");
|
||||
sb.Append(string.Join(",", probes.Select(p => string.Format(CultureInfo.InvariantCulture, "[{0},{1}]", p.Longitude, p.Latitude)).ToArray()));
|
||||
sb.Append("]}}");
|
||||
|
||||
sb.Append("]}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c911a8f62439664b8f8743ebfa8700a
|
||||
timeCreated: 1511187181
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user