Eclipse SUMO - Simulation of Urban MObility
Loading...
Searching...
No Matches
GNEShapeFrame.cpp
Go to the documentation of this file.
1/****************************************************************************/
2// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// https://www.eclipse.org/legal/epl-2.0/
7// This Source Code may also be made available under the following Secondary
8// Licenses when the conditions for such availability set forth in the Eclipse
9// Public License 2.0 are satisfied: GNU General Public License, version 2
10// or later which is available at
11// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13/****************************************************************************/
18// The Widget for add polygons
19/****************************************************************************/
20
22#include <netedit/GNENet.h>
31
32#include "GNEShapeFrame.h"
33
34// ===========================================================================
35// FOX callback mapping
36// ===========================================================================
37
43
44// Object implementation
45FXIMPLEMENT(GNEShapeFrame::GEOPOICreator, MFXGroupBoxModule, GEOPOICreatorMap, ARRAYNUMBER(GEOPOICreatorMap))
46
47
48// ===========================================================================
49// method definitions
50// ===========================================================================
51
52// ---------------------------------------------------------------------------
53// GNEShapeFrame::GEOPOICreator - methods
54// ---------------------------------------------------------------------------
55
57 MFXGroupBoxModule(polygonFrameParent, TL("GEO POI Creator")),
58 myShapeFrameParent(polygonFrameParent) {
59 // create RadioButtons for formats
60 myLonLatRadioButton = new FXRadioButton(getCollapsableFrame(), TL("Format: Lon-Lat"), this, MID_CHOOSEN_OPERATION, GUIDesignRadioButton);
61 myLatLonRadioButton = new FXRadioButton(getCollapsableFrame(), TL("Format: Lat-Lon"), this, MID_CHOOSEN_OPERATION, GUIDesignRadioButton);
62 // set lat-lon as default
63 myLatLonRadioButton->setCheck(TRUE);
64 // create text field for coordinates
65 myCoordinatesTextField = new FXTextField(getCollapsableFrame(), GUIDesignTextFieldNCol, this, MID_GNE_SET_ATTRIBUTE, GUIDesignTextField);
66 // create checkBox
67 myCenterViewAfterCreationCheckButton = new FXCheckButton(getCollapsableFrame(), TL("Center View after creation"), this, MID_GNE_SET_ATTRIBUTE, GUIDesignCheckButton);
68 // create button for create GEO POIs
69 myCreateGEOPOIButton = GUIDesigns::buildFXButton(getCollapsableFrame(), TL("Create GEO POI (clipboard)"), "", "", nullptr, this, MID_GNE_CREATE, GUIDesignButton);
70 // create information label
71 myLabelCartesianPosition = new MFXDynamicLabel(getCollapsableFrame(),
72 (TL("Cartesian equivalence:") + std::string("\n") +
73 TL("- X = give valid longitude") + std::string("\n") +
74 TL("- Y = give valid latitude")).c_str(),
76}
77
78
80
81
82void
84 // check if there is an GEO Proj string is defined
85 if (GeoConvHelper::getFinal().getProjString() != "!") {
86 myCoordinatesTextField->enable();
87 myCoordinatesTextField->setText("");
88 myCoordinatesTextField->enable();
89 myCreateGEOPOIButton->enable();
90 } else {
91 myCoordinatesTextField->setText(TL("No geo-conversion defined"));
92 myCoordinatesTextField->disable();
93 myCreateGEOPOIButton->disable();
94 }
95 show();
96}
97
98
99void
103
104
105long
107 // check if input contains spaces
108 std::string input = myCoordinatesTextField->getText().text();
109 std::string inputWithoutSpaces;
110 for (const auto& i : input) {
111 if (i != ' ') {
112 inputWithoutSpaces.push_back(i);
113 }
114 }
115 // if input contains spaces, call this function again, and in other case set red text color
116 if (input.size() != inputWithoutSpaces.size()) {
117 myCoordinatesTextField->setText(inputWithoutSpaces.c_str());
118 }
119 if (inputWithoutSpaces.size() > 0) {
120 myCreateGEOPOIButton->setText(TL("Create GEO POI"));
121 } else {
122 myCreateGEOPOIButton->setText(TL("Create GEO POI (clipboard)"));
123 }
124 // simply check if given value can be parsed to Position
125 if (GNEAttributeCarrier::canParse<Position>(myCoordinatesTextField->getText().text())) {
126 myCoordinatesTextField->setTextColor(GUIDesignTextColorBlack);
127 myCoordinatesTextField->killFocus();
128 // convert coordinates into lon-lat
129 Position geoPos = GNEAttributeCarrier::parse<Position>(myCoordinatesTextField->getText().text());
130 if (myLatLonRadioButton->getCheck() == TRUE) {
131 geoPos.swapXY();
132 }
134 // check if GEO Position has to be swapped
135 // update myLabelCartesianPosition
136 myLabelCartesianPosition->setText(
137 (TL("Cartesian equivalence:") + std::string("\n- X = ") + toString(geoPos.x()) + std::string("\n- Y = ") + toString(geoPos.y())).c_str());
138 } else {
139 myCoordinatesTextField->setTextColor(GUIDesignTextColorRed);
140 myLabelCartesianPosition->setText(
141 (TL("Cartesian equivalence:") + std::string("\n") +
142 TL("- X = give valid longitude") + std::string("\n") +
143 TL("- Y = give valid latitude")).c_str());
144 };
145 return 1;
146}
147
148
149long
150GNEShapeFrame::GEOPOICreator::onCmdSetFormat(FXObject* obj, FXSelector, void*) {
151 //disable other radio button depending of selected option
152 if (obj == myLonLatRadioButton) {
153 myLonLatRadioButton->setCheck(TRUE);
154 myLatLonRadioButton->setCheck(FALSE);
155 } else if (obj == myLatLonRadioButton) {
156 myLonLatRadioButton->setCheck(FALSE);
157 myLatLonRadioButton->setCheck(TRUE);
158 }
159 // in both cases call onCmdSetCoordinates(0,0,0) to set new cartesian equivalence
160 onCmdSetCoordinates(0, 0, 0);
161 return 1;
162}
163
164
165long
167 // first check if current GEO Position is valid
168 if (myShapeFrameParent->myShapeAttributesEditor->checkAttributes(true)) {
169 std::string geoPosStr = myCoordinatesTextField->getText().text();
170 if (geoPosStr.empty()) {
171 // use clipboard
172 WRITE_WARNING(TL("Using clipboard"));
173 geoPosStr = GUIUserIO::copyFromClipboard(*getApp());
174 myCoordinatesTextField->setText(geoPosStr.c_str());
175 // remove spaces, update cartesian value
176 onCmdSetCoordinates(0, 0, 0);
177 geoPosStr = myCoordinatesTextField->getText().text();
178 myCoordinatesTextField->setText("");
179 myCreateGEOPOIButton->setText(TL("Create GEO POI (clipboard)"));
180 }
181 if (GNEAttributeCarrier::canParse<Position>(geoPosStr)) {
182 // create baseShape object
183 myShapeFrameParent->createBaseShapeObject(SUMO_TAG_POI);
184 // obtain shape attributes and values
185 myShapeFrameParent->myShapeAttributesEditor->fillSumoBaseObject(myShapeFrameParent->myBaseShape);
186 // force GEO attribute to true and obtain position
187 myShapeFrameParent->myBaseShape->addBoolAttribute(SUMO_ATTR_GEO, true);
188 Position geoPos = GNEAttributeCarrier::parse<Position>(geoPosStr);
189 // convert coordinates into lon-lat
190 if (myLatLonRadioButton->getCheck() == TRUE) {
191 geoPos.swapXY();
192 }
193 // add lon/lat
194 myShapeFrameParent->myBaseShape->addDoubleAttribute(SUMO_ATTR_LON, geoPos.x());
195 myShapeFrameParent->myBaseShape->addDoubleAttribute(SUMO_ATTR_LAT, geoPos.y());
196 // set GEO Position as true
197 myShapeFrameParent->myBaseShape->addBoolAttribute(SUMO_ATTR_GEO, true);
198 // declare additional handler
199 GNEAdditionalHandler additionalHandler(myShapeFrameParent->myViewNet->getNet(),
200 myShapeFrameParent->myBaseShape->hasStringAttribute(GNE_ATTR_ADDITIONAL_FILE) ?
201 myShapeFrameParent->myBaseShape->getStringAttribute(GNE_ATTR_ADDITIONAL_FILE) : "",
202 myShapeFrameParent->myViewNet->getViewParent()->getGNEAppWindows()->isUndoRedoAllowed());
203 // build shape
204 additionalHandler.parseSumoBaseObject(myShapeFrameParent->myBaseShape);
205 // check if view has to be centered over created GEO POI
206 if (myCenterViewAfterCreationCheckButton->getCheck() == TRUE) {
207 // create a boundary over given GEO Position and center view over it
208 Boundary centerPosition;
210 centerPosition.add(geoPos);
211 centerPosition = centerPosition.grow(10);
212 myShapeFrameParent->myViewNet->getViewParent()->getView()->centerTo(centerPosition);
213 }
214 }
215 // refresh shape attributes
216 myShapeFrameParent->myShapeAttributesEditor->refreshAttributesEditor();
217 }
218 return 1;
219}
220
221
222// ---------------------------------------------------------------------------
223// GNEShapeFrame - methods
224// ---------------------------------------------------------------------------
225
227 GNEFrame(viewParent, viewNet, TL("Shapes")),
228 myBaseShape(nullptr) {
229
230 // create item Selector module for shapes
232
233 // Create shape parameters
235
236 // Create drawing controls
238
240 myGEOPOICreator = new GEOPOICreator(this);
241}
242
243
245 // check if we have to delete base additional object
246 if (myBaseShape) {
247 delete myBaseShape;
248 }
249}
250
251
252void
254 // refresh tag selector
256 // show frame
258}
259
260
261bool
262GNEShapeFrame::processClick(const Position& clickedPosition, const GNEViewNetHelper::ViewObjectsSelector& viewObjects, bool& updateTemporalShape) {
263 // reset updateTemporalShape
264 updateTemporalShape = false;
265 // check if current selected shape is valid
266 if (myShapeTagSelector->getCurrentTemplateAC() != nullptr) {
267 // get tag
269 // continue depending of tag
270 switch (shapeTag) {
271 case SUMO_TAG_POI:
272 return processClickPOI(shapeTag, clickedPosition);
273 case GNE_TAG_POIGEO:
274 return processClickPOIGeo(clickedPosition);
275 case GNE_TAG_POILANE:
276 return processClickPOILanes(viewObjects);
277 case SUMO_TAG_POLY:
280 return processClickPolygons(clickedPosition, updateTemporalShape);
281 default:
282 break;
283 }
284 }
285 myViewNet->setStatusBarText(TL("Current selected shape isn't valid."));
286 return false;
287}
288
289
290std::string
291GNEShapeFrame::getIdsSelected(const FXList* list) {
292 // Obtain Id's of list
293 std::string vectorOfIds;
294 for (int i = 0; i < list->getNumItems(); i++) {
295 if (list->isItemSelected(i)) {
296 if (vectorOfIds.size() > 0) {
297 vectorOfIds += " ";
298 }
299 vectorOfIds += (list->getItem(i)->getText()).text();
300 }
301 }
302 return vectorOfIds;
303}
304
305
310
311
312void
314 // check if baseShape exist, and if yes, delete it
315 if (myBaseShape) {
316 // delete baseShape (and all children)
317 delete myBaseShape;
318 }
319 // just create a base shape
321 // set tag
322 myBaseShape->setTag(shapeTag);
323}
324
325
326bool
328 // show warning dialogbox and stop check if input parameters are valid
330 return false;
331 } else if (myDrawingShape->getTemporalShape().size() == 0) {
332 WRITE_WARNING(TL("Polygon shape cannot be empty"));
333 return false;
334 } else {
335 // get tag
337 // create baseShape object
338 createBaseShapeObject(shapeTag);
339 // obtain shape attributes and values
341 // obtain shape and check if has to be closed
344 (shapeTag == GNE_TAG_JPS_WALKABLEAREA) || (shapeTag == GNE_TAG_JPS_OBSTACLE)) {
345 temporalShape.closePolygon();
346 }
348 // declare additional handler
352 // build shape
353 additionalHandler.parseSumoBaseObject(myBaseShape);
354 // refresh shape attributes
356 // shape added, then return true;
357 return true;
358 }
359}
360
361
362void
365 // show editors
367 // get shape tag
369 // Check if drawing mode has to be shown
370 if ((shapeTag == SUMO_TAG_POLY) || (shapeTag == GNE_TAG_JPS_WALKABLEAREA) || (shapeTag == GNE_TAG_JPS_OBSTACLE)) {
372 } else {
374 }
375 // Check if GEO POI Creator has to be shown
376 if (shapeTag == GNE_TAG_POIGEO) {
378 } else {
380 }
381 } else {
382 // hide all widgets
386 }
387}
388
389
390bool
391GNEShapeFrame::processClickPolygons(const Position& clickedPosition, bool& updateTemporalShape) {
392 if (myDrawingShape->isDrawing()) {
393 // add or delete a new point depending of flag "delete last created point"
396 } else {
397 myDrawingShape->addNewPoint(clickedPosition);
398 }
399 // set temporal shape
400 updateTemporalShape = true;
401 return true;
402 } else {
403 return false;
404 }
405}
406
407
408bool
409GNEShapeFrame::processClickPOI(SumoXMLTag POITag, const Position& clickedPosition) {
410 // show warning dialogbox and stop if input parameters are invalid
412 return false;
413 }
414 // create baseShape object
415 createBaseShapeObject(POITag);
416 // obtain shape attributes and values
418 // add X-Y
419 myBaseShape->addDoubleAttribute(SUMO_ATTR_X, clickedPosition.x());
420 myBaseShape->addDoubleAttribute(SUMO_ATTR_Y, clickedPosition.y());
421 // set GEO Position as false (because we have created POI clicking over View
423 // declare additional handler
427 // build shape
428 additionalHandler.parseSumoBaseObject(myBaseShape);
429 // refresh shape attributes
431 // shape added, then return true
432 return true;
433}
434
435
436bool
438 // show warning dialogbox and stop if input parameters are invalid
440 return false;
441 }
442 // create baseShape object
444 // obtain shape attributes and values
446 // convert position to cartesian
447 Position GEOPos = clickedPosition;
449 // add X-Y in geo format
452 // set GEO Position as false (because we have created POI clicking over View
454 // declare additional handler
458 // build shape
459 additionalHandler.parseSumoBaseObject(myBaseShape);
460 // refresh shape attributes
462 // shape added, then return true
463 return true;
464}
465
466
467bool
469 // abort if lane is nullptr
470 if (viewObjects.getLaneFront() == nullptr) {
471 WRITE_WARNING(TL("POILane can be only placed over lanes"));
472 return false;
473 }
474 // show warning dialogbox and stop if input parameters are invalid
476 return false;
477 }
478 // create baseShape object
480 // obtain shape attributes and values
482 // obtain Lane
484 // obtain position over lane
487 viewObjects.getLaneFront()->getLengthGeometryFactor());
488 // declare additional handler
492 // build shape
493 additionalHandler.parseSumoBaseObject(myBaseShape);
494 // refresh shape attributes
496 // shape added, then return true
497 return true;
498}
499
500/****************************************************************************/
FXDEFMAP(GNEShapeFrame::GEOPOICreator) GEOPOICreatorMap[]
@ MID_GNE_SET_ATTRIBUTE
attribute edited
Definition GUIAppEnum.h:991
@ MID_CHOOSEN_OPERATION
set type of selection
Definition GUIAppEnum.h:597
@ MID_GNE_CREATE
create element
Definition GUIAppEnum.h:993
#define GUIDesignTextColorRed
red color (for invalid text)
Definition GUIDesigns.h:44
#define GUIDesignButton
Definition GUIDesigns.h:100
#define GUIDesignTextField
Definition GUIDesigns.h:74
#define GUIDesignTextFieldNCol
Num of column of text field.
Definition GUIDesigns.h:92
#define GUIDesignTextColorBlack
black color (for correct text)
Definition GUIDesigns.h:38
#define GUIDesignCheckButton
checkButton placed in left position
Definition GUIDesigns.h:194
#define GUIDesignRadioButton
Definition GUIDesigns.h:231
#define GUIDesignLabelFrameInformation
label extended over frame without thick and with text justify to left, used to show information in fr...
Definition GUIDesigns.h:281
#define WRITE_WARNING(msg)
Definition MsgHandler.h:287
#define TL(string)
Definition MsgHandler.h:305
SumoXMLTag
Numbers representing SUMO-XML - element names.
@ SUMO_TAG_POI
begin/end of the description of a Point of interest
@ GNE_TAG_POIGEO
Point of interest over view with GEO attributes.
@ SUMO_TAG_POLY
begin/end of the description of a polygon
@ GNE_TAG_POILANE
Point of interest over Lane.
@ GNE_TAG_JPS_OBSTACLE
polygon used for draw juPedSim obstacles
@ GNE_TAG_JPS_WALKABLEAREA
polygon used for draw juPedSim walkable areas
@ GNE_ATTR_DEMAND_FILE
demand demand file
@ SUMO_ATTR_LANE
@ SUMO_ATTR_LON
@ SUMO_ATTR_Y
@ SUMO_ATTR_X
@ GNE_ATTR_ADDITIONAL_FILE
additional save file
@ SUMO_ATTR_GEO
@ GNE_ATTR_CLOSE_SHAPE
Close shape of a polygon (Used by GNEPolys)
@ SUMO_ATTR_SHAPE
edge: the shape in xml-definition
@ SUMO_ATTR_LAT
@ SUMO_ATTR_POSITION
std::string toString(const T &t, std::streamsize accuracy=gPrecision)
Definition ToString.h:46
void parseSumoBaseObject(CommonXMLStructure::SumoBaseObject *obj)
parse SumoBaseObject (it's called recursivelly)
A class that stores a 2D geometrical boundary.
Definition Boundary.h:39
void add(double x, double y, double z=0)
Makes the boundary include the given coordinate.
Definition Boundary.cpp:75
Boundary & grow(double by)
extends the boundary by the given amount
Definition Boundary.cpp:340
bool hasBoolAttribute(const SumoXMLAttr attr) const
check if current SumoBaseObject has the given bool attribute
bool hasStringAttribute(const SumoXMLAttr attr) const
has function
void setTag(const SumoXMLTag tag)
set SumoBaseObject tag
void addPositionVectorAttribute(const SumoXMLAttr attr, const PositionVector &value)
add PositionVector attribute into current SumoBaseObject node
void addBoolAttribute(const SumoXMLAttr attr, const bool value)
add bool attribute into current SumoBaseObject node
void addDoubleAttribute(const SumoXMLAttr attr, const double value)
add double attribute into current SumoBaseObject node
bool getBoolAttribute(const SumoXMLAttr attr) const
get bool attribute
void addStringAttribute(const SumoXMLAttr attr, const std::string &value)
add string attribute into current SumoBaseObject node
const std::string & getStringAttribute(const SumoXMLAttr attr) const
get string attribute
const std::string getID() const
get ID (all Attribute Carriers have one)
const GNETagProperties * getTagProperty() const
get tagProperty associated with this Attribute Carrier
SumoXMLAttr fillSumoBaseObject(CommonXMLStructure::SumoBaseObject *baseObject) const
fill sumo Base object
void showAttributesEditor(GNEAttributeCarrier *AC, const bool primaryAttributeEditor)
edit attributes of the given AC (usually the edited template AC)
void refreshAttributesEditor()
refresh attribute editor
bool checkAttributes(const bool showWarning)
check if current edited attributes are valid
void hideAttributesEditor()
hide attribute editor
bool isDrawing() const
return true if currently a shape is drawed
void addNewPoint(const Position &P)
add new point to temporal shape
bool getDeleteLastCreatedPoint()
get flag delete last created point
void removeLastPoint()
remove last added point
void showDrawingShape()
show Drawing mode
void hideDrawingShape()
hide Drawing mode
const PositionVector & getTemporalShape() const
get Temporal shape
GNEViewNet * myViewNet
FOX need this.
Definition GNEFrame.h:121
virtual void show()
show Frame
Definition GNEFrame.cpp:117
virtual void hide()
hide Frame
Definition GNEFrame.cpp:126
const PositionVector & getLaneShape() const
get elements shape
Definition GNELane.cpp:221
double getLengthGeometryFactor() const
get length geometry factor
Definition GNELane.cpp:2002
long onCmdSetFormat(FXObject *, FXSelector, void *)
called when user select a format radio button
void hideGEOPOICreatorModule()
hide GEOPOICreator Module
long onCmdSetCoordinates(FXObject *, FXSelector, void *)
void showGEOPOICreatorModule()
Show list of GEOPOICreator Module.
long onCmdCreateGEOPOI(FXObject *, FXSelector, void *)
called when user type in search box
void tagSelected()
Tag selected in GNETagSelector.
void show()
show Frame
bool processClickPOIGeo(const Position &clickedPosition)
process click for POIGeo
bool processClickPolygons(const Position &clickedPosition, bool &updateTemporalShape)
process click for Polygons
bool processClick(const Position &clickedPosition, const GNEViewNetHelper::ViewObjectsSelector &viewObjects, bool &updateTemporalShape)
process click over Viewnet
GNEDrawingShape * getDrawingShapeModule() const
get drawing mode editor
bool processClickPOILanes(const GNEViewNetHelper::ViewObjectsSelector &viewObjects)
process click for POILanes
bool processClickPOI(SumoXMLTag POITag, const Position &clickedPosition)
process click for POIs over view
void createBaseShapeObject(const SumoXMLTag shapeTag)
GNEDrawingShape * myDrawingShape
Drawing shape.
CommonXMLStructure::SumoBaseObject * myBaseShape
SumoBaseObject used for create shape.
GEOPOICreator * myGEOPOICreator
GEOPOICreator.
~GNEShapeFrame()
Destructor.
static std::string getIdsSelected(const FXList *list)
get list of selecte id's in string format
GNEShapeFrame(GNEViewParent *viewParent, GNEViewNet *viewNet)
Constructor.
GNEAttributesEditor * myShapeAttributesEditor
shape attributes editor
bool shapeDrawed()
build a shaped element using the drawed shape return true if was successfully created
GNETagSelector * myShapeTagSelector
shape tag selector
SumoXMLTag getTag() const
get Tag vinculated with this attribute Property
void refreshTagSelector()
refresh tagSelector (used when frameParent is show)
GNEAttributeCarrier * getCurrentTemplateAC() const
get current templateAC
class used to group all variables related with objects under cursor after a click over view
GNELane * getLaneFront() const
get front lane or a pointer to nullptr
GNENet * getNet() const
get the net object
GNEViewParent * getViewParent() const
get the net object
void setStatusBarText(const std::string &text)
set statusBar text
A single child window which contains a view of the simulation area.
GNEApplicationWindow * getGNEAppWindows() const
get GNE Application Windows
static FXButton * buildFXButton(FXComposite *p, const std::string &text, const std::string &tip, const std::string &help, FXIcon *ic, FXObject *tgt, FXSelector sel, FXuint opts=BUTTON_NORMAL, FXint x=0, FXint y=0, FXint w=0, FXint h=0, FXint pl=DEFAULT_PAD, FXint pr=DEFAULT_PAD, FXint pt=DEFAULT_PAD, FXint pb=DEFAULT_PAD)
build button
Position snapToActiveGrid(const Position &pos, bool snapXY=true) const
Returns a position that is mapped to the closest grid point if the grid is active.
virtual Position getPositionInformation() const
Returns the cursor's x/y position within the network.
static std::string copyFromClipboard(const FXApp &app)
Copies text from the clipboard.
Definition GUIUserIO.cpp:44
static const GeoConvHelper & getFinal()
the coordinate transformation for writing the location element and for tracking the original coordina...
void cartesian2geo(Position &cartesian) const
Converts the given cartesian (shifted) position to its geo (lat/long) representation.
bool x2cartesian_const(Position &from) const
Converts the given coordinate into a cartesian using the previous initialisation.
A list item which allows for custom coloring.
MFXGroupBoxModule (based on FXGroupBox)
C++ TraCI client API implementation.
A point in 2D or 3D with translation and scaling methods.
Definition Position.h:37
double x() const
Returns the x-position.
Definition Position.h:52
void swapXY()
swap position X and Y
Definition Position.h:309
double y() const
Returns the y-position.
Definition Position.h:57
A list of positions.
void closePolygon()
ensures that the last position equals the first
double nearest_offset_to_point2D(const Position &p, bool perpendicular=true) const
return the nearest offest to point 2D