I’m placing a marker on the map and displaying some information about the nearest road centerline. How can i determine where the road segment starts and ends? I’m trying to define left from right of each road, but they could vary and won’t be determined solely on north south east west. So if I can get the end of the segment, i can define whether my marker is on the left side or right side of the road. I was able to draw endcaps on the centerline overlay for each road segment, so I know there is a way to find it, just need some help.
Example
if(marker.x < segment.end)
{
var text = “marker is on the left”;
}
Road Centerline Start and End Points
Hi Chad,
A possible option is that we can try the following steps to get the left-to-right direction following the segment, please check it out:
1. Loop all the segments of a line shape.
2. For each segment, 2 vertices should be available, start and end. Given the coordinate of start is (x0,y0) and the end is (x1,y1), then we can get its vector direction following the formula:
a. If x0 - x1 > 0, it means that the start point should be on the right of end point. otherwise, on the left.
b. if y0 - y1 > 0, it means that the start point should be the north of the end point, in other words, means the top of the end point.
Based on the materials shown above, we can calculate the vector from marker to the start and end points of segment. Now given the direction of segment is south-west (which can be calculated based on the above as well), if the result of following result of equation is true, the marker should be on the left of line, otherwise, on the right.
( (marker.X – x1) / sqr(pow(marker.X – x1, 2) +
pow(marker.Y – y1, 2)) > (x2 – x1) / sqr(pow(x2 – x1, 2) + pow(y2 – y1, 2)))
Hope it help.
Thanks,
Johnny
Great, thank you for the math, but how do I get the start and end verticies of a segment.
Hi Chad,
If you have a lineshape only contains two vertexes, the vertex 0 should be the start and the vertex 1 should be the end.
So you can loop the lineshape and get the start and end points for each segment one by one.
Regards,
Don
Ok I set up some case statements using your logic but can’t seem to get any consistency on getting the correct parity of the line. I would appreciate some code on the entire logic you are trying to accomplish. Either I’m not looping through the segments correctly or something. Below is the basics of what I’m using.
Collection<string> columnNames = new Collection<string>();
columnNames.Add(“LeftMax”);
columnNames.Add(“RightMax”);
FeatureLayer streetLayer = (FeatureLayer)(map.CustomOverlays[“Roads”] as LayerOverlay).Layers[0];
streetLayer.Open();
Collection<Feature> streetFeatures = streetLayer.FeatureSource.GetFeaturesNearestTo(markerPoint, GeographyUnit.Meter, 1, columnNames, 50, DistanceUnit.Meter);
streetLayer.Close();
Hi Chad,
The basic point for the logic is your data follow the direction you think, for example, if you think the “left” means left hand when you go east on target road, so the logic require the road vertex start from west and direction to east. You should want to watch the data first to make sure that, or else you will get the opposite result.
That’s because in fact shapefile don’t contains direction information, so if the data don’t contains further description information, we can just try to get a direction by loop all the vertexes.
You code looks only get the nearest street feature, I want to know more detail about your scenario to see whether we can choose another solution for it.
Regards,
Don
Johnny & Don,
So far this is sample of what I have coded so far. However, when testing a road segment going southwest, RowStreetNumber always values as "Left3", meaning that the road goes into the 3rd quadrant of a typical xy axis from orgin startPoint, and that the marker is on the left hand side of the directional vector from startPoint to endPoint.
PointShape markerPoint = new PointShape(Convert.ToDouble(args["x"]), Convert.ToDouble(args["y"]));
PointShape closestRoadPoint = SnapToFeature(new Collection<FeatureLayer> { streetLayer }, markerPoint, GeographyUnit.Meter, 50, DistanceUnit.Meter);
foreach(Feature feature in streetFeatures)
{
LineShape lineShape = (LineShape)feature.GetShape();
for (int i = 0; i < lineShape.Vertices.Count - 1; i++)
{
PointShape startPoint = new PointShape(lineShape.Vertices);
PointShape endPoint = new PointShape(lineShape.Vertices[i | +1]);
while(startPoint.X == endPoint.X && startPoint.Y == endPoint.Y) // Prevents duplicate verticies
{
endPoint = new PointShape(lineShape.Vertices[i++]);
}
bool test = ((markerPoint.X - startPoint.X) / Math.Sqrt(Math.Pow(markerPoint.X - startPoint.X, 2) + Math.Pow(markerPoint.Y - startPoint.Y, 2)) > (endPoint.X - startPoint.X) / Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) + Math.Pow(endPoint.Y - startPoint.Y, 2)));
if(startPoint.X - endPoint.X > 0) // line goes west
{
if(startPoint.Y - endPoint.Y > 0) // line goes south
{
// 3rd Quadrant
if (test == true) // if (markerPoint.X < closestRoadPoint.X)
{
RowStreetNumber = "Left3";
}
else
{
RowStreetNumber = "Right3";
}
}
}
}
}
I also tried the following test to replace the previous test which seemed to work a little nicer:
bool test = ((closestRoadPoint.X - startPoint.X) * (markerPoint.Y - startPoint.Y) - (markerPoint.X - startPoint.X) * (closestRoadPoint.Y - startPoint.Y)) > 0;
if (test)
{
RowStreetNumber = “Left”;
}
else
{
RowStreetNumber = “Right”;
}
If either or both of the logics are correct, then I’m wondering if there is something weird going on particularly when the road lines were initially created. Because, what I’m finding is that for nearly any road the test runs perfect, but when I get to a road that has many points or verticies, sometimes the test will switch the return value L/R from line segment to line segment. So I’m curious if I can have the app redraw the line from initial startpoint to last endpoint using a “best-fit” line with new verticies and line segments to check. I’m curious if when the lines were first created, if the verticies weren’t entered in order from start to endpoint.
Hi Chad,
I am not sure whether your road lines have some problem, but I suggest you print your startpoint, endpoint, closestRoadPoint and markerPoint into a highlight layer, you can also print the target road segement. So when you handle one part, you can see whether some weird thing happen there, it’s more intuitively view and can easily found where is the problem.
Regards,
Don
Ok, what I’ve noticed is that the loop only goes to lineshape.verticies[2] on a particular line that has 21 verticies. I think i need an if statement to determine if my closestPoint is between lineshape.Verticies and lineshape.Verticies[i++]. So after setting up an if statement, I’ve seen the loop parse through the lineshape much better but it still isn’t perfect. Is there a better way to check if closestPoint is on the line from startPoint to endPoint?
if (startPoint.GetDistanceTo(closestRoadPoint, GeographyUnit.Meter, DistanceUnit.Meter) + endPoint.GetDistanceTo(closestRoadPoint, GeographyUnit.Meter, DistanceUnit.Meter) == startPoint.GetDistanceTo(endPoint, GeographyUnit.Meter, DistanceUnit.Meter))
Simple diagnosis on the if statement is that it works about 1/3 of the time. Other times it returns null values. So I tried building in a buffer of a few meters but it still won’t grab the line like it should. Does ThinkGeo have a predefined function to determine if the closestPoint is on the line from startPoint to endPoint? Or is there a better mathematical formula to determine the issue?
Hi Chad,
Your “if statement” won’t works is because the precision issue, if the segment is not a parallel to X-axis or Y-axis it won’t works well.
For your issue, create a function based on mathematical algorithm is the better way, but I think maybe we can get another solution, please see my steps as below:
1. You have a point(pointA) and a target line
2. Loop the target line and separate it to multiply straight line
3. Use shape.GetDistanceTo API to calculate the distance from pointA to all the separated lines
4. Find the shortest distance and it’s the nearest straight line (a segment of target line)
5. Calculate the nearest point(pointB) in line by line.GetClosestPointTo
6. Here we can set 8 conditions
a, Straight line point to North: if pointA.X < pointB.X, it’s left
b, Straight line point to South: if pointA.X > pointB.X, it’s left
c, Straight line point to East: if pointA.Y > pointB.Y, it’s left
d, Straight line point to West: if pointA.Y < pointB.Y, it’s left
e, Straight line point to NorthEast: if pointA.X < pointB.X, it’s left
f, Straight line point to SouthEast: if pointA.X > pointB.X, it’s left
g, Straight line point to NorthWest: if pointA.X < pointB.X, it’s left
h, Straight line point to SouthWest: if pointA.X > pointB.X, it’s left
Please let me know whether this thinking is helpful.
Regards,
Don
Don,
Thanks for the help but I think i got it figured out, the problem was with the following:
PointShape endPoint = new PointShape(lineShape.Vertices[i | +1]);
Changed to Vertices[i+1] and it works great. Tested on lines in all directions.
Now for the last issue I have. Is there a way to filter out addressFeatures to only have features where a certain column name’s value matches a string. I’m trying to get only the addresses/homes that are along the same road I’m selecting.
FeatureLayer addressLayer = (FeatureLayer)(map.CustomOverlays[“Addresses”] as LayerOverlay).Layers[0];
addressLayer.Open();
Collection<Feature> addressFeatures = addressLayer.FeatureSource.GetFeaturesNearestTo(markerPoint, GeographyUnit.Meter, 200, columnNames, 200, DistanceUnit.Meter);
addressLayer.Close();
right now I’m getting the closest addresses that are within a 200 meter radius. I’d really like it to grad all address that are along the selected road. I’ve tried getFeaturesWithinBoundingBox but if the address point is away from the road further than my bbox it won’t add the feature, it will also grad features from other roads which are not wanted.
I’ve also tried looping through each feature in addressFeatures and if the column value doesn’t match the string then addressFeatures.Remove(feature). But the function crashes and displays null values.
Hi Chad,
Do you think the layer.QueryTools.GetFeaturesByColumnValue api can works for that?
Regards,
Don