Skip to content
Snippets Groups Projects
Unverified Commit 50f4f0ea authored by vmaubert's avatar vmaubert Committed by GitHub
Browse files

chore(périmètre): réinjecte les anciens périmètres (#1063)

parent 938e55fa
No related branches found
No related tags found
No related merge requests found
/* eslint-disable sql/no-unsafe-query */
import { EtapeId, etapeIdValidator } from 'camino-common/src/etape'
import { FeatureCollectionPoints, FeatureMultiPolygon } from 'camino-common/src/perimetre'
import { GeoSystemeId, geoSystemeIdValidator } from 'camino-common/src/static/geoSystemes'
import { isNullOrUndefined } from 'camino-common/src/typescript-tools'
import knexBuilder from 'knex'
import { knexSnakeCaseMappers } from 'objection'
import { z } from 'zod'
export const knexConfig = {
client: 'pg',
connection: {
host: 'localhost',
port: 5432,
database: 'camino2',
user: 'postgres',
password: 'password',
},
...knexSnakeCaseMappers(),
}
const knex = knexBuilder(knexConfig)
const entry = z.object({
coordonnees: z.object({ x: z.number(), y: z.number() }),
geo_systeme_id: geoSystemeIdValidator,
titre_etape_id: etapeIdValidator,
groupe: z.number(),
contour: z.number(),
point: z.number(),
nom: z.string().nullable(),
description: z.string().nullable(),
})
type Row = z.infer<typeof entry>
const etapeIdsIgnored: string[] = [
// un seul point opposable
'ib3azhoNoXQHxd1Bnumak5ww',
// le contour commence à 2
'FixGOMcVwkauQyH1byBKpglp',
// Devrait être plusieurs polygones
'h4XRWr88Xa0YDMjPe3gVdhCd',
'IXHTQEu5EX5QYqUzHV1tugjp',
'mXvPgzER5mj3gLKnM0lLMs8R',
'pTPoON8dWU4r4ylo8kerDNNe',
'yBNXGAeopu8yFmM6LqbpnFtt',
// Les points sont surement pas le bon ordre
'IlwGuEUZwB3rS5ydlzsg8l6T',
'VMXNEznTgy8bVNyJIOJWRHZS',
// Il manque une lacune
'LmNB8aPDs9u2Z9861AfL5fUW',
// Pas bon geosysteme 27573
'Nfd0MiVO16xmUL7OyRGaMMgJ',
]
const generate = async () => {
const pointsReferences: { rows: Row[] } = await knex.raw(
`select tpr.coordonnees, tpr.geo_systeme_id, tp.titre_etape_id, tp.groupe, tp.contour, tp.point, tp.nom, tp.description from titres_points_references tpr join titres_points tp on tp.id = tpr.titre_point_id where tpr.opposable is true and tpr.geo_systeme_id != '4326' and tp.titre_etape_id not in (${etapeIdsIgnored
.map(id => `'${id}'`)
.join(', ')}) order by titre_etape_id, groupe, contour, point`
)
const points = z.array(entry).parse(pointsReferences.rows)
const pointsByEtape = points.reduce<Record<EtapeId, Row[]>>((acc, point) => {
if (isNullOrUndefined(acc[point.titre_etape_id])) {
acc[point.titre_etape_id] = []
}
acc[point.titre_etape_id].push(point)
return acc
}, {})
for (const points of Object.values(pointsByEtape)) {
const origineGeosystemeId: GeoSystemeId = points[0].geo_systeme_id
const originePoints: FeatureCollectionPoints = {
type: 'FeatureCollection',
features: points.map(point => ({
type: 'Feature',
properties: { nom: point.nom?.replace(/'/g, ''), description: point.description?.replace(/'/g, '') },
geometry: { type: 'Point', coordinates: [point.coordonnees.x, point.coordonnees.y] },
})),
}
const originePerimetre: FeatureMultiPolygon = {
type: 'Feature',
properties: {},
geometry: {
type: 'MultiPolygon',
coordinates: points.reduce<[number, number][][][]>((acc, point) => {
if (acc.length < point.groupe) {
acc.push([])
}
if (acc[point.groupe - 1].length < point.contour) {
acc[point.groupe - 1].push([])
}
acc[point.groupe - 1][point.contour - 1][point.point - 1] = [point.coordonnees.x, point.coordonnees.y]
acc[point.groupe - 1][point.contour - 1][point.point] = acc[point.groupe - 1][point.contour - 1][0]
return acc
}, []),
},
}
const result: { rows: { result: boolean }[] } = await knex.raw(
`select ST_ISVALID(ST_Multi(ST_SetSRID(ST_GeomFromGeoJSON('${JSON.stringify(originePerimetre.geometry)}'), ${origineGeosystemeId}))) as result`
)
if (!result.rows[0].result) {
console.error(`étape invalide ${points[0].titre_etape_id}`, origineGeosystemeId, JSON.stringify(originePerimetre))
throw new Error('boom')
} else {
const sqlQuery = `update titres_etapes set geojson_origine_geo_systeme_id = '${origineGeosystemeId}', geojson_origine_points = '${JSON.stringify(
originePoints
)}', geojson_origine_perimetre = '${JSON.stringify(originePerimetre)}' where id='${points[0].titre_etape_id}';`
console.info(sqlQuery)
}
}
}
generate()
.then(() => {
process.exit(0)
})
.catch(e => {
console.error(e)
process.exit(1)
})
......@@ -44,8 +44,8 @@ export const transformableGeoSystemeIds = ['4326', '2154', '5490', '2972', '2975
export const transformableGeoSystemeIdValidator = z.enum(transformableGeoSystemeIds)
export type TransformableGeoSystemeId = z.infer<typeof transformableGeoSystemeIdValidator>
const geoSystemeIdValidator = z.enum(IDS)
type GeoSystemeId = z.infer<typeof geoSystemeIdValidator>
export const geoSystemeIdValidator = z.enum(IDS)
export type GeoSystemeId = z.infer<typeof geoSystemeIdValidator>
// TODO 2024-01-18 issue https://github.com/MTES-MCT/camino/issues/919 --> pour les degrès, on affiche la notation DMS également
......
import { AsyncData } from '@/api/client-rest'
import { contentTypes } from 'camino-common/src/rest'
import { GeoSysteme, GeoSystemes, TransformableGeoSystemeId } from 'camino-common/src/static/geoSystemes'
import { GeoSysteme, GeoSystemes, TransformableGeoSystemeId, transformableGeoSystemeIdValidator } from 'camino-common/src/static/geoSystemes'
import { defineComponent, ref, watch, computed } from 'vue'
import { DsfrLink } from '../_ui/dsfr-button'
import { TableRow, TextColumnData } from '../_ui/table'
......@@ -11,6 +11,7 @@ import { capitalize } from 'camino-common/src/strings'
import { indexToLetter, toDegresMinutes } from 'camino-common/src/number'
import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools'
import { GeoSystemeTypeahead } from './geosysteme-typeahead'
import { Alert } from '../_ui/alert'
interface Props {
geojson_origine_points: FeatureCollectionPoints
......@@ -111,7 +112,12 @@ export const TabCaminoTable = defineComponent<Props>(props => {
return () => (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<GeoSystemeTypeahead disabled={true} geoSystemeId={props.geo_systeme_id} />
{transformableGeoSystemeIdValidator.safeParse(props.geo_systeme_id).success ? (
<GeoSystemeTypeahead disabled={true} geoSystemeId={props.geo_systeme_id} />
) : (
<Alert small={true} title={`Nous affichons les points dans un référentiel ${props.geo_systeme_id} qui n'est plus utilisable dans Camino`} type="warning" />
)}
<TableAuto caption="" class="fr-mb-1w" columns={columns.value} rows={rowsToDisplay.value} initialSort="noSort" />
<DsfrLink
......
......@@ -7,6 +7,7 @@ import { TitresStatutIds } from 'camino-common/src/static/titresStatuts'
import { TITRES_TYPES_IDS } from 'camino-common/src/static/titresTypes'
import { FeatureCollectionPoints, FeatureMultiPolygon } from 'camino-common/src/perimetre'
import { ApiClient } from '@/api/api-client'
import { GEO_SYSTEME_IDS, TransformableGeoSystemeId } from 'camino-common/src/static/geoSystemes'
const meta: Meta = {
title: 'Components/Common/Perimetre',
......@@ -429,3 +430,26 @@ export const CustomPointsWithAnotherGeoSysteme: StoryFn = () => (
/>
</>
)
export const CustomPointsWithAnotherLegacyGeoSysteme: StoryFn = () => (
<>
<MapPattern />
<DsfrPerimetre
perimetre={{
geojson4326_perimetre,
geojson4326_points: customPointWithoutNameAndDesc,
geojson_origine_perimetre: geojson4326_perimetre,
geojson_origine_points: {
type: 'FeatureCollection',
properties: {},
features: [{ type: 'Feature', properties: { nom: 'Nom', description: 'Description' }, geometry: { type: 'Point', coordinates: [338097.8, 462518.2] } }],
},
geojson_origine_geo_systeme_id: GEO_SYSTEME_IDS.RGFG95 as unknown as TransformableGeoSystemeId,
}}
initTab="points"
calculateNeighbours={false}
apiClient={apiClientMock}
titreSlug={titreSlugValidator.parse('titre-slug')}
/>
</>
)
......@@ -8,6 +8,7 @@ import { toCaminoDate } from 'camino-common/src/date'
import { EtapesTypes } from 'camino-common/src/static/etapesTypes'
import { titreSlugValidator } from 'camino-common/src/validators/titres'
import { km2Validator } from 'camino-common/src/number'
import { GEO_SYSTEME_IDS, TransformableGeoSystemeId } from 'camino-common/src/static/geoSystemes'
const meta: Meta = {
title: 'Components/Etape/PerimetreEdit',
......@@ -170,3 +171,25 @@ export const FilledNoHeritage: StoryFn = () => (
onPointsChange={onPointsChange}
/>
)
const etapeLegacy: Props['etape'] = {
...etapeEmptyHeritage,
geojson4326Perimetre: perimetre,
surface: km2Validator.parse(2),
geojsonOriginePerimetre: perimetre,
geojsonOriginePoints: null,
geojsonOrigineGeoSystemeId: GEO_SYSTEME_IDS.RGFG95 as unknown as TransformableGeoSystemeId,
heritageProps: { perimetre: { actif: false } },
}
export const LegacyGeoSysteme: StoryFn = () => (
<PerimetreEdit
initTab="points"
completeUpdate={completeUpdate}
onEtapeChange={onEtapeChange}
apiClient={apiClient}
etape={etapeLegacy}
titreTypeId="arm"
titreSlug={titreSlug}
onPointsChange={onPointsChange}
/>
)
<div class="dsfr">
<div class="dsfr">
<div class="mb-s">
<div><button class="fr-btn fr-btn--primary fr-btn--md" title="Importer un périmètre…" aria-label="Importer un périmètre…" type="button">Importer un périmètre…</button>
<!---->
<!---->
<div class="fr-mt-2w">
<div class="dsfr">
<div class="fr-tabs">
<ul class="fr-tabs__list" role="tablist" aria-label="Affichage des titres en vue carte ou tableau">
<li role="presentation"><button id="tabpanel-carte-271" class="fr-tabs__tab fr-icon-earth-fill fr-tabs__tab--icon-left" tabindex="-1" role="tab" aria-label="Carte" aria-selected="false" aria-controls="tabpanel-carte-271-panel">Carte</button></li>
<li role="presentation"><button id="tabpanel-points-271" class="fr-tabs__tab fr-icon-list-unordered fr-tabs__tab--icon-left" tabindex="0" role="tab" aria-label="Tableau" aria-selected="true" aria-controls="tabpanel-points-271-panel">Tableau</button></li>
</ul>
<div id="tabpanel-carte-271-panel" class="fr-tabs__panel" role="tabpanel" aria-labelledby="tabpanel-carte-271" tabindex="0">
<!---->
</div>
<div id="tabpanel-points-271-panel" class="fr-tabs__panel fr-tabs__panel--selected" role="tabpanel" aria-labelledby="tabpanel-points-271" tabindex="0">
<div style="display: flex; flex-direction: column;">
<div class="fr-alert fr-alert--warning fr-alert--sm">
<p>Nous affichons les points dans un référentiel 4624 qui n'est plus utilisable dans Camino</p>
</div>
<div style="overflow-x: auto;" class="fr-mb-1w">
<div class="fr-table">
<table style="display: table;">
<caption></caption>
<thead>
<tr>
<th scope="col" class="nowrap">
<div class="fr-text--md">Nom du point</div>
</th>
<th scope="col" class="nowrap">
<div class="fr-text--md">Description</div>
</th>
<th scope="col" class="nowrap">
<div class="fr-text--md">Longitude</div>
</th>
<th scope="col" class="nowrap">
<div class="fr-text--md">Latitude</div>
</th>
<th scope="col" class="nowrap">
<div class="fr-text--md">Longitude (E)</div>
</th>
<th scope="col" class="nowrap">
<div class="fr-text--md">Latitude (N)</div>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class=""><span class="">A</span></td>
<td class=""><span class="">Polygone 1</span></td>
<td class=""><span class="">-52.54</span></td>
<td class=""><span class="">4.22269896902571</span></td>
<td class=""><span class="">-52°32,4'</span></td>
<td class=""><span class="">4°13,362'</span></td>
</tr>
<tr>
<td class=""><span class="">B</span></td>
<td class=""><span class="">Polygone 1</span></td>
<td class=""><span class="">-52.55</span></td>
<td class=""><span class="">4.22438936251509</span></td>
<td class=""><span class="">-52°33'</span></td>
<td class=""><span class="">4°13,463'</span></td>
</tr>
<tr>
<td class=""><span class="">C</span></td>
<td class=""><span class="">Polygone 1</span></td>
<td class=""><span class="">-52.55</span></td>
<td class=""><span class="">4.24113309117193</span></td>
<td class=""><span class="">-52°33'</span></td>
<td class=""><span class="">4°14,468'</span></td>
</tr>
</tbody>
</table>
</div>
</div><a class="fr-btn fr-btn--secondary fr-btn--icon-right fr-icon-download-line" title="Télécharge les points au format csv" href="data:text/csv;charset=utf-8,Nom%20du%20point;Description;Longitude;Latitude;Longitude%20(E);Latitude%20(N)%0AA;Polygone%201;-52.54;4.22269896902571;-52%C2%B032,4';4%C2%B013,362'%0AB;Polygone%201;-52.55;4.22438936251509;-52%C2%B033';4%C2%B013,463'%0AC;Polygone%201;-52.55;4.24113309117193;-52%C2%B033';4%C2%B014,468'" download="points-titre-slug.csv" style="align-self: end;">.csv</a>
</div>
</div>
</div>
</div>
<div class="fr-text--md">Surface : 2 Km²</div>
</div>
</div>
<!---->
</div>
<!---->
<!---->
</div>
</div>
\ No newline at end of file
......@@ -13,7 +13,7 @@ import { KM2 } from 'camino-common/src/number'
import { EtapeWithHeritage, EtapeFondamentale } from 'camino-common/src/etape'
import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools'
import { PointsImportPopup } from './points-import-popup'
import { TransformableGeoSystemeId } from 'camino-common/src/static/geoSystemes'
import { TransformableGeoSystemeId, transformableGeoSystemeIdValidator } from 'camino-common/src/static/geoSystemes'
export interface Props {
apiClient: Pick<ApiClient, 'uploadTempDocument' | 'geojsonImport' | 'getGeojsonByGeoSystemeId' | 'geojsonPointsImport'>
......@@ -146,7 +146,9 @@ export const PerimetreEdit = defineComponent<Props>(props => {
write={() => (
<div>
<DsfrButton onClick={openPerimetrePopup} title="Importer un périmètre…" />
{isNotNullNorUndefined(props.etape.geojson4326Perimetre) && isNotNullNorUndefined(props.etape.geojsonOrigineGeoSystemeId) ? (
{isNotNullNorUndefined(props.etape.geojson4326Perimetre) &&
isNotNullNorUndefined(props.etape.geojsonOrigineGeoSystemeId) &&
transformableGeoSystemeIdValidator.safeParse(props.etape.geojsonOrigineGeoSystemeId).success ? (
<DsfrButton class="fr-ml-2w" onClick={openPointsPopup} buttonType="secondary" title="Éditer les points" />
) : null}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment