LocationHub/server/src/services/locationService.ts

153 lines
4.0 KiB
TypeScript

import { inject, injectable } from "tsyringe";
import { Op } from 'sequelize';
import { Location } from "../models/location";
import { LocationRepository } from "../repositories/locationRepository";
import { WifiLocationService } from "./wifiLocationService";
interface CreateLocationParams {
lp_ttn_end_device_uplinks_id: string;
wifi?: Coordinates;
gnss?: Coordinates;
ttn_gw?: Coordinates;
gnss_timestamp?: Date;
}
interface CreateLocationTriangulationParams {
lp_ttn_end_device_uplinks_id: string;
wifi: {
mac: string;
rssi: number;
}[];
ttn_gw: LocationSignal[];
gnss?: Coordinates;
gnss_timestamp?: Date;
}
interface LocationSignal extends Coordinates {
rssi: number;
}
interface Coordinates {
latitude: number;
longitude: number;
}
interface UpdateTtnGatewayReceptionParams {
ttn_gateway_reception_id: string;
gateway_id?: string;
eui?: string;
rssi?: number;
latitude?: number;
longitude?: number;
altitude?: number;
}
@injectable()
export class LocationService {
constructor(
@inject(LocationRepository)
private repository: LocationRepository,
@inject(WifiLocationService)
private wifiLocationService: WifiLocationService
) { }
public async getAllLocations() {
return this.repository.findAll();
}
public async getAllGnssLocations() {
return this.repository.findAll({
where: {
gnss_latitude: { [Op.ne]: null },
gnss_longitude: { [Op.ne]: null }
}
});
}
public async getLocationById(id: string) {
return this.repository.findById(id);
}
public async createLocation(data: CreateLocationParams) {
return this.repository.create({
lp_ttn_end_device_uplinks_id: data.lp_ttn_end_device_uplinks_id,
wifi_latitude: data.wifi?.latitude,
wifi_longitude: data.wifi?.longitude,
ttn_gw_latitude: data.ttn_gw?.latitude,
ttn_gw_longitude: data.ttn_gw?.longitude,
gnss_latitude: data.gnss?.latitude,
gnss_longitude: data.gnss?.longitude,
gnss_location_at_utc: data.gnss_timestamp,
});
}
public async createLocationFromTriangulation(
data: CreateLocationTriangulationParams
) {
const wifi_location = this.calculateVirtualLocation(
await this.enrichWifiObjectWithLocation(data.wifi)
);
const gateway_location = this.calculateVirtualLocation(data.ttn_gw);
return this.createLocation({
lp_ttn_end_device_uplinks_id: data.lp_ttn_end_device_uplinks_id,
wifi: wifi_location,
ttn_gw: gateway_location,
gnss: data.gnss,
gnss_timestamp: data.gnss_timestamp,
});
}
public async updateLocation(id: string, data: Partial<Location>) {
return this.repository.update(id, data);
}
public async deleteLocation(id: string) {
return this.repository.delete(id);
}
private async enrichWifiObjectWithLocation(
wifis: CreateLocationTriangulationParams["wifi"]
) {
const enrichedWifi = await Promise.all(
wifis.map(async (wifi) => {
const location = await this.wifiLocationService.getWifiLocationByMac(
wifi.mac
);
return location?.latitude !== undefined &&
location.longitude !== undefined
? {
rssi: wifi.rssi,
latitude: location.latitude,
longitude: location.longitude,
}
: null;
})
);
return enrichedWifi.filter((wifi) => wifi !== null);
}
private calculateVirtualLocation(locations: LocationSignal[]) {
if (locations.length === 0) return undefined;
const { totalWeight, weightedLatitude, weightedLongitude } =
locations.reduce(
(acc, { latitude, longitude, rssi }) => {
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 }
);
return {
latitude: weightedLatitude / totalWeight,
longitude: weightedLongitude / totalWeight,
};
}
}