I am trying to calculate distance between two points using NetTopologySuite. Since I am referencing Microsoft documentation, I Came up with following GeometryExtension and GeometryHelper classes:
public static class GeometryExtensions
{
private static readonly CoordinateSystemServices _coordinateSystemServices = new CoordinateSystemServices(new Dictionary<int, string>
{
// Coordinate systems:
[4326] = GeographicCoordinateSystem.WGS84.WKT,
// CRS for Denmark ESPG 25832. Source: https://epsg.io/25832 and https://sdfe.dk/
[25832] = #"PROJCS[""ETRS89 / UTM zone 32N"",
GEOGCS[""ETRS89"",
DATUM[""European_Terrestrial_Reference_System_1989"",
SPHEROID[""GRS 1980"",6378137,298.257222101,
AUTHORITY[""EPSG"",""7019""]],
TOWGS84[0,0,0,0,0,0,0],
AUTHORITY[""EPSG"",""6258""]],
PRIMEM[""Greenwich"",0,
AUTHORITY[""EPSG"",""8901""]],
UNIT[""degree"",0.0174532925199433,
AUTHORITY[""EPSG"",""9122""]],
AUTHORITY[""EPSG"",""4258""]],
PROJECTION[""Transverse_Mercator""],
PARAMETER[""latitude_of_origin"",0],
PARAMETER[""central_meridian"",9],
PARAMETER[""scale_factor"",0.9996],
PARAMETER[""false_easting"",500000],
PARAMETER[""false_northing"",0],
UNIT[""metre"",1,
AUTHORITY[""EPSG"",""9001""]],
AXIS[""Easting"",EAST],
AXIS[""Northing"",NORTH],
AUTHORITY[""EPSG"",""25832""]]"
}
);
/// <summary>
/// Projects a geometry to another SRID
/// </summary>
/// <param name="geometry"></param>
/// <param name="targetSrid"></param>
/// <param name="defaultSourceSrid">If the geometry SRID has not been specified (i.e. equals 0) defaultSourceSrid is used</param>
/// <returns></returns>
public static Geometry ProjectTo(this Geometry geometry, int targetSrid, int? defaultSourceSrid = null)
{
if (geometry == null)
throw new Exception("Geometry is null, cannot project");
var sourceSrid = geometry.SRID == 0 && defaultSourceSrid.HasValue ? defaultSourceSrid.Value : geometry.SRID;
var transformation = _coordinateSystemServices.CreateTransformation(sourceSrid, targetSrid);
var result = geometry.Copy();
result.Apply(new MathTransformFilter(transformation.MathTransform));
return result;
}
}
public class GeometryHelper
{
private static readonly int _longLatSRID = 4326;
private static readonly int _targetSRID = 25832;
/// <summary>
/// In order to get the distance in meters, we need to project to an appropriate
/// coordinate system. If no SRID is provided 25832, which covers Denmark is used.
/// If the provided Points have no SRID, 4326 (longitude/latitude) is assumed.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="targetSrid"></param>
/// <returns></returns>
public static double DistanceInMeters(Point a, Point b, int? targetSrid = null)
{
targetSrid ??= _targetSRID;
try
{
//If SRID is not set, assume long/lat, ie. 4326
return a.ProjectTo(targetSrid.Value, _longLatSRID).Distance(b.ProjectTo(targetSrid.Value, _longLatSRID));
}
catch (Exception e)
{
throw new Exception("Failed to calculate distance", e);
}
}
}
To test if my distance is being calculated properly, I opened Google Maps and selected the calculate distance between 3 different pairs of points. Then I copy pasted the values in my tests to see if they match. The values I am getting from my code is 2 times larger than what I am getting from Google Maps. What am I doing wrong? Here are my tests:
public class GeometryHelperTests
{
[Theory]
[InlineData(55.676518126293466, 12.567203858066554, 55.67645068023813, 12.56698376863065, 15.68)]//I am getting 36.1
[InlineData(55.659368700924475, 12.546625254609248, 55.65940085355421, 12.546601114728679, 3.77)]//I am getting 6.2
[InlineData(55.65896978705746, 12.546674114514795, 55.6596855501795, 12.547258269821455, 87.09)]//I am getting 173.7
public void DistanceInMeters(double x1, double y1, double x2, double y2, double expected)
{
// setup
Point point1 = new Point(x1, y1) { SRID = 4326 };
Point point2 = new Point(x2, y2) { SRID = 4326 };
//CoordinateSystemFactory csFact = new CoordinateSystemFactory();
// act
var result = GeometryHelper.DistanceInMeters(point1, point2);
// assert
result.Should().Be(expected);
}
}
You need to calculate great circle distance.
NetTopologySuite Point.Distance method returns the cartesian distance.
Try the following:
public static double Radians(double x)
{
return x * Math.PI / 180;
}
public static double GreatCircleDistance(double lon1, double lat1, double lon2, double lat2)
{
double R = 6371e3; // m
double sLat1 = Math.Sin(Radians(lat1));
double sLat2 = Math.Sin(Radians(lat2));
double cLat1 = Math.Cos(Radians(lat1));
double cLat2 = Math.Cos(Radians(lat2));
double cLon = Math.Cos(Radians(lon1) - Radians(lon2));
double cosD = sLat1*sLat2 + cLat1*cLat2*cLon;
double d = Math.Acos(cosD);
double dist = R * d;
return dist;
}
You can also use the built-in GeoCoordinate.GetDistanceTo() which implements the Haversine formula that is more accurate for small distances.
Even though saeedkazemi's answer is correct and gives results closer to the real value, results given by my code were also quite close, but I figured out that I had to flip the coordinates given by Google maps. So if I was given 55.6765, 12.5672 I need to give 12.5672,55.6765 to the formula. That being said, saeedkazemi's answer provides results closer to the real value.
Related
My first task is simple: find the points of the ellipse to be drawn on screen. I made an Ellipse class below with a method that takes in an angle between 0 to 2*PI and returns the point.
public class Ellipse
{
public PointF Center { get; set; }
public float A { get; set; } /* horizontal semiaxis */
public float B { get; set; } /* vertical semiaxis */
public Ellipse(PointF center, float a, float b)
{
this.Center = center;
this.A = a;
this.B = b;
}
public PointF GetXYWhenT(float t_rad)
{
float x = this.Center.X + (this.A * (float)Math.Cos(t_rad));
float y = this.Center.Y + (this.B * (float)Math.Sin(t_rad));
return new PointF(x, y);
}
}
I use the parametric equation of the ellipse as it is convenient for this task. Parameter t is the angle. X and Y values are calculated and put together as a point on the ellipse. By increasing parameter t, I can obtain the points in the order that makes drawing the ellipse as simple as connecting the dots.
private void RunTest1()
{
PointF center = new PointF(0, 0);
float a = 3; /* horizontal semiaxis */
float b = 4; /* vertical semiaxis */
Ellipse ellipse = new Ellipse(center, a, b);
List<PointF> curve = new List<PointF>(); /* collects all points needed to draw the ellipse */
float start = 0;
float end = (float)(2 * Math.PI); /* 360 degrees */
float step = 0.0174533f; /* 1 degree */
for (float t_rad = start; t_rad <= end; t_rad += step)
{
PointF point = ellipse.GetXYWhenT(t_rad);
curve.Add(point);
}
}
RunTestX are methods I run in Main. The first will give me the points I need to draw this ellipse. The points are correct. I have visual confirmation of the ellipse being drawn to specification using a draw method that I will not be including here. Drawing it is NOT the issue. The takeaway here is that for every value of t_rad, I have a corresponding point on the curve.
Now I need to perform a different task after I draw the ellipse. To do this, I need to reverse the process in that I take an arbitrary point on the ellipse and convert it back to the t_rad. Math.Atan2 should do the trick. The method is called GetTWhenPoint. It's an extension method in MyMath class.
public static class MyMath
{
public static float GetTWhenPoint(this PointF center, PointF point)
{
float x = point.X - center.X;
float y = point.Y - center.Y;
float retval = (float)Math.Atan2(y, x);
if (retval < 0)
{
retval += (float)(2 * Math.PI);
}
return retval;
}
}
Simple trigonometry, right? However...
private void RunTest2()
{
PointF center = new PointF(0, 0);
float a = 3; /* horizontal semiaxis */
float b = 4; /* vertical semiaxis */
Ellipse ellipse = new Ellipse(center, a, b);
string debug = "TEST 2\r\n";
float start = 0;
float end = (float)(2 * Math.PI);
float step = 0.0174533f;
for (float t_rad = start; t_rad <= end; t_rad += step)
{
PointF point = ellipse.GetXYWhenT(t_rad);
double t_rad2 = center.GetTWhenPoint(point);
debug += t_rad.ToString() + "\t" + t_rad2.ToString() + "\r\n";
}
Clipboard.SetText(debug);
}
When I use it to convert the point back to t_rad2, I expect it to be equal to or pretty darn close to the original t_rad.
TEST 2
0 0
0.0174533 0.0232692267745733
0.0349066 0.0465274415910244
0.0523599 0.0697636753320694
0.0698132 0.0929670184850693
0.0872665 0.116126760840416
...
6.178444 6.14392471313477
6.195897 6.1670298576355
6.21335 6.19018936157227
6.230803 6.21339273452759
6.248257 6.23662853240967
6.26571 6.25988674163818
6.283163 6.28315591812134
What am I missing here? All my numbers so far have been in radians (as far as I can tell). Now here's where it gets weirder...
private void RunTest3()
{
PointF center = new PointF(0, 0);
float a = 4; /* horizontal semiaxis */
float b = 4; /* vertical semiaxis */
Ellipse ellipse = new Ellipse(center, a, b);
string debug = "TEST 3\r\n";
float start = 0;
float end = (float)(2 * Math.PI);
float step = 0.0174533f;
for (float t_rad = start; t_rad <= end; t_rad += step)
{
PointF point = ellipse.GetXYWhenT(t_rad);
double t_rad2 = center.GetTWhenPoint(point);
debug += t_rad.ToString() + "\t" + t_rad2.ToString() + "\r\n";
}
Clipboard.SetText(debug);
}
If I set a and b equal to make the ellipse a perfect circle then everything looks normal!
TEST 3
0 0
0.0174533 0.0174532998353243
0.0349066 0.0349065996706486
0.0523599 0.0523599050939083
0.0698132 0.0698131918907166
0.0872665 0.0872664898633957
...
6.178444 6.17844390869141
6.195897 6.19589710235596
6.21335 6.21335029602051
6.230803 6.23080348968506
6.248257 6.24825668334961
6.26571 6.26570987701416
6.283163 6.28316307067871
What this tells me is that when I convert the point back to t_rad2 it is somehow affected by the dimensions of the ellipse. But how? Apart from the center adjustment of the ellipse with respect to the Cartesian origin (0,0), GetTWhenPoint method does not make use of any other information from the Ellipse class specifically the semi-axes. Math.Atan2 only needs the x and y values of the point to find the angle it makes with the 0-degree vector. That's basic trigonometry.
It shouldn't even care that it's a point on the ellipse. From the context of the method, it's just a point just like infinitely any other. How is my extension method somehow being affected by the dimensions of my ellipse?
Is it my math that's wrong? I mean it's been a while since I used trig but I think I remember the simple ones correctly.
Thanks in advance!
I think this is what you want.
public class Ellipse
{
public PointF Center { get; set; }
public float A { get; set; } /* horizontal semiaxis */
public float B { get; set; } /* vertical semiaxis */
public Ellipse(PointF center, float a, float b)
{
this.Center=center;
this.A=a;
this.B=b;
}
public PointF GetXYWhenT(float t_rad)
{
float x = this.Center.X+(this.A*(float)Math.Cos(t_rad));
float y = this.Center.Y+(this.B*(float)Math.Sin(t_rad));
return new PointF(x, y);
}
public float GetParameterFromPoint(PointF point)
{
var x = point.X-Center.X;
var y = point.Y-Center.Y;
// Since x=a*cos(t) and y=b*sin(t), then
// tan(t) = sin(t)/cos(t) = (y/b) / (x/a)
return (float)Math.Atan2(A*y, B*x);
}
}
class Program
{
static readonly Random rng = new Random();
static void Main(string[] args)
{
var center = new PointF(35.5f, -12.2f);
var ellipse = new Ellipse(center, 18f, 44f);
// Get t between -π and +π
var t = (float)(2*Math.PI*rng.NextDouble()-Math.PI);
var point = ellipse.GetXYWhenT(t);
var t_check = ellipse.GetParameterFromPoint(point);
Debug.WriteLine($"t={t}, t_check={t_check}");
// t=-0.7434262, t_check=-0.7434263
}
}
I would consider the proper parametrization of a curve as one with a parameter that spans between 0 and 1. Hence the need to specify radians goes away
x = A*Cos(2*Math.PI*t)
y = B*Sin(2*Math.PI*t)
and the reverse
t = Atan2(A*y, B*x)/(2*PI)
Also, consider what the ellipse looks like in polar coordinates relative to the center.
x = A*Cos(t) = R*Cos(θ) | TAN(θ) = B/A*TAN(t)
y = B*Sin(t) = R*Sin(θ) |
| R = Sqrt(B^2+(A^2-B^2)*Cos(t)^2)
A*B
R(θ) = ----------------------------
Sqrt(A^2+(B^2-A^2)*Cos(θ)^2)
Also, consider the following helper functions that wrap angles around the desired quadrants (radian versions)
/// <summary>
/// Wraps angle between 0 and 2π
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapTo2PI(this double angle)
=> angle-(2*Math.PI)*Math.Floor(angle/(2*Math.PI));
/// <summary>
/// Wraps angle between -π and π
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapBetweenPI(this double angle)
=> angle+(2*Math.PI)*Math.Floor((Math.PI-angle)/(2*Math.PI));
and the degree versions
/// <summary>
/// Wraps angle between 0 and 360
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapTo360(this double angle)
=> angle-360*Math.Floor(angle/360);
/// <summary>
/// Wraps angle between -180 and 180
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
/// <remarks>see: http://stackoverflow.com/questions/7271527/inconsistency-with-math-round</remarks>
public static double WrapBetween180(this double angle)
=> angle+360*Math.Floor((180-angle)/360);
I have written the following resizing algorithm which can correctly scale an image up or down. It's far too slow though due to the inner iteration through the array of weights on each loop.
I'm fairly certain I should be able to split out the algorithm into two passes much like you would with a two pass Gaussian blur which would vastly reduce the operational complexity and speed up performance. Unfortunately I can't get it to work. Would anyone be able to help?
Parallel.For(
startY,
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
Weight[] verticalValues = this.verticalWeights[y].Values;
for (int x = startX; x < endX; x++)
{
Weight[] horizontalValues = this.horizontalWeights[x].Values;
// Destination color components
Color destination = new Color();
// This is where there is too much operation complexity.
foreach (Weight yw in verticalValues)
{
int originY = yw.Index;
foreach (Weight xw in horizontalValues)
{
int originX = xw.Index;
Color sourceColor = Color.Expand(source[originX, originY]);
float weight = yw.Value * xw.Value;
destination += sourceColor * weight;
}
}
destination = Color.Compress(destination);
target[x, y] = destination;
}
}
});
Weights and indices are calculated as follows. One for each dimension:
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="destinationSize">The destination section size.</param>
/// <param name="sourceSize">The source section size.</param>
/// <returns>
/// The <see cref="T:Weights[]"/>.
/// </returns>
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{
IResampler sampler = this.Sampler;
float ratio = sourceSize / (float)destinationSize;
float scale = ratio;
// When shrinking, broaden the effective kernel support so that we still
// visit every source pixel.
if (scale < 1)
{
scale = 1;
}
float scaledRadius = (float)Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize];
// Make the weights slices, one source for each column or row.
Parallel.For(
0,
destinationSize,
i =>
{
float center = ((i + .5f) * ratio) - 0.5f;
int start = (int)Math.Ceiling(center - scaledRadius);
if (start < 0)
{
start = 0;
}
int end = (int)Math.Floor(center + scaledRadius);
if (end > sourceSize)
{
end = sourceSize;
if (end < start)
{
end = start;
}
}
float sum = 0;
result[i] = new Weights();
List<Weight> builder = new List<Weight>();
for (int a = start; a < end; a++)
{
float w = sampler.GetValue((a - center) / scale);
if (w < 0 || w > 0)
{
sum += w;
builder.Add(new Weight(a, w));
}
}
// Normalise the values
if (sum > 0 || sum < 0)
{
builder.ForEach(w => w.Value /= sum);
}
result[i].Values = builder.ToArray();
result[i].Sum = sum;
});
return result;
}
/// <summary>
/// Represents the weight to be added to a scaled pixel.
/// </summary>
protected class Weight
{
/// <summary>
/// The pixel index.
/// </summary>
public readonly int Index;
/// <summary>
/// Initializes a new instance of the <see cref="Weight"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="value">The value.</param>
public Weight(int index, float value)
{
this.Index = index;
this.Value = value;
}
/// <summary>
/// Gets or sets the result of the interpolation algorithm.
/// </summary>
public float Value { get; set; }
}
/// <summary>
/// Represents a collection of weights and their sum.
/// </summary>
protected class Weights
{
/// <summary>
/// Gets or sets the values.
/// </summary>
public Weight[] Values { get; set; }
/// <summary>
/// Gets or sets the sum.
/// </summary>
public float Sum { get; set; }
}
Each IResampler provides the appropriate series of weights based on the given index. the bicubic resampler works as follows.
/// <summary>
/// The function implements the bicubic kernel algorithm W(x) as described on
/// <see href="https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm">Wikipedia</see>
/// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation.
/// </summary>
public class BicubicResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
// The coefficient.
float a = -0.5f;
if (x < 0)
{
x = -x;
}
float result = 0;
if (x <= 1)
{
result = (((1.5f * x) - 2.5f) * x * x) + 1;
}
else if (x < 2)
{
result = (((((a * x) + 2.5f) * x) - 4) * x) + 2;
}
return result;
}
}
Here's an example of an image resized by the existing algorithm. The output is correct (note the silvery sheen is preserved).
Original image
Image halved in size using the bicubic resampler.
The code is part of a much larger library that I am writing to add image processing to corefx.
Judging from an abstract point of view (not knowing much about image manipulation) I think it looks like you're computing the values for weight and sourcecolor (in the innermost foreach loop) multiple times (whenever the same pair of indices crops up again); would it be feasible to simply precompute them in advance?
You would need to compute a 'direct product' Matrix for your HorizontalWeight and VerticalWeight matrices (simply multiplying the values for each pair of indices (x, y)) and could also apply Color.Expand to the source in advance.
These tasks can be done in parallel and the 'direct product' (sorry, I don't know the correct Name for that beast) should be available in lots of libraries.
You can try a weighted voronoi diagram. Try randomly a set of points and compute the voronoi diagram. Smooth the polygons with Lloyd's algorithm and interpolate the color of the polygon. With the weight compute the weighted voronoi diagram. For example voronoi stippling and mosaic:http://www.mrl.nyu.edu/~ajsecord/stipples.html and http://www.evilmadscientist.com/2012/stipplegen-weighted-voronoi-stippling-and-tsp-paths-in-processing/.
Ok so here's how I went about it.
The trick is to first resize only the width of the image keeping the height the same as the original image. We store the resultant pixels in a temporary image.
Then it's a case of resizing that image down to our final output.
As you can see we are no longer iterating through both weight collections on each pixel. Despite having to iterate though the outer pixel loop twice the algorithm was much faster in it's operation averaging around 25% faster on my test images.
// Interpolate the image using the calculated weights.
// First process the columns.
Parallel.For(
0,
sourceBottom,
y =>
{
for (int x = startX; x < endX; x++)
{
Weight[] horizontalValues = this.HorizontalWeights[x].Values;
// Destination color components
Color destination = new Color();
foreach (Weight xw in horizontalValues)
{
int originX = xw.Index;
Color sourceColor = Color.Expand(source[originX, y]);
destination += sourceColor * xw.Value;
}
destination = Color.Compress(destination);
this.firstPass[x, y] = destination;
}
});
// Now process the rows.
Parallel.For(
startY,
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
Weight[] verticalValues = this.VerticalWeights[y].Values;
for (int x = startX; x < endX; x++)
{
// Destination color components
Color destination = new Color();
foreach (Weight yw in verticalValues)
{
int originY = yw.Index;
int originX = x;
Color sourceColor = Color.Expand(this.firstPass[originX, originY]);
destination += sourceColor * yw.Value;
}
destination = Color.Compress(destination);
target[x, y] = destination;
}
}
});
I'm using GDI+ to visualize the schema of some user-specified building.
There are no complex objects in it - all of them can be represented by rectangles.
I do it, but have one issue: many rectangles are overlap, e.g. then rooms are adjacent.
So some lines drawed many times!
This looks bad (fat line) and decrease the application performance (extra work).
Is there a way to draw each line only once on a screen?
My code (simplified) looks like this:
private void Visualizator_Paint( object sender, PaintEventArgs e )
{
if ( m_building == null ) return;
var g = e.Graphics;
// Smooth graphics output and scale
g.SmoothingMode = SmoothingMode.HighQuality;
ScaleGraphics( g );
...
foreach( var room in m_rooms )
{
RectangleF extent = room.Extent;
g.DrawRectangle( brownPen, extent.X, extent.Y, extent.Width, extent.Height );
}
...
}
void ScaleGraphics( Graphics g )
{
// Set margins inside the control client area in pixels
var margin = new Margins( 16, 16, 16, 16 );
// Set the domain of (x,y) values
var range = m_building.Extents;
// Make it smaller by 5%
range.Inflate( 0.05f * range.Width, 0.05f * range.Height );
// Scale graphics
ScaleGraphics( g, Visualizator, range, margin );
}
void ScaleGraphics( Graphics g, Control control, RectangleF domain, Margins margin )
{
// Find the drawable area in pixels (control-margins)
int W = control.Width - margin.Left - margin.Right;
int H = control.Height - margin.Bottom - margin.Top;
// Ensure drawable area is at least 1 pixel wide
W = Math.Max( 1, W );
H = Math.Max( 1, H );
// Find the origin (0,0) in pixels
float OX = margin.Left - W * ( domain.Left / domain.Width );
float OY = margin.Top + H * ( 1 + domain.Top / domain.Height );
// Find the scale to fit the control
float SX = W / domain.Width;
float SY = H / domain.Height;
// Transform the Graphics scene
if ( m_panPoint.IsEmpty )
m_panPoint = new PointF( OX, OY );
g.TranslateTransform( m_panPoint.X, m_panPoint.Y, MatrixOrder.Append );
g.ScaleTransform( SX * m_scale, -SY * m_scale );
}
Screenshot of defect:
I was unable to reproduce the blurring/smearing effect described in the question. However, the basic request to be able to avoid over-drawing lines seems reasonably clear and not terribly complicated to address. So I offer this class which can do that work:
/// <summary>
/// Consolidates horizontal and vertical lines.
/// </summary>
class LineConsolidator : IEnumerable<LineConsolidator.Line>
{
/// <summary>
/// A pair of points defining a line
/// </summary>
public struct Line
{
public Point Start { get; private set; }
public Point End { get; private set; }
public Line(Point start, Point end)
: this()
{
Start = start;
End = end;
}
}
private struct Segment
{
public int Start { get; private set; }
public int End { get; private set; }
public Segment(int start, int end)
: this()
{
if (end < start)
{
throw new ArgumentException("start must be less than or equal to end");
}
Start = start;
End = end;
}
public Segment Union(Segment other)
{
if (End < other.Start || other.End < Start)
{
throw new ArgumentException("Only overlapping segments may be consolidated");
}
return new Segment(
Math.Min(Start, other.Start),
Math.Max(End, other.End));
}
public Segment? Intersect(Segment other)
{
int start = Math.Max(Start, other.Start),
end = Math.Min(End, other.End);
if (end < start)
{
return null;
}
return new Segment(start, end);
}
}
private Dictionary<int, List<Segment>> _horizontalLines = new Dictionary<int, List<Segment>>();
private Dictionary<int, List<Segment>> _verticalLines = new Dictionary<int, List<Segment>>();
/// <summary>
/// Add horizontal line
/// </summary>
/// <param name="y">The Y coordinate of the line to add</param>
/// <param name="start">The first X coordinate of the line to add (must not be larger than <paramref name="end"/></param>
/// <param name="end">The second X coordinate of the line to add (must not be smaller than <paramref name="start"/></param>
/// <remarks>
/// This method submits a new horizontal line to the collection. It is merged with any other
/// horizontal lines with exactly the same Y coordinate that it overlaps.
/// </remarks>
public void AddHorizontal(int y, int start, int end)
{
_AddLine(y, new Segment(start, end), _horizontalLines);
}
/// <summary>
/// Add vertical line
/// </summary>
/// <param name="y">The X coordinate of the line to add</param>
/// <param name="start">The first Y coordinate of the line to add (must not be larger than <paramref name="end"/></param>
/// <param name="end">The second Y coordinate of the line to add (must not be smaller than <paramref name="start"/></param>
/// <remarks>
/// This method submits a new vertical line to the collection. It is merged with any other
/// vertical lines with exactly the same X coordinate that it overlaps.
/// </remarks>
public void AddVertical(int x, int start, int end)
{
_AddLine(x, new Segment(start, end), _verticalLines);
}
/// <summary>
/// Add all four sides of a rectangle as individual lines
/// </summary>
/// <param name="rect">The rectangle containing the lines to add</param>
public void AddRectangle(Rectangle rect)
{
AddHorizontal(rect.Top, rect.Left, rect.Right);
AddHorizontal(rect.Bottom, rect.Left, rect.Right);
AddVertical(rect.Left, rect.Top, rect.Bottom);
AddVertical(rect.Right, rect.Top, rect.Bottom);
}
/// <summary>
/// Gets all of the horizontal lines in the collection
/// </summary>
public IEnumerable<Line> HorizontalLines
{
get
{
foreach (var kvp in _horizontalLines)
{
foreach (var segment in kvp.Value)
{
yield return new Line(new Point(segment.Start, kvp.Key), new Point(segment.End, kvp.Key));
}
}
}
}
/// <summary>
/// Gets all of the vertical lines in the collection
/// </summary>
public IEnumerable<Line> VerticalLines
{
get
{
foreach (var kvp in _verticalLines)
{
foreach (var segment in kvp.Value)
{
yield return new Line(new Point(kvp.Key, segment.Start), new Point(kvp.Key, segment.End));
}
}
}
}
private static void _AddLine(int lineKey, Segment newSegment, Dictionary<int, List<Segment>> segmentKeyToSegments)
{
// Get the list of segments for the given key (X for vertical lines, Y for horizontal lines)
List<Segment> segments;
if (!segmentKeyToSegments.TryGetValue(lineKey, out segments))
{
segments = new List<Segment>();
segmentKeyToSegments[lineKey] = segments;
}
int isegmentInsert = 0, isegmentMergeFirst = -1, ilineSegmentLast = -1;
// Find all existing segments that should be merged with the new one
while (isegmentInsert < segments.Count && segments[isegmentInsert].Start <= newSegment.End)
{
Segment? intersectedSegment = newSegment.Intersect(segments[isegmentInsert]);
if (intersectedSegment != null)
{
// If they overlap, merge them together, keeping track of all the existing
// segments which were merged
newSegment = newSegment.Union(segments[isegmentInsert]);
if (isegmentMergeFirst == -1)
{
isegmentMergeFirst = isegmentInsert;
}
ilineSegmentLast = isegmentInsert;
}
isegmentInsert++;
}
if (isegmentMergeFirst == -1)
{
// If there was no merge, just insert the new segment
segments.Insert(isegmentInsert, newSegment);
}
else
{
// If more than one segment was merged, remove all but one
if (ilineSegmentLast > isegmentMergeFirst)
{
segments.RemoveRange(isegmentMergeFirst + 1, ilineSegmentLast - isegmentMergeFirst);
}
// Copy the new, merged segment back to the first original segment's slot
segments[isegmentMergeFirst] = newSegment;
}
}
public IEnumerator<LineConsolidator.Line> GetEnumerator()
{
return HorizontalLines.Concat(VerticalLines).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Note that this is based on integer coordinates. It's a little trickier to apply this sort of logic to floating point coordinates, if one is actually concerned about accommodating round-off error. But if the floating point coordinates are assured of always coming from the same source when they overlap, then they will meet the equality condition this implementation requires and you can just change the types to floating point.
I included the properties to retrieve just the horizontal or vertical lines, so that I could draw them differently from each other (different line end-caps) to verify the algorithm was working. Normally I think you'd just enumerate the whole collection when drawing.
You use it by first creating an empty instance of the collection, then adding your rectangles (or individual lines if desired) via the AddRectangle() method, then finally enumerating all of the resulting lines.
I would expect this to perform just fine up to thousands of lines or so. In my tests, I just recreated the collection from scratch every time I painted the window.
It might perform well enough even at higher magnitudes depending on the PC, but I didn't try to do any specific optimizations, opting instead for easy-to-understand code. In a situation where you're dealing with an extremely large number of rectangles, you might want to keep a persistent instance to collect lines/rectangles as they are generated. Then you don't have to regenerate it every paint event. That may or may not then require adding features to the class to support removal of lines.
I have a need to create a spline chart natively in WPF via either Dynamic Data Display or using the WPF DataVisualization toolkit. Does anyone know if it's possible?
If so, can you point me to an example of a Spline chart?
Thanks in advance.
Have you tried the PolyBezierSegment
Sounds like you are looking for a best fit curve to draw in WPF. You would have to create a custom control to draw it, but a Bezier spline should work nicely for you if you can do the drawing. Here is a class I wrote that will allow you to generate a set of smooth points from a small set of seed points. Instantiate the object, set the SeedPoints property to a set of points, then call the GenerateSpline(int NumberPointsToGenerate) to get the points for a smooth curve. I generally use about 40 points to generate a really smooth curve.
/// <summary>
/// Class to generate a Bezier Curve from a set of seed points.
/// Note that this is a best fit curve through a set of points
/// and a Bezier does NOT require the curve to pass through the points.
/// </summary>
public class BezierSpline
{
/// <summary>
/// Default Constructor
/// </summary>
public BezierSpline()
{
SeedPoints = new List<PointF>();
}
/// <summary>
/// Constructs a BezierSpline object using SeedPoints
/// </summary>
/// <param name="seedPoints"></param>
public BezierSpline(IList<PointF> seedPoints)
: this()
{
SeedPoints = new List<PointF>(seedPoints);
}
/// <summary>
/// Set of SeedPoints to run the spline through
/// </summary>
public List<PointF> SeedPoints { get; set; }
/// <summary>
/// Generates a smooth curve through a series of points
/// </summary>
/// <param name="numberPointsToGenerate">Number of points to generate between P0 and Pn</param>
/// <returns>IList of Points along the curve</returns>
public IList<PointF> GenerateSpline(int numberPointsToGenerate)
{
List<double> ps = new List<double>();
List<PointF> np = new List<PointF>();
for (int i = 0; i < SeedPoints.Count; i++)
{
ps.Add(SeedPoints[i].X);
ps.Add(SeedPoints[i].Y);
}
double[] newpts = Bezier2D(ps.ToArray(), numberPointsToGenerate);
for (int i = 0; i < newpts.Length; i += 2)
np.Add(new PointF(newpts[i], newpts[i + 1], 0));
return np;
}
private double[] Bezier2D(double[] b, int cpts)
{
double[] p = new double[cpts * 2];
int npts = (b.Length) / 2;
int icount, jcount;
double step, t;
// Calculate points on curve
icount = 0;
t = 0;
step = (double)1 / (cpts - 1);
for (int i1 = 0; i1 < cpts; i1++)
{
if ((1.0 - t) < 5e-6)
t = 1.0;
jcount = 0;
p[icount] = 0.0;
p[icount + 1] = 0.0;
for (int i = 0; i != npts; i++)
{
double basis = Bernstein(npts - 1, i, t);
p[icount] += basis * b[jcount];
p[icount + 1] += basis * b[jcount + 1];
jcount += 2;
}
icount += 2;
t += step;
}
return p;
}
private double Ni(int n, int i)
{
return Factorial(n) / (Factorial(i) * Factorial(n - i));
}
/// <summary>
/// Bernstein basis formula
/// </summary>
/// <param name="n">n</param>
/// <param name="i">i</param>
/// <param name="t">t</param>
/// <returns>Bernstein basis</returns>
private double Bernstein(int n, int i, double t)
{
double basis;
double ti; /* t^i */
double tni; /* (1 - t)^i */
if (t == 0.0 && i == 0)
ti = 1.0;
else
ti = System.Math.Pow(t, i);
if (n == i && t == 1.0)
tni = 1.0;
else
tni = System.Math.Pow((1 - t), (n - i));
//Bernstein basis
basis = Ni(n, i) * ti * tni;
return basis;
}
/// <summary>
/// Gets a single y value for a given x value
/// </summary>
/// <param name="x">X value</param>
/// <returns>Y value at the given location</returns>
public PointF GetPointAlongSpline(double x)
{
return new PointF();
}
public static int Factorial(int n)
{
int f = 1;
for (int i = n; i > 1; i--)
f *= i;
return f;
}
}
I need a function in C# to do the following: move 50 meters from gps-point A in the direction of gps-point B and calculate the GPS-coordinates for that point.
For instance I've got two coordinates:
LatLon LatLonA = new LatLon(51.83966, 5.04631); // Latitude 51.83966, Longitude 5.04631
LatLon LatLonB = new LatLon(51.84172, 5.01961); // Latitude 51.84172, Longitude 5.01961
What I would like is a function like this:
function LatLon MoveTowards(LatLon A, LatLon B, double MetersOverGround)
{
//code here
}
That function would return the coordinate which is x meters away from A in the direction of B.
The Earth is not a sphere, nor even an ellipse. The best you can hope for without purchasing a commercial library would be an approximation (which for most people is good enough).
You could start by looking into the Haversine formula, and this page will be of great help.
Or if you want a commercial library, I have used ProLat with great success
Here is what you want. Just use Math.Atan2 to obtain the bearing of your A-to-B vector and obtain the bearing parameter.
/// <summary>
/// Calculates the end-point from a given source at a given range (meters) and bearing (degrees).
/// This methods uses simple geometry equations to calculate the end-point.
/// </summary>
/// <param name="source">Point of origin</param>
/// <param name="range">Range in meters</param>
/// <param name="bearing">Bearing in degrees</param>
/// <returns>End-point from the source given the desired range and bearing.</returns>
public static PointLatLng CalculateDerivedPosition(PointLatLng source, double range, double bearing)
{
const double DEGREES_TO_RADIANS = Math.PI/180;
const double EARTH_RADIUS_M = 6371000;
double latA = source.Lat * DEGREES_TO_RADIANS;
double lonA = source.Lng * DEGREES_TO_RADIANS;
double angularDistance = range / EARTH_RADIUS_M;
double trueCourse = bearing * DEGREES_TO_RADIANS;
double lat = Math.Asin(
Math.Sin(latA) * Math.Cos(angularDistance) +
Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse));
double dlon = Math.Atan2(
Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA),
Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat));
double lon = ((lonA + dlon + Math.PI) % (Math.PI * 2)) - Math.PI;
return new PointLatLng(
lat / DEGREES_TO_RADIANS,
lon / DEGREES_TO_RADIANS);
}