Skip to content
Snippets Groups Projects
tclStopPoint.service.ts 9 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { HttpService } from '@nestjs/axios';
    
    import { Injectable, Logger } from '@nestjs/common';
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    import { InjectModel } from '@nestjs/mongoose';
    import { Model } from 'mongoose';
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
    import { BusLine } from './interface/BusLine';
    import { StopPoint } from './interface/StopPoint';
    import { SubLine } from './interface/SubLine';
    import { TramLine } from './interface/TramLine';
    
    import { PgisCoord } from './schemas/pgisCoord.schema';
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    import { TclStopPoint, TclStopPointDocument } from './tclStopPoint.schema';
    
    interface Lines {
      busLines: string[];
      subLines: string[];
      tramLines: string[];
    }
    
    @Injectable()
    export class TclStopPointService {
    
      private readonly logger = new Logger(TclStopPointService.name);
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      private receivedStopPoints: StopPoint[];
      private receivedBusLines: BusLine[];
      private receivedSubLines: SubLine[];
      private receivedTramLines: TramLine[];
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
    
      constructor(
        private http: HttpService,
        @InjectModel(TclStopPoint.name) private tclStopPointModel: Model<TclStopPointDocument>
      ) {}
    
      /**
       * Clear 'tclstoppoint' and fill it with data from Data Grand Lyon
       */
      public async updateStopPoints(): Promise<void> {
    
        this.logger.debug('updateStopPoints');
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        await this.getUpdatedData();
        const newStopPoints = await this.processReceivedStopPoints(this.receivedStopPoints);
    
        await this.tclStopPointModel.deleteMany({}).exec();
        await this.tclStopPointModel.insertMany(newStopPoints);
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
      }
    
      /**
       * Get all tcl data from Data Grand Lyon
       */
      private async getUpdatedData(): Promise<void> {
        this.receivedStopPoints = await this.http
          .get(
            // tslint:disable-next-line: max-line-length
            'https://download.data.grandlyon.com/wfs/rdata?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=tcl_sytral.tclarret&outputFormat=application/json; subtype=geojson&SRSNAME=EPSG:4326&startIndex=0'
          )
          .toPromise()
          .then(async (res) => res.data.features);
    
        this.receivedBusLines = await this.http
          .get(`https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tcllignebus_2_0_0/all.json`)
          .toPromise()
          .then(async (res) => res.data.values);
    
        this.receivedSubLines = await this.http
          .get(`https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tcllignemf_2_0_0/all.json`)
          .toPromise()
          .then(async (res) => res.data.values);
    
        this.receivedTramLines = await this.http
          .get(`https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tcllignetram_2_0_0/all.json`)
          .toPromise()
          .then(async (res) => res.data.values);
      }
    
      /**
       * Get all lines names and remove duplications
       */
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      private async processReceivedStopPoints(receivedStopPoints: StopPoint[]): Promise<TclStopPoint[]> {
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        const newStopPoints: TclStopPoint[] = [];
    
        for (const receivedStopPoint of receivedStopPoints) {
          const lines: Lines = await this.processReceivedLines(receivedStopPoint.properties.desserte);
    
          const newStopPoint = new TclStopPoint();
          newStopPoint.id = parseInt(receivedStopPoint.properties.id, 10);
          newStopPoint.name = receivedStopPoint.properties.nom;
          newStopPoint.busLines = [...new Set(lines.busLines)];
          newStopPoint.subLines = lines.subLines;
          newStopPoint.tramLines = lines.tramLines;
          newStopPoint.prm = JSON.parse(receivedStopPoint.properties.pmr);
          newStopPoint.elevator = JSON.parse(receivedStopPoint.properties.ascenseur);
          newStopPoint.escalator = JSON.parse(receivedStopPoint.properties.escalator);
          newStopPoint.gid = parseInt(receivedStopPoint.properties.gid, 10);
          newStopPoint.lastUpdate = new Date(receivedStopPoint.properties.last_update);
          newStopPoint.lastUpdateFme = new Date(receivedStopPoint.properties.last_update_fme);
          newStopPoint.pgisCoord = receivedStopPoint.geometry;
    
          newStopPoints.push(newStopPoint);
        }
    
        return newStopPoints;
      }
    
      /**
       * Based on received data, check type and get it's real name in order to sort it.
       */
      private async processReceivedLines(receivedLines: string): Promise<Lines> {
        const receivedLinesArray = receivedLines.split(',');
        const lines: Lines = {
          busLines: [],
          subLines: [],
          tramLines: [],
        };
    
    
        for (let line of receivedLinesArray) {
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
          let cleanLine: string;
          let lineType: string[];
    
    
          line = line.split(':')[0];
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
          if (this.isExceptionLine(line)) {
            // Ne rien faire
          } else if (this.isSubLine(line)) {
            cleanLine = await this.getCleanSubLine(line);
            lineType = lines.subLines;
          } else if (this.isTramLine(line)) {
            cleanLine = await this.getCleanTramLine(line);
            lineType = lines.tramLines;
          } else {
            /* Les codes des lignes de bus ne respectant pas de logique générale,
            on considère que toutes les lignes qui n'ont pas été interceptées au dessus
            sont des lignes de bus */
            cleanLine = await this.getCleanBusLine(line);
            lineType = lines.busLines;
          }
    
          if (cleanLine) {
            lineType.push(cleanLine);
          }
        }
    
        return lines;
      }
    
      /**
       * Return true if bus line code is : XXX11
       */
      private isSubLine(line: string): boolean {
        const regex = /^3\d{2}/; // NOSONAR
        return regex.test(line);
      }
    
      /**
       * Return true if bus line code is starting with a T
       */
      private isTramLine(line: string): boolean {
        const regex = /^T/; // NOSONAR
        return regex.test(line);
      }
    
      /**
       * Return true if it's a known exception (ex: Rhônexpress)
       */
      private isExceptionLine(line: string): boolean {
    
        const regex = /(^RX|^TGS|^BGS|^NAV|^PL)/; // NOSONAR
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        return regex.test(line);
      }
    
      /**
       * Get back bus line name from TCL code in the corresponding table
       */
    
    Bastien DUMONT's avatar
    Bastien DUMONT committed
      private async getCleanLine(line: string, receivedLines: BusLine[] | TramLine[] | SubLine[]): Promise<string> {
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
        const foundLine = receivedLines.find((receivedLine) => receivedLine.code_ligne === line);
    
        // Exception for line 132. Does'nt exist anymore
        if (foundLine && foundLine.ligne && foundLine.ligne !== '132') {
          return foundLine.ligne;
        } else {
          return '';
        }
      }
    
      /**
       * Get back bus line name from TCL code
       */
      private async getCleanBusLine(line: string): Promise<string> {
        return this.getCleanLine(line, this.receivedBusLines);
      }
    
      /**
       * Get back bus subway name from TCL code
       */
      private async getCleanSubLine(line: string): Promise<string> {
        return this.getCleanLine(line, this.receivedSubLines);
      }
    
      /**
       * Get back tram line name from TCL code
       */
      private async getCleanTramLine(line: string): Promise<string> {
        return this.getCleanLine(line, this.receivedTramLines);
      }
    
      /**
       * Get TCL nearast point.
       * If none is found, we increase the default search radius.
       * The amount of stop return if defined in the function.
       */
      public async getClosestStopPoints(pgisCoord: PgisCoord): Promise<TclStopPoint[]> {
        const NUMBER_STOPS = 5;
        const RADIUS_FIRST_TRY = 100;
        const RADIUS_SECOND_TRY = 500;
    
        let stopPoints = await this.getStopPointsByDistance(pgisCoord, RADIUS_FIRST_TRY);
    
        if (!stopPoints.length) {
          stopPoints = await this.getStopPointsByDistance(pgisCoord, RADIUS_SECOND_TRY);
        }
    
        stopPoints = this.groupStopPointsByName(stopPoints);
        return stopPoints.slice(0, NUMBER_STOPS);
      }
    
      /**
       * Aggregate stops
       */
      private groupStopPointsByName(stopPoints: TclStopPoint[]): TclStopPoint[] {
        const uniqueStopPoints: TclStopPoint[] = [];
    
        for (const stopPoint of stopPoints) {
          const stopPointIndex = uniqueStopPoints.findIndex((uniqueStopPoint) => uniqueStopPoint.name === stopPoint.name);
    
          if (stopPointIndex > -1) {
            uniqueStopPoints[stopPointIndex].busLines = this.getUniqueCombinedLines(
              uniqueStopPoints[stopPointIndex].busLines,
              stopPoint.busLines
            );
    
            uniqueStopPoints[stopPointIndex].subLines = this.getUniqueCombinedLines(
              uniqueStopPoints[stopPointIndex].subLines,
              stopPoint.subLines
            );
    
            uniqueStopPoints[stopPointIndex].tramLines = this.getUniqueCombinedLines(
              uniqueStopPoints[stopPointIndex].tramLines,
              stopPoint.tramLines
            );
          } else {
            uniqueStopPoints.push(stopPoint);
          }
        }
    
        return uniqueStopPoints;
      }
    
      /**
       * Merge two lines array and return a map of unique lines ordered alphabetically
       */
      private getUniqueCombinedLines(stop1Lines: string[], stop2Lines: string[]): string[] {
        // Natural line order
        // Ex :  69, 296, C7, C25 instead of 296, 69, C25, C7
        const collator = new Intl.Collator(undefined, {
          numeric: true,
          sensitivity: 'base',
        });
    
        return Array.from(new Set([...stop1Lines, ...stop2Lines])).sort(collator.compare);
      }
    
      /**
    
       * Query collection to get nearest coord
    
    Hugo SUBTIL's avatar
    Hugo SUBTIL committed
       */
      public async getStopPointsByDistance(pgisCoord: PgisCoord, maxDistance: number): Promise<TclStopPoint[]> {
        return this.tclStopPointModel
          .find({
            pgisCoord: {
              $near: {
                $geometry: pgisCoord,
                $maxDistance: maxDistance,
              },
            },
          })
          .sort('-distance')
          .exec();
      }
    }