Commit c942d0cf authored by Andres Käver's avatar Andres Käver
Browse files

gradient track

parent caf2881f
Pipeline #795 passed with stages
in 1 minute and 17 seconds
......@@ -5688,6 +5688,11 @@
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
"dev": true
},
"gradstop": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/gradstop/-/gradstop-2.2.3.tgz",
"integrity": "sha512-omtiHZCI/vykWcXNDYdrHhe7VUnnZvya94wAHRLI8ciDkAviXYrT+pwP7ybYqK7uwYir59auBCY5ggwBlVSmsg=="
},
"gulp": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
......
......@@ -6,4 +6,5 @@ export interface IGpsLocation {
altitude: number;
verticalAccuracy: number;
gpsLocationTypeId: string;
recordedAt: string;
}
......@@ -34,3 +34,14 @@ export function validateLogLevel(logLevel: string | undefined): string {
}
}
export function decimalToHex(d: number, padding? : number | null): string {
let hex = Number(d).toString(16);
padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;
while (hex.length < padding) {
hex = "0" + hex;
}
return hex;
}
import { IGpsLocation } from 'domain/IGpsLocation';
import { log } from 'app';
export function distanceBetweenLatLon(lat1: number, lon1: number, lat2: number, lon2: number): number {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
const radlat1 = Math.PI * lat1 / 180;
const radlat2 = Math.PI * lat2 / 180;
const theta = lon1 - lon2;
const radtheta = Math.PI * theta / 180;
let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {
dist = 1;
}
dist = Math.acos(dist);
dist = dist * 180 / Math.PI;
dist = dist * 60 * 1853.159616; // in meters
return dist;
}
}
function timeBetweenDates(date0: Date, date1: Date): number {
return Math.abs(date0.getTime() - date1.getTime());
}
function addToBucket(buckets: L.LatLngExpression[][][], bucketNo: number, latLon: L.LatLngExpression, latLonPrev: L.LatLngExpression): L.LatLngExpression[][][] {
if (bucketNo >= buckets.length) return buckets;
buckets[bucketNo].push([latLonPrev, latLon]);
return buckets;
}
// minSpeed and maxSpeed - seconds per km
export function getColorCodedPolylines(locations: IGpsLocation[], minPace: number = 6, maxPace: number = 10, paceBuckets: number = 256): L.LatLngExpression[][][] {
if (!locations || locations.length == 0) return [];
const result: L.LatLngExpression[][][] = [];
for (let index = 0; index < paceBuckets; index++) {
result.push([]);
}
const paceRange = maxPace - minPace;
const paceStep = paceRange / paceBuckets;
let prevLocation: IGpsLocation | null = null;
for (let index = 0; index < locations.length; index++) {
const location = locations[index];
if (prevLocation) {
const distance = distanceBetweenLatLon(location.latitude, location.longitude, prevLocation.latitude, prevLocation.longitude);
const duration = timeBetweenDates(new Date(location.recordedAt), new Date(prevLocation.recordedAt));
// skip bad locations
if (distance < 1 || duration < 1000) continue;
// calculate the speed in minutes per km
const paceMinutesPerKm = duration / distance / 60;
let bucketNo = Math.round(((paceMinutesPerKm - minPace) / paceStep));
if (bucketNo < 0) {
bucketNo = 0
} else if (bucketNo >= paceBuckets){
bucketNo = paceBuckets - 1;
}
console.log(paceMinutesPerKm, bucketNo);
addToBucket(result, bucketNo, [location.latitude, location.longitude], [prevLocation.latitude, prevLocation.longitude]);
//log.debug('distance duration pace', distance, duration, paceMinutesPerKm);
}
prevLocation = location;
}
console.log(result);
return result;
}
......@@ -28,5 +28,5 @@
</div>
</div>
<div id="map"></div>
<div show.bind="selectedGpsSession">Track length: ${trackLength} km</div>
<div show.bind="selectedGpsSession">Track length: ${trackLength / 1000} km</div>
</template>
......@@ -7,10 +7,14 @@ import { PLATFORM } from 'aurelia-pal';
import { autoinject, LogManager, View, observable } from 'aurelia-framework';
import { RouterConfiguration, Router, RouteConfig, NavigationInstruction } from 'aurelia-router';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
import 'leaflet/dist/leaflet.css';
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css';
import * as L from 'leaflet';
import 'leaflet-defaulticon-compatibility';
import { distanceBetweenLatLon, getColorCodedPolylines } from 'utils/utils-leaflet';
import { decimalToHex } from 'utils/utils-general';
import gradstop from 'gradstop';
export const log = LogManager.getLogger('app.HomeIndex');
......@@ -35,10 +39,14 @@ export class HomeIndex {
viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
constructor(private gpsSessionService: GpsSessionService, private gpsLocationService: GpsLocationService) {
private paceColorGradient: string[] = [];
constructor(private gpsSessionService: GpsSessionService, private gpsLocationService: GpsLocationService) {
this.paceColorGradient = gradstop({
stops: 1024,
inputFormat: 'hex',
colorArray: ['#00FF00', '#FFFF00', '#FF0000']
});
}
// ================================= view lifecycle ===============================
......@@ -158,60 +166,57 @@ export class HomeIndex {
const polylinePoints: L.LatLngExpression[] = [];
this.trackLength = 0;
const paceBuckets = getColorCodedPolylines(this.gpsLocations, 6, 18, 1024);
this.gpsLocations.forEach((location, index) => {
polylinePoints.push([location.latitude, location.longitude]);
if (index > 0) {
this.trackLength = this.trackLength + this.distance(
this.trackLength = this.trackLength + distanceBetweenLatLon(
this.gpsLocations[index - 1].latitude, this.gpsLocations[index - 1].longitude,
location.latitude, location.longitude);
}
if (location.gpsLocationTypeId == GpsLocationTypes.wayPoint && this.showWp) {
log.debug('adding wp to ', [location.latitude, location.longitude])
//log.debug('adding wp to ', [location.latitude, location.longitude])
L.marker([location.latitude, location.longitude], { icon: iconWp }).addTo(this.map);
} else
if (location.gpsLocationTypeId == GpsLocationTypes.checkPoint && this.showCp) {
log.debug('adding cp to ', [location.latitude, location.longitude])
//log.debug('adding cp to ', [location.latitude, location.longitude])
L.marker([location.latitude, location.longitude], { icon: iconCp }).addTo(this.map);
}
});
paceBuckets.forEach((paceSegment, bucketNo) => {
paceSegment.forEach(lineSegment => {
const polyline = L.polyline(lineSegment).setStyle({
color: this.paceColorGradient[bucketNo],
weight: 5
}).addTo(this.map);
})
})
// add start marker
if (polylinePoints.length > 0) {
L.marker([this.gpsLocations[0].latitude, this.gpsLocations[0].longitude], { icon: iconS }).addTo(this.map);
this.map.setView([this.gpsLocations[0].latitude, this.gpsLocations[0].longitude], 15);
}
// add finish marker
if (polylinePoints.length > 1) {
L.marker([this.gpsLocations[this.gpsLocations.length - 1].latitude, this.gpsLocations[this.gpsLocations.length - 1].longitude], { icon: iconF }).addTo(this.map);
}
/*
if (polylinePoints.length > 0) {
const polyline = L.polyline(polylinePoints).addTo(this.map);
this.map.fitBounds(polyline.getBounds());
}
*/
}
distance(lat1: number, lon1: number, lat2: number, lon2: number): number {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
const radlat1 = Math.PI * lat1 / 180;
const radlat2 = Math.PI * lat2 / 180;
const theta = lon1 - lon2;
const radtheta = Math.PI * theta / 180;
let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {
dist = 1;
}
dist = Math.acos(dist);
dist = dist * 180 / Math.PI;
dist = dist * 60 * 1.853159616;
return dist;
}
}
}
declare module 'gradstop' {
function gradstop(options: any): string[];
export = gradstop;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment