diff --git a/src/COM classes/ShapeEditor.cpp b/src/COM classes/ShapeEditor.cpp index de7300ad..cd0d6f13 100644 --- a/src/COM classes/ShapeEditor.cpp +++ b/src/COM classes/ShapeEditor.cpp @@ -782,6 +782,22 @@ STDMETHODIMP CShapeEditor::put_SnapBehavior(tkLayerSelection newVal) return S_OK; } +// ******************************************************* +// get_SnapMode() +// ******************************************************* +STDMETHODIMP CShapeEditor::get_SnapMode(tkSnapMode* pVal) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + *pVal = _snapMode; + return S_OK; +} +STDMETHODIMP CShapeEditor::put_SnapMode(tkSnapMode newVal) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + _snapMode = newVal; + return S_OK; +} + // ******************************************************* // EditorState // ******************************************************* diff --git a/src/COM classes/ShapeEditor.h b/src/COM classes/ShapeEditor.h index f1c1e480..d69968bd 100644 --- a/src/COM classes/ShapeEditor.h +++ b/src/COM classes/ShapeEditor.h @@ -27,6 +27,7 @@ class ATL_NO_VTABLE CShapeEditor : _highlightShapes = lsAllLayers; _snapTolerance = 10; _snapBehavior = lsAllLayers; + _snapMode = smVerticesAndLines; _state = esNone; _mapCallback = NULL; _isSubjectShape = false; @@ -113,6 +114,8 @@ class ATL_NO_VTABLE CShapeEditor : STDMETHOD(put_HighlightVertices)(tkLayerSelection newVal); STDMETHOD(get_SnapBehavior)(tkLayerSelection* pVal); STDMETHOD(put_SnapBehavior)(tkLayerSelection newVal); + STDMETHOD(get_SnapMode)(tkSnapMode* pVal); + STDMETHOD(put_SnapMode)(tkSnapMode newVal); STDMETHOD(get_EditorState)(tkEditorState* pVal); STDMETHOD(put_EditorState)(tkEditorState newVal); STDMETHOD(StartEdit)(LONG LayerHandle, LONG ShapeIndex, VARIANT_BOOL* retVal); @@ -154,6 +157,7 @@ class ATL_NO_VTABLE CShapeEditor : ICallback * _globalCallback; VARIANT_BOOL _visible; tkLayerSelection _highlightShapes; + tkSnapMode _snapMode; double _snapTolerance; tkLayerSelection _snapBehavior; EditorBase* _activeShape; diff --git a/src/COM classes/Shapefile.cpp b/src/COM classes/Shapefile.cpp index 5a0969a3..84b6a65a 100644 --- a/src/COM classes/Shapefile.cpp +++ b/src/COM classes/Shapefile.cpp @@ -3174,6 +3174,41 @@ STDMETHODIMP CShapefile::GetClosestVertex(double x, double y, double maxDistance return S_OK; } +// ***************************************************************** +// GetClosestSnapPosition() +// ***************************************************************** +STDMETHODIMP CShapefile::GetClosestSnapPosition(double x, double y, double maxDistance, + long* shapeIndex, double* fx, double* fy, double* distance, VARIANT_BOOL* retVal) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + + *retVal = VARIANT_FALSE; + *shapeIndex = -1; + + bool result = false; + if (maxDistance <= 0.0) + { + // search through all shapefile + std::vector ids; + for (size_t i = 0; i < _shapeData.size(); i++) + { + ids.push_back(i); + } + result = ShapefileHelper::GetClosestSnapPosition(this, x, y, maxDistance, ids, shapeIndex, *fx, *fy, *distance); + } + else + { + std::vector ids; + Extent box(x - maxDistance, x + maxDistance, y - maxDistance, y + maxDistance); + if (this->SelectShapesCore(box, 0.0, SelectMode::INTERSECTION, ids, false)) + { + result = ShapefileHelper::GetClosestSnapPosition(this, x, y, maxDistance, ids, shapeIndex, *fx, *fy, *distance); + } + } + *retVal = result ? VARIANT_TRUE : VARIANT_FALSE; + return S_OK; +} + // ***************************************************************** // HasInvalidShapes() // ***************************************************************** diff --git a/src/COM classes/Shapefile.h b/src/COM classes/Shapefile.h index f57f474f..76af0f21 100644 --- a/src/COM classes/Shapefile.h +++ b/src/COM classes/Shapefile.h @@ -213,6 +213,7 @@ class ATL_NO_VTABLE CShapefile : STDMETHOD(EditAddShape)(IShape* shape, long* shapeIndex); STDMETHOD(EditAddField)(BSTR name, FieldType type, int precision, int width, long* fieldIndex); STDMETHOD(GetClosestVertex)(double x, double y, double maxDistance, long* shapeIndex, long* pointIndex, double* distance, VARIANT_BOOL* retVal); + STDMETHOD(GetClosestSnapPosition)(double x, double y, double maxDistance, long* shapeIndex, double* fx, double* fy, double* distance, VARIANT_BOOL* retVal); STDMETHOD(get_ShapeCategory2)(long ShapeIndex, BSTR* categoryName); STDMETHOD(put_ShapeCategory2)(long ShapeIndex, BSTR categoryName); STDMETHOD(get_ShapeCategory3)(long ShapeIndex, IShapefileCategory** category); diff --git a/src/ComHelpers/ShapefileHelper.cpp b/src/ComHelpers/ShapefileHelper.cpp index cf59307c..84ee41f9 100644 --- a/src/ComHelpers/ShapefileHelper.cpp +++ b/src/ComHelpers/ShapefileHelper.cpp @@ -382,10 +382,78 @@ bool ShapefileHelper::GetClosestPoint(IShapefile* sf, double x, double y, double shp->Release(); } } - // dist = minDist; + dist = minDist; return minDist < maxDistance; } +// ***************************************************************** +// GetClosestPoint() +// ***************************************************************** +bool ShapefileHelper::GetClosestSnapPosition(IShapefile* sf, double x, double y, double maxDistance, std::vector& ids, + long* shapeIndex, double& fx, double& fy, double& dist) +{ + if (!sf) return false; + + VARIANT_BOOL vb; + double minDist = DBL_MAX; + + IPoint* pnt = NULL; + IShape* ptShp = NULL; + IShape* resShp = NULL; + + for (long id : ids) + { + VARIANT_BOOL visible; + sf->get_ShapeVisible(id, &visible); + if (!visible) continue; + + IShape* shp = NULL; + sf->get_Shape(id, &shp); + + if (shp != NULL) + { + // Create point shape + ComHelper::CreateShape(&ptShp); + ptShp->Create(SHP_POINT, &vb); + if (vb != VARIANT_TRUE) + { + shp->Release(); + pnt->Release(); + continue; + } + + // Add point + ComHelper::CreatePoint(&pnt); + pnt->put_X(x); + pnt->put_Y(y); + long position = 0; + ptShp->InsertPoint(pnt, &position, &vb); + pnt->Release(); + + // Get closest points: + shp->ClosestPoints(ptShp , &resShp); + ptShp->Release(); + shp->Release(); + + if (resShp != NULL) { + // Get the point snapped on geometry + double xPnt, yPnt; + resShp->get_XY(0, &xPnt, &yPnt, &vb); + + // Check if this is allowed and/or smaller than the previous found point: + const double distance = sqrt(pow(x - xPnt, 2.0) + pow(y - yPnt, 2.0)); + if (distance < minDist && distance < maxDistance) { + fx = xPnt; + fy = yPnt; + minDist = distance; + } + } + } + } + dist = minDist; + return minDist < maxDistance; +} + // ******************************************************************** // PointInPolygon() // ******************************************************************** diff --git a/src/ComHelpers/ShapefileHelper.h b/src/ComHelpers/ShapefileHelper.h index d2d4d6b0..18d0a028 100644 --- a/src/ComHelpers/ShapefileHelper.h +++ b/src/ComHelpers/ShapefileHelper.h @@ -20,6 +20,8 @@ class ShapefileHelper static IShapefile* CloneSelection(IShapefile* sf); static bool ShapeSelected(IShapefile* sf, int shapeIndex); static bool GetClosestPoint(IShapefile* sf, double x, double y, double maxDistance, std::vector& ids, long* shapeIndex, long* pointIndex, double& dist); + static bool GetClosestSnapPosition(IShapefile * sf, double x, double y, double maxDistance, std::vector& ids, long * shapeIndex, double& fx, double& fy, double & dist); + static bool GetClosestPointOnSegment(double ax, double ay, double bx, double by, double px, double py, double * rx, double * ry); static bool PointInPolygon(IShapefile* sf, long ShapeIndex, double x, double y); static bool BoundsWithinPolygon(IShapefile* sf, int shapeIndex, double b_minX, double b_minY, double b_maxX, double b_maxY); static bool ShapeTypeIsM(IShapefile* sf); diff --git a/src/Control/Map_Edit.cpp b/src/Control/Map_Edit.cpp index 52a7995f..7d35e7ef 100644 --- a/src/Control/Map_Edit.cpp +++ b/src/Control/Map_Edit.cpp @@ -77,22 +77,32 @@ bool CMapView::SnappingIsOn(bool shift) // ************************************************************ bool CMapView::HandleOnMouseMoveShapeEditor(int x, int y, long nFlags) { + + tkLayerSelection behavior; + _shapeEditor->get_SnapBehavior(&behavior); + + double projX, projY; + VARIANT_BOOL snapped = FindSnapPointCore(x, y, &projX, &projY); + if (!snapped) { + PixelToProjection(x, y, projX, projY); + GetEditorBase()->ClearSnapPoint(); + } + else { + double sX, sY; + ProjToPixel(projX, projY, &sX, &sY); + GetEditorBase()->SetSnapPoint(sX, sY, true); + } + + if ((_dragging.Operation == DragMoveVertex || _dragging.Operation == DragMoveShape || _dragging.Operation == DragMovePart)) // && (nFlags & MK_LBUTTON) && _leftButtonDown { _dragging.Snapped = false; - tkLayerSelection behavior; - _shapeEditor->get_SnapBehavior(&behavior); - if (behavior == lsAllLayers && _dragging.Operation == DragMoveVertex) - { - double xFound, yFound; - if (this->FindSnapPointCore(x, y, &xFound, &yFound)) { - double xNew, yNew; - ProjToPixel(xFound, yFound, &xNew, &yNew); - _dragging.SetSnapped(xFound, yFound); - } - } + if (behavior == lsAllLayers && _dragging.Operation == DragMoveVertex && snapped) + _dragging.SetSnapped(projX, projY); + else + GetEditorBase()->ClearSnapPoint(); _dragging.HasMoved = true; // in case of vertex moving underlying data is changed in the process (to update displayed length); @@ -122,12 +132,8 @@ bool CMapView::HandleOnMouseMoveShapeEditor(int x, int y, long nFlags) } else { - tkEditorBehavior behavior; - _shapeEditor->get_EditorBehavior(&behavior); EditorBase* base = GetEditorBase(); bool handled = false; - double projX, projY; - this->PixelToProjection(x, y, projX, projY); // highlighting of vertices if (behavior == ebVertexEditor) @@ -146,7 +152,7 @@ bool CMapView::HandleOnMouseMoveShapeEditor(int x, int y, long nFlags) } else { if (base->ClearHighlightedVertex()) { - _canUseMainBuffer = false; + _canUseMainBuffer = false; return true; } } @@ -155,6 +161,7 @@ bool CMapView::HandleOnMouseMoveShapeEditor(int x, int y, long nFlags) // highlighting parts if (behavior == ebPartEditor) //if (nFlags & MK_CONTROL) { { + if (base->ClearHighlightedVertex()) _canUseMainBuffer = false; diff --git a/src/Control/Map_Events.cpp b/src/Control/Map_Events.cpp index b3916db6..2d5f0ee7 100644 --- a/src/Control/Map_Events.cpp +++ b/src/Control/Map_Events.cpp @@ -1258,7 +1258,10 @@ void CMapView::OnMouseMove(UINT nFlags, CPoint point) snapped = this->FindSnapPointCore(point.x, point.y, &x, &y); if (snapped) { ProjToPixel(x, y, &x, &y); + _shapeEditor->GetActiveShape()->SetSnapPoint(x, y, true); } + else + _shapeEditor->GetActiveShape()->ClearSnapPoint(); } shp->SetMousePosition(x, y); refreshNeeded = true; diff --git a/src/Control/Map_Identifier.cpp b/src/Control/Map_Identifier.cpp index 5a95d910..a806125f 100644 --- a/src/Control/Map_Identifier.cpp +++ b/src/Control/Map_Identifier.cpp @@ -39,18 +39,18 @@ VARIANT_BOOL CMapView::FindSnapPoint(double tolerance, double xScreen, double yS double distance; double minDist = DBL_MAX; - IShapefile* foundShapefile = NULL; - long foundShapeIndex; - long foundPointIndex; bool digitizing = EditorHelper::IsDigitizingCursor((tkCursorMode)m_cursorMode); tkLayerSelection behavior; _shapeEditor->get_SnapBehavior(&behavior); + tkSnapMode mode; + _shapeEditor->get_SnapMode(&mode); long currentHandle = -1; bool currentLayerOnly = behavior == lsActiveLayer && digitizing; if (currentLayerOnly) _shapeEditor->get_LayerHandle(¤tHandle); + bool result = false; for (long i = 0; i < this->GetNumLayers(); i++) { long layerHandle = this->GetLayerHandle(i); @@ -68,31 +68,40 @@ VARIANT_BOOL CMapView::FindSnapPoint(double tolerance, double xScreen, double yS sf->get_Snappable(&snappable); if (snappable) { - sf->GetClosestVertex(x, y, maxDist, &shapeIndex, &pointIndex, &distance, &vb); - if (vb) - { - if (distance < minDist) + double xF, yF; + bool snapped = false; + // Try snapping to a vertex first: + if (mode == smVertices || mode == smVerticesAndLines) { + sf->GetClosestVertex(x, y, maxDist, &shapeIndex, &pointIndex, &distance, &vb); + if (vb && distance < minDist) + { + IShape* shape = NULL; + sf->get_Shape(shapeIndex, &shape); + if (shape) + { + minDist = distance; + shape->get_XY(pointIndex, &xF, &yF, &vb); + shape->Release(); + snapped = true; + } + } + } + // If not snapped to a vertex, maybe try to snap to a segment: + if (!snapped && (mode == smVerticesAndLines || mode == smLines)) { + sf->GetClosestSnapPosition(x, y, maxDist, &shapeIndex, &xF, &yF, &distance, &vb); + if (vb && distance < minDist) { minDist = distance; - foundShapefile = sf; - foundPointIndex = pointIndex; - foundShapeIndex = shapeIndex; + snapped = true; } } - } - } - } - bool result = false; - if (minDist != DBL_MAX && foundShapefile) - { - IShape* shape = NULL; - foundShapefile->get_Shape(foundShapeIndex, &shape); - if (shape) - { - shape->get_XY(foundPointIndex, xFound, yFound, &vb); - shape->Release(); - result = true; + if (snapped) { + *xFound = xF; + *yFound = yF; + result = true; + } + } } } return result; diff --git a/src/Editor/ActiveShape.cpp b/src/Editor/ActiveShape.cpp index 416341d0..2d18ed9b 100644 --- a/src/Editor/ActiveShape.cpp +++ b/src/Editor/ActiveShape.cpp @@ -202,6 +202,11 @@ void ActiveShape::DrawData( Gdiplus::Graphics* g, bool dynamicBuffer, DrawPolygonArea(g, polyData, polySize, dynamicBuffer); delete[] polyData; } + + // finally draw the snap point position if needed + if (_showSnapPoint) { + g->DrawRectangle(&_redPen, (int)(_snapPointX - 3.0f + 0.5f), (int)(_snapPointY - 3.0f + 0.5f), 6, 6); + } } // **************************************************************** diff --git a/src/Editor/ActiveShape.h b/src/Editor/ActiveShape.h index 75221a58..5be462e1 100644 --- a/src/Editor/ActiveShape.h +++ b/src/Editor/ActiveShape.h @@ -27,6 +27,10 @@ class ActiveShape: public GeoShape _highlightedPart = -1; _inputMode = simMeasuring; + _showSnapPoint = false; + _snapPointX = 0; + _snapPointY = 0; + AnglePrecision = 1; AngleFormat = afDegrees; AreaDisplayMode = admMetric; @@ -90,6 +94,9 @@ class ActiveShape: public GeoShape int _highlightedVertex; int _selectedPart; int _highlightedPart; + bool _showSnapPoint; + double _snapPointX; + double _snapPointY; public: diff --git a/src/Editor/EditorBase.cpp b/src/Editor/EditorBase.cpp index 4dd6161f..45ad87ee 100644 --- a/src/Editor/EditorBase.cpp +++ b/src/Editor/EditorBase.cpp @@ -84,6 +84,18 @@ bool EditorBase::ClearHighlightedVertex() return false; } +void EditorBase::SetSnapPoint(double x, double y, bool visible) +{ + _snapPointX = x; + _snapPointY = y; + _showSnapPoint = true; +} + +void EditorBase::ClearSnapPoint() +{ + _showSnapPoint = false; +} + // ************************************************ // ClearHighlightedPart // ************************************************ diff --git a/src/Editor/EditorBase.h b/src/Editor/EditorBase.h index 48ff4a83..0669ac70 100644 --- a/src/Editor/EditorBase.h +++ b/src/Editor/EditorBase.h @@ -39,6 +39,8 @@ class EditorBase: public ActiveShape bool SetSelectedVertex(int index); bool ClearHighlightedPart(); bool ClearHighlightedVertex(); + void SetSnapPoint(double x, double y, bool visible); + void ClearSnapPoint(); int SelectPart(double xProj, double yProj); int GetClosestVertex(double projX, double projY, double tolerance); bool HasClosedPolygon(); diff --git a/src/Editor/EditorHelper.cpp b/src/Editor/EditorHelper.cpp index a7b6c31b..5560f119 100644 --- a/src/Editor/EditorHelper.cpp +++ b/src/Editor/EditorHelper.cpp @@ -133,4 +133,15 @@ tkLayerSelection EditorHelper::GetSnappingBehavior(IShapeEditor* editor) tkLayerSelection behavior; editor->get_SnapBehavior(&behavior); return behavior; +} + +// ******************************************************* +// GetSnappingBehavior() +// ******************************************************* +tkSnapMode EditorHelper::GetSnappingMode(IShapeEditor* editor) +{ + if (!editor) return smVerticesAndLines; + tkSnapMode mode; + editor->get_SnapMode(&mode); + return mode; } \ No newline at end of file diff --git a/src/Editor/EditorHelper.h b/src/Editor/EditorHelper.h index e7592e19..2270c4bf 100644 --- a/src/Editor/EditorHelper.h +++ b/src/Editor/EditorHelper.h @@ -11,6 +11,7 @@ class EditorHelper static void CopyOptionsFrom(IShapeEditor* editor, IShapeDrawingOptions* options); static bool OnCursorChanged(CShapeEditor* editor, bool clearEditor, tkCursorMode newCursor, bool& redrawNeeded); static tkLayerSelection GetSnappingBehavior(IShapeEditor* editor); + static tkSnapMode GetSnappingMode(IShapeEditor * editor); static bool IsSnappableCursor(tkCursorMode mode); }; diff --git a/src/MapWinGIS.idl b/src/MapWinGIS.idl index 422635e0..66583701 100644 --- a/src/MapWinGIS.idl +++ b/src/MapWinGIS.idl @@ -393,6 +393,18 @@ enum tkLayerSelection { lsActiveLayer = 2, }tkLayerSelection; +/************************** tkSnapMode Enumeration **********************/ +typedef +[ + uuid(AC54A94F-072E-4D91-BD22-E985BF2F957F), + helpstring("tkSnapMode"), +] +enum tkSnapMode { + smVertices = 0, + smVerticesAndLines = 1, + smLines = 2 +}tkSnapMode; + /************************** tkUndoOperation Enumeration **********************/ typedef [ @@ -3655,6 +3667,8 @@ enum tkScalebarUnits [propput, id(59)] HRESULT ShowLength([in] VARIANT_BOOL newVal); [id(60)] HRESULT Serialize([out, retval] BSTR* retVal); [id(61)] HRESULT Deserialize([in] BSTR state, [out, retval] VARIANT_BOOL* retVal); + [propget, id(63)] HRESULT SnapMode([out, retval] tkSnapMode* pVal); + [propput, id(63)] HRESULT SnapMode([in] tkSnapMode newVal); }; @@ -4170,6 +4184,8 @@ interface IShapefile : IDispatch{ [propget, id(142)] HRESULT IsGeographicProjection([out, retval] VARIANT_BOOL* retVal); [propget, id(143)] HRESULT Selectable([out, retval] VARIANT_BOOL* retVal); [propput, id(143)] HRESULT Selectable([in] VARIANT_BOOL newVal); + [id(200), helpstring("method GetClosestSnapPosition")] HRESULT GetClosestSnapPosition([in]double x, [in]double y, [in]double maxDistance, + [out]long* shapeIndex, [out] double* fx, [out] double* fy, [out] double* distance, [out, retval]VARIANT_BOOL* retVal); }; /**************************** Shape Interface ***********************/