diff --git a/frontend/src/app/editor/editor.component.html b/frontend/src/app/editor/editor.component.html index 03fc909604039c787f2f7f2713bdda96c81dbf20..61ac43138c2d10ec1bf95981dc5d915abd495b45 100644 --- a/frontend/src/app/editor/editor.component.html +++ b/frontend/src/app/editor/editor.component.html @@ -215,11 +215,11 @@ </button> <button class="btn btn-secondary toolbar-button" - [ngClass]="{selected: drawingMode === 'drawing' && drawingShape === 'stripes'}" + [ngClass]="{selected: drawingMode === 'drawing' && drawingShape === 'rectangle'}" tooltip="{{ 'top-bar.edition-bar.pedestrian-hint' | translate }}" placement="bottom" - (click)="drawStripes()"> - <fa-icon [icon]="faPedestrian"></fa-icon> + (click)="drawRectangle()"> + <fa-icon [icon]="faSquare"></fa-icon> </button> <button class="btn btn-secondary toolbar-button" diff --git a/frontend/src/app/editor/editor.component.ts b/frontend/src/app/editor/editor.component.ts index 944580e67b281da573fa9a8478dfc59073f72a7e..96cacf7b6f4212facc9c6a19e31c900585eeb790 100644 --- a/frontend/src/app/editor/editor.component.ts +++ b/frontend/src/app/editor/editor.component.ts @@ -19,7 +19,7 @@ import { faCircle, faVectorSquare, faEllipsisV, - faBars, + faSquare, faMapMarkerAlt, faSave, faPrint, @@ -69,7 +69,7 @@ export class EditorComponent implements OnInit { faCircle = faCircle; faPolygon = faVectorSquare; faGuideline = faEllipsisV; - faPedestrian = faBars; + faSquare = faSquare; faMarker = faMapMarkerAlt; faSave = faSave; faPrint = faPrint; @@ -449,16 +449,17 @@ export class EditorComponent implements OnInit { } /** - * Action to perform when the stripes button is pressed: - * start drawing stripes on the map with the drawing service. + * Action to perform when the rectangle button is pressed: + * start drawing rectangle on the map with the drawing service. */ - drawStripes() { + drawRectangle() { this.drawingMode = DrawingMode.DRAWING; - this.drawingShape = DrawingShape.STRIPES; - this.drawingService.drawingShape = DrawingShape.STRIPES; - this.drawingService.draw(this.map); + this.drawingShape = DrawingShape.RECTANGLE; + this.drawingService.drawingShape = DrawingShape.RECTANGLE; + this.textureSelectionModal.open = true; } + /** * Action to perform when the marker button is pressed: * start drawing markers on the map with the drawing service. diff --git a/frontend/src/app/editor/modals/main-menu/main-menu.component.html b/frontend/src/app/editor/modals/main-menu/main-menu.component.html index 23ce022879050155771502ee5685dbb02352f14c..8b1cb2e533b2d0412776c88bda6a06e15d8f8a80 100644 --- a/frontend/src/app/editor/modals/main-menu/main-menu.component.html +++ b/frontend/src/app/editor/modals/main-menu/main-menu.component.html @@ -1,5 +1,5 @@ <!-- Template for the main menu modal --> -<div +<div *ngIf="open" [config]="{ show: true, backdrop: 'static' }" (onHidden)="open = false" @@ -8,7 +8,7 @@ class="modal fade"> <div class="modal-dialog modal-lg"> <div class="modal-content"> - + <!-- Header --> <div class="modal-header"> <h1 class="modal-title"> {{ 'editor-modals.main-menu.title' | translate }} </h1> @@ -22,12 +22,12 @@ id="create-map-button" type="button" class="btn btn-primary btn-lg btn-block" - (click)="open = false"> + (click)="open = false; state = 'edition'"> {{ 'editor-modals.main-menu.create-map' | translate }} </button> <!-- Show existing maps button --> - <button + <button type="button" class="btn btn-secondary btn-lg btn-block" (click)="showMapSelectionMenu()"> @@ -46,15 +46,15 @@ <!-- Language selection dropdown --> <div class="btn-group" dropdown> - <button + <button class="btn btn-secondary btn-lg dropdown-toggle" dropdownToggle type="button" aria-controls="dropdown-basic"> {{ 'languages.' + translate.currentLang | translate }} </button> - <ul - *dropdownMenu + <ul + *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-basic"> @@ -78,4 +78,4 @@ </div> </div> -</div> \ No newline at end of file +</div> diff --git a/frontend/src/app/map/drawing/drawing.service.ts b/frontend/src/app/map/drawing/drawing.service.ts index 92c7282ad21523d3c649223b706d7231796a8d7c..e8afe1c2374c87159ac60477ecf5af6cd23b5582 100644 --- a/frontend/src/app/map/drawing/drawing.service.ts +++ b/frontend/src/app/map/drawing/drawing.service.ts @@ -6,9 +6,9 @@ import { MapComponent } from '../map.component'; import { CircleDrawer } from './circle-drawer'; import { PolygonDrawer } from './polygon-drawer'; import { LineDrawer } from './line-drawer'; -import { StripesDrawer } from './stripes-drawer'; +import { RectangleDrawer } from './rectangle-drawer'; import { MarkerDrawer } from './marker-drawer'; -import { DrawingShape, DrawingTexture, stripesWidth } from './utils'; +import { DrawingShape, DrawingTexture} from './utils'; import { subtract, arrayToVector, scalarMultiplication, add, normal } from '../../geometry/geometry2d'; import { BsModalService } from 'ngx-bootstrap'; @@ -27,7 +27,7 @@ export class DrawingService { private circleDrawer = new CircleDrawer(); private polygonDrawer = new PolygonDrawer(); private lineDrawer = new LineDrawer(); - private stripesDrawer = new StripesDrawer(); + private rectangleDrawer = new RectangleDrawer(); private markerDrawer: MarkerDrawer; constructor( @@ -51,7 +51,7 @@ export class DrawingService { this.circleDrawer.clearListeners(); this.polygonDrawer.clearListeners(); this.lineDrawer.clearListeners(); - this.stripesDrawer.clearListeners(); + this.rectangleDrawer.clearListeners(); this.markerDrawer.clearListeners(); } @@ -72,8 +72,8 @@ export class DrawingService { this.circleDrawer.draw(map, this.drawingTexture); } else if (this.drawingShape === DrawingShape.LINE) { this.lineDrawer.draw(map); - } else if (this.drawingShape === DrawingShape.STRIPES) { - this.stripesDrawer.draw(map); + } else if (this.drawingShape === DrawingShape.RECTANGLE) { + this.rectangleDrawer.draw(map, this.drawingTexture); } else if (this.drawingShape === DrawingShape.MARKER) { this.markerDrawer.draw(map); } @@ -114,7 +114,7 @@ export class DrawingService { ); // A listener is added on the 'update' event of the editor to handle special cases - // such as lines and stripes. + // such as lines and rectangle. this.actionListeners.push( this.editor.on( @@ -128,35 +128,6 @@ export class DrawingService { this.editor.layer.remove(event.graphics[0]); await this.lineDrawer.setBackground(map, event.graphics[0].attributes.id, event.graphics[0].geometry); this.editor.layer.add(event.graphics[0]); - - } else if (event.graphics[0].attributes && - event.graphics[0].attributes.temporaryStripes) { - - // When stripes are edited, the graphics (black and white stripes) that compose it - // are removed from the maps drawingsLayer and it is replaced by a simple temporary rectangle for edition. - // Once edition on is complete, the stripes must be added back to the graphics layer, - // and the temporary replacement rectangle is deleted. - const line = subtract( - arrayToVector(event.graphics[0].geometry.rings[0][0]), - arrayToVector(event.graphics[0].geometry.rings[0][1]) - ); - const lineNormal = scalarMultiplication(normal(line), stripesWidth); - - const start = add( - arrayToVector(event.graphics[0].geometry.rings[0][0]), - lineNormal - ); - const end = add( - arrayToVector(event.graphics[0].geometry.rings[0][1]), - lineNormal - ); - - this.editor.layer.remove(event.graphics[0]); - this.stripesDrawer.createFinalStripes( - map, - start, - end - ); } } } @@ -199,9 +170,6 @@ export class DrawingService { } else if (result.graphic.geometry.type === 'point') { this.markerDrawer.setTextAttribute(result.graphic); - } else if (result.graphic.attributes && result.graphic.attributes.stripes) { - this.handleStripesEdition(map, result.graphic); - } else { this.editor.update([result.graphic], {tool: 'transform'}); } @@ -209,25 +177,6 @@ export class DrawingService { }); } - /** - * Handle a click on stripes to edit them. - * - * @param map The MapComponent on which the stripes that must be edited are displayed. - * @param graphic The graphic belonging to the pedestrian passage - * that must be edited and which was clicked. - */ - private async handleStripesEdition( - map: MapComponent, - graphic: __esri.Graphic - ) { - /* When a passage is edited, its graphics are retrieved and removed from the graphics layer, - and it is temporarily replaced by a simple rectangle. */ - const id = graphic.attributes.id; - const startEnd = this.stripesDrawer.deleteStripes(map, id); - const temporaryStripes = await this.stripesDrawer.createTemporaryStripes(map.mapView, startEnd[0], startEnd[1]); - this.editor.layer.add(temporaryStripes); - this.editor.update([temporaryStripes], {tool: 'transform'}); - } /** * Start a handler to react to clicks on the input MapComponent to delete graphics. @@ -286,13 +235,7 @@ export class DrawingService { ) { matchingGraphics.forEach( (graphic) => { - // Stripes are represented by a set of graphics, they hence need to be handled - // differently (each graphic which is part of it must be retrieved and deleted separately). - if (graphic.attributes && graphic.attributes.stripes) { - this.stripesDrawer.deleteStripes(map, graphic.attributes.id); - } else { map.drawingsLayer.remove(graphic); - } } ); } diff --git a/frontend/src/app/map/drawing/rectangle-drawer.ts b/frontend/src/app/map/drawing/rectangle-drawer.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c5f2626daac566ba66fa44b63fa5b3610710eaf --- /dev/null +++ b/frontend/src/app/map/drawing/rectangle-drawer.ts @@ -0,0 +1,202 @@ +import { loadModules } from 'esri-loader'; + +import { Drawer } from './drawer'; +import { MapComponent } from '../map.component'; +import { DrawActionEvent, getDefaultOutline } from './utils'; +import { Vector2d, clone, add, subtract, norm, normal, scalarMultiplication, vectorToArray } from '../../geometry/geometry2d'; +import { DrawingTexture, getDrawingSymbol } from './utils'; + +export class RectangleDrawer extends Drawer { + + /** + * Draw rectangle on a map. + * + * @param map The MapComponent on which the rectangle must be drawn. + */ + async draw( + map: MapComponent, + texture: DrawingTexture + ) { + const [Draw] = await loadModules(['esri/views/2d/draw/Draw']); + + this.clearListeners(); + + const draw: __esri.Draw = new Draw({ + view: map.mapView + }); + const drawAction = draw.create('rectangle', {}); + + this.drawActionListeners.push( + drawAction.on( + 'vertex-add', + async (event) => await this.createRectanglePolygon(event, map, texture) + ), + + drawAction.on( + 'cursor-update', + async (event) => await this.createRectanglePolygon(event, map, texture) + ), + + drawAction.on( + 'draw-complete', + async (event) => { + await this.createRectanglePolygon(event, map, texture); + this.currentGraphic = null; + this.draw(map, texture); + } + ) + ); + } + + /** + * Generate rectangle for given vertices captured by a DrawActionEvent and add them + * to a MapComponent. + * + * @param event The DrawActionEvent which captured the vertices for which rectangle must be generated. + * @param map The MapComponent on which the rectangle must be added. + */ + private async createRectanglePolygon( + event: DrawActionEvent, + map: MapComponent, + texture: DrawingTexture + ) { + if (this.currentGraphic != null) { + map.drawingsLayer.remove(this.currentGraphic); + } + + /* Only start drawing the passage when the user has clicked and is + dragging the mouse. */ + if (event.vertices.length === 2) { + const start: Vector2d = {x: event.vertices[0][0], y: event.vertices[0][1]}; + const end: Vector2d = {x: event.vertices[1][0], y: event.vertices[1][1]}; + + if (event.type === 'draw-complete') { + this.createFinalRectangle(map, start, end, texture); + + } else { + this.currentGraphic = await this.createTemporaryRectangle(map.mapView, start, end); + map.drawingsLayer.add(this.currentGraphic); + } + } + } + + /** + * Create a temporary rectangle rectanle to use while drawing or editing rectangle on a map. + * + * @param mapView The Esri MapView on which the temporary rectangle must be drawn. + * @param start The coordinates of the starting point of the rectangle rectangle. + * @param end The coordinates of the ending point of the rectangle rectangle. + * + * @returns A Graphic containing the temporary rectangle rectangle to display. + */ + async createTemporaryRectangle( + mapView: __esri.MapView, + start: Vector2d, + end: Vector2d + ): Promise<__esri.Graphic> { + const [ + Graphic, + SimpleFillSymbol + ] = await loadModules([ + 'esri/Graphic', + 'esri/symbols/SimpleFillSymbol' + ]); + + const rectangle = await this.createRectangle(mapView, start, end); + + const drawingSymbol: __esri.Symbol = new SimpleFillSymbol({ + color: [143, 143, 143, 0.75] + }); + const outline: __esri.SimpleLineSymbol = await getDefaultOutline(); + + const temporaryRectangle = new Graphic({ + geometry: rectangle, + symbol: drawingSymbol, + visible: true, + }); + (temporaryRectangle.symbol as __esri.FillSymbol).outline = outline; + + return temporaryRectangle; + } + + /** + * Create the Graphics for a rectangle drawing and add them to a map. + * + * @param map The MapComponent on which the rectangle must be drawn. + * @param start The coordinates of the starting point of the rectangle on the map. + * @param end The coordinates of the ending point of the rectangle. + */ + async createFinalRectangle( + map: MapComponent, + start: Vector2d, + end: Vector2d, + texture: DrawingTexture + ) { + const [ + Graphic, + SimpleFillSymbol + ] = await loadModules([ + 'esri/Graphic', + 'esri/symbols/SimpleFillSymbol', + ]); + + const rectangle = await this.createRectangle(map.mapView, start, end); + + const currentSymbol = new SimpleFillSymbol({color: '#000000'}) + const symbol = await getDrawingSymbol(texture); + console.log(symbol); + + const stripe = new Graphic({ + geometry: rectangle, + symbol: symbol, + visible: true, + attributes: { + start: start, + end: end + } + }); + + map.drawingsLayer.add(stripe); + } + + /** + * Create a rectangle geometry between a start and end point on an Esri MapView. + * + * @param mapView The MapView holding the spatial reference to which the + * rectangle must be associated. + * @param start The starting point of the rectangle. + * @param end The ending point of the rectangle. + * + * @returns A new poylgon geometry with the shape of a rectangle between + * the input start and end points. + */ + private async createRectangle( + mapView: __esri.MapView, + start: Vector2d, + end: Vector2d, + ): Promise<__esri.Geometry> { + const [Polygon] = await loadModules(['esri/geometry/Polygon']); + + + // Vertices of a rectangle between the two points. + const p1: Vector2d = {x: end.x, y: start.y}; + const p2: Vector2d = {x: start.x, y: end.y}; + const vertices = [ + start, + p1, + end, + p2, + ]; + + // Convert the vertices to arrays to pass them to the Polygon constructor. + const verticesArray = vertices.map((vertex) => vectorToArray(vertex)); + + const rectangle: __esri.Geometry = new Polygon({ + spatialReference: mapView.spatialReference.clone(), + rings: verticesArray + }); + + return rectangle; + } + +} diff --git a/frontend/src/app/map/drawing/stripes-drawer.ts b/frontend/src/app/map/drawing/stripes-drawer.ts deleted file mode 100644 index 7c3d6bd9454ec5c2a4126bc1635fa613994e1728..0000000000000000000000000000000000000000 --- a/frontend/src/app/map/drawing/stripes-drawer.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { loadModules } from 'esri-loader'; - -import { Drawer } from './drawer'; -import { MapComponent } from '../map.component'; -import { DrawActionEvent, getDefaultOutline, stripesWidth } from './utils'; -import { Vector2d, clone, add, subtract, norm, normal, scalarMultiplication, vectorToArray } from '../../geometry/geometry2d'; - -export class StripesDrawer extends Drawer { - - private lastId = 0; - - /** - * Draw stripes on a map. - * - * @param map The MapComponent on which the stripes must be drawn. - */ - async draw( - map: MapComponent - ) { - const [Draw] = await loadModules(['esri/views/2d/draw/Draw']); - - this.clearListeners(); - - const draw: __esri.Draw = new Draw({ - view: map.mapView - }); - const drawAction = draw.create('rectangle', {}); - - this.drawActionListeners.push( - drawAction.on( - 'vertex-add', - async (event) => await this.createStripes(event, map) - ), - - drawAction.on( - 'cursor-update', - async (event) => await this.createStripes(event, map) - ), - - drawAction.on( - 'draw-complete', - async (event) => { - await this.createStripes(event, map); - this.currentGraphic = null; - this.draw(map); - } - ) - ); - } - - /** - * Generate stripes for given vertices captured by a DrawActionEvent and add them - * to a MapComponent. - * - * @param event The DrawActionEvent which captured the vertices for which stripes must be generated. - * @param map The MapComponent on which the stripes must be added. - */ - private async createStripes( - event: DrawActionEvent, - map: MapComponent - ) { - if (this.currentGraphic != null) { - map.drawingsLayer.remove(this.currentGraphic); - } - - /* Only start drawing the passage when the user has clicked and is - dragging the mouse. */ - if (event.vertices.length === 2) { - const start: Vector2d = {x: event.vertices[0][0], y: event.vertices[0][1]}; - const end: Vector2d = {x: event.vertices[1][0], y: event.vertices[1][1]}; - - if (event.type === 'draw-complete') { - this.createFinalStripes(map, start, end); - - } else { - this.currentGraphic = await this.createTemporaryStripes(map.mapView, start, end); - map.drawingsLayer.add(this.currentGraphic); - } - } - } - - /** - * Create a temporary stripes rectanle to use while drawing or editing stripes on a map. - * - * @param mapView The Esri MapView on which the temporary stripes must be drawn. - * @param start The coordinates of the starting point of the stripes rectangle. - * @param end The coordinates of the ending point of the stripes rectangle. - * - * @returns A Graphic containing the temporary stripes rectangle to display. - */ - async createTemporaryStripes( - mapView: __esri.MapView, - start: Vector2d, - end: Vector2d - ): Promise<__esri.Graphic> { - const [ - Graphic, - SimpleFillSymbol - ] = await loadModules([ - 'esri/Graphic', - 'esri/symbols/SimpleFillSymbol' - ]); - - const rectangle = await this.createRectangle(mapView, start, end); - - const drawingSymbol: __esri.Symbol = new SimpleFillSymbol({ - color: [143, 143, 143, 0.75] - }); - const outline: __esri.SimpleLineSymbol = await getDefaultOutline(); - - const temporaryStripes = new Graphic({ - geometry: rectangle, - symbol: drawingSymbol, - visible: true, - attributes: { - temporaryStripes: true - } - }); - (temporaryStripes.symbol as __esri.FillSymbol).outline = outline; - - return temporaryStripes; - } - - /** - * Create the Graphics for a stripes drawing and add them to a map. - * - * @param map The MapComponent on which the stripes must be drawn. - * @param start The coordinates of the starting point of the stripes on the map. - * @param end The coordinates of the ending point of the stripes. - */ - async createFinalStripes( - map: MapComponent, - start: Vector2d, - end: Vector2d - ) { - const [ - Graphic, - SimpleFillSymbol - ] = await loadModules([ - 'esri/Graphic', - 'esri/symbols/SimpleFillSymbol', - ]); - - const line = subtract(start, end); - const lineNorm = norm(line); - - // Set the number of stripes composing the drawing. - let nbIters = Math.max(3, Math.floor(lineNorm / 2.5)); - nbIters = nbIters % 2 ? nbIters + 1 : nbIters; - const jump = scalarMultiplication(line, 1 / nbIters); - - /* Create a graphic for each stripe and add it to the graphics layer. - Stripes belonging to the same drawing will have the same id so they can - be processed together when editing or deleting stripes. */ - let currentStart = clone(start); - for (let i = 0; i < nbIters; i++) { - const currentEnd = subtract(currentStart, jump); - - const rectangle = await this.createRectangle(map.mapView, currentStart, currentEnd); - - const currentSymbol = (i + 1) % 2 ? - new SimpleFillSymbol({color: '#000000'}) : - new SimpleFillSymbol({color: '#ffffff'}); - - const startVector = i === 0 ? clone(currentStart) : null; - const endVector = i === nbIters - 1 ? clone(currentEnd) : null; - - const stripe = new Graphic({ - geometry: rectangle, - symbol: currentSymbol, - visible: true, - attributes: { - id: this.lastId, - stripes: true, - start: startVector, - end: endVector - } - }); - - map.drawingsLayer.add(stripe); - currentStart = clone(currentEnd); - } - this.lastId++; - } - - /** - * Create a rectangle geometry between a start and end point on an Esri MapView. - * - * @param mapView The MapView holding the spatial reference to which the - * rectangle must be associated. - * @param start The starting point of the rectangle. - * @param end The ending point of the rectangle. - * - * @returns A new poylgon geometry with the shape of a rectangle between - * the input start and end points. - */ - private async createRectangle( - mapView: __esri.MapView, - start: Vector2d, - end: Vector2d, - ): Promise<__esri.Geometry> { - const [Polygon] = await loadModules(['esri/geometry/Polygon']); - - // Line connecting the point clicked and the current position of the mouse. - const line = subtract(start, end); - - // Normal vector to the line connecting the points. - const lineNormal = scalarMultiplication(normal(line), stripesWidth); - - // Vertices of a rectangle between the two points. - const vertices = [ - subtract(start, lineNormal), - subtract(end, lineNormal), - add(end, lineNormal), - add(start, lineNormal) - ]; - - // Convert the vertices to arrays to pass them to the Polygon constructor. - const verticesArray = vertices.map((vertex) => vectorToArray(vertex)); - - const rectangle: __esri.Geometry = new Polygon({ - spatialReference: mapView.spatialReference.clone(), - rings: verticesArray - }); - - return rectangle; - } - - /** - * Remove stripes from a map and return the coordinates of - * its starting and ending points. - * - * @param map The MapComponent from which the stripes must be removed. - * @param id The id of the stripes group which must be removed from the map - * - * @returns An array of 2 Vector2d instances containing the coordinates of the - * starting and ending points of the deleted stripes. - */ - deleteStripes( - map: MapComponent, - id: number - ): Vector2d[] { - let start: Vector2d = {x: 0, y: 0}; - let end: Vector2d = {x: 0, y: 0}; - const toRemove: __esri.Graphic[] = []; - - map.drawingsLayer.graphics.forEach( - (graphic) => { - if (graphic.attributes && - graphic.attributes.stripes && - graphic.attributes.id === id) { - if (graphic.attributes.start) { - start = graphic.attributes.start; - } else if (graphic.attributes.end) { - end = graphic.attributes.end; - } - toRemove.push(graphic); - } - } - ); - - // Only remove graphics after iterating over all of them. - map.drawingsLayer.removeMany(toRemove); - return [start, end]; - } -} diff --git a/frontend/src/app/map/drawing/utils.ts b/frontend/src/app/map/drawing/utils.ts index c1cee6b39ea5c41b2d7485f40716198c69a40432..4d7ba89b7bda50a62d88b447082bb800dade016d 100644 --- a/frontend/src/app/map/drawing/utils.ts +++ b/frontend/src/app/map/drawing/utils.ts @@ -19,7 +19,7 @@ export enum DrawingShape { CIRCLE = 'circle', POLYGON = 'polygon', LINE = 'line', - STRIPES = 'stripes', + RECTANGLE = 'rectangle', MARKER = 'marker' } @@ -88,10 +88,6 @@ export async function getDefaultOutline(): Promise<__esri.SimpleLineSymbol> { }); } -/** - * The default width for stripes (pedestrian passages) drawings on maps. - */ -export const stripesWidth = 7; /** * The interface for events triggered when a shape is being drawn in the application. diff --git a/frontend/src/assets/i18n/fr.json b/frontend/src/assets/i18n/fr.json index 8138ef8ab974f2b31a1cafae598e5b59eee0ab0b..db1aca1aa2672e431b79cc1930accd669570da6f 100644 --- a/frontend/src/assets/i18n/fr.json +++ b/frontend/src/assets/i18n/fr.json @@ -95,18 +95,18 @@ "city": "Ville" }, - "modify-hint": "Modifier un dessin sur la carte", - "delete-hint": "Supprimer un dessin sur la carte", - - "circle-hint": "Dessiner un cercle sur la carte", - "polygon-hint": "Dessiner un polygone sur la carte", - "guideline-hint": "Dessiner une ligne de guidage sur la carte", - "pedestrian-hint": "Dessiner un passage piéton sur la carte", - "marker-hint": "Placer un point d'intérêt sur la carte", - - "save-hint": "Enregistrer la carte", - "print-hint": "Imprimer la carte", - "touchpad-hint": "Ouvrir la carte dans le lecteur tactile", + "modify-hint": "Modifier un dessin", + "delete-hint": "Supprimer un dessin", + + "circle-hint": "Dessiner un cercle", + "polygon-hint": "Dessiner un polygone", + "guideline-hint": "Dessiner une ligne", + "pedestrian-hint": "Dessiner un rectangle", + "marker-hint": "Placer un point d'intérêt", + + "save-hint": "Enregistrer le projet", + "print-hint": "Imprimer le projet", + "touchpad-hint": "Ouvrir le projet dans le lecteur tactile", "leave-hint": "Retourner au menu principal", "help-hint": "Afficher l'aide"