Browse Source

docs: Add comments to most important files

master
Konstantinos Kamaropoulos 5 years ago
parent
commit
a648a49a7c
  1. 68
      src/app/chart/chart.component.ts
  2. 23
      src/app/logs.service.ts
  3. 5
      src/app/map/map.component.html
  4. 73
      src/app/map/map.component.ts

68
src/app/chart/chart.component.ts

@ -11,14 +11,21 @@ import { interval } from 'rxjs';
styleUrls: ['./chart.component.scss']
})
export class ChartComponent implements OnInit {
// The subscribe object for the rxjs interval
sub: any;
constructor(private logsService: LogsService) {}
// We're going to keep all our data here.
// This is a two dimmensional Object array.
// It keeps all the data points for every sensor we have to display on the chart.
dataPoints: Array<Array<Object>> = [];
// Global object to keep our chart.
chart: any;
// Method to toggle data series visibility on and off.
// This will be used on the chart legend.
toggleDataSeries(e: any) {
if (typeof e.dataSeries.visible === 'undefined' || e.dataSeries.visible) {
e.dataSeries.visible = false;
@ -28,14 +35,22 @@ export class ChartComponent implements OnInit {
this.chart.render();
}
// Variable to store the data config of our chart.
// We'll fill this with stuff about all of our sensors later on.
dataConfig: Array<Object> = [];
async ngOnInit() {
// OnInit, get the logs for the first time
let data = await this.logsService.getLogsFirstRun();
// For every sensor in the sensorReadings
for (const key in data[0]['sensorReadings']) {
if (data[0]['sensorReadings'].hasOwnProperty(key)) {
// Get the value for the sensor
const element = data[0]['sensorReadings'][key];
// Create a new array inside dataPoint for the sensor
this.dataPoints[key] = [];
// Add configuration for the sensor
this.dataConfig.push({
type: 'line',
xValueType: 'dateTime',
@ -47,20 +62,31 @@ export class ChartComponent implements OnInit {
});
}
}
let dpsLength = 0;
// Create a new CanvasJS chart on #chartContainer
this.chart = new CanvasJS.Chart('chartContainer', {
zoomEnabled: true,
animationEnabled: true,
exportEnabled: true,
// When we have large time gaps between our data,
// it'd be better to shrink it so it doesn't mess up
// the way our useful data is displayed.
// That's where scaleBreaks come in.
axisX: {
scaleBreaks: {
autoCalculate: true,
maxNumberOfAutoBreaks: 5
}
},
// Share the same tooltip between on data lines.
toolTip: {
shared: true
},
// Set up a legend.
// We also want to be able to toggle our data series,
// so we do that with toggleDataSeries on click.
legend: {
cursor: 'pointer',
verticalAlign: 'top',
@ -68,46 +94,84 @@ export class ChartComponent implements OnInit {
fontColor: 'dimGrey',
itemclick: this.toggleDataSeries
},
// That's the sensor series configuration we created earlier.
data: [...this.dataConfig]
});
// Add the createdAt field of the log to the sensorReadings object
// for ever log item we have. This way we will have it available
// without doing anything weird later on.
let sensorReadings: any = data.map(log => {
// Dump the logs into a new object so we don't have to touch the original one
let d = log['sensorReadings'];
// and add the createdAt field to it.
d['time'] = log['createdAt'];
// Then we just return the whole thing.
return d;
});
let i = 0;
// For every log we have
sensorReadings.forEach((element: any) => {
for (const key in element) {
// and for every field/sensor that is not time, since we just need this for the X Axis
if (element.hasOwnProperty(key) && key != 'time') {
// keep the value of the sensor
const value = element[key];
// and add a new data point for that sensor, with it's createdAt time
// on the X Axis and the sensor value on the Y Axis.
this.dataPoints[key].push({ x: new Date(element['time']), y: parseInt(value) });
// Set the lenght of the datapoints to the amount of datapoints after we added them
dpsLength = this.dataPoints.length;
}
}
});
// Now that we've added some data to the chart, render it.
this.chart.render();
// Run the chart update every second.
this.sub = interval(1000).subscribe(async val => {
// If live updates are enabled:
if (this.logsService.liveUpdate) {
// Get the changes since the last time we got the logs.
let data = await this.logsService.getUpdates();
// Again, we need to add the createAt field of the log to the sensor readings object.
let sensorReadings: any = data.map(log => {
// Dump the logs into a new object so we don't have to touch the original one
let d = log['sensorReadings'];
// and add the createdAt field to it.
d['time'] = log['createdAt'];
// Then we just return the whole thing.
return d;
});
// For every new log we got
sensorReadings.forEach((element: any) => {
for (const key in element) {
// and for every field/sensor that is not time, since we just need this for the X Axis
if (element.hasOwnProperty(key) && key != 'time') {
// keep the value of the sensor
const value = element[key];
// and add a new data point for that sensor, with it's createdAt time
// on the X Axis and the sensor value on the Y Axis.
this.dataPoints[key].push({ x: new Date(element['time']), y: parseInt(value) });
// Set the lenght of the datapoints to the amount of datapoints after we added them
dpsLength = this.dataPoints.length;
}
}
dpsLength++;
});
// Render the chart again, in case we've added any new log values to it.
this.chart.render();
}
});

23
src/app/logs.service.ts

@ -9,12 +9,16 @@ import { map, catchError, last } from 'rxjs/operators';
export class LogsService {
constructor(private httpClient: HttpClient) {}
// Toggle whether to run live updates on the data or not
liveUpdate: Boolean = true;
// Global variable for getting only the changes from latest logs
previousData: Array<Object> | undefined = undefined;
getLogs(): Promise<Array<Object>> {
return this.httpClient
// Get the logs from the API
return (
this.httpClient
.get('/logs')
.pipe(
map((body: any) => {
@ -22,27 +26,36 @@ export class LogsService {
}),
catchError(() => of('Error, could not load logs'))
)
.toPromise();
// Convert Observable to Promise for easier manipulation and get some async/await convinience
.toPromise()
);
}
// Get the logs from the API on the first time we ever need them
async getLogsFirstRun(): Promise<Array<Object>> {
let data = await this.getLogs();
this.previousData = data;
return data;
}
// Get only the logs we haven't seen before
async getUpdates(): Promise<Array<Object>> {
let prevData = this.previousData;
// If previousData is undefined, aka this is our first time here:
if (!this.previousData) {
// Get the logs from the API
let data = await this.getLogs();
// and store them to previousData
this.previousData = data;
// Lastly, return the data we got from the API
return data;
} else {
// It's not our first time here, get the logs
let latestData = await this.getLogs();
// Get only the differences between latestData and previousData
var changes = latestData.filter(item1 => !this.previousData.some(item2 => item2['_id'] === item1['_id']));
// Set previousData to the latest version of the logs we just got
this.previousData = latestData;
// Return the changes
return changes;
}
}

5
src/app/map/map.component.html

@ -1,9 +1,4 @@
<div class="container-fluid">
<!-- <div class="jumbotron text-center"> -->
<!-- <h1>
<span translate>APP_NAME</span>
</h1> -->
<!-- <p><i class="far fa-bookmark"></i> <span translate>Version</span> {{ version }}</p> -->
<div class="mapDiv">
<div #mapContainer id="map"></div>
</div>

73
src/app/map/map.component.ts

@ -13,26 +13,40 @@ import { interval } from 'rxjs';
styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit {
version: string | null = environment.version;
pointsToDisplay = 10;
// Global map object
map: mapboxgl.Map;
// Style of the map we'll be loading.
// Can also be 'mapbox://styles/mapbox/satellite-v9' for a satellite map
style = 'mapbox://styles/mapbox/streets-v11';
// Coordinates for the location we're going to focus initially.
// In this case Athens, Greece.
lat = 37.9838;
lng = 23.7275;
// The subscribe object for the rxjs interval
sub: any;
// The last point we've added to the map.
// It's going to be a two element array, one for longitude and one for latitude.
lastPoint: number[];
ommitedPoints: number = 0;
// The maximum number of points to display on the first load.
// This will be the last X points we get from the logs API.
pointsToDisplay = 10;
// The number of displayed points in the map
// and how many we've ommited in order to increase performance
displayedPoints: number = 0;
ommitedPoints: number = 0;
constructor(private logsService: LogsService) {}
async ngOnInit() {
// Load access token into the mapboxx object
Object.getOwnPropertyDescriptor(mapboxgl, 'accessToken').set(environment.mapbox.accessToken);
// Create map on the #map div
this.map = new mapboxgl.Map({
container: 'map',
style: this.style,
@ -40,51 +54,94 @@ export class MapComponent implements OnInit {
zoom: 9
});
// Add map controls
// this.map.addControl(new mapboxgl.NavigationControl());
// Check to see if it's the first run of the component
var firstRun: Boolean = true;
// Run every second
this.sub = interval(1000).subscribe(async val => {
// If it's the first time we run this or if live updates are enabled (firstTime overides liveUpdate)
if (this.logsService.liveUpdate || firstRun) {
let data: Array<Object>;
if (firstRun) {
// Get logs for the first time
data = await this.logsService.getLogsFirstRun();
// Since we got our data, set firstRun to false
firstRun = false;
// If we get more data than the maximum allowed displayed points,
// ommit the excess data points and keep the count of how many points we ommited.
if (data.length > this.pointsToDisplay) {
this.ommitedPoints = data.length - this.pointsToDisplay;
data = data.slice(data.length - this.pointsToDisplay, data.length);
}
} else {
// If this is not the first time we run this, get only the changes since the last time we got the logs
data = await this.logsService.getUpdates();
}
// Here we are now getting new points every second.
// Those will be added on top of the maximum loaded points
// so we should keep count of how many points we're adding so we can display
// the number of displayed points on the map.
// Initialize new point counter.
// This will decide if we actually got any new points
// and whether we actually have to fly to the new marker in the map.
let newPointsCount = 0;
// Add the number of data points we got to the number of displayed points
this.displayedPoints = this.displayedPoints + data.length;
// For every log we got
for (let log of data) {
// We only add markers for logs with valid GPS data, aka only status == A.
if (log['gps_data']['status'] == 'A') {
// We got a new valid point to add a marker for.
// This means we'll have to fly to it.
// Increase the relevant counter.
newPointsCount++;
// Some dirty templating for the data inside the popup box.
// This should normally be done in a new component but yeah...
// Here we will display the sensor readings for the specific log,
// as well as it's date and time.
// Those are going to be displayed withing an HTML table.
// Create the table and add the header.
let html =
log['gps_data']['datestamp'] +
' ' +
log['gps_data']['timestamp'] +
'<br><table><tr><th>Sensor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th><th>Reading</th></tr>';
// Add tr with name and value for every sensor
for (let record in log['sensorReadings']) {
if (Object.prototype.hasOwnProperty.call(log['sensorReadings'], record)) {
html += '<tr><td>' + record + '</td>' + '<td>' + log['sensorReadings'][record] + '</td></tr>';
}
}
// Finally, add the table closing tag.
html += '</table>';
// Create a new popup with the HTML code we created above
var popup = new mapboxgl.Popup({ offset: 25 }).setHTML(html);
// Create a new marker on the log location and attach the newly created popup to it
var marker = new mapboxgl.Marker({
draggable: false
})
.setLngLat([log['gps_data']['longitude'], log['gps_data']['latitude']])
.setPopup(popup)
.addTo(this.map);
// When we are adding multiple markers, we need a way to keep track of the last one,
// in order to be able to fly to it. That's what we'll be doing here.
this.lastPoint = [log['gps_data']['longitude'], log['gps_data']['latitude']];
}
}
// If we got any new markers
if (newPointsCount) {
// fly to the last marker we added.
this.map.flyTo({
center: [this.lastPoint[0], this.lastPoint[1]],
essential: true, // this animation is considered essential with respect to prefers-reduced-motion

Loading…
Cancel
Save