[mapguide-users] Markup perpendicular
Jackie Ng
jumpinjackie at gmail.com
Tue Jan 5 02:47:30 EST 2010
I may be reading your question the wrong way, but anyway here's my attempt at
an algorithm:
Given a digitized point [PT]:
1. Create a [Test Geometry] of circular arcs [Test Distance] away from [PT]
(ie. A circle centered on [PT] with radius [Test Distance])
2. Query all roads where [Road Geometry] WITHIN OR TOUCHES [Test Geometry]
3. If no results, increase [Test Distance] and go to 1. Repeat until there
are results or after a certain number of failed attempts.
4. If there are results, iterate through each geometry.
4.1. For each geometry determine the vertex (coordinate) with the shortest
distance. The only coordinates worth considering are ones which are WITHIN
OR TOUCHES [Test Geometry]
4.2. Record the shortest distance, and the associated coordinate, store in a
dictionary (eg. SortedList) with the associated feature.
5. Get the shortest distance in this dictionary, the associated feature will
be the closest road from the digitized point.
6. To get the distance measure from the digitized point to the closest
vertex on the associated feature. Use the map's coordinate system to get the
real world distance.
Where this algorithm could break down is if you digitized your point at a
location that is *very* far away from any roads, and "inflating" your test
circle will still not touch or intersect any roads after many iterations. I
guess you could "inflate" your test circle in fairly large distance
increments to avoid too many iterations.
Anyway, here's a source code approximation off the top of my head. I make no
guarantees as to whether this source code compiles :-)
- Jackie
class ClosestFeature
{
public double Distance { get; set; }
public MgProperty FeatureId { get; set; }
public MgCoordinate ClosestVertex { get; set; }
}
MgGeometry CreateTestCircle(MgPoint pt, double joinDist)
{
MgGeometryFactory GF = new MgGeometryFactory();
MgCoordinate arcPt1 = GF.CreateCoordinateXY(click.Coordinate.X + joinDist,
click.Coordinate.Y);
MgCoordinate arcPt2 = GF.CreateCoordinateXY(click.Coordinate.X - joinDist,
click.Coordinate.Y);
MgCoordinate arcPt3 = GF.CreateCoordinateXY(click.Coordinate.X,
click.Coordinate.Y + joinDist);
MgCoordinate arcPt4 = GF.CreateCoordinateXY(click.Coordinate.X,
click.Coordinate.Y - joinDist);
MgArcSegment newGeom = GF.CreateArcSegment(arcPt1,arcPt2,arcPt3);
MgArcSegment newGeom1 = GF.CreateArcSegment(arcPt2, arcPt1, arcPt4);
MgCurveSegmentCollection myCurve = new MgCurveSegmentCollection();
myCurve.Add(newGeom);
myCurve.Add(newGeom1);
MgCurvePolygon myPoly = GF.CreateCurvePolygon(GF.CreateCurveRing(myCurve),
null);
Response.Write(closest.Touches(myPoly));
return myPoly;
}
//
// Gets the outermost coordinates of a given geometry
//
List<MgCoordinate> GetOuterCoordinates(MgGeometry geom)
{
List<MgCoordinate> coords = new List<MgCoordinate>();
switch(geom.GeometryType)
{
case MgGeometryType.Point:
{
coords.Add(((MgPoint)geom).Coordinate);
}
break;
case MgGeometryType.Polygon:
{
MgCoordinateIterator iter = ((MgPolygon)geom).ExteriorRing.Coordinates
while(iter.ReadNext())
{
coords.Add(iter.GetCurrent());
}
}
break;
case MgGeometryType.CurvePolygon:
{
MgCoordinateIterator iter =
((MgCurvePolygon)geom).ExteriorRing.Coordinates
while(iter.ReadNext())
{
coords.Add(iter.GetCurrent());
}
}
break;
case MgGeometryType.LineString:
{
MgLineString lstr = (MgLineString)geom;
coords.Add(lstr.StartCoordinate);
coords.Add(lstr.EndCoordinate);
}
break;
case MgGeometryType.CurveString:
{
MgLineString lstr = (MgLineString)geom;
coords.Add(lstr.StartCoordinate);
coords.Add(lstr.EndCoordinate);
}
break;
default: //MgMulti* geometry types
{
//I'm not bothering to code this :-P
//But you'd basically go through each sub-geometry and aggregate its
coordinates
//If sub-geometry is polygon, aggregate its exterior ring coordinates.
}
break;
}
return coords;
}
// Gets the closest road feature from the map at the given point.
//
// A test circle is constructed to test for touching road features. If no
features are found, the
// circle is "inflated" by the specified distance. This is repeated until
there are results, or
// the max number of iterations has been reached.
//
// Returns the closes matching feature (ID, and its distance) or null if no
result is found after
// the specified number of iterations.
//
ClosestFeature GetClosestFeature(MgMap map, MgPoint pt. int maxIterations,
double incrementDist)
{
MgLayer roadLayer = map.GetLayers().GetItem("Roads");
MgCoordinateSystem mapCs =
MgCoordinateSystemFactory.Create(map.GetMapSRS());
SortedList<double, ClosetFeature> matches = new SortedList<double,
ClosestFeature>();
double distance = 1.0;
int iterations = 0;
MgWktReaderWriter wktIO = new MgWktReaderWriter();
MgAgfReaderWriter agfIO = new MgAgfReaderWriter();
MgGeometryFactory geomFact = new MgGeometryFactory();
do
{
MgFeatureQueryOptions options = new MgFeatureQueryOptions();
//Create our test circle.
MgGeometry testCircle = CreateTestCircle(pt, distance);
string testWkt = wktIO.Write(testCircle);
options.SetFilter("Geometry TOUCHES GeomFromText('" + testWkt + "')
OR Geometry WITHIN GeomFromText('" + testWkt + "')");
//Run Query, see if there are any matches.
MgFeatureReader reader = layer.SelectFeatures(options);
while (reader.ReadNext())
{
//We have a match.
int featureId = reader.GetInt32("FeatureId"); //Assuming
FeatureId is the identity property
MgByteReader agf = reader.GetGeometry("Geometry");
MgGeometry matchGeom = agfIO.Read(agf);
//Get its outermost coordinates
List<MgCoordinate> outerCoords = GetOuterCoordinates(matchGeom);
if (outerCoords.Count > 0)
{
foreach (MgCoordinate coord in outerCoords)
{
MgPoint testPoint = geomFact.CreatePoint(coord);
//If touches or within our test circle, record this result
if (testCircle.Touches(testPoint) ||
testCircle.Contains(testPoint))
{
double dist = mapCs.MeasureEuclideanDistance(coord,
pt.Coordinate);
ClosestFeature result = new ClosestFeature();
result.Distance = dist;
result.ClosestVertex = coord;
result.FeatureId = new MgInt32Property("FeatureId",
featureId);
matches.Add(distance, result);
}
}
}
}
//We have at least one result, we can stop iterating.
if (matches.Count > 0)
break;
//Otherwise, inflate our test circle.
iterations++;
distance += incrementDist;
}
while (matches.Count == 0 && iterations < maxIterations);
//If there are results, return the first one.
if (matches.Count > 0)
{
return matches.Values[0]; //First entry is the closest since
entries are sorted by distance.
}
return null; //Couldn't find a result after maxIteration attempts,
return null.
}
Jamo wrote:
>
> ok a little app i'm working on... it allows users to click near a road
> (linear feature) and record a proposed road works site. It will markup the
> GIS with propsed positions of signage and outline of site of works.
>
> I'm still playing around witha proof of concept.... seem to do this a
> lot.
>
> The program will function as follows
> 1. the user digitizes a point near the centre of the proposed site of
> works on the side of the road the works will be done.
>
> function DigitizePoint()
> {
> parent.mapFrame.DigitizePoint(findNearest);
> }
> function findNearest(Point)
> {
> var params = new Array(
> "x0", Point.X,
> "y0", Point.Y,
> "MAPNAME",parent.mapFrame.GetMapName(),
> "SESSION",parent.mapFrame.GetSessionId());
> parent.formFrame.Submit("../portbris/markup/distancebetween/Default.aspx",params,
> "taskPaneFrame");
> }
> DigitizePoint()
>
> 2. Mapguide will figure out what road centreline is closest to the
> digitized point.
>
> //userclick is a mgPoint object created from the digitize function
> MgFeatureReader feats =
> map.GetLayers().GetItem("plnCLRoads").SelectFeatures(new
> MgFeatureQueryOptions());
> double shortest = -1;
> String RoadName = String.Empty;
> MgGeometry road = null;
> while (feats.ReadNext())
> {
>
> MgAgfReaderWriter agf = new MgAgfReaderWriter();
> //gets geometry from feature
> MgGeometry myGeom = agf.Read(feats.GetGeometry("Geometry"));
> if (feats.IsNull("Name") == false)
> Response.Write(Math.Round(myGeom.Distance(userClick,
> null),2).ToString() + "m to " + feats.GetString("Name"));
> else
> Response.Write(Math.Round(myGeom.Distance(userClick,
> null), 2).ToString() + "m");
> Response.Write(@"<BR />");
>
> if (shortest == -1)
> shortest = myGeom.Distance(userClick, null);
> else if (myGeom.Distance(userClick, null) < shortest)
> {
>
> road = myGeom;
> shortest = myGeom.Distance(userClick, null);
> if (feats.IsNull("Name") == false)
> {
> RoadName = feats.GetString("Name") + " " +
> feats.GetString("Type");
>
> }
> }
> }
> Response.Write("<BR/><BR/>Therefore the closest Road is <BR/>" + RoadName
> + " and is " +Math.Round(shortest,2).ToString() + "m away<BR/>");
>
>
> Problem: how do i get the geometry between the point and the closest road
> .... the perpendicular link? would it matter if this road is curved?
>
> First Try: create a circle at the distance found to be the distance from
> the point the user clicked to the point on the closest road .... the
> result ends in a circle that according to MG does not touch the road :(
>
> MgGeometry shortGeometry(MgPoint click, MgGeometry closest, double
> joinDist)
> {
> MgGeometryFactory GF = new MgGeometryFactory();
> //maybe add some drafting fudge in here?
> //joinDist = joinDist + 0.001;
> MgCoordinate arcPt1 = GF.CreateCoordinateXY(click.Coordinate.X +
> joinDist, click.Coordinate.Y);
>
> MgCoordinate arcPt2 = GF.CreateCoordinateXY(click.Coordinate.X -
> joinDist, click.Coordinate.Y);
> MgCoordinate arcPt3 = GF.CreateCoordinateXY(click.Coordinate.X,
> click.Coordinate.Y + joinDist);
> MgCoordinate arcPt4 = GF.CreateCoordinateXY(click.Coordinate.X,
> click.Coordinate.Y - joinDist);
> MgArcSegment newGeom = GF.CreateArcSegment(arcPt1,arcPt2,arcPt3);
> MgArcSegment newGeom1 = GF.CreateArcSegment(arcPt2, arcPt1,
> arcPt4);
>
> MgCurveSegmentCollection myCurve = new MgCurveSegmentCollection();
> myCurve.Add(newGeom);
> myCurve.Add(newGeom1);
>
> MgCurvePolygon myPoly =
> GF.CreateCurvePolygon(GF.CreateCurveRing(myCurve), null);
>
> Response.Write(closest.Touches(myPoly));
>
> return myPoly;
> }
>
> 3. The user is prompted with a few questions to better define the type of
> works and how best to diagram it on the GIS (Yet to be finalised)
>
> Just a fancy dialog.
>
> 4. by using the information along with the digitized point MG will now
> produce a diagram markup of proposed safety signage.
>
> Stumped until the first problem is solved, but will involve offsetting,
> buffering and various geometry operations to produce the rest of the
> diagram
>
> I realise i'm not showing much here... but i figure if i put the question
> out to the community someone might be able to pick it too pieces and make
> something of it :), is there an area for community driven snap ins for
> mapguide? I would like to try and code for mapguide to keep it robust and
> transferable to other users, but am finding it hard to do. some examples
> of others work would be great
>
--
View this message in context: http://n2.nabble.com/Markup-perpendicular-tp4253574p4253983.html
Sent from the MapGuide Users mailing list archive at Nabble.com.
More information about the mapguide-users
mailing list