Skip to content

Commit

Permalink
Merge pull request #19 from reedu-reengineering-education/feat/data-p…
Browse files Browse the repository at this point in the history
…rocessing

feat: optimize data processing
  • Loading branch information
felixerdy authored Oct 26, 2023
2 parents 30f2cd0 + ea74f99 commit 0035c01
Show file tree
Hide file tree
Showing 15 changed files with 482 additions and 303 deletions.
2 changes: 1 addition & 1 deletion android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ android {

apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-community-background-geolocation')
implementation project(':capacitor-community-bluetooth-le')
implementation project(':capacitor-app')
implementation project(':capacitor-preferences')
implementation project(':felixerdy-background-geolocation')

}

Expand Down
6 changes: 3 additions & 3 deletions android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')

include ':capacitor-community-background-geolocation'
project(':capacitor-community-background-geolocation').projectDir = new File('../node_modules/@capacitor-community/background-geolocation/android')

include ':capacitor-community-bluetooth-le'
project(':capacitor-community-bluetooth-le').projectDir = new File('../node_modules/@capacitor-community/bluetooth-le/android')

Expand All @@ -13,3 +10,6 @@ project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/

include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')

include ':felixerdy-background-geolocation'
project(':felixerdy-background-geolocation').projectDir = new File('../node_modules/@felixerdy/background-geolocation/android')
2 changes: 1 addition & 1 deletion ios/App/App/capacitor.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"appName": "senseBox:Bike",
"webDir": "out",
"server": {
"url": "http://192.168.2.135:3000"
"url": "http://192.168.0.220:3000"
}
}
2 changes: 1 addition & 1 deletion ios/App/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCommunityBackgroundGeolocation', :path => '../../node_modules/@capacitor-community/background-geolocation'
pod 'CapacitorCommunityBluetoothLe', :path => '../../node_modules/@capacitor-community/bluetooth-le'
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
pod 'FelixerdyBackgroundGeolocation', :path => '../../node_modules/@felixerdy/background-geolocation'
end

target 'App' do
Expand Down
14 changes: 7 additions & 7 deletions ios/App/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,44 @@ PODS:
- CapacitorCordova
- CapacitorApp (5.0.6):
- Capacitor
- CapacitorCommunityBackgroundGeolocation (0.0.1):
- Capacitor
- CapacitorCommunityBluetoothLe (3.0.2):
- Capacitor
- CapacitorCordova (5.5.0)
- CapacitorPreferences (5.0.6):
- Capacitor
- FelixerdyBackgroundGeolocation (0.0.1):
- Capacitor

DEPENDENCIES:
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
- "CapacitorCommunityBackgroundGeolocation (from `../../node_modules/@capacitor-community/background-geolocation`)"
- "CapacitorCommunityBluetoothLe (from `../../node_modules/@capacitor-community/bluetooth-le`)"
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
- "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)"
- "FelixerdyBackgroundGeolocation (from `../../node_modules/@felixerdy/background-geolocation`)"

EXTERNAL SOURCES:
Capacitor:
:path: "../../node_modules/@capacitor/ios"
CapacitorApp:
:path: "../../node_modules/@capacitor/app"
CapacitorCommunityBackgroundGeolocation:
:path: "../../node_modules/@capacitor-community/background-geolocation"
CapacitorCommunityBluetoothLe:
:path: "../../node_modules/@capacitor-community/bluetooth-le"
CapacitorCordova:
:path: "../../node_modules/@capacitor/ios"
CapacitorPreferences:
:path: "../../node_modules/@capacitor/preferences"
FelixerdyBackgroundGeolocation:
:path: "../../node_modules/@felixerdy/background-geolocation"

SPEC CHECKSUMS:
Capacitor: 57890b363df14d5d2d5d8461aa23e886cb34da2a
CapacitorApp: 024e1b1bea5f883d79f6330d309bc441c88ad04a
CapacitorCommunityBackgroundGeolocation: 6f26f4290377dbd1d6dec21e62cb66f008f61ec7
CapacitorCommunityBluetoothLe: 83b0de348b2ec461e0f9fa0e48c9e79c8378ddb0
CapacitorCordova: 3d3908a3d208a11a75f9df3b18c4405c4de76e1d
CapacitorPreferences: f03954bcb0ff09c792909e46bff88e3183c16b10
FelixerdyBackgroundGeolocation: 1e6a92a836b58a62b20e0eb6b78bff201265deb8

PODFILE CHECKSUM: 0b2ce188826fd8b5860c476484770aab80e94b53
PODFILE CHECKSUM: f2e3708e22e094f8a9b03f2e5f0e3688df829e58

COCOAPODS: 1.11.3
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
"export": "yarn build && NODE_ENV=production npx cap sync"
},
"dependencies": {
"@capacitor-community/background-geolocation": "felixerdy/background-geolocation",
"@capacitor-community/bluetooth-le": "^3.0.1",
"@capacitor/android": "^5.3.0",
"@capacitor/app": "^5.0.6",
"@capacitor/core": "^5.3.0",
"@capacitor/ios": "^5.3.0",
"@capacitor/preferences": "^5.0.6",
"@felixerdy/background-geolocation": "1.2.15",
"@heroicons/react": "^2.0.18",
"@hookform/resolvers": "^3.3.1",
"@radix-ui/react-dialog": "^1.0.4",
Expand All @@ -31,6 +31,7 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-toggle": "^1.0.3",
"@react-spring/web": "^9.7.3",
"@tremor/react": "^3.10.0",
"@turf/circle": "^6.5.0",
"@turf/helpers": "^6.5.0",
Expand Down
3 changes: 1 addition & 2 deletions src/components/Device/PreviewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import useSenseBox from '@/lib/useSenseBox'
import { Button } from '../ui/button'

export default function PreviewModal() {
const { isConnected, connect, values, disconnect, resetValues } =
useSenseBox()
const { isConnected, connect, values, disconnect } = useSenseBox()

return (
<div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Device/SettingsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { DialogClose } from '@radix-ui/react-dialog'
import useSenseBox from '@/lib/useSenseBox'
import { numbersToDataView } from '@capacitor-community/bluetooth-le'
import { registerPlugin } from '@capacitor/core'
import { BackgroundGeolocationPlugin } from '@capacitor-community/background-geolocation'
import { BackgroundGeolocationPlugin } from '@felixerdy/background-geolocation'
import { Drawer } from 'vaul'
import { useEffect, useState } from 'react'

Expand Down
2 changes: 1 addition & 1 deletion src/components/Map/LocationMarker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Location } from '@capacitor-community/background-geolocation'
import { Location } from '@felixerdy/background-geolocation'
import { Source, Layer } from 'react-map-gl/maplibre'

// @ts-ignore
Expand Down
5 changes: 3 additions & 2 deletions src/components/Map/MeasurementsGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import useSenseBox from '@/lib/useSenseBox'
import { AreaChart } from '@tremor/react'
import AnimatedNumber from '../ui/animated-number'

export default function MeasurementsGrid() {
const { values } = useSenseBox()
Expand Down Expand Up @@ -111,7 +112,7 @@ function GridItem({
sparklineData?: { x: Date; y: number }[]
}) {
return (
<div className="relative flex w-full flex-col justify-between p-2">
<div className="relative flex w-full flex-1 flex-col justify-between overflow-hidden p-2">
{sparklineData && sparklineData.length > 2 && (
<div className="absolute left-0 top-0 h-full w-full">
<AreaChart
Expand All @@ -134,7 +135,7 @@ function GridItem({
{value === undefined && (
<div className="my-1.5 h-5 animate-pulse rounded-full bg-accent" />
)}
{value?.toFixed(2)}
{value && <AnimatedNumber decimals={2}>{value}</AnimatedNumber>}
</p>
<p className="text-xs font-semibold">{unit}</p>
</div>
Expand Down
26 changes: 26 additions & 0 deletions src/components/ui/animated-number.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client'

import { animated, useSpring } from '@react-spring/web'

type AnimatedNumberProps = React.HTMLAttributes<HTMLSpanElement> & {
children: number
decimals?: number
}

export default function AnimatedNumber({
children,
decimals,
...props
}: AnimatedNumberProps) {
const springProps = useSpring({ val: children, from: { val: 0 } })

return (
<animated.span {...props}>
{springProps.val.to(val =>
new Intl.NumberFormat('de-DE', {
maximumFractionDigits: decimals || 0,
}).format(val),
)}
</animated.span>
)
}
135 changes: 135 additions & 0 deletions src/lib/SenseBoxDataParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { registerPlugin } from '@capacitor/core'
import {
senseBoxDataRecord,
useSenseBoxValuesStore,
} from './store/useSenseBoxValuesStore'
import { BackgroundGeolocationPlugin } from '@felixerdy/background-geolocation'

const BackgroundGeolocation = registerPlugin<BackgroundGeolocationPlugin>(
'BackgroundGeolocation',
)

export class SenseBoxDataParser {
private static instance: SenseBoxDataParser

private dataBuffer: senseBoxDataRecord[] = []
private timestampInterval: number

constructor(interval: number) {
this.timestampInterval = interval
}

// Singleton
static getInstance(interval: number) {
if (!SenseBoxDataParser.instance) {
SenseBoxDataParser.instance = new SenseBoxDataParser(interval)
}

const instance = SenseBoxDataParser.instance
instance.setTimestampInterval(interval)
return instance
}

getTimestampInterval() {
return this.timestampInterval
}

setTimestampInterval(interval: number) {
this.timestampInterval = interval
}

async checkCompleteData() {
if (this.dataBuffer.length < 5) return

// merge the data by timestamp
const buckets = this.dataBuffer
.reduce((acc, record) => {
const { timestamp, ...data } = record

// check if there is already a record with similar timestamp
const existingTimestamp = acc.find(
e =>
Math.abs(new Date(e.timestamp).getTime() - timestamp.getTime()) <
this.timestampInterval,
) // 5 seconds

// add new record or update existing one
if (!existingTimestamp) {
acc.push({ timestamp, ...data })
} else {
const existingIndex = acc.indexOf(existingTimestamp)
acc[existingIndex] = {
...existingTimestamp,
...data,
}
}

return acc
}, [] as senseBoxDataRecord[])
.filter(
b =>
b.temperature !== undefined &&
b.humidity !== undefined &&
b.pm1 !== undefined &&
b.pm2_5 !== undefined &&
b.pm4 !== undefined &&
b.pm10 !== undefined &&
b.acceleration_x !== undefined &&
b.acceleration_y !== undefined &&
b.acceleration_z !== undefined &&
b.gps_lat !== undefined &&
b.gps_lng !== undefined &&
b.gps_spd !== undefined &&
b.distance_l !== undefined,
)

const bucketsProcessedLocation = await Promise.all(
buckets.map(async b => {
const processedLocation = await BackgroundGeolocation.processLocation({
location: {
latitude: b.gps_lat!,
longitude: b.gps_lng!,
speed: b.gps_spd!,
accuracy: 0,
simulated: false,
altitude: null,
bearing: null,
altitudeAccuracy: null,
time: b.timestamp.getTime(),
},
})
return {
...b,
gps_lat: processedLocation.latitude,
gps_lng: processedLocation.longitude,
gps_spd: processedLocation.speed,
} as senseBoxDataRecord
}),
)

useSenseBoxValuesStore.getState().addValues(bucketsProcessedLocation)

const completeTimestamps = buckets.map(b => b.timestamp)

// remove all data that is already complete
this.dataBuffer = this.dataBuffer.filter(
d =>
!completeTimestamps.some(
t =>
Math.abs(t.getTime() - d.timestamp.getTime()) <
this.timestampInterval,
),
)

console.log(this.dataBuffer.length)
}

pushData(data: Omit<senseBoxDataRecord, 'timestamp'>) {
this.dataBuffer.push({
...data,
timestamp: new Date(),
})

this.checkCompleteData()
}
}
9 changes: 7 additions & 2 deletions src/lib/store/useSenseBoxValuesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ export type senseBoxDataRecord = {
interface senseBoxValuesStore {
values: senseBoxDataRecord[]
setValues: (values: senseBoxDataRecord[]) => void
resetValues: () => void
addValues: (values: senseBoxDataRecord[]) => void
}

export const useSenseBoxValuesStore = create<senseBoxValuesStore>(set => ({
values: [],
setValues: values => set({ values }),
resetValues: () => set({ values: [] }),
addValues: values =>
set(state => ({
values: [...state.values, ...values].sort(
(a, b) => a.timestamp.getTime() - b.timestamp.getTime(),
),
})),
}))
Loading

0 comments on commit 0035c01

Please sign in to comment.