diff --git a/front/src/applications/editor/tools/pointEdition/CustomPosition.tsx b/front/src/applications/editor/tools/pointEdition/CustomPosition.tsx new file mode 100644 index 00000000000..2165a33ee43 --- /dev/null +++ b/front/src/applications/editor/tools/pointEdition/CustomPosition.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { WidgetProps } from '@rjsf/core'; +import InputSNCF from 'common/BootstrapSNCF/InputSNCF'; + +export const CustomPosition: React.FC = (props) => { + const { schema, onChange, title, value } = props; + const POINT_NAME = 'point-parameter'; + + return ( +
+

{title}

+ { + onChange(e.target.value); + }} + type="number" + min={0} + /> +
+ ); +}; + +export default CustomPosition; diff --git a/front/src/applications/editor/tools/pointEdition/components.tsx b/front/src/applications/editor/tools/pointEdition/components.tsx index f1852f3f28d..ab6485d6214 100644 --- a/front/src/applications/editor/tools/pointEdition/components.tsx +++ b/front/src/applications/editor/tools/pointEdition/components.tsx @@ -20,6 +20,8 @@ import { TrackSectionEntity, RouteEntity, SignalEntity, + DetectorEntity, + BufferStopEntity, } from 'types'; import { osrdEditoastApi } from 'common/api/osrdEditoastApi'; import EditorForm from 'applications/editor/components/EditorForm'; @@ -38,12 +40,16 @@ import { getEditRouteState } from 'applications/editor/tools/routeEdition/utils' import TOOL_TYPES from 'applications/editor/tools/toolTypes'; import { EditoastType } from 'applications/editor/tools/types'; import { getIsLoading } from 'reducers/main/mainSelector'; +import length from '@turf/length'; import { CustomFlagSignalCheckbox } from './CustomFlagSignalCheckbox'; import { PointEditionState } from './types'; import { formatSignalingSystems } from './utils'; +import { CustomPosition } from './CustomPosition'; export const POINT_LAYER_ID = 'pointEditionTool/new-entity'; +type EditorPoint = BufferStopEntity | DetectorEntity | SignalEntity; + /** * Generic component to show routes starting or ending from the edited waypoint: */ @@ -218,7 +224,9 @@ export const PointEditionLeftPanel: FC<{ type: EditoastType }> = = { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -297,8 +308,8 @@ export const PointEditionLeftPanel: FC<{ type: EditoastType }> = { - const additionalUpdate: Partial = {}; + onChange={(entity: Entity | EditorPoint) => { + const additionalUpdate: Partial = {}; const additionalPropertiesUpdate: Partial = {}; const newPosition = entity.properties?.position; const oldPosition = state.entity.properties?.position; @@ -311,16 +322,17 @@ export const PointEditionLeftPanel: FC<{ type: EditoastType }> = { @@ -29,6 +36,14 @@ interface PointEditionToolParams { requiresAngle?: boolean; } +function calculateDistanceAlongTrack(track: Feature, point: Point) { + const wrongPointOnTrack = nearestPointOnLine(track.geometry, point, { units: 'meters' }); + return approximateDistanceWithEditoastData( + track as TrackSectionEntity, + wrongPointOnTrack.geometry + ); +} + function getPointEditionTool({ layer, icon, @@ -61,20 +76,6 @@ function getPointEditionTool({ getInitialState, actions: [ [ - { - id: 'reset-entity', - icon: BiReset, - labelTranslationKey: `Editor.tools.${id}-edition.actions.reset-entity`, - onClick({ setState, state }) { - setState({ - ...getInitialState(), - entity: state.initialEntity, - }); - }, - isDisabled({ state }) { - return isEqual(state.entity, state.initialEntity); - }, - }, { id: 'new-entity', icon: AiOutlinePlus, @@ -83,6 +84,19 @@ function getPointEditionTool({ setState(getInitialState()); }, }, + { + id: 'reset-entity', + icon: BiReset, + labelTranslationKey: `Editor.tools.${id}-edition.actions.reset-entity`, + isDisabled({ state: { entity, initialEntity } }) { + return isEqual(entity, initialEntity); + }, + onClick({ setState, state: { initialEntity } }) { + setState({ + entity: cloneDeep(initialEntity), + }); + }, + }, ], [ { @@ -116,6 +130,12 @@ function getPointEditionTool({ ], // Interactions: + getCursor({ state }, { isDragging }) { + if (isDragging || !state.entity.geometry || isEqual(state.entity.geometry, NULL_GEOMETRY)) + return 'move'; + if (state.isHoveringTarget) return 'pointer'; + return 'default'; + }, onClickMap(_e, { setState, state, infraID, dispatch }) { const { isHoveringTarget, entity, nearestPoint } = state; if (entity.geometry && !isEqual(entity.geometry, NULL_GEOMETRY) && isHoveringTarget) { @@ -125,7 +145,6 @@ function getPointEditionTool({ entity: omit(entity, 'geometry') as T, }); } - if ((!entity.geometry || isEqual(entity.geometry, NULL_GEOMETRY)) && nearestPoint) { const newEntity = cloneDeep(entity); newEntity.geometry = { @@ -135,15 +154,13 @@ function getPointEditionTool({ newEntity.properties = newEntity.properties || {}; newEntity.properties.track = nearestPoint.trackSectionID; - // retrieve the track section to be sure that the computation of the distance will be good - // we can't trust maplibre, because the stored gemetry is not necessary the real one getEntity(infraID as number, newEntity.properties.track, 'TrackSection', dispatch).then( (track) => { - newEntity.properties.position = nearestPointOnLine( - (track as Feature).geometry, - newEntity.geometry as Point, - { units: 'meters' } - ).properties?.location; + const distanceAlongTrack = calculateDistanceAlongTrack( + track as TrackSectionEntity, + newEntity.geometry as Point + ); + newEntity.properties.position = distanceAlongTrack; setState({ ...state, @@ -244,12 +261,6 @@ function getPointEditionTool({ getInteractiveLayers() { return ['editor/geo/track-main', POINT_LAYER_ID]; }, - getCursor({ state }, { isDragging }) { - if (isDragging || !state.entity.geometry || isEqual(state.entity.geometry, NULL_GEOMETRY)) - return 'move'; - if (state.isHoveringTarget) return 'pointer'; - return 'default'; - }, layersComponent, leftPanelComponent: getPointEditionLeftPanel(LAYER_TO_EDITOAST_DICT[layer]), diff --git a/front/src/applications/editor/tools/utils.ts b/front/src/applications/editor/tools/utils.ts index 1a0aa4944d0..e955b85c918 100644 --- a/front/src/applications/editor/tools/utils.ts +++ b/front/src/applications/editor/tools/utils.ts @@ -30,13 +30,19 @@ import { getEntity } from '../data/api'; * Since Turf and Editoast do not compute the lengths the same way (see #1751) * we can have data "end" being larger than Turf's computed length, which * throws an error. Until we find a way to get similar computations, we can - * approximate this way: + * approximate it with a rule of Three. + * + * This approximation is not good if the track is long. */ export function approximateDistanceWithEditoastData(track: TrackSectionEntity, point: Point) { - const distanceAlongTrack = - (length(lineSlice(track.geometry.coordinates[0], point, track)) * track.properties.length) / - length(track); - return distanceAlongTrack; + const wrongDistanceAlongTrack = length(lineSlice(track.geometry.coordinates[0], point, track)); + const wrongTrackLength = length(track); + const realTrackLength = track.properties.length; + + const distanceAlongTrack = (wrongDistanceAlongTrack * realTrackLength) / wrongTrackLength; + + if (Math.abs(distanceAlongTrack - realTrackLength) < 0.1) return realTrackLength; + return Math.round(distanceAlongTrack * 100) / 100; } /** return the trackRanges near the mouse thanks to the hover event */