Drawing point geometry with StreamGeometryContext - c#

Trying to draw a polar type chart using GeometryContext in C#. I have input of direction as an xRange (start & stop) which is in degrees. I'm converting this to Radians. All good. The yRange is cut in & cut out wind speed currently in m/s as a double. I'm trying to acheive a simplified version of the image below without the axis labels etc.
For each object to be charted I am returning an XY range:
public IEnumerable<Styled2DRange> Query()
{
SolidColorBrush brush = new SolidColorBrush(Colors.Maroon);
brush.Freeze();
Pen linePen = new Pen(brush, 3);
linePen.Freeze();
SolidColorBrush fillBrush = new SolidColorBrush(Colors.Maroon);
fillBrush.Freeze();
foreach (var range in this.Charts)
{
Range xRange = new Range(ConvertToRadians(range.StartDirection), ConvertToRadians(range.EndDirection));
Range yRange = new Range(range.CutInWindSpeed, range.CutOutWindSpeed);
yield return new 2DRange()
{
Range = new XYRange()
{
XRange = xRange,
YRange = yRange
},
Line = linePen,
Fill = fillBrush
};
}
yield break;
}
This method is called from my override of Onrender. Obviously my points to be drawn by the StreamGeometryContext don't make sense as the Range.Y values are just wind speeds in m/s:
protected override void OnRender(DrawingContext dc)
{
Point origin = new Point(0, 0);
double maxR = 0;
SweepDirection outerSweep = SweepDirection.Clockwise;
SweepDirection innerSweep = SweepDirection.Counterclockwise;
outerSweep = SweepDirection.Counterclockwise;
innerSweep = SweepDirection.Clockwise;
foreach (Styled2DRange range in Query())
{
maxR = Math.Max(maxR, range.Range.YRange.End);
Point outerScreenPointBefore = new Point(range.Range.XRange.Start, range.Range.YRange.End);
Point outerScreenPointAfter = new Point(range.Range.XRange.End, range.Range.YRange.End);
Point innerScreenPointBefore = new Point(range.Range.XRange.Start, range.Range.YRange.Start);
Point innerScreenPointAfter = new Point(range.Range.XRange.End, range.Range.YRange.Start);
StreamGeometry sectorGeometry = new StreamGeometry();
sectorGeometry.FillRule = FillRule.Nonzero;
using (StreamGeometryContext geometryContext = sectorGeometry.Open())
{
geometryContext.BeginFigure(innerScreenPointBefore, true, true);
geometryContext.LineTo(outerScreenPointBefore, true, false);
double outerCircleRadius = Math.Sqrt(Math.Pow(outerScreenPointBefore.X - origin.X, 2) + Math.Pow(outerScreenPointBefore.Y - origin.Y, 2));
geometryContext.ArcTo(outerScreenPointAfter, new Size(outerCircleRadius, outerCircleRadius), 0, false, outerSweep, true, false);
geometryContext.LineTo(innerScreenPointAfter, true, false);
double innerCircleRadius = Math.Sqrt(Math.Pow(innerScreenPointBefore.X - origin.X, 2) + Math.Pow(innerScreenPointBefore.Y - origin.Y, 2));
geometryContext.ArcTo(innerScreenPointBefore, new Size(innerCircleRadius, innerCircleRadius), 0, false, innerSweep, true, false);
}
sectorGeometry.Freeze();
dc.DrawGeometry(range.Fill, range.Line, sectorGeometry);
}
}
So how do I reference the wind speed to create an actual point within the bounds of the drawing?

The four points of your chart sector are lying on two concentric circles, where the radius of the inner circle is given by the start wind speed, and that of the outer circle by the end wind speed. The position of the points on each circle is directly given by the wind directions in radians.
Provided that you have the variables startDirection and endDirection for the wind directions and startSpeed and endSpeed for the wind speeds, a sector would be constructed like this:
var pStart = new Point(Math.Sin(startDirection), -Math.Cos(startDirection));
var pEnd = new Point(Math.Sin(endDirection), -Math.Cos(endDirection));
var isLargeArc = Math.Abs(endDirection - startDirection) > Math.PI;
var geometry = new StreamGeometry();
using (var sgc = geometry.Open())
{
sgc.BeginFigure( // start point on inner circle
new Point(startSpeed * pStart.X, startSpeed * pStart.Y),
true, true);
sgc.ArcTo( // end point on inner circle
new Point(startSpeed * pEnd.X, startSpeed * pEnd.Y),
new Size(startSpeed, startSpeed), // radius of inner circle
0d, isLargeArc, SweepDirection.Clockwise, true, true);
sgc.LineTo( // end point on outer circle
new Point(endSpeed * pEnd.X, endSpeed * pEnd.Y),
true, true);
sgc.ArcTo( // start point on outer circle
new Point(endSpeed * pStart.X, endSpeed * pStart.Y),
new Size(endSpeed, endSpeed), // radius of outer circle
0d, isLargeArc, SweepDirection.Counterclockwise, true, true);
}

Related

Why is my reversed MagnitudeAxis Polar Plot not rendering with Oxyplot?

I'm trying to create a Polar Plot with Oxyplot in my WPF application. I want the MagnitudeAxis to be reversed so I made the StartPosition = 1 and the EndPosition = 0. However this causes the plot not to render. It looks it may be due to the variables a0 and a1 being set to 0 in the UpdateTransform() method in MagnitudeAxis.cs but it may be something else (not exactly sure what all these variables represent).
Here's my code to set up the polar plot:
/// dictionary of angles: magnitudes
dict = {-180: -18, -179: -17.9, ... , 0: 0, ... , 178: -17.8, 179: -17.9}
PlotModel myPlot = new PlotModel
{
Title = "Polar Plot",
PlotType = PlotType.Polar,
PlotAreaBorderThickness = new OxyThickness(0),
};
var series1 = new ScatterSeries
{
Title = "Polar Series",
MarkerType = MarkerType.Circle,
MarkerSize = 3,
MarkerFill = OxyColor.FromRgb(255, 0, 0),
};
var axis1 = new AngleAxis
{
StartPosition = 1,
EndPosition = 0,
StartAngle = 270,
EndAngle = -90,
Title = "Angle",
LabelFormatter = d => $"{d}",
MajorStep = 30,
Minimum = -180,
Maximum = 180,
MinorGridlineStyle = LineStyle.None,
};
var axis2 = new MagnitudeAxis
{
/// here's where I set the Start and End Position
StartPosition = 1,
EndPosition = 0,
MajorStep = 6,
Title = "Magnitude",
MinorGridlineStyle = LineStyle.None,
Minimum = dict.Values.Min();
Maximum = dict.Values.Max()
};
foreach (KeyValuePair<double, double> keyValuePair in dict)
{
series1.Points.Add(new ScatterPoint(keyValuePair.Value, keyValuePair.Key));
}
myPlot.Axes.Add(axis1);
myPlot.Axes.Add(axis2);
myPlot.Series.Add(series1);
This is what I get when the plot tries rendering:
This is what I get when I use default Start/End Positions:
What I want is the default plot with 0 in the center and -18 on the edge instead.
This is my first question ever on SO, so let me know if there's any other information I should provide.

How to make a circle type buffer around NetTopologySuite Point with radius in N meters?

I have to draw a circle with N meters diameter around some geolocation.
I could able to generate coordinates for vertically oriented ellipse only.
C#
using NetTopologySuite;
//
const int SRID = 4326;
var fact = new GeometryFactory(new PrecisionModel(), SRID);
var point = fact.CreatePoint(new Coordinate(lat, long));
var bufferParameters = new BufferParameters();
var poly = point.Buffer(0.0005, bufferParameters) as NetTopologySuite.Geometries.Polygon;
var coords = new List<BasicGeoposition>();
foreach (var cItem in poly.Coordinates)
{
coords.Add(new BasicGeoposition() { Latitude = cItem.X, Longitude = cItem.Y });
}
Please help me to understand how to set the radius and make circle instead of an ellipse.

Draw a circle on the MapControl

I am aware of technique to draw a circle by generating points by basic math and then use the points to create a MapPolygon.
Is there another way?
For example, I see there is a class for circles:
public sealed class Geocircle : IGeocircle, IGeoshape
But I don't know how to use it, there doesn't seem to be any MapLayer for it.
Geocircle is used to create a geographic circle object for the given position and radius. It often use to make map Geofence, but not to display the cycle on the map.
There many way that draw a circle on the map
generating points by basic math
for (var i = 0; i < 360; i++)
{
//draw a cycle
BasicGeoposition point = new BasicGeoposition()
{ Latitude = centerLatitude + ri * Math.Cos(3.6 * i * 3.14 / 180), Longitude = centerLongitude + ri * Math.Sin(3.6 * i * 3.14 / 180) };
list.Add(point);
}
Add Ellipse to map
private void MyMap_Loaded(object sender, RoutedEventArgs e)
{
// Specify a known location.
BasicGeoposition snPosition = new BasicGeoposition { Latitude = 47.620, Longitude = -122.349 };
Geopoint snPoint = new Geopoint(snPosition);
// Create a XAML border.
var ellipse1 = new Ellipse();
ellipse1.Fill = new SolidColorBrush(Windows.UI.Colors.Coral);
ellipse1.Width = 200;
ellipse1.Height = 200;
// Center the map over the POI.
MyMap.Center = snPoint;
MyMap.ZoomLevel = 14;
// Add XAML to the map.
MyMap.Children.Add(ellipse1);
MapControl.SetLocation(ellipse1, snPoint);
MapControl.SetNormalizedAnchorPoint(ellipse1, new Point(0.5, 0.5));
}
You can create XAML shapes and add them as a child to the MapControl. Then you set their location using SetLocation:
var circle = new Ellipse() {
Height = 20,
Width = 20,
Fill = new SolidColorBrush(Colors.Blue)
};
map.Children.Add(circle);
var location = new Geopoint(new BasicGeoposition()
{
Latitude = 51.1789,
Longitude = -1.8261
});
map.SetLocation(circle, location);
The Geocircle class is used for geofencing, so it is not applicable here.

Calculate area of polygon having WGS coordinates using DotSpatial?

Calculating area:
var coordinates = new List<Coordinate> {
new Coordinate(55, 35),
new Coordinate(55, 35.1),
new Coordinate(55.1, 35.1),
new Coordinate(55.1, 35),
};
Console.WriteLine(new Polygon(coordinates).Area); // ~0.01
Calculation is right, because it's happen in orthogonal coordinate system.
But how to mark that coordinates are in WGS?
It seems that task is more complicated that I've expected. I found this useful discussion on google groups
Firstly we need to found projection system, that is most suitable for our region where we need to compute area. For example you can take one of UTM zones
using DotSpatial.Projections;
using DotSpatial.Topology;
public static double CalculateArea(IEnumerable<double> latLonPoints)
{
// source projection is WGS1984
var projFrom = KnownCoordinateSystems.Geographic.World.WGS1984;
// most complicated problem - you have to find most suitable projection
var projTo = KnownCoordinateSystems.Projected.UtmWgs1984.WGS1984UTMZone37N;
// prepare for ReprojectPoints (it mutates array)
var z = new double[latLonPoints.Count() / 2];
var pointsArray = latLonPoints.ToArray();
Reproject.ReprojectPoints(pointsArray, z, projFrom, projTo, 0, pointsArray.Length / 2);
// assemblying new points array to create polygon
var points = new List<Coordinate>(pointsArray.Length / 2);
for (int i = 0; i < pointsArray.Length / 2; i++)
points.Add(new Coordinate(pointsArray[i * 2], pointsArray[i * 2 + 1]));
var poly = new Polygon(points);
return poly.Area;
}
You can get the area directly from IGeometry or from Feature.Geometry. Also You need to repeat the first coordinate to close your polygon.
FeatureSet fs = new FeatureSet(FeatureType.Polygon);
Coordinate[] coord = new Coordinate[]
{
new Coordinate(55, 35),
new Coordinate(55, 35.1),
new Coordinate(55.1, 35.1),
new Coordinate(55.1, 35),
new Coordinate(55, 35)
};
fs.AddFeature(new Polygon(new LinearRing(coord)));
var area = fs.Features.First().Geometry.Area;

WPF Modify Path Data at Runtime

Hi I have a path from GetFlattenedPathGeometry where i can iterate through the figures and segments to get the points to add to a PointCollection.
I then multiply each point.x/y by a scale factor to get a full scaled version of the original path data. (not using scaletransform as it doesn't suit my requirements).
If i use something like:
public static PathGeometry GetPathGeometry(PointCollection polygonCorners)
{
List<PathSegment> pathSegments = new List<PathSegment> { new PolyLineSegment(polygonCorners, true) };
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(new PathFigure(polygonCorners[0], pathSegments, true));
return pathGeometry;
}
It returns a new path geometry but doesn't handle ellipses with excluded path geometry in that the path is just one continuous line.
Is there a way to convert the PointCollection to Path.Data (eg: with the "M" "L" and such) for me to re-use Geometry.Parse(the new string)?
Here is the code i'm using to get the flattenedgeometry pointcollection:
PathGeometry g = path.Data.GetFlattenedPathGeometry();
foreach (var f in g.Figures)
{
foreach (var s in f.Segments)
{
if (s is PolyLineSegment)
{
foreach (var pt in ((PolyLineSegment) s).Points)
{
strGeom += pt.ToString();
Point ptn = new Point(pt.X * ScaleX, pt.Y * ScaleY);
pcol.Add(ptn);
}
}
}
}
< Edit Images >
Here is the original path with rectangles and ellipses subtracted from the geometry.
And here is what is looks like re-creating from the code.
If i use the original GetFlattenedPathGeometry, it looks like the original but i need to scale the points to a new resolution.
Hope this makes it clearer.
You could simply call ToString on the PathGeometry to get the whole path data string at once:
var sourceGeometry = path.Data.GetFlattenedPathGeometry();
var geometryString = sourceGeometry.ToString(CultureInfo.InvariantCulture);
var targetGeometry = Geometry.Parse(geometryString);
And why can't you just apply a ScaleTransform to the whole geometry before calling GetFlattenedPathGeometry? The following works perfectly for me (with two EllipseGeometries in an excluding CombinedGeometry):
var pathGeometry = path.Data.Clone();
pathGeometry.Transform = new ScaleTransform(0.5, 0.5);
var scaledGeometry = pathGeometry.GetFlattenedPathGeometry();
EDIT: From what you write in your question and comments, I'm guessing that all you actually want to do is to add or combine geometries with different scaling factors. If that is true, your flattened geometry approach is by far to complicated, as you could easily do that with the following two methods:
private PathGeometry AddGeometries(
Geometry geometry1, Geometry geometry2, double scale)
{
geometry2 = geometry2.Clone();
geometry2.Transform = new ScaleTransform(scale, scale);
var pathGeometry = PathGeometry.CreateFromGeometry(geometry1);
pathGeometry.AddGeometry(geometry2);
return pathGeometry;
}
private PathGeometry CombineGeometries(
Geometry geometry1, Geometry geometry2, GeometryCombineMode mode, double scale)
{
geometry2 = geometry2.Clone();
geometry2.Transform = new ScaleTransform(scale, scale);
return Geometry.Combine(geometry1, geometry2, mode, null);
}
Given a Path with some geometry in its Data property, you may now add (or combine) an arbitray other geometry with a scaling factor with a call like this:
Geometry newGeometry1 = ...
double scale1 = ...
path.Data = AddGeometries(path.Data, newGeometry1, scale1);
Geometry newGeometry2 = ...
double scale2 = ...
path.Data = CombineGeometries(path.Data, newGeometry2,
GeometryCombineMode.Exclude, scale2);
Found the answer by perseverance.
The code to get each point of flattenedpathgeometry and add a scale to each point and recreate the same flattenedpathgeometry with the new points. hope it helps someone. And thanks Clemens. Appreciate your efforts.
path.Data = Geometry.Parse(CurrentObject.Geometry1);
PathGeometry g = path.Data.GetFlattenedPathGeometry();
PathGeometry g = path.Data.GetFlattenedPathGeometry();
foreach (var f in g.Figures)
{
Point pt1 = f.StartPoint;
pt1.X = pt1.X * ScaleX;
pt1.Y = pt1.Y * ScaleY;
strGeom += "M" + pt1.ToString();
foreach (var s in f.Segments)
if (s is PolyLineSegment)
{
count = 0;
foreach (var pt in ((PolyLineSegment)s).Points)
{
int scount = ((PolyLineSegment)s).Points.Count;
if (count == 0)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += "L" + pts.ToString();
}
else if (count < scount)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += " " + pts.ToString();
}
else if (count == scount)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += " " + pts.ToString() + "Z";
}
count++;
}
}
}
path.Data = Geometry.Parse(strGeom);
Here's an image of the paths sent from a remote session: 1366x768 scales to 1920x1080

Categories

Resources