feat: tracker location based on multiple location providers #5

Merged
localhorst merged 19 commits from feature/ttn-location-algo into main 2025-01-02 14:57:34 +01:00
3 changed files with 53 additions and 7 deletions
Showing only changes of commit 50721114e3 - Show all commits
+1 -1
View File
@@ -7,6 +7,6 @@ DB_PORT=""
WIGLE_TOKEN=""
WIGLE_BASE_URL="https://api.wigle.net"
WIGLE_NETWORK_SEARCH="/api/v2/network/search"
GET_LOCATION_WIFI_MAX_AGE=1209600000 # 14 Tage in Millisekunden (14 * 24 * 60 * 60 * 1000)
GET_LOCATION_WIFI_MAX_AGE=1209600000 # 14 days in milliseconds (14 * 24 * 60 * 60 * 1000)
GET_LOCATION_WIFI_MAX=10000
GET_LOCATION_WIFI_PRIMITIVE=true
+2 -2
View File
@@ -13,11 +13,11 @@
"license": "ISC",
"devDependencies": {
"@types/express": "^5.0.0",
"@types/memoizee": "^0.4.11",
"@types/node": "^22.10.2",
"nodemon": "^3.1.9",
"ts-node": "^10.9.2",
"typescript": "^5.7.2",
"@types/memoizee": "^0.4.11"
"typescript": "^5.7.2"
},
"dependencies": {
"cors": "^2.8.5",
@@ -5,21 +5,25 @@ import {
} from "../event/ttnMessageReceivedEvent";
import { container } from "tsyringe";
import { LocationService } from "../services/locationService";
import { WifiScanService } from "../services/wifiScanService";
import { getLocationForWifiMemoized } from "../proxy/wigle";
const locationService = container.resolve(LocationService);
const wifiScanService = container.resolve(WifiScanService);
domainEventEmitter.on(
TtnMessageReceivedEventName,
async (event: TtnMessageReceivedEvent) => {
console.log(event);
var wifi_based_latitude: number;
var wifi_based_longitude: number;
var wifi_based_latitude: number | undefined = undefined;
var wifi_based_longitude: number | undefined = undefined;
var gnss_based_latitude: number | undefined = undefined;
localhorst marked this conversation as resolved Outdated
Outdated
Review

Das ! kann weg.

Das ! kann weg.
Outdated
Review

Bzw. kann alles weg, wird nicht verwendet

Bzw. kann alles weg, wird nicht verwendet
var gnss_based_longitude: number | undefined = undefined;
var ttn_gw_based_latitude: number | undefined = undefined;
var ttn_gw_based_longitude: number | undefined = undefined;
localhorst marked this conversation as resolved Outdated
Outdated
Review

Ich würde nach dem console.log ein return machen. Dann sparst du dir die else

Ich würde nach dem console.log ein return machen. Dann sparst du dir die else
Outdated
Review

ne, dann würdest ja evtl. die Location von GNSS oder Wifi nicht "parsen"

ne, dann würdest ja evtl. die Location von GNSS oder Wifi nicht "parsen"
Outdated
Review

Ich hätte es so aufgebaut Vorschlag:
Für jede Berechnung eine eigene Funktion die die Werte ausrechnet und dann zurück gibt:

const CalculateTtngatewayLocation = (event: TtnMessageReceivedEvent) => {
//... do stuff
return {
gnss_latitude: virtualLocation.latitude,
gnss_longitude: virtualLocation.longitude,
};
};

In der "Hauptfunktion" dann prüfen ob die werte da sind und wenn ja die Funktion aufrufen und die Werte zuweisen:
var location: Partial<Location> = {};

`// Get location based on TTN Gateways`
`if (event.ttnGateways && event.ttnGateways.length > 0) {`
  `// Option 1`
  `const virtualLocation = CalculateTtngatewayLocation(event);`
  `location.ttn_gw_latitude = virtualLocation.gnss_latitude;`
  `location.ttn_gw_longitude = virtualLocation.gnss_latitude;`

  `// Option 2`
  `const virtualLocation = CalculateTtngatewayLocation(event);`
 `location = { ...location, ...virtualLocation };`

  `// Option 3 -> fancy :D`
  `location = { ...location, ...CalculateTtngatewayLocation(event) };`
  `}`

So werden drei if else zu drei if und man hat weniger Komplexität in einer Funktion.

Ich hätte es so aufgebaut Vorschlag: Für jede Berechnung eine eigene Funktion die die Werte ausrechnet und dann zurück gibt: `const CalculateTtngatewayLocation = (event: TtnMessageReceivedEvent) => {` `//... do stuff` `return {` `gnss_latitude: virtualLocation.latitude,` `gnss_longitude: virtualLocation.longitude,` `};` `};` In der "Hauptfunktion" dann prüfen ob die werte da sind und wenn ja die Funktion aufrufen und die Werte zuweisen: `var location: Partial<Location> = {};` `// Get location based on TTN Gateways` `if (event.ttnGateways && event.ttnGateways.length > 0) {` `// Option 1` `const virtualLocation = CalculateTtngatewayLocation(event);` `location.ttn_gw_latitude = virtualLocation.gnss_latitude;` `location.ttn_gw_longitude = virtualLocation.gnss_latitude;` `// Option 2` `const virtualLocation = CalculateTtngatewayLocation(event);` `location = { ...location, ...virtualLocation };` `// Option 3 -> fancy :D` `location = { ...location, ...CalculateTtngatewayLocation(event) };` `}` So werden drei if else zu drei if und man hat weniger Komplexität in einer Funktion.
// Get location based on TTN Gateways
if (!event.ttnGateways || event.ttnGateways.length === 0) {
console.log("No TTN Gateway location received!")
} else {
@@ -45,11 +49,51 @@ domainEventEmitter.on(
ttn_gw_based_longitude = virtualLocation.longitude;
}
// Get location based on WiFi Scans
if (!event.wifis || event.wifis.length === 0) {
console.log("No WiFi scans received!")
} else {
let totalWeight = 0;
let weightedLatitude = 0;
let weightedLongitude = 0;
{
// TODO: parse Wifi location here
// Process Wi-Fi data to compute weighted location
await Promise.all(
localhorst marked this conversation as resolved Outdated
Outdated
Review

Der Wifi eintrag wird nur die die Datenbank geschrieben, wenn es eine Location gibt, sollte dieser nicht auch ohne reingeschrieben werden mit latitude und longitude undefined? Ansonsten würde ich es in drei steps unterteilen: Api Call, Datenbank entry erstellen, totalWeight + weightedLatitude + weightedLongitude ausrechnen. Ich glaube das macht es übersichtlicher ist aber vermutlich Geschmackssache. Mein Vorschlag:

` const wifiScans = await Promise.all(
event.wifis.map(async (wifi) => {
const apiResponse = await getLocationForWifiMemoized(wifi.mac);

      return {
        lp_ttn_end_device_uplinks_id: event.lp_ttn_end_device_uplinks_id,
        mac: wifi.mac,
        rssi: wifi.rssi,
        latitude: apiResponse?.results[0]?.trilat,
        longitude: apiResponse?.results[0]?.trilong,
      };
    })
  );

  await wifiScanService.createWifiScans(wifiScans);

  const { totalWeight, weightedLatitude, weightedLongitude } =
    wifiScans.reduce(
      (acc, { latitude, longitude, rssi }) => {
        if (latitude && longitude && rssi !== 0) {
          const weight = 1 / Math.abs(rssi);

          acc.totalWeight += weight;
          acc.weightedLatitude += latitude * weight;
          acc.weightedLongitude += longitude * weight;
        }

        return acc;
      },
      {
        totalWeight: 0,
        weightedLatitude: 0,
        weightedLongitude: 0,
      }
    );

  const virtualLocation = {
    latitude: weightedLatitude / totalWeight,
    longitude: weightedLongitude / totalWeight,
  };`
Der Wifi eintrag wird nur die die Datenbank geschrieben, wenn es eine Location gibt, sollte dieser nicht auch ohne reingeschrieben werden mit latitude und longitude undefined? Ansonsten würde ich es in drei steps unterteilen: Api Call, Datenbank entry erstellen, totalWeight + weightedLatitude + weightedLongitude ausrechnen. Ich glaube das macht es übersichtlicher ist aber vermutlich Geschmackssache. Mein Vorschlag: ` const wifiScans = await Promise.all( event.wifis.map(async (wifi) => { const apiResponse = await getLocationForWifiMemoized(wifi.mac); return { lp_ttn_end_device_uplinks_id: event.lp_ttn_end_device_uplinks_id, mac: wifi.mac, rssi: wifi.rssi, latitude: apiResponse?.results[0]?.trilat, longitude: apiResponse?.results[0]?.trilong, }; }) ); await wifiScanService.createWifiScans(wifiScans); const { totalWeight, weightedLatitude, weightedLongitude } = wifiScans.reduce( (acc, { latitude, longitude, rssi }) => { if (latitude && longitude && rssi !== 0) { const weight = 1 / Math.abs(rssi); acc.totalWeight += weight; acc.weightedLatitude += latitude * weight; acc.weightedLongitude += longitude * weight; } return acc; }, { totalWeight: 0, weightedLatitude: 0, weightedLongitude: 0, } ); const virtualLocation = { latitude: weightedLatitude / totalWeight, longitude: weightedLongitude / totalWeight, };`
event.wifis.map(async (wifi) => {
const apiResponse = await getLocationForWifiMemoized(wifi.mac);
if ((apiResponse != undefined) && (apiResponse?.totalResults > 0)) {
// Create new WiFi Scan entry if wigle.net reported location
const newWifiScan = wifiScanService.createWifiScan({
localhorst marked this conversation as resolved Outdated
Outdated
Review

await wifiScanService.createWifiScan

await wifiScanService.createWifiScan
lp_ttn_end_device_uplinks_id: event.lp_ttn_end_device_uplinks_id,
mac: wifi.mac,
rssi: wifi.rssi,
latitude: apiResponse?.results[0]?.trilat,
longitude: apiResponse?.results[0]?.trilong,
})
// Calculate weight based on RSSI (higher signal strength gives more weight)
const weight = 1 / Math.abs((await newWifiScan).rssi);
localhorst marked this conversation as resolved Outdated
Outdated
Review

await kann weg

await kann weg
totalWeight += weight;
// Accumulate weighted latitude and longitude
weightedLatitude += (await newWifiScan).latitude * weight;
weightedLongitude += (await newWifiScan).longitude * weight;
localhorst marked this conversation as resolved Outdated
Outdated
Review

await kann weg

await kann weg
}
})
);
// Calculate the weighted average to get the virtual location
const virtualLocation = {
latitude: weightedLatitude / totalWeight,
longitude: weightedLongitude / totalWeight
};
console.log("Tracker location based on WiFi Scan location:", virtualLocation);
wifi_based_latitude = virtualLocation.latitude;
wifi_based_longitude = virtualLocation.longitude;
}
// Get location based on GNSS
if ((event.gnssLocation.latitude) && (event.gnssLocation.longitude)) {
gnss_based_latitude = event.gnssLocation.latitude;
gnss_based_longitude = event.gnssLocation.longitude;
@@ -63,6 +107,8 @@ domainEventEmitter.on(
ttn_gw_longitude: ttn_gw_based_longitude,
gnss_latitude: gnss_based_latitude,
gnss_longitude: gnss_based_longitude,
wifi_latitude: wifi_based_latitude,
wifi_longitude: wifi_based_longitude,
});
console.log(newLocation)