Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

front: fix punctual object position calculation #5635

Merged
merged 1 commit into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { WidgetProps } from '@rjsf/core';
import InputSNCF from 'common/BootstrapSNCF/InputSNCF';

export const CustomPosition: React.FC<WidgetProps> = (props) => {
const { schema, onChange, title, value } = props;
const POINT_NAME = 'point-parameter';

return (
<div key={`${POINT_NAME}-${schema.description}`}>
<p>{title}</p>
<InputSNCF
name={POINT_NAME}
id={`${POINT_NAME}-${schema.description}`}
value={value}
onChange={(e) => {
onChange(e.target.value);
}}
type="number"
min={0}
/>
</div>
);
};

export default CustomPosition;
24 changes: 18 additions & 6 deletions front/src/applications/editor/tools/pointEdition/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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:
*/
Expand Down Expand Up @@ -218,7 +224,9 @@ export const PointEditionLeftPanel: FC<{ type: EditoastType }> = <Entity extends

if (!firstLoading) {
const { position } = state.entity.properties;
const point = along(track, position, { units: 'meters' });
const turfPosition =
(position * length(track, { units: 'meters' })) / track.properties.length;
const point = along(track, turfPosition, { units: 'meters' });

setState({ ...state, entity: { ...state.entity, geometry: point.geometry } });
}
Expand Down Expand Up @@ -261,6 +269,9 @@ export const PointEditionLeftPanel: FC<{ type: EditoastType }> = <Entity extends
},
},
},
position: {
'ui:widget': CustomPosition,
},
}}
onSubmit={async (savedEntity) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -297,8 +308,8 @@ export const PointEditionLeftPanel: FC<{ type: EditoastType }> = <Entity extends
});
}
}}
onChange={(entity: Entity | SignalEntity) => {
const additionalUpdate: Partial<Entity> = {};
onChange={(entity: Entity | EditorPoint) => {
const additionalUpdate: Partial<EditorPoint> = {};
const additionalPropertiesUpdate: Partial<SignalEntity['properties']> = {};
const newPosition = entity.properties?.position;
const oldPosition = state.entity.properties?.position;
Expand All @@ -311,16 +322,17 @@ export const PointEditionLeftPanel: FC<{ type: EditoastType }> = <Entity extends
typeof oldPosition === 'number' &&
newPosition !== oldPosition
) {
const point = along(trackState.track, newPosition, { units: 'meters' });
const turfPosition =
(newPosition * length(trackState.track, { units: 'meters' })) /
trackState.track.properties.length;
const point = along(trackState.track, turfPosition, { units: 'meters' });
additionalUpdate.geometry = point.geometry;
}

if (entity.objType === 'Signal' && entity.properties.logical_signals) {
additionalPropertiesUpdate.logical_signals = formatSignalingSystems(
entity as SignalEntity
);
}

setState({
...state,
entity: {
Expand Down
69 changes: 40 additions & 29 deletions front/src/applications/editor/tools/pointEdition/tool-factory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@ import { Map } from 'maplibre-gl';
import { save } from 'reducers/editor';
import { ConfirmModal } from 'common/BootstrapSNCF/ModalSNCF';
import { NEW_ENTITY_ID } from 'applications/editor/data/utils';
import {
NULL_GEOMETRY,
BufferStopEntity,
DetectorEntity,
SignalEntity,
TrackSectionEntity,
} from 'types';
import { LAYER_TO_EDITOAST_DICT, LayerType } from '../types';
import { getNearestPoint } from '../../../../utils/mapHelper';
import { getPointEditionLeftPanel, POINT_LAYER_ID, PointEditionMessages } from './components';
import { PointEditionState } from './types';
import { NULL_GEOMETRY, BufferStopEntity, DetectorEntity, SignalEntity } from '../../../../types';
import { getEntity } from '../../data/api';
import { Tool } from '../editorContextTypes';
import { DEFAULT_COMMON_TOOL_STATE } from '../commonToolState';
import { approximateDistanceWithEditoastData } from '../utils';

type EditorPoint = BufferStopEntity | DetectorEntity | SignalEntity;
interface PointEditionToolParams<T extends EditorPoint> {
Expand All @@ -29,6 +36,14 @@ interface PointEditionToolParams<T extends EditorPoint> {
requiresAngle?: boolean;
}

function calculateDistanceAlongTrack(track: Feature<LineString>, point: Point) {
const wrongPointOnTrack = nearestPointOnLine(track.geometry, point, { units: 'meters' });
return approximateDistanceWithEditoastData(
track as TrackSectionEntity,
wrongPointOnTrack.geometry
);
}

function getPointEditionTool<T extends EditorPoint>({
layer,
icon,
Expand Down Expand Up @@ -61,20 +76,6 @@ function getPointEditionTool<T extends EditorPoint>({
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,
Expand All @@ -83,6 +84,19 @@ function getPointEditionTool<T extends EditorPoint>({
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),
});
},
},
],
[
{
Expand Down Expand Up @@ -116,6 +130,12 @@ function getPointEditionTool<T extends EditorPoint>({
],

// 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) {
Expand All @@ -125,7 +145,6 @@ function getPointEditionTool<T extends EditorPoint>({
entity: omit(entity, 'geometry') as T,
});
}

if ((!entity.geometry || isEqual(entity.geometry, NULL_GEOMETRY)) && nearestPoint) {
const newEntity = cloneDeep(entity);
newEntity.geometry = {
Expand All @@ -135,15 +154,13 @@ function getPointEditionTool<T extends EditorPoint>({
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<LineString>).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,
Expand Down Expand Up @@ -244,12 +261,6 @@ function getPointEditionTool<T extends EditorPoint>({
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]),
Expand Down
16 changes: 11 additions & 5 deletions front/src/applications/editor/tools/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down