From 0aa7d125a641218a9d70ac19c67a67fbd3bedc2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Romain?= <francoisromain@gmail.com>
Date: Mon, 30 Nov 2020 12:40:12 +0100
Subject: [PATCH] =?UTF-8?q?perf:=20d=C3=A9normalise=20les=20coordonn=C3=A9?=
 =?UTF-8?q?es=20du=20centre=20du=20titre=20(#599)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* perf: dénormalise les coordonnées du centre du titre

* test: corrige la couverture de tests
---
 knex/migrations/20180522000001_titres.js      |  1 +
 package.json                                  |  2 +-
 src/api/_format/titres.ts                     | 31 ++++----
 src/business/_updates-log.ts                  |  8 ++
 src/business/daily.ts                         |  4 +
 .../titres-coordonnees-update.test.ts         | 73 +++++++++++++++++++
 .../processes/titres-coordonnees-update.ts    | 49 +++++++++++++
 .../processes/titres-props-etape-id-update.ts |  6 +-
 .../rules/__mocks__/titre-coordonnees-find.ts | 44 +++++++++++
 .../rules/titre-coordonnees-find.test.ts      | 23 ++++++
 src/business/rules/titre-coordonnees-find.ts  | 14 ++++
 src/business/titre-demarche-update.ts         |  3 +
 src/business/titre-etape-update.ts            |  3 +
 src/database/models/documents.ts              |  6 +-
 src/database/models/titres-activites.ts       |  6 +-
 src/database/models/titres-demarches.ts       |  6 +-
 src/database/models/titres-etapes.ts          |  7 +-
 src/database/models/titres-points.ts          |  5 +-
 src/database/models/titres-travaux-etapes.ts  |  6 +-
 src/database/models/titres-travaux.ts         |  6 +-
 src/database/models/titres.ts                 | 14 +++-
 src/database/models/utilisateurs.ts           |  6 +-
 src/database/queries/graph/format.ts          |  8 +-
 src/tools/__mock__/geojson.ts                 |  0
 src/tools/geojson.ts                          | 30 +-------
 src/types.ts                                  |  2 +-
 tsconfig.json                                 |  2 +-
 27 files changed, 273 insertions(+), 92 deletions(-)
 create mode 100644 src/business/processes/titres-coordonnees-update.test.ts
 create mode 100644 src/business/processes/titres-coordonnees-update.ts
 create mode 100644 src/business/rules/__mocks__/titre-coordonnees-find.ts
 create mode 100644 src/business/rules/titre-coordonnees-find.test.ts
 create mode 100644 src/business/rules/titre-coordonnees-find.ts
 create mode 100644 src/tools/__mock__/geojson.ts

diff --git a/knex/migrations/20180522000001_titres.js b/knex/migrations/20180522000001_titres.js
index e596c5dd8..876092294 100644
--- a/knex/migrations/20180522000001_titres.js
+++ b/knex/migrations/20180522000001_titres.js
@@ -24,6 +24,7 @@ exports.up = knex =>
     table.string('communesTitreEtapeId', 128)
     table.string('doublonTitreId', 128)
     table.jsonb('propsTitreEtapesIds')
+    table.specificType('coordonnees', 'POINT').index(null, 'GIST')
   })
 
 exports.down = knex => knex.schema.dropTable('titres')
diff --git a/package.json b/package.json
index bdaa759f6..afdae2af3 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
     "node": ">=14.15.0",
     "npm": ">=6.14.0"
   },
-  "main": "index.js",
+  "main": "dist/index.js",
   "scripts": {
     "bim": "npm i && npm run build && npm run db:recreate && npm run db:import",
     "build": "rm -rf dist && tsc && mkdir -p ./dist/api/graphql && cp -r ./src/api/graphql/schemas ./dist/api/graphql",
diff --git a/src/api/_format/titres.ts b/src/api/_format/titres.ts
index 05d1d335b..e0110225e 100644
--- a/src/api/_format/titres.ts
+++ b/src/api/_format/titres.ts
@@ -9,8 +9,7 @@ import {
 
 import {
   geojsonFeatureMultiPolygon,
-  geojsonFeatureCollectionPoints,
-  geojsonCentre
+  geojsonFeatureCollectionPoints
 } from '../../tools/geojson'
 
 import { dupRemove } from '../../tools/index'
@@ -128,20 +127,22 @@ const titreFormat = (
 ) => {
   if (!fields) return t
 
-  if (t.points?.length) {
-    if (fields.geojsonMultiPolygon) {
-      t.geojsonMultiPolygon = geojsonFeatureMultiPolygon(t.points) as IGeoJson
-    }
+  if (fields.geojsonMultiPolygon && t.points?.length) {
+    t.geojsonMultiPolygon = geojsonFeatureMultiPolygon(t.points) as IGeoJson
+  }
 
-    if (fields.geojsonPoints) {
-      t.geojsonPoints = geojsonFeatureCollectionPoints(t.points) as IGeoJson
-    }
-    if (fields.geojsonCentre) {
-      let geojsonMultiPolygon = t.geojsonMultiPolygon
-      if (!geojsonMultiPolygon) {
-        geojsonMultiPolygon = geojsonFeatureMultiPolygon(t.points) as IGeoJson
-      }
-      t.geojsonCentre = geojsonCentre(geojsonMultiPolygon, t.pointsTitreEtapeId)
+  if (fields.geojsonPoints && t.points?.length) {
+    t.geojsonPoints = geojsonFeatureCollectionPoints(t.points) as IGeoJson
+  }
+
+  if (fields.geojsonCentre && t.coordonnees && t.pointsTitreEtapeId) {
+    t.geojsonCentre = {
+      type: 'Feature',
+      geometry: {
+        type: 'Point',
+        coordinates: [t.coordonnees.x, t.coordonnees.y]
+      },
+      properties: { etapeId: t.pointsTitreEtapeId }
     }
   }
 
diff --git a/src/business/_updates-log.ts b/src/business/_updates-log.ts
index e63941d15..d47843051 100644
--- a/src/business/_updates-log.ts
+++ b/src/business/_updates-log.ts
@@ -30,6 +30,7 @@ const updatesLog = ({
   titresEtapesAdministrationsLocalesDeleted,
   titresPropsEtapeIdUpdated,
   titresPropsContenuUpdated,
+  titresCoordonneesUpdated,
   titresTravauxEtapesOrdreUpdated,
   titresTravauxOrdreUpdated,
   titresActivitesCreated,
@@ -62,6 +63,7 @@ const updatesLog = ({
   titresEtapesAdministrationsLocalesDeleted?: ITitreAdministrationLocale[]
   titresPropsEtapeIdUpdated?: string[]
   titresPropsContenuUpdated?: string[]
+  titresCoordonneesUpdated?: string[]
   titresTravauxEtapesOrdreUpdated?: string[]
   titresTravauxOrdreUpdated?: string[]
   titresActivitesCreated?: string[]
@@ -203,6 +205,12 @@ const updatesLog = ({
     )
   }
 
+  if (titresCoordonneesUpdated?.length) {
+    console.info(
+      `mise à jour: ${titresCoordonneesUpdated.length} titres(s) (propriétés-étapes)`
+    )
+  }
+
   if (titresTravauxEtapesOrdreUpdated?.length) {
     console.info(
       `mise à jour: ${titresTravauxEtapesOrdreUpdated.length} étape(s) de travaux (ordre)`
diff --git a/src/business/daily.ts b/src/business/daily.ts
index ceb98b8b3..bd18c0aee 100644
--- a/src/business/daily.ts
+++ b/src/business/daily.ts
@@ -19,6 +19,7 @@ import titresTravauxOrdreUpdate from './processes/titres-travaux-ordre-update'
 import titresTravauxEtapesOrdreUpdate from './processes/titres-travaux-etapes-ordre-update'
 import { matomoCacheInit } from '../tools/api-matomo'
 import updatesLog from './_updates-log'
+import titresCoordonneesUpdate from './processes/titres-coordonnees-update'
 
 const daily = async () => {
   try {
@@ -59,6 +60,8 @@ const daily = async () => {
     } = await titresEtapesAdministrationsLocalesUpdate()
     const titresPropsEtapeIdUpdated = await titresPropsEtapeIdUpdate()
     const titresPropsContenuUpdated = await titresPropsContenuUpdate()
+
+    const titresCoordonneesUpdated = await titresCoordonneesUpdate()
     const titresTravauxEtapesOrdreUpdated = await titresTravauxEtapesOrdreUpdate()
     const titresTravauxOrdreUpdated = await titresTravauxOrdreUpdate()
     const titresActivitesCreated = await titresActivitesUpdate()
@@ -93,6 +96,7 @@ const daily = async () => {
       titresEtapesAdministrationsLocalesDeleted,
       titresPropsEtapeIdUpdated,
       titresPropsContenuUpdated,
+      titresCoordonneesUpdated,
       titresTravauxEtapesOrdreUpdated,
       titresTravauxOrdreUpdated,
       titresActivitesCreated,
diff --git a/src/business/processes/titres-coordonnees-update.test.ts b/src/business/processes/titres-coordonnees-update.test.ts
new file mode 100644
index 000000000..6536a3539
--- /dev/null
+++ b/src/business/processes/titres-coordonnees-update.test.ts
@@ -0,0 +1,73 @@
+import { mocked } from 'ts-jest/utils'
+import titreCoordonneesFind from '../rules/titre-coordonnees-find'
+import { titresGet } from '../../database/queries/titres'
+import Titres from '../../database/models/titres'
+import titresCoordonneesUpdate from './titres-coordonnees-update'
+
+jest.mock('../../database/queries/titres', () => ({
+  titreUpdate: jest.fn().mockResolvedValue(true),
+  titresGet: jest.fn()
+}))
+jest.mock('../rules/titre-coordonnees-find', () => ({
+  default: jest.fn()
+}))
+
+const titresGetMock = mocked(titresGet, true)
+const titreCoordonneesFindMock = mocked(titreCoordonneesFind, true)
+
+console.info = jest.fn()
+
+describe('coordoonnées des titres', () => {
+  test.each([{ x: 1, y: 1 }, { x: 1 }, { y: 1 }, undefined])(
+    "met à jour les coordonnees d'un titre",
+    async coordonnees => {
+      titresGetMock.mockResolvedValue([
+        { id: 'titre-id', coordonnees }
+      ] as Titres[])
+      titreCoordonneesFindMock.mockReturnValue({ x: 1, y: 0.5 })
+
+      const titresCoordonneesUpdated = await titresCoordonneesUpdate()
+
+      expect(titresCoordonneesUpdated.length).toEqual(1)
+    }
+  )
+
+  test.each([null, { x: null, y: 1 }])(
+    "enlève les coordonnees d'un titre sans points",
+    async coordonnees => {
+      titresGetMock.mockResolvedValue([
+        { id: 'titre-id', coordonnees: { x: 1, y: 1 } }
+      ] as Titres[])
+      titreCoordonneesFindMock.mockReturnValue(coordonnees)
+
+      const titresCoordonneesUpdated = await titresCoordonneesUpdate()
+
+      expect(titresCoordonneesUpdated.length).toEqual(1)
+    }
+  )
+
+  test("met à jour les coordonnees d'un titre", async () => {
+    titresGetMock.mockResolvedValue([
+      { id: 'titre-id', coordonnees: null }
+    ] as Titres[])
+    titreCoordonneesFindMock.mockReturnValue({ x: null, y: 1 })
+
+    const titresCoordonneesUpdated = await titresCoordonneesUpdate()
+
+    expect(titresCoordonneesUpdated.length).toEqual(1)
+  })
+
+  test('ne met à jour aucun titre', async () => {
+    titresGetMock.mockResolvedValue([
+      {
+        id: 'titre-type-id',
+        coordonnees: { x: 1, y: 0.5 }
+      }
+    ] as Titres[])
+    titreCoordonneesFindMock.mockReturnValue({ x: 1, y: 0.5 })
+
+    const titresCoordonneesUpdated = await titresCoordonneesUpdate()
+
+    expect(titresCoordonneesUpdated.length).toEqual(0)
+  })
+})
diff --git a/src/business/processes/titres-coordonnees-update.ts b/src/business/processes/titres-coordonnees-update.ts
new file mode 100644
index 000000000..6814a5700
--- /dev/null
+++ b/src/business/processes/titres-coordonnees-update.ts
@@ -0,0 +1,49 @@
+import PQueue from 'p-queue'
+
+import { titresGet, titreUpdate } from '../../database/queries/titres'
+import titreCoordoneesFind from '../rules/titre-coordonnees-find'
+
+const titresCoordonneesUpdate = async (titresIds?: string[]) => {
+  console.info()
+  console.info('coordonnées des titres…')
+  const queue = new PQueue({ concurrency: 100 })
+
+  const titres = await titresGet(
+    { ids: titresIds },
+    { fields: { points: { id: {} } } },
+    'super'
+  )
+
+  const titresCoordonneesUpdated = [] as string[]
+
+  titres.forEach(titre => {
+    const coordonnees = titreCoordoneesFind(titre.points)
+
+    if (
+      (coordonnees &&
+        titre.coordonnees &&
+        (coordonnees.x !== titre.coordonnees.x ||
+          coordonnees.y !== titre.coordonnees.y)) ||
+      !coordonnees !== !titre.coordonnees
+    ) {
+      queue.add(async () => {
+        await titreUpdate(titre.id, { coordonnees })
+
+        const log = {
+          type: 'titre : coordonnées (mise à jour) ->',
+          value: `${titre.id} : ${coordonnees?.x}, ${coordonnees?.y}`
+        }
+
+        console.info(log.type, log.value)
+
+        titresCoordonneesUpdated.push(titre.id)
+      })
+    }
+  })
+
+  await queue.onIdle()
+
+  return titresCoordonneesUpdated
+}
+
+export default titresCoordonneesUpdate
diff --git a/src/business/processes/titres-props-etape-id-update.ts b/src/business/processes/titres-props-etape-id-update.ts
index 3798d8f81..86dd664fb 100644
--- a/src/business/processes/titres-props-etape-id-update.ts
+++ b/src/business/processes/titres-props-etape-id-update.ts
@@ -44,7 +44,7 @@ const titresPropsEtapeIdsUpdate = async (titresIds?: string[]) => {
     'super'
   )
 
-  const titresIdsUpdated = [] as string[]
+  const titresPropsEtapeIdsUpdated = [] as string[]
 
   titres.forEach(titre => {
     const props = titrePropsEtapes.reduce(
@@ -78,14 +78,14 @@ const titresPropsEtapeIdsUpdate = async (titresIds?: string[]) => {
 
         console.info(log.type, log.value)
 
-        titresIdsUpdated.push(titre.id)
+        titresPropsEtapeIdsUpdated.push(titre.id)
       })
     }
   })
 
   await queue.onIdle()
 
-  return titresIdsUpdated
+  return titresPropsEtapeIdsUpdated
 }
 
 export { titrePropsEtapes }
diff --git a/src/business/rules/__mocks__/titre-coordonnees-find.ts b/src/business/rules/__mocks__/titre-coordonnees-find.ts
new file mode 100644
index 000000000..1e0e620e5
--- /dev/null
+++ b/src/business/rules/__mocks__/titre-coordonnees-find.ts
@@ -0,0 +1,44 @@
+import { ITitrePoint } from '../../../types'
+
+const titrePoints = [
+  {
+    id: 'h-cx-courdemanges-1988-oct01-dpu01-1',
+    groupe: 1,
+    contour: 1,
+    point: 1,
+    coordonnees: { x: 0, y: 0 }
+  },
+  {
+    id: 'h-cx-courdemanges-1988-oct01-dpu01-2',
+    groupe: 1,
+    contour: 1,
+    point: 2,
+    coordonnees: { x: 0, y: 1 }
+  },
+  {
+    id: 'h-cx-courdemanges-1988-oct01-dpu01-3',
+    groupe: 1,
+    contour: 1,
+    point: 1,
+    coordonnees: { x: 1, y: 1 }
+  }
+] as ITitrePoint[]
+
+const titreGeojson = {
+  type: 'Feature',
+  properties: { etapeId: 'h-cx-courdemanges-1988-oct01-dpu01' },
+  geometry: {
+    type: 'MultiPolygon',
+    coordinates: [
+      [
+        [
+          [1, 1],
+          [0, 1],
+          [1, 1]
+        ]
+      ]
+    ]
+  }
+}
+
+export { titrePoints, titreGeojson }
diff --git a/src/business/rules/titre-coordonnees-find.test.ts b/src/business/rules/titre-coordonnees-find.test.ts
new file mode 100644
index 000000000..7fd7e5c8b
--- /dev/null
+++ b/src/business/rules/titre-coordonnees-find.test.ts
@@ -0,0 +1,23 @@
+import { mocked } from 'ts-jest/utils'
+import titreCoordonneesFind from './titre-coordonnees-find'
+import { geojsonFeatureMultiPolygon } from '../../tools/geojson'
+
+import { titrePoints, titreGeojson } from './__mocks__/titre-coordonnees-find'
+
+jest.mock('../../tools/geojson', () => ({
+  geojsonFeatureMultiPolygon: jest.fn()
+}))
+
+const geojsonFeatureMultiPolygonMock = mocked(geojsonFeatureMultiPolygon, true)
+
+describe("coordonnées d'un titre", () => {
+  test("retourne les coordonnées d'un titre", () => {
+    geojsonFeatureMultiPolygonMock.mockReturnValue(titreGeojson)
+    expect(titreCoordonneesFind(titrePoints)).toMatchObject({ x: 0.5, y: 1 })
+  })
+
+  test("retourne null si le titre n'a pas de points", () => {
+    expect(titreCoordonneesFind([])).toBeNull()
+    expect(titreCoordonneesFind(null)).toBeNull()
+  })
+})
diff --git a/src/business/rules/titre-coordonnees-find.ts b/src/business/rules/titre-coordonnees-find.ts
new file mode 100644
index 000000000..dbbd1d821
--- /dev/null
+++ b/src/business/rules/titre-coordonnees-find.ts
@@ -0,0 +1,14 @@
+import { ITitrePoint } from '../../types'
+import center from '@turf/center'
+import { geojsonFeatureMultiPolygon } from '../../tools/geojson'
+
+const titreCoordonneesFind = (titrePoints?: ITitrePoint[] | null) => {
+  if (!titrePoints?.length) return null
+
+  const geojson = geojsonFeatureMultiPolygon(titrePoints)
+  const [x, y] = center(geojson).geometry.coordinates
+
+  return { x, y }
+}
+
+export default titreCoordonneesFind
diff --git a/src/business/titre-demarche-update.ts b/src/business/titre-demarche-update.ts
index 4fae2fb74..d43beca1d 100644
--- a/src/business/titre-demarche-update.ts
+++ b/src/business/titre-demarche-update.ts
@@ -11,6 +11,7 @@ import titresDemarchesOrdreUpdate from './processes/titres-demarches-ordre-updat
 import titresPublicUpdate from './processes/titres-public-update'
 import { titresIdsUpdate } from './processes/titres-ids-update'
 import updatesLog from './_updates-log'
+import titresCoordonneesUpdate from './processes/titres-coordonnees-update'
 
 const titreDemarcheUpdate = async (
   titreDemarcheId: string | null,
@@ -53,6 +54,7 @@ const titreDemarcheUpdate = async (
     const titresDatesUpdated = await titresDatesUpdate([titreId])
     const titresPropsEtapeIdUpdated = await titresPropsEtapeIdUpdate([titreId])
     const titresPropsContenuUpdated = await titresPropsContenuUpdate([titreId])
+    const titresCoordonneesUpdated = await titresCoordonneesUpdate([titreId])
     const titresActivitesCreated = await titresActivitesUpdate([titreId])
 
     // met à jour l'id dans le titre par effet de bord
@@ -72,6 +74,7 @@ const titreDemarcheUpdate = async (
       titresDatesUpdated,
       titresPropsEtapeIdUpdated,
       titresPropsContenuUpdated,
+      titresCoordonneesUpdated,
       titresActivitesCreated,
       titresUpdatedIndex
     })
diff --git a/src/business/titre-etape-update.ts b/src/business/titre-etape-update.ts
index d2a7f306a..3b3ead3de 100644
--- a/src/business/titre-etape-update.ts
+++ b/src/business/titre-etape-update.ts
@@ -16,6 +16,7 @@ import titresPropsContenuUpdate from './processes/titres-props-contenu-update'
 import { titresIdsUpdate } from './processes/titres-ids-update'
 import titresPublicUpdate from './processes/titres-public-update'
 import updatesLog from './_updates-log'
+import titresCoordonneesUpdate from './processes/titres-coordonnees-update'
 
 const titreEtapeUpdate = async (
   titreEtapeId: string | null,
@@ -93,6 +94,7 @@ const titreEtapeUpdate = async (
     } = await titresEtapesAdministrationsLocalesUpdate([titreId])
     const titresPropsEtapeIdUpdated = await titresPropsEtapeIdUpdate([titreId])
     const titresPropsContenuUpdated = await titresPropsContenuUpdate([titreId])
+    const titresCoordonneesUpdated = await titresCoordonneesUpdate([titreId])
     const titresActivitesCreated = await titresActivitesUpdate([titreId])
 
     // met à jour l'id dans le titre par effet de bord
@@ -121,6 +123,7 @@ const titreEtapeUpdate = async (
       titresEtapesAdministrationsLocalesDeleted,
       titresPropsEtapeIdUpdated,
       titresPropsContenuUpdated,
+      titresCoordonneesUpdated,
       titresActivitesCreated,
       titresUpdatedIndex
     })
diff --git a/src/database/models/documents.ts b/src/database/models/documents.ts
index d99bbe1a0..f8857a1a1 100644
--- a/src/database/models/documents.ts
+++ b/src/database/models/documents.ts
@@ -91,19 +91,17 @@ class Document extends Model {
   }
 
   public $formatDatabaseJson(json: Pojo): Pojo {
-    json = super.$formatDatabaseJson(json)
-
     delete json.modification
     delete json.suppression
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
 
   public $parseJson(json: Pojo): Pojo {
-    json = super.$parseJson(json)
-
     delete json.modification
     delete json.suppression
+    json = super.$parseJson(json)
 
     return json
   }
diff --git a/src/database/models/titres-activites.ts b/src/database/models/titres-activites.ts
index ac0307dc6..1c4a090d0 100644
--- a/src/database/models/titres-activites.ts
+++ b/src/database/models/titres-activites.ts
@@ -100,8 +100,6 @@ class TitresActivites extends Model {
   }
 
   public $parseJson(json: Pojo) {
-    json = super.$parseJson(json)
-
     if (!json.id && json.titreId && json.typeId && json.frequencePeriodeId) {
       const id = `${json.titreId}-${json.typeId}-${
         json.annee
@@ -111,15 +109,15 @@ class TitresActivites extends Model {
 
     delete json.modification
     delete json.documentsCreation
+    json = super.$parseJson(json)
 
     return json
   }
 
   public $formatDatabaseJson(json: Pojo) {
-    json = super.$formatDatabaseJson(json)
-
     delete json.modification
     delete json.documentsCreation
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
diff --git a/src/database/models/titres-demarches.ts b/src/database/models/titres-demarches.ts
index 22c221278..6740bbd56 100644
--- a/src/database/models/titres-demarches.ts
+++ b/src/database/models/titres-demarches.ts
@@ -115,8 +115,6 @@ class TitresDemarches extends Model {
   }
 
   public $parseJson(json: Pojo) {
-    json = super.$parseJson(json)
-
     if (!json.id && json.titreId && json.typeId) {
       json.id = `${json.titreId}-${json.typeId}99`
     }
@@ -124,16 +122,16 @@ class TitresDemarches extends Model {
     delete json.modification
     delete json.suppression
     delete json.etapesCreation
+    json = super.$parseJson(json)
 
     return json
   }
 
   public $formatDatabaseJson(json: Pojo) {
-    json = super.$formatDatabaseJson(json)
-
     delete json.modification
     delete json.suppression
     delete json.etapesCreation
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
diff --git a/src/database/models/titres-etapes.ts b/src/database/models/titres-etapes.ts
index 8a861a9b3..7cef04b38 100644
--- a/src/database/models/titres-etapes.ts
+++ b/src/database/models/titres-etapes.ts
@@ -194,8 +194,6 @@ class TitresEtapes extends Model {
   }
 
   public $formatDatabaseJson(json: Pojo) {
-    json = super.$formatDatabaseJson(json)
-
     if (json.pays) {
       delete json.pays
     }
@@ -203,13 +201,12 @@ class TitresEtapes extends Model {
     delete json.modification
     delete json.suppression
     delete json.justificatifsAssociation
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
 
   public $parseJson(json: Pojo) {
-    json = super.$parseJson(json)
-
     if (!json.id && json.titreDemarcheId && json.typeId) {
       json.id = `${json.titreDemarcheId}-${json.typeId}99`
     }
@@ -249,10 +246,10 @@ class TitresEtapes extends Model {
 
     delete json.geojsonMultiPolygon
     delete json.geojsonPoints
-
     delete json.modification
     delete json.suppression
     delete json.justificatifsAssociation
+    json = super.$parseJson(json)
 
     return json
   }
diff --git a/src/database/models/titres-points.ts b/src/database/models/titres-points.ts
index 699978b2f..ead8d8b1d 100644
--- a/src/database/models/titres-points.ts
+++ b/src/database/models/titres-points.ts
@@ -18,10 +18,7 @@ class TitresPoints extends Model {
       description: { type: ['string', 'null'] },
       coordonnees: {
         type: 'object',
-        properties: {
-          x: { type: 'number' },
-          y: { type: 'number' }
-        }
+        properties: { x: { type: 'number' }, y: { type: 'number' } }
       },
       groupe: { type: 'integer' },
       contour: { type: 'integer' },
diff --git a/src/database/models/titres-travaux-etapes.ts b/src/database/models/titres-travaux-etapes.ts
index d0269a042..e21624a05 100644
--- a/src/database/models/titres-travaux-etapes.ts
+++ b/src/database/models/titres-travaux-etapes.ts
@@ -69,23 +69,21 @@ class TitresTravauxEtapes extends Model {
   }
 
   public $formatDatabaseJson(json: Pojo) {
-    json = super.$formatDatabaseJson(json)
-
     delete json.modification
     delete json.suppression
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
 
   public $parseJson(json: Pojo) {
-    json = super.$parseJson(json)
-
     if (!json.id && json.titreTravauxId && json.typeId) {
       json.id = `${json.titreTravauxId}-${json.typeId}99`
     }
 
     delete json.modification
     delete json.suppression
+    json = super.$parseJson(json)
 
     return json
   }
diff --git a/src/database/models/titres-travaux.ts b/src/database/models/titres-travaux.ts
index cafdfaf4b..3386caede 100644
--- a/src/database/models/titres-travaux.ts
+++ b/src/database/models/titres-travaux.ts
@@ -65,8 +65,6 @@ class TitresTravaux extends Model {
   }
 
   public $parseJson(json: Pojo) {
-    json = super.$parseJson(json)
-
     if (!json.id && json.titreId && json.typeId) {
       json.id = `${json.titreId}-${json.typeId}99`
     }
@@ -74,16 +72,16 @@ class TitresTravaux extends Model {
     delete json.modification
     delete json.suppression
     delete json.etapesCreation
+    json = super.$parseJson(json)
 
     return json
   }
 
   public $formatDatabaseJson(json: Pojo) {
-    json = super.$formatDatabaseJson(json)
-
     delete json.modification
     delete json.suppression
     delete json.etapesCreation
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
diff --git a/src/database/models/titres.ts b/src/database/models/titres.ts
index 40c0a0fdc..fc06a2b83 100644
--- a/src/database/models/titres.ts
+++ b/src/database/models/titres.ts
@@ -40,7 +40,11 @@ class Titres extends Model {
       administrationsTitreEtapeId: { type: ['string', 'null'], maxLength: 128 },
       surfaceTitreEtapeId: { type: ['string', 'null'], maxLength: 128 },
       communesTitreEtapeId: { type: ['string', 'null'], maxLength: 128 },
-      propsTitreEtapesIds: { type: 'json' }
+      propsTitreEtapesIds: { type: 'json' },
+      coordonnees: {
+        type: ['object', 'null'],
+        properties: { x: { type: 'number' }, y: { type: 'number' } }
+      }
     }
   }
 
@@ -218,17 +222,19 @@ class Titres extends Model {
   }
 
   public $parseJson(json: Pojo) {
-    json = super.$parseJson(json)
-
     json = titreInsertFormat(json)
+    json = super.$parseJson(json)
 
     return json
   }
 
   public $formatDatabaseJson(json: Pojo) {
-    json = super.$formatDatabaseJson(json)
+    if (json.coordonnees) {
+      json.coordonnees = `${json.coordonnees.x},${json.coordonnees.y}`
+    }
 
     json = titreInsertFormat(json)
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
diff --git a/src/database/models/utilisateurs.ts b/src/database/models/utilisateurs.ts
index d13a11969..f649d294c 100644
--- a/src/database/models/utilisateurs.ts
+++ b/src/database/models/utilisateurs.ts
@@ -67,21 +67,19 @@ class Utilisateurs extends Model {
   }
 
   public $parseJson(json: Pojo) {
-    json = super.$parseJson(json)
-
     delete json.modification
     delete json.suppression
     delete json.permissionModification
+    json = super.$parseJson(json)
 
     return json
   }
 
   public $formatDatabaseJson(json: Pojo) {
-    json = super.$formatDatabaseJson(json)
-
     delete json.modification
     delete json.suppression
     delete json.permissionModification
+    json = super.$formatDatabaseJson(json)
 
     return json
   }
diff --git a/src/database/queries/graph/format.ts b/src/database/queries/graph/format.ts
index 4540df4c9..592739ed3 100644
--- a/src/database/queries/graph/format.ts
+++ b/src/database/queries/graph/format.ts
@@ -16,12 +16,8 @@ const fieldsOrderAsc = [
   'titresTypesTitresStatuts'
 ]
 const fieldsToRemove = ['coordonnees']
-const titreFieldsToRemove = [] as string[]
-const geoFieldsToReplace = [
-  'geojsonPoints',
-  'geojsonMultiPolygon',
-  'geojsonCentre'
-]
+const titreFieldsToRemove = ['geojsonCentre'] as string[]
+const geoFieldsToReplace = ['geojsonPoints', 'geojsonMultiPolygon']
 const titrePropsEtapesFields = ['surface']
 
 interface IFields {
diff --git a/src/tools/__mock__/geojson.ts b/src/tools/__mock__/geojson.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/tools/geojson.ts b/src/tools/geojson.ts
index ea669642c..082a28410 100644
--- a/src/tools/geojson.ts
+++ b/src/tools/geojson.ts
@@ -1,6 +1,5 @@
-import { ITitrePoint, IGeometry, IGeoJson } from '../types'
+import { ITitrePoint, IGeometry } from '../types'
 import * as rewind from 'geojson-rewind'
-import center from '@turf/center'
 
 // convertit des points
 // en un geojson de type 'MultiPolygon'
@@ -38,27 +37,6 @@ const geojsonFeatureCollectionPoints = (points: ITitrePoint[]) => ({
   }))
 })
 
-const geojsonCentre = (
-  geojsonMultiPolygon: IGeoJson | undefined,
-  etapeId?: string | null
-) => {
-  if (geojsonMultiPolygon) {
-    const centreCoords = center(geojsonMultiPolygon).geometry?.coordinates
-    if (centreCoords && centreCoords.length === 2) {
-      return {
-        type: 'Feature',
-        geometry: {
-          type: 'Point',
-          coordinates: centreCoords
-        },
-        properties: { etapeId }
-      }
-    }
-  }
-
-  return undefined
-}
-
 // convertit une liste de points
 // en un tableau 'coordinates' geoJson
 // (le premier et le dernier point d'un contour ont les mêmes coordonnées)
@@ -92,8 +70,4 @@ const multiPolygonContoursClose = (groupes: number[][][][]) =>
     }, [])
   )
 
-export {
-  geojsonFeatureMultiPolygon,
-  geojsonFeatureCollectionPoints,
-  geojsonCentre
-}
+export { geojsonFeatureMultiPolygon, geojsonFeatureCollectionPoints }
diff --git a/src/types.ts b/src/types.ts
index 72abf189a..52cde5956 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -497,6 +497,7 @@ interface ITitre {
   substances?: ISubstance[] | null
   pointsTitreEtapeId?: string | null
   points?: ITitrePoint[] | null
+  coordonnees?: ICoordonnees | null
   geojsonMultiPolygon?: IGeoJson | null
   geojsonPoints?: IGeoJson | null
   geojsonCentre?: IGeoJsonCentre | null
@@ -923,7 +924,6 @@ export {
   IFrequence,
   IGeoJson,
   IGeoJsonProperties,
-  IGeoJsonCentre,
   IApiGeoResult,
   IGeometry,
   IGeoSysteme,
diff --git a/tsconfig.json b/tsconfig.json
index 95c82ba4c..a18a74f24 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -49,7 +49,7 @@
           ]
         },
         {
-          "title": "Developpement",
+          "title": "Développement",
           "pages": [
             {
               "title": "Introduction",
-- 
GitLab