All -
I presume this should be 'Project points onto polylines' not 'Project pints
onto polylines' but there you go I often fancy a pint myself.
Heres some code that works for me - its VB wrapping up MI Pro but the maths
are all there. You could easily port it to MB. It takes the x,y of a point
and drops a perpendicular onto a specified polyline (which I have turned
into a collection of points). The code is along the lines discussed on this
list over the last day or so.
I'll also post it on Directions incase anyones mail server spits it out.
The mObjInfo.Distance statement is just a call to the Distance function in
MapBasic - the clsPoint class is pretty much a type storing x and y, and
clsPoints is a custom collection class (of points) that you could
reimplement as an array of types.
Public Function ReturnPointOfShortestPerpendicular(ByVal pt As clsPoint, _
ByVal pts As
clsPoints, _
ptPerpendicular As
clsPoint) As Boolean
'*****************************************************
' Purpose: Routine to return the end point of the perpendicular
' on to nearest line segment from a point
' Inputs: Point, points collection from line
'
' Returns: by ref point
'*************************
On Error GoTo labelError
Dim i As Integer
Dim ptTemp As New clsPoint
Dim intCount As Integer
Dim iClosestPoint As Integer
Dim dblShortestDist As Double
Dim dblTempDist As Double
intCount = pts.Count
'Treat line feature line as a series of lines and get the perpendicular
onto each -
'take the shortest as the return value
'Initialise the values from the first line segment then compare each of
the rest
'Give dblShortestDist a value just in case
ptPerpendicular.SetXY pts(1).X, pts(1).Y
dblShortestDist = Sqr((pt.X - pts(1).X) ^ 2 + (pt.Y - pts(1).Y) ^ 2)
iClosestPoint = 1
For i = 1 To intCount - 1
If FindClosestPointOnLineSegment(pt, pts(i), pts(i + 1), ptTemp) Then
dblTempDist = mObjInfo.Distance(pt, ptTemp)
If dblTempDist < dblShortestDist Then
dblShortestDist = dblTempDist
ptPerpendicular.SetXY ptTemp.X, ptTemp.Y
iClosestPoint = i
End If
End If
Next
ReturnPointOfShortestPerpendicular = True
Exit Function
labelError:
ReturnPointOfShortestPerpendicular = False
Call ErrorHandler("GenericUtils: ReturnPointOfShortestPerpendicular")
End Function
Public Function FindClosestPointOnLineSegment(ptNearbyPoint As clsPoint, _
ByVal ptLineSegmentEnd1 As clsPoint,
_
ByVal ptLineSegmentEnd2 As clsPoint,
_
ptClosestPoint As clsPoint) As
Boolean
'*****************************************************
' Purpose: Generic Routine to take three points, two on a line and
' the other to one side, and return the closest point on the
line
' i.e. give a point where the perpendicular from the third point
will fall
' on the line
' Inputs: three points
'
' Returns: boolean - closest point by ref
'*************************
Dim dblX As Double
Dim dblX2 As Double
Dim dblY As Double
Dim dblY2 As Double
Dim dblDeltaX As Double
Dim dblDeltaY As Double
Dim dblReturnX As Double
Dim dblReturnY As Double
Dim dblM As Double
Dim dblC As Double
Dim dblD As Double
Dim dblTempDist1 As Double
Dim dblTempDist2 As Double
On Error GoTo labelError
'If the point is close enough to either of the segment ends then return
them
FindClosestPointOnLineSegment = False
If Abs(ptLineSegmentEnd1.X - ptNearbyPoint.X) < 0.1 And _
Abs(ptLineSegmentEnd1.Y - ptNearbyPoint.Y) < 0.1 Then
'its within 10cm - this will do
ptClosestPoint.SetXY ptLineSegmentEnd1.X, ptLineSegmentEnd1.Y
FindClosestPointOnLineSegment = True
Exit Function
End If
If Abs(ptLineSegmentEnd2.X - ptNearbyPoint.X) < 0.1 And _
Abs(ptLineSegmentEnd2.Y - ptNearbyPoint.Y) < 0.1 Then
'its within 10cm - this will do
ptClosestPoint.SetXY ptLineSegmentEnd2.X, ptLineSegmentEnd2.Y
FindClosestPointOnLineSegment = True
Exit Function
End If
'Otherwise we need to do some maths
'Get the numbers into the right order - mdblMapX2 needs to be bigger
If ptLineSegmentEnd1.X > ptLineSegmentEnd2.X Then
dblX = ptLineSegmentEnd2.X
dblX2 = ptLineSegmentEnd1.X
dblY = ptLineSegmentEnd2.Y
dblY2 = ptLineSegmentEnd1.Y
Else
dblX = ptLineSegmentEnd1.X
dblX2 = ptLineSegmentEnd2.X
dblY = ptLineSegmentEnd1.Y
dblY2 = ptLineSegmentEnd2.Y
End If
dblDeltaX = dblX2 - dblX
dblDeltaY = dblY2 - dblY
If dblDeltaX = 0 Then 'Vertical line
dblReturnX = dblX2
'Y value can be same as the existing point
dblReturnY = ptNearbyPoint.Y
ElseIf dblDeltaY = 0 Then 'horizontal line
dblReturnY = dblY2
'X value can be the same as the existing point
dblReturnX = ptNearbyPoint.X
Else
dblM = dblDeltaY / dblDeltaX
dblC = dblY - (dblM * dblX)
dblD = ptNearbyPoint.Y + (ptNearbyPoint.X / dblM)
'Now do the new coords
dblReturnX = (dblD - dblC) / (dblM + (1 / dblM))
dblReturnY = (dblM * dblReturnX) + dblC
End If
ptClosestPoint.SetXY dblReturnX, dblReturnY
'Check this closest point is actually between the other two
If Not PointIsBetween(ptLineSegmentEnd1, ptLineSegmentEnd2,
ptClosestPoint) Then
'The nearest point on that segment is actually one of the vertices
dblTempDist1 = mObjInfo.Distance(ptClosestPoint, ptLineSegmentEnd1)
dblTempDist2 = mObjInfo.Distance(ptClosestPoint, ptLineSegmentEnd2)
If dblTempDist1 < dblTempDist2 Then
ptClosestPoint.SetXY ptLineSegmentEnd1.X, ptLineSegmentEnd1.Y
Else
ptClosestPoint.SetXY ptLineSegmentEnd2.X, ptLineSegmentEnd2.Y
End If 'Vertex 1 is close
End If 'Returned point is not on line segment
FindClosestPointOnLineSegment = True
Exit Function
labelError:
FindClosestPointOnLineSegment = False
Call ErrorHandler("FindClosestPointOnLineSegment")
End Function
Public Function PointIsBetween(ByVal pt1 As clsPoint, ByVal pt2 As clsPoint,
_
ByVal ptInBetween As clsPoint) As Boolean
'*****************************************************
' Purpose: Routine to check whether a point falls between two others
' Inputs: Three Points
'
' Returns: boolean
'*************************
On Error GoTo labelError
Dim dblMapX As Double
Dim dblMapX2 As Double
Dim dblMapY As Double
Dim dblMapY2 As Double
PointIsBetween = False
'Sort the values so x2, y2 are the larger
If pt1.X > pt2.X Then
dblMapX = pt2.X
dblMapX2 = pt1.X
Else
dblMapX = pt1.X
dblMapX2 = pt2.X
End If
If pt1.Y > pt2.Y Then
dblMapY = pt2.Y
dblMapY2 = pt1.Y
Else
dblMapY = pt1.Y
dblMapY2 = pt2.Y
End If
'Use a 10cm tolerance and see if the point falls between the other two
If (ptInBetween.X + 0.1 >= dblMapX) And (ptInBetween.X - 0.1 <= dblMapX2)
And _
(ptInBetween.Y + 0.1 >= dblMapY) And (ptInBetween.Y - 0.1 <= dblMapY2)
Then
PointIsBetween = True
End If
Exit Function
labelError:
PointIsBetween = False
Call ErrorHandler("GenericUtils: PointIsBetween")
End Function
********************************************************************
This email may contain information which is privileged or confidential. If you are not
the intended recipient of this email, please notify the sender immediately and delete
it without reading, copying, storing, forwarding or disclosing its contents to any
other person
Thank you
Check us out at http://www.syntegra.com
********************************************************************