diff --git a/src/app.module.ts b/src/app.module.ts
index 33d12ac3d4f46c276275fc53f8c38bc57a136ec5..d3a7ebdbbc56ff9f03b326d78405b5cb81008137 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -7,6 +7,7 @@ import { CategoriesModule } from './categories/categories.module';
 import { AuthModule } from './auth/auth.module';
 import { UsersModule } from './users/users.module';
 import { MailerModule } from './mailer/mailer.module';
+import { TclModule } from './tcl/tcl.module';
 @Module({
   imports: [
     ConfigurationModule,
@@ -18,6 +19,7 @@ import { MailerModule } from './mailer/mailer.module';
     AuthModule,
     UsersModule,
     MailerModule,
+    TclModule,
   ],
   controllers: [AppController],
 })
diff --git a/src/tcl/interfaces/pgis.coord.ts b/src/tcl/interfaces/pgis.coord.ts
new file mode 100644
index 0000000000000000000000000000000000000000..61d354af70a9bb79fae1aedbb22bfd2ec52fccd6
--- /dev/null
+++ b/src/tcl/interfaces/pgis.coord.ts
@@ -0,0 +1,4 @@
+export interface PgisCoord {
+  type: string;
+  coordinates: [number, number];
+}
diff --git a/src/tcl/tcl.module.ts b/src/tcl/tcl.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f34de0b278a15d2ade2b6eaa2513ba23886026ea
--- /dev/null
+++ b/src/tcl/tcl.module.ts
@@ -0,0 +1,12 @@
+import { HttpModule, Module } from '@nestjs/common';
+import { TclStopPointService } from './tclStopPoint.service';
+import { TclStopPointController } from './tclStopPoint.controller';
+import { MongooseModule } from '@nestjs/mongoose';
+import { TclStopPoint, TclStopPointSchema } from './tclStopPoint.schema';
+
+@Module({
+  imports: [MongooseModule.forFeature([{ name: TclStopPoint.name, schema: TclStopPointSchema }]), HttpModule],
+  providers: [TclStopPointService],
+  controllers: [TclStopPointController],
+})
+export class TclModule {}
diff --git a/src/tcl/tclStopPoint.controller.ts b/src/tcl/tclStopPoint.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f8b75148e41fd90186763ecec5958e4c2d8c388
--- /dev/null
+++ b/src/tcl/tclStopPoint.controller.ts
@@ -0,0 +1,35 @@
+import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
+import { ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { PgisCoord } from './interfaces/pgis.coord';
+import { TclStopPoint } from './tclStopPoint.schema';
+import { TclStopPointService } from './tclStopPoint.service';
+
+@Controller('tcl')
+export class TclStopPointController {
+  constructor(private tclStopPointService: TclStopPointService) {}
+
+  @ApiOperation({
+    description: `Mettre à jour les points d'arrêt TCL à partir de Data Grand Lyon`,
+  })
+  @ApiResponse({
+    status: 204,
+    description: 'The stop points have been updated successfully.',
+  })
+  @Get('/update')
+  //TODO: protect with admin guard when available
+  public updateStopPoints(): Promise<void> {
+    return this.tclStopPointService.updateStopPoints();
+  }
+
+  @ApiOperation({
+    description: `Récupérer les arrêts les plus proches d'un point géographique`,
+  })
+  @ApiResponse({
+    status: 200,
+    description: 'The closest stop points have been fetched successfully.',
+  })
+  @Post('/closest')
+  public getClosestStopPoints(@Body() pgisCoord: PgisCoord): Promise<TclStopPoint[]> {
+    return this.tclStopPointService.getClosestStopPoints(pgisCoord);
+  }
+}
diff --git a/src/tcl/tclStopPoint.schema.ts b/src/tcl/tclStopPoint.schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..acc072fa3925d225627e39b5f675c565ee192c3e
--- /dev/null
+++ b/src/tcl/tclStopPoint.schema.ts
@@ -0,0 +1,50 @@
+import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
+import { Document } from 'mongoose';
+
+export type TclStopPointDocument = TclStopPoint & Document;
+
+@Schema()
+export class TclStopPoint {
+  @Prop()
+  id: number;
+
+  @Prop()
+  name?: string;
+
+  @Prop()
+  busLines?: string[];
+
+  @Prop()
+  subLines?: string[];
+
+  @Prop()
+  tramLines?: string[];
+
+  @Prop()
+  prm?: boolean;
+
+  @Prop()
+  elevator?: boolean;
+
+  @Prop()
+  escalator?: boolean;
+
+  @Prop()
+  gid: number;
+
+  @Prop()
+  lastUpdate?: Date;
+
+  @Prop()
+  lastUpdateFme?: Date;
+
+  @Prop()
+  pgisCoord?: string | any;
+
+  @Prop()
+  distance?: number;
+}
+
+export const TclStopPointSchema = SchemaFactory.createForClass(TclStopPoint);
+
+TclStopPointSchema.index({ pgisCoord: '2dsphere' });
diff --git a/src/tcl/tclStopPoint.service.spec.ts b/src/tcl/tclStopPoint.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4958e59315416027ed003c26d79fbbe8d82fcbf6
--- /dev/null
+++ b/src/tcl/tclStopPoint.service.spec.ts
@@ -0,0 +1,18 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { TclStopPointService } from './tclStopPoint.service';
+
+describe('TclService', () => {
+  let service: TclStopPointService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [TclStopPointService],
+    }).compile();
+
+    service = module.get<TclStopPointService>(TclStopPointService);
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+});
diff --git a/src/tcl/tclStopPoint.service.ts b/src/tcl/tclStopPoint.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..390ff30b18db65e405a2a51a48925da2f0a9f0a3
--- /dev/null
+++ b/src/tcl/tclStopPoint.service.ts
@@ -0,0 +1,293 @@
+import { HttpService, Injectable } from '@nestjs/common';
+import { InjectModel } from '@nestjs/mongoose';
+import { Model } from 'mongoose';
+import { PgisCoord } from './interfaces/pgis.coord';
+import { TclStopPoint, TclStopPointDocument } from './tclStopPoint.schema';
+
+interface ReceivedStopPoint {
+  type: string;
+  properties: {
+    id: string;
+    nom: string;
+    desserte: string;
+    pmr: string;
+    ascenseur: string;
+    escalator: string;
+    gid: string;
+    last_update: string;
+    last_update_fme: string;
+  };
+  geometry: PgisCoord;
+}
+
+interface Lines {
+  busLines: string[];
+  subLines: string[];
+  tramLines: string[];
+}
+
+@Injectable()
+export class TclStopPointService {
+  private receivedStopPoints: any[];
+  private receivedBusLines: any[];
+  private receivedSubLines: any[];
+  private receivedTramLines: any[];
+
+  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> {
+    await this.getUpdatedData();
+    const newStopPoints = await this.processReceivedStopPoints(this.receivedStopPoints);
+
+    this.tclStopPointModel.deleteMany({}, () => {
+      this.tclStopPointModel.insertMany(newStopPoints);
+    });
+  }
+
+  /**
+   * 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
+   */
+  private async processReceivedStopPoints(receivedStopPoints: ReceivedStopPoint[]): Promise<TclStopPoint[]> {
+    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) {
+      line = line.split(':')[0];
+      let cleanLine: string;
+      let lineType: string[];
+
+      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)/; // NOSONAR
+    return regex.test(line);
+  }
+
+  /**
+   * Get back bus line name from TCL code in the corresponding table
+   */
+  private async getCleanLine(line: string, receivedLines: any[]): Promise<string> {
+    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 neareast coord
+   * @param pgisCoord PgisCoord
+   * @param maxDistance number
+   */
+  public async getStopPointsByDistance(pgisCoord: PgisCoord, maxDistance: number): Promise<TclStopPoint[]> {
+    return this.tclStopPointModel
+      .find({
+        pgisCoord: {
+          $near: {
+            $geometry: pgisCoord,
+            $maxDistance: maxDistance,
+          },
+        },
+      })
+      .sort('-distance')
+      .exec();
+  }
+}