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) { 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, }; } }