I am integrating a WMS with Telerik map. In the Telerik forums, I found some sample code that works correctly until the zoom is great and performing the calculation is incorrect because MaxX and MinX returns the same value and Miny and Maxy returns the same value.
I do not quite understand the functioning of QuadKey, BBox, Tilex, Tiley ... and so as not correct the code. Here I put the sample code provided in your forum telerik.
See if someone sees where this error.
public class WMSCustomSource : TiledMapSource
{
private const string TileUrlFormat = #"http://www1.sedecatastro.gob.es/Cartografia/WMS/ServidorWMS.aspx?SERVICE=WMS&REQUEST=GetMap&SRS=EPSG:4326&BBOX={0},{1},{2},{3}&WIDTH={4}&HEIGHT={4}&Layers=Catastro&TRANSPARENT=TRUE&STYLES=PositionStyle&FORMAT=image/png";
private const int TileSize = 256;
/// <summary>
/// Earth Circumference.
/// </summary>
private double earthCircumference;
private double halfEarthCircumference;
private double earthRadius;
/// <summary>
/// Initializes a new instance of the OSMCustomSource class.
/// </summary>
public WMSCustomSource(ISpatialReference spatialReference)
: base(1, 20, TileSize, TileSize)
{
this.earthRadius = spatialReference.SpheroidRadius;
this.earthCircumference = this.earthRadius * 2 * Math.PI;
this.halfEarthCircumference = this.earthCircumference / 2d;
}
/// <summary>
/// Initialize provider.
/// </summary>
public override void Initialize()
{
// Raise provider intialized event.
this.RaiseIntializeCompleted();
}
/// <summary>
/// Returns the bounding BBox for a grid square represented by the given quad key
/// </summary>
/// <param name="quadKey"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="zoomLevel"></param>
/// <returns></returns>
private BBOX ConvertQuadKeyToBBox(string quadKey, int x, int y, int zoomLevel)
{
char c = quadKey[0];
int tileSize = 2 << (18 - zoomLevel - 1);
if (c == '0')
{
y = y - tileSize;
}
else if (c == '1')
{
y = y - tileSize;
x = x + tileSize;
}
else if (c == '3')
{
x = x + tileSize;
}
if (quadKey.Length > 1)
{
return ConvertQuadKeyToBBox(quadKey.Substring(1), x, y, zoomLevel + 1);
}
return new BBOX(x, y, tileSize, tileSize);
}
private BBOX ConvertQuadKeyToBBox(string quadKey)
{
const int x = 0;
const int y = 262144;
return ConvertQuadKeyToBBox(quadKey, x, y, 1);
}
/// <summary>
/// Converts radians to degrees
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
private double RadiansToDegrees(double radians)
{
return radians / Math.PI * 180d;
}
/// <summary>
/// Converts a grid row to Latitude
/// </summary>
/// <param name="y"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertYToLatitude(int y, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersY = this.halfEarthCircumference - ((double)y * (double)TileSize * arc);
double a = Math.Exp(metersY * 2d / this.earthRadius);
double result = RadiansToDegrees(Math.Asin((a - 1d) / (a + 1d)));
return result;
}
/// <summary>
/// Converts a grid column to Longitude
/// </summary>
/// <param name="x"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertXToLongitude(int x, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersX = ((double)x * (double)TileSize * arc) - this.halfEarthCircumference;
double result = RadiansToDegrees(metersX / this.earthRadius);
return result;
}
private static string GetQuadKey(int tileX, int tileY, int levelOfDetail)
{
var quadKey = new StringBuilder();
for (int i = levelOfDetail; i > 0; i--)
{
char digit = '0';
int mask = 1 << (i - 1);
if ((tileX & mask) != 0)
{
digit++;
}
if ((tileY & mask) != 0)
{
digit++;
digit++;
}
quadKey.Append(digit);
}
return quadKey.ToString();
}
/// <summary>
/// Gets the image URI.
/// </summary>
/// <param name="tileLevel">Tile level.</param>
/// <param name="tilePositionX">Tile X.</param>
/// <param name="tilePositionY">Tile Y.</param>
/// <returns>URI of image.</returns>
protected override Uri GetTile(int tileLevel, int tilePositionX, int tilePositionY)
{
int zoomLevel = ConvertTileToZoomLevel(tileLevel);
string quadKey = GetQuadKey(tilePositionX, tilePositionY, zoomLevel);
BBOX boundingBox = ConvertQuadKeyToBBox(quadKey);
**double longitude = ConvertXToLongitude(boundingBox.x, 18);
double latitude = ConvertYToLatitude(boundingBox.y, 18);
double longitude2 = ConvertXToLongitude(boundingBox.x + boundingBox.width, 18);
double latitude2 = ConvertYToLatitude(boundingBox.y - boundingBox.height, 18);**
string url = string.Format(CultureInfo.InvariantCulture, TileUrlFormat,
longitude, latitude, longitude2, latitude2, TileSize);
return new Uri(url);
}
/// <summary>
/// The box of the bounds of a tile
/// </summary>
private class BBOX
{
public int x;
public int y;
public int width;
public int height;
public BBOX(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
}
The functions that I return the same values when the zoom is high are:
ConvertYToLatitude and ConvertXToLongitude. Although the value I provide to X1 and X2 are different. And the value of Y1 and Y2 are also different.
I do not know if it can be a problem with decimal and double.
Thanks and sorry for my English
I found the solution!
I use another function found in http://alastaira.wordpress.com/2011/01/07/accessing-a-wms-tile-server-from-bing-maps-v7/ to convert the QuadKey in BBox
The final class will read:
public class WMSCustomSource : TiledMapSource
{
private const string TileUrlFormat = #"http://www1.sedecatastro.gob.es/Cartografia/WMS/ServidorWMS.aspx?SERVICE=WMS&REQUEST=GetMap&SRS=EPSG:4326&BBOX={0}&WIDTH={1}&HEIGHT={1}&Layers=Catastro&TRANSPARENT=TRUE&STYLES=PositionStyle&FORMAT=image/png";
private const int TileSize = 256;
/// <summary>
/// Earth Circumference.
/// </summary>
private double earthCircumference;
private double halfEarthCircumference;
private double earthRadius;
/// <summary>
/// Initializes a new instance of the OSMCustomSource class.
/// </summary>
public WMSCustomSource(ISpatialReference spatialReference)
: base(1, 20, TileSize, TileSize)
{
this.earthRadius = spatialReference.SpheroidRadius;
this.earthCircumference = this.earthRadius * 2 * Math.PI;
this.halfEarthCircumference = this.earthCircumference / 2d;
}
/// <summary>
/// Initialize provider.
/// </summary>
public override void Initialize()
{
// Raise provider intialized event.
this.RaiseIntializeCompleted();
}
public string QuadKeyToBBox(string quadKey)
{
int zoom = quadKey.Length;
int x = 0, y = 0;
// Work out the x and y position of this tile
for (int i = zoom; i > 0; i--)
{
int mask = 1 << (i - 1);
switch (quadKey[zoom - i])
{
case '0':
break;
case '1':
x |= mask;
break;
case '2':
y |= mask;
break;
case '3':
x |= mask;
y |= mask;
break;
default:
throw new ArgumentException("Invalid QuadKey digit sequence.");
}
}
// From the grid position and zoom, work out the min and max Latitude / Longitude values of this tile
double W = (float)(x * TileSize) * 360 / (float)(TileSize * Math.Pow(2, zoom)) - 180;
double N = (float)Math.Asin((Math.Exp((0.5 - (y * TileSize) / (TileSize) / Math.Pow(2, zoom)) * 4 * Math.PI) - 1) / (Math.Exp((0.5 - (y * TileSize) / 256 / Math.Pow(2, zoom)) * 4 * Math.PI) + 1)) * 180 / (float)Math.PI;
double E = (float)((x + 1) * TileSize) * 360 / (float)(TileSize * Math.Pow(2, zoom)) - 180;
double S = (float)Math.Asin((Math.Exp((0.5 - ((y + 1) * TileSize) / (TileSize) / Math.Pow(2, zoom)) * 4 * Math.PI) - 1) / (Math.Exp((0.5 - ((y + 1) * TileSize) / 256 / Math.Pow(2, zoom)) * 4 * Math.PI) + 1)) * 180 / (float)Math.PI;
double[] bounds = new double[] { W, S, E, N };
// Return a comma-separated string of the bounding coordinates
return string.Join(",", Array.ConvertAll(bounds, s => s.ToString().Replace(',','.')));
}
/// <summary>
/// Converts radians to degrees
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
private double RadiansToDegrees(double radians)
{
return radians / Math.PI * 180d;
}
/// <summary>
/// Converts a grid row to Latitude
/// </summary>
/// <param name="y"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertYToLatitude(int y, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersY = this.halfEarthCircumference - ((double)y * (double)TileSize * arc);
double a = Math.Exp(metersY * 2d / this.earthRadius);
double result = RadiansToDegrees(Math.Asin((a - 1d) / (a + 1d)));
return result;
}
/// <summary>
/// Converts a grid column to Longitude
/// </summary>
/// <param name="x"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertXToLongitude(int x, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersX = ((double)x * (double)TileSize * arc) - this.halfEarthCircumference;
double result = RadiansToDegrees(metersX / this.earthRadius);
return result;
}
private static string GetQuadKey(int tileX, int tileY, int levelOfDetail)
{
var quadKey = new StringBuilder();
for (int i = levelOfDetail; i > 0; i--)
{
char digit = '0';
int mask = 1 << (i - 1);
if ((tileX & mask) != 0)
{
digit++;
}
if ((tileY & mask) != 0)
{
digit++;
digit++;
}
quadKey.Append(digit);
}
return quadKey.ToString();
}
/// <summary>
/// Gets the image URI.
/// </summary>
/// <param name="tileLevel">Tile level.</param>
/// <param name="tilePositionX">Tile X.</param>
/// <param name="tilePositionY">Tile Y.</param>
/// <returns>URI of image.</returns>
protected override Uri GetTile(int tileLevel, int tilePositionX, int tilePositionY)
{
int zoomLevel = ConvertTileToZoomLevel(tileLevel);
string quadKey = GetQuadKey(tilePositionX, tilePositionY, zoomLevel);
**string bbox = QuadKeyToBBox(quadKey);**
string url = string.Format(CultureInfo.InvariantCulture, TileUrlFormat, bbox, TileSize);
return new Uri(url);
}
}
Related
those are 2 example cases of what I need to solve, it is just finding the coordinate of D, given position of A, and the direction vector of red and green line
red/green line vector (or direction) is known
point A is an intersection between the red line and red circle tangent point
point B is the center of the red circle with radius = R (known)
point C is an intersection between the green line and the green circle tangent point
point D is unknown and this one that needs to be calculated
point D will always located in green circle (radius of 2R from point B)
both red and green line has the same radius of R
V is the angle of the red line relative to north up
W is the angle of the green line relative to north up
the distance between point B and D is always 2R since the circle adjacent (touching each other)
much help and hint appreciated, preferred in some code instead of math equation
Having coordinates A,B,C, we can write two vector equations using scalar (dot) product:
AC.dot.DC = 0
DB.dot.DB = 4*R^2
The first one refers to perpendicularity between tangent to circle and radius to tangency point, the second one - just squared distance between circle centers.
In coordinates:
(cx-ax)*(cx-dx) + (cy-ay)*(cy-dy) = 0
(bx-dx)*(bx-dx) + (by-dy)*(by-dy) = 4*R^2
Solve this system for unknown dx, dy - two solutions in general case.
If A and C are not known, as #Mike 'Pomax' Kamermans noticed:
Let
cr = sin(v) sr = cos(v)
cg = sin(w) sg = cos(w)
So
ax = bx + R * cr
ay = by + R * sr
and
dx = cx - R * cg
dy = cy + R * sg
Substituting expressions into the system above we have:
(dx+R*cg-bx-R*cr)*cg - (dy-R*sg-by-R*sr)*sg = 0
(bx-dx)*(bx-dx) + (by-dy)*(by-dy) = 4*R^2
Again - solve system for unknowns dx, dy
As a hint: draw it out some more:
We can construct D by constructing the line segment AG, for which we know both the angle and length, because AC⟂AG, and the segment has length R.
We can then construct a line perpendicular to AG, through G, which gives us a chord on the blue circle, one endpoint of which is D. We know the distance from B to GD (because we know trigonometry) and we know that the distance BD is 2R (because that's a given). Pythagoras then trivially gives us D.
You know where A is and the angle θ it makes from vertical.
So specify the line though C called line(C) above and the offset the line by R in order to get line(D) above that goes through point D.
In C# code this is
Line line_C = Line.ThroughPointAtAngle(A, theta);
Line line_D = line_C.Offset(radius);
Now find the intersection of this line to the greater circle
Circle circle = new Circle(B, 2 * radius);
if (circle.Intersect(line_D, out Point D, alternate: false))
{
Console.WriteLine(D);
float d_BD = B.DistanceTo(D);
Console.WriteLine(d_BD);
}
else
{
Console.WriteLine("Does not intersect.");
}
This produces point D either above line(C) or below line(C) depending on the bool argument alternate.
The code example below produces the following output:
D=Point(-0.4846499,-1.94039)
|BD|=2
The source code is
Program.cs
using static Float;
static class Program
{
static void Main(string[] args)
{
float radius = 1;
Point A = new Point(-radius, 0);
Point B = new Point(0, 0);
float theta = deg(15);
Line line_C = Line.ThroughPointAtAngle(A, theta);
Line line_D = line_C.Offset(radius);
Circle circle = new Circle(B, 2 * radius);
if (circle.Intersect(line_D, out Point D, alternate: false))
{
Console.WriteLine($"D={D}");
float d_BD = B.DistanceTo(D);
Console.WriteLine($"|BD|={d_BD}");
}
else
{
Console.WriteLine("Does not intersect.");
}
}
}
Point.cs
Describes a point in cartesian space using two coordinates (x,y)
using static Float;
public readonly struct Point
{
readonly (float x, float y) data;
public Point(float x, float y)
{
this.data = (x, y);
}
public static Point Origin { get; } = new Point(0, 0);
public static Point FromTwoLines(Line line1, Line line2)
{
float x = line1.B * line2.C - line1.C * line2.B;
float y = line1.C * line2.A - line1.A * line2.C;
float w = line1.A * line2.B - line1.B * line2.A;
return new Point(x / w, y / w);
}
public float X => data.x;
public float Y => data.y;
public float SumSquares => data.x * data.x + data.y * data.y;
#region Algebra
public static Point Negate(Point a)
=> new Point(
-a.data.x,
-a.data.y);
public static Point Scale(float factor, Point a)
=> new Point(
factor * a.data.x,
factor * a.data.y);
public static Point Add(Point a, Point b)
=> new Point(
a.data.x + b.data.x,
a.data.y + b.data.y);
public static Point Subtract(Point a, Point b)
=> new Point(
a.data.x - b.data.x,
a.data.y - b.data.y);
public static float Dot(Point point, Line line)
=> line.A * point.data.x + line.B * point.data.y + line.C;
public static Point operator +(Point a, Point b) => Add(a, b);
public static Point operator -(Point a) => Negate(a);
public static Point operator -(Point a, Point b) => Subtract(a, b);
public static Point operator *(float f, Point a) => Scale(f, a);
public static Point operator *(Point a, float f) => Scale(f, a);
public static Point operator /(Point a, float d) => Scale(1 / d, a);
#endregion
#region Geometry
public Point Offset(float dx, float dy)
=> new Point(data.x + dx, data.y + dy);
public Point Offset(Vector2 delta) => Offset(delta.X, delta.Y);
public float DistanceTo(Point point)
=> sqrt(sqr(data.x - point.data.x) + sqr(data.y - point.data.y));
#endregion
#region Formatting
public string ToString(string formatting, IFormatProvider provider)
{
return $"Point({data.x.ToString(formatting, provider)},{data.y.ToString(formatting, provider)})";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
#endregion
}
Line.cs
Describes a line in cartesian space using the coefficients (a,b,c) such that the equation of the line is a x + b y + c = 0
using static Float;
public readonly struct Line
{
readonly (float a, float b, float c) data;
public Line(float a, float b, float c) : this()
{
data = (a, b, c);
}
public static Line AlongX { get; } = new Line(0, 1, 0);
public static Line AlongY { get; } = new Line(-1, 0, 0);
public static Line ThroughPointAtAngle(Point point, float angle)
{
return new Line(cos(angle), -sin(angle), point.Y * sin(angle) - point.X * cos(angle));
}
public static Line ThroughTwoPoints(Point point1, Point point2)
=> new Line(
point1.Y - point2.Y,
point2.X - point1.X,
point1.X * point2.Y - point1.Y * point2.X);
public float A => data.a;
public float B => data.b;
public float C => data.c;
#region Algebra
public static float Dot(Line line, Point point)
=> line.data.a * point.X + line.data.b * point.Y + line.data.c;
#endregion
#region Geometry
public Line ParallelThrough(Point point)
{
return new Line(data.a, data.b, -data.a * point.X - data.b * point.Y);
}
public Line PerpendicularThrough(Point point)
{
return new Line(data.b, -data.a, -data.b * point.X + data.a * point.Y);
}
public Line Offset(float amount)
=> new Line(data.a, data.b, data.c - amount * sqrt(sqr(data.a) + sqr(data.b)));
public Line Offset(float dx, float dy)
=> new Line(data.a, data.b, data.c + data.a * dx + data.b * dy);
public Line Offset(Vector2 delta) => Offset(delta.X, delta.Y);
public float DistanceTo(Point point)
=> Dot(this, point) / (data.a * data.a + data.b * data.b);
#endregion
#region Formatting
public string ToString(string formatting, IFormatProvider provider)
{
return $"Line({data.a.ToString(formatting, provider)}x+{data.b.ToString(formatting, provider)}y+{data.c.ToString(formatting, provider)}=0)";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
#endregion
}
Circle.cs
Describes a circle using the center and radius.
using static Float;
public readonly struct Circle
{
readonly (Point center, float radius) data;
public Circle(Point center, float radius)
{
this.data = (center, radius);
}
public static Circle FromTwoPoints(Point point1, Point point2)
{
float radius = point1.DistanceTo(point2) / 2;
Point center = (point1 + point2) / 2;
return new Circle(center, radius);
}
public static Circle FromThreePoints(Point point1, Point point2, Point point3)
{
float k_1 = point1.SumSquares / 2;
float k_2 = point2.SumSquares / 2;
float k_3 = point3.SumSquares / 2;
float dx_12 = point2.X - point1.X;
float dy_12 = point2.Y - point1.Y;
float dx_23 = point3.X - point2.X;
float dy_23 = point3.Y - point2.Y;
float det = dx_12 * dy_23 - dx_23 * dy_12;
Point center = new Point(
(dy_12 * (k_2 - k_3) + dy_23 * (k_2 - k_1)) / det,
(dx_12 * (k_3 - k_2) + dx_23 * (k_1 - k_2)) / det);
float radius = center.DistanceTo(point1);
return new Circle(center, radius);
}
public Point Center => data.center;
public float Radius => data.radius;
#region Geometry
public float DistanceTo(Point point)
=> data.center.DistanceTo(point) - data.radius;
public float DistanceTo(Line line)
{
float d = line.DistanceTo(Center);
if (d > 0)
{
return d - data.radius;
}
else
{
return d + data.radius;
}
}
public bool Intersect(Line line, out Point point, bool alternate = false)
{
line = line.Offset(-Center.X, -Center.Y);
int sign = alternate ? -1 : 1;
float discr = sqr(line.A * data.radius) + sqr(line.B * data.radius) - sqr(line.C);
if (discr >= 0)
{
float d = sign * sqrt(discr);
float ab = line.A * line.A + line.B * line.B;
point = new Point((line.B * d - line.A * line.C) / ab, -(line.A * d + line.B * line.C) / ab);
point += Center;
return true;
}
else
{
float ab = line.A * line.A + line.B * line.B;
point = new Point((-line.A * line.C) / ab, -(+line.B * line.C) / ab);
point += Center;
return false;
}
}
#endregion
#region Formatting
public string ToString(string formatting, IFormatProvider provider)
{
return $"Circle({data.center.ToString(formatting, provider)},{data.radius.ToString(formatting, provider)})";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
#endregion
}
Float.cs
Helper functions dealing with float math which is lacking from System.Math.
public static class Float
{
/// <summary>
/// A factor of π.
/// </summary>
/// <param name="x">The factor.</param>
public static float pi(float x) => (float)(Math.PI * x);
/// <summary>
/// Degree to Radian conversion
/// </summary>
/// <param name="x">The angle in degrees.</param>
/// <returns>Angle in radians</returns>
public static float deg(float x) => pi(x) / 180;
/// <summary>
/// Radian to Degree conversion
/// </summary>
/// <param name="x">The angle in radians.</param>
/// <returns>Angle in degrees</returns>
public static float rad(float x) => x * 180 / pi(1);
public static float sqr(float x) => x * x;
public static float sqrt(float x) => (float)Math.Sqrt(x);
public static float sin(float x) => (float)Math.Sin(x);
public static float cos(float x) => (float)Math.Cos(x);
}
thx for all the answer (will upvote them), I did the work on it myself, I will share my result :
and here is length of d, to solve the equation :
I'm trying to convert a working VB class to Java, but it's giving incorrect results.
This is the VB class:
Public Class Triangle
Public A As PointF
Public B As PointF
Public C As PointF
''' <summary>
'''
''' </summary>
''' <param name="sideA">The length of the known side BC</param>
''' <param name="sideB">The length of the known side AC</param>
''' <param name="sideC">The length of the known side AB</param>
''' <param name="angleA">The internal angle in Radians at vertex A</param>
''' <param name="angleB">The internal angle in Radians at vertex B</param>
''' <param name="angleC">The internal angle in Radians at vertex C</param>
''' <remarks></remarks>
Public Sub New(ByVal sideA As Decimal, ByVal sideB As Decimal, ByVal sideC As Decimal, ByVal angleA As Decimal, ByVal angleB As Decimal, ByVal angleC As Decimal)
Dim bX As Decimal = CDec(Math.Cos(angleA) * sideC)
Dim bY As Decimal = CDec(Math.Sin(angleA) * sideC)
Me.A = PointF.Empty
Me.C = PointF.Add(A, New SizeF(sideB, 0))
Me.B = New PointF(bX, -bY)
If bX < 0 Then
Me.A = PointF.Add(Me.A, New SizeF(-bX, 0))
Me.B = PointF.Add(Me.B, New SizeF(-bX, 0))
Me.C = PointF.Add(Me.C, New SizeF(-bX, 0))
End If
End Sub
Public Sub ScaleToFit(ByVal maxWidthOrHeight As Decimal)
Dim xCoords() As Single = {Me.A.X, Me.B.X, Me.C.X}
Dim OverallWidth As Decimal = CDec(xCoords.Max - xCoords.Min)
Dim OverallHeight As Decimal = CDec(Math.Abs(Me.B.Y)) 'B.Y is negative owing to GDI+ coordinates
Dim scaleFactor As Decimal = If(OverallWidth > OverallHeight, _
maxWidthOrHeight / OverallWidth, _
maxWidthOrHeight / OverallHeight)
Scale(scaleFactor)
centreTriangle(25, 300)
End Sub
Private Sub Scale(ByVal scaleFactor As Decimal)
Me.A = ScalePointF(Me.A, scaleFactor)
Me.B = ScalePointF(Me.B, scaleFactor)
Me.C = ScalePointF(Me.C, scaleFactor)
End Sub
Private Function ScalePointF(ByVal pf As PointF, ByVal factor As Decimal) As PointF
Return New PointF(pf.X * factor, pf.Y * factor)
End Function
Private Sub centreTriangle(ByVal border As Integer, ByVal displaySize As Integer)
If B.Y > A.Y Then B.Y -= ((B.Y - A.Y) * 2)
Dim pts() As PointF = New PointF() {A, B, C}
Dim offset_X As Integer = pts.Min(Function(p) CInt(p.X)) - border
Dim offset_Y As Integer = pts.Max(Function(p) CInt(p.Y)) - (displaySize - border)
A = New PointF(A.X - offset_X, A.Y - offset_Y)
B = New PointF(B.X - offset_X, B.Y - offset_Y)
C = New PointF(C.X - offset_X, C.Y - offset_Y)
End Sub
End Class
This is the triangle the VB class plots:
Triangle plotted with VB class
This is the Java class:
public class Triangle
{
private Point A;
private Point B;
private Point C;
/**
* #return Point A
*/
public Point getA()
{
return this.A;
}
/**
* #return Point B
*/
public Point getB()
{
return this.B;
}
/**
* #return Point C
*/
public Point getC()
{
return this.C;
}
/**
#param sideA The length of the known side BC
#param sideB The length of the known side AC
#param sideC The length of the known side AB
#param angleA The internal angle in Radians at vertex A
#param angleB The internal angle in Radians at vertex B
#param angleC The internal angle in Radians at vertex C
*/
public Triangle(float sideA, float sideB, float sideC, float angleA, float angleB, float angleC)
{
float bX = (float)(Math.cos(angleA) * sideC);
float bY = (float)(Math.sin(angleA) * sideC);
this.A = new Point(0, 0);
this.C = new Point((int)(this.A.getX() + sideB), (int)this.A.getY());
this.B = new Point((int)bX, (int)-bY);
if (bX < 0)
{
this.A = new Point((int)(this.A.getX() - bX), (int)this.A.getY());
this.B = new Point((int)(this.B.getX() - bX), (int)this.B.getY());
this.C = new Point((int)(this.C.getX() - bX), (int)this.C.getY());
}
}
public final void ScaleToFit(float maxWidthOrHeight)
{
float[] xCoords = {(float)this.getA().getX(), (float)this.getB().getX(), (float)this.getC().getX()};
float min = 10000;
float max = -1;
for(int x = 0; x < 3; x++) {
if(xCoords[x] < min) {min = xCoords[x];}
if(xCoords[x] > max) {max = xCoords[x];}
}
float OverallWidth = (float)(max - min);
float OverallHeight = (float)Math.abs(this.getB().getY()); //B.Y is negative owing to Graphics coordinates
float scaleFactor = (float)(OverallWidth > OverallHeight ? maxWidthOrHeight / OverallWidth : maxWidthOrHeight / OverallHeight);
Scale(scaleFactor);
centreTriangle(25, 300);
}
private void Scale(float scaleFactor)
{
this.A = ScalePoint(this.A, scaleFactor);
this.B = ScalePoint(this.B, scaleFactor);
this.C = ScalePoint(this.C, scaleFactor);
}
private Point ScalePoint(Point p, float factor)
{
return new Point((int)(p.getX() * factor), (int)(p.getY() * factor));
}
private void centreTriangle(int border, int displaySize)
{
int y1 = (int)this.A.getY();
int y2 = (int)this.B.getY();
if(y2 > y1) {y2 -= ((y2 - y1) * 2);}
this.B = new Point((int)this.B.getX(), y2);
Point[] pts = new Point[] {this.A, this.B, this.C};
float min = 10000;
float max = -1;
for(int x = 0; x < 3; x++) {
if(pts[x].getX() < min) {min = (float)pts[x].getX();}
if(pts[x].getY() > max) {max = (float)pts[x].getY();}
}
int offset_X = (int)(min - border);
int offset_Y = (int)(max - (displaySize - border));
this.A = new Point((int)(this.A.getX() - offset_X), (int)(this.A.getY() - offset_Y));
this.B = new Point((int)(this.B.getX() - offset_X), (int)(this.B.getY() - offset_Y));
this.C = new Point((int)(this.C.getX() - offset_X), (int)(this.C.getY() - offset_Y));
}
}
This doesn't plot the triangle properly. This is a triangle plotted by the Java class, with the same inputs as the VB class:
Same triangle plotted with Java class
The answer is that converting float values to int was causing a noticeable difference. This is the fixed code...
package html.triangle;
import java.awt.Point;
import javax.swing.JOptionPane;
import html.shared.points.*;
/**
*
* #author Paul Long
*/
public class Triangle
{
private PointF A;
private PointF B;
private PointF C;
/**
* #return Point A
*/
public Point getA()
{
return new Point((int)this.A.getX(), (int)this.A.getY());
}
/**
* #return Point B
*/
public Point getB()
{
return new Point((int)this.B.getX(), (int)this.B.getY());
}
/**
* #return Point C
*/
public Point getC()
{
return new Point((int)this.C.getX(), (int)this.C.getY());
}
/**
#param sideA The length of the known side BC
#param sideB The length of the known side AC
#param sideC The length of the known side AB
#param angleA The internal angle in Radians at vertex A
#param angleB The internal angle in Radians at vertex B
#param angleC The internal angle in Radians at vertex C
*/
public Triangle(float sideA, float sideB, float sideC, float angleA, float angleB, float angleC)
{
float bX = (float)(Math.cos(angleA) * sideC);
float bY = (float)(Math.sin(angleA) * sideC);
this.A = new PointF(0, 0);
this.C = new PointF(this.A.getX() + sideB, this.A.getY());
this.B = new PointF(bX, bY);
if (bX < 0)
{
this.A = new PointF((this.A.getX() + Math.abs(bX)), this.A.getY());
this.B = new PointF((this.B.getX() + Math.abs(bX)), this.B.getY());
this.C = new PointF((this.C.getX() + Math.abs(bX)), this.C.getY());
}
}
public final void ScaleToFit(float maxWidthOrHeight)
{
float[] xCoords = {(float)this.A.getX(), (float)this.B.getX(), (float)this.C.getX()};
float min = 10000;
float max = -1;
for(int x = 0; x < 3; x++) {
if(xCoords[x] < min) {min = xCoords[x];}
if(xCoords[x] > max) {max = xCoords[x];}
}
float OverallWidth = (float)(max - min);
float[] yCoords = {(float)this.A.getY(), (float)this.B.getY(), (float)this.C.getY()};
min = 10000;
max = -1;
for(int x = 0; x < 3; x++) {
if(yCoords[x] < min) {min = yCoords[x];}
if(yCoords[x] > max) {max = yCoords[x];}
}
float OverallHeight = (float)(max - min);
float scaleFactor = (float)(OverallWidth > OverallHeight ? maxWidthOrHeight / OverallWidth : maxWidthOrHeight / OverallHeight);
Scale(scaleFactor);
centreTriangle(25, 300);
}
private void Scale(float scaleFactor)
{
this.A = ScalePoint(this.A, scaleFactor);
this.B = ScalePoint(this.B, scaleFactor);
this.C = ScalePoint(this.C, scaleFactor);
}
private PointF ScalePoint(PointF p, float factor)
{
return new PointF((p.getX() * factor), (p.getY() * factor));
}
private void centreTriangle(int border, int displaySize)
{
int y1 = (int)this.A.getY();
int y2 = (int)this.B.getY();
if(y2 > y1) {y2 -= ((y2 - y1) * 2);}
this.B = new PointF(this.B.getX(), y2);
PointF[] pts = new PointF[] {this.A, this.B, this.C};
float min = 10000;
float max = -1;
for(int x = 0; x < 3; x++) {
if(pts[x].getX() < min) {min = (float)pts[x].getX();}
if(pts[x].getY() > max) {max = (float)pts[x].getY();}
}
int offset_X = (int)(min - border);
int offset_Y = (int)(max - (displaySize - border));
this.A = new PointF((this.A.getX() - offset_X), (this.A.getY() - offset_Y));
this.B = new PointF((this.B.getX() - offset_X), (this.B.getY() - offset_Y));
this.C = new PointF((this.C.getX() - offset_X), (this.C.getY() - offset_Y));
}
}
package html.shared.points;
/**
*
* #author Paul Long
*/
public class PointF
{
private float X;
private float Y;
/**
* #return the X
*/
public float getX()
{
return X;
}
/**
* #return the Y
*/
public float getY()
{
return Y;
}
public PointF(float X, float Y) {
this.X = X;
this.Y = Y;
}
}
I have created wafermap chart. I want to make chips(die) in wafer selectable on mouse click and insert labels as well lines from one chip to another. Anyone expert in jfree chart?
wafermap chart
Here are the basic pieces for a tooltip listener for wafer maps, which is a form of selecting the die. Add the following to WaferMapPlot:
public String findChipAtPoint(double x, double y, Rectangle2D plotArea){
double[] xValues = this.getChipXValues(plotArea, dataset.getMaxChipX()
+ 2, dataset.getChipSpace());
double startX = xValues[1];
double chipWidth = xValues[0];
int ychips = this.dataset.getMaxChipY()+ 2;
double[] yValues = this.getChipYValues(plotArea, ychips,
dataset.getChipSpace());
double startY = yValues[1];
double chipHeight = yValues[0];
double chipSpace = dataset.getChipSpace();
int chipX = (int)Math.floor((x - startX + chipWidth + chipSpace) /
(chipWidth + chipSpace));
int chipY = (int)Math.floor((y - startY + chipHeight + chipSpace) /
(chipHeight + chipSpace));
chipX = chipX - dataset.getXOffset() - 1;
chipY = ychips - chipY - dataset.getYOffset() - 1;
StringBuilder sb = new StringBuilder("(");
Number value = dataset.getChipValue(chipX, chipY);
if (value instanceof Double)
value = value.intValue();
sb.append(chipX).append(",").append(chipY).append(") ").append(
(value == null) ? "" : value.toString());
return sb.toString();
}
Then make a subclass of ChartPanel that will be the listener:
public class WaferMapChartPanel extends ChartPanel {
WaferMapPlot waferPlot = null;
WaferMapDataset dataSet = null;
public WaferMapChartPanel(JFreeChart chart){
super(chart);
waferPlot = (WaferMapPlot)chart.getPlot();
if (waferPlot != null)
dataSet = waferPlot.getDataset();
}
/**
* Returns a string for the tooltip.
* #param e the mouse event.
* #return A tool tip or <code>null</code> if no tooltip is available.
*/
#Override
public String getToolTipText(MouseEvent e) {
if (waferPlot != null){
Object source = e.getSource();
if (source instanceof WaferMapChartPanel){
WaferMapChartPanel chartSource= (WaferMapChartPanel)e.getSource();
Rectangle2D plotArea = chartSource.getChartRenderingInfo().getPlotInfo().getPlotArea();
Insets insets = this.getInsets();
double x = (e.getX() - insets.left) / this.getScaleX();
double y = (e.getY() - insets.top) / this.getScaleY();
return waferPlot.findChipAtPoint(x, y, plotArea);
}
}
return "";
}
}
This creates a tooltip of the die's x,y and bin or whatever value you are using instead of bin.
I am trying to determine if a point is between two angles coming from an original point (in order to determine whether or not to draw it with OpenGL, although that is irrelevant). What is the easiest way of doing this?
If absolute value of angles CAB + BAD = 45, then point is inside. If CAB + BAD > 45, then point is outside.
The 2-dimensional cross-product of 2 vectors u = (ux, uy), v = (vx, vy), is
u x v = ux * vy - uy * vx = |u| * |v| * sin(phi)
where phi is the angle between u to v (measured from u to v). The cross-product is positive if the angle is between 0 and 180 degrees.
Therefore
(B - A) x (D - A) > 0
if B lies in the half plane "left of" the vector from A to D, and thus
(B - A) x (D - A) > 0 and (B - A) x (C - A) < 0
exactly if B lies in the sector. If you want also to catch the case that B lies on the boundary of the sector, use >= resp. <=.
(Note: This works as long as the angle of the sector at A is less than 180 degrees, and can probably generalized for greater angles. Since your angle is 45 degrees, these formulas can be used.)
If you have point coordinates and no angles then use polar coordinates to convert [X,Y] -> [R,Theta] (Radius and Angle) relative to center (A in your figure) and then compare angles (thetas).
This code converts Point to PolarPoint relative to center point:
/// <summary>
/// Converts Point to polar coordinate point
/// </summary>
public static PolarPoint PointToPolarPoint(Point center, Point point)
{
double dist = Distance(center, point);
double theta = Math.Atan2(point.Y - center.Y, point.X - center.X);
if (theta < 0) // provide 0 - 2Pi "experience"
theta = 2 * Math.PI + theta;
return new PolarPoint(dist, theta);
}
/// <summary>
/// Calculates distance between two points
/// </summary>
public static int Distance(Point p1, Point p2)
{
return (int) Math.Sqrt
(
Math.Pow(p1.X - p2.X, 2) +
Math.Pow(p1.Y - p2.Y, 2)
);
}
The Polar Point Class in C# (That includes conversion back to Point):
/* NFX by ITAdapter
* Originated: 2006.01
* Revision: NFX 0.2 2009.02.10
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace NFX.Geometry
{
/// <summary>
/// Represents a point with polar coordinates
/// </summary>
public struct PolarPoint
{
#region .ctor
/// <summary>
/// Initializes polar coordinates
/// </summary>
public PolarPoint(double r, double theta)
{
m_R = r;
m_Theta = 0;
Theta = theta;
}
/// <summary>
/// Initializes polar coordinates from 2-d cartesian coordinates
/// </summary>
public PolarPoint(Point center, Point point)
{
this = CartesianUtils.PointToPolarPoint(center, point);
}
#endregion
#region Private Fields
private double m_R;
private double m_Theta;
#endregion
#region Properties
/// <summary>
/// R coordinate component which is coordinate distance from point of coordinates origin
/// </summary>
public double R
{
get { return m_R; }
set { m_R = value; }
}
/// <summary>
/// Angular azimuth coordinate component. An angle must be between 0 and 2Pi.
/// Note: Due to screen Y coordinate going from top to bottom (in usual orientation)
/// Theta angle may be reversed, that is - be positive in the lower half coordinate plane.
/// Please refer to:
/// http://en.wikipedia.org/wiki/Polar_coordinates
/// </summary>
public double Theta
{
get { return m_Theta; }
set
{
if ((value < 0) || (value > Math.PI * 2))
throw new NFXException("Invalid polar coordinates angle");
m_Theta = value;
}
}
/// <summary>
/// Returns polar coordinate converted to 2-d cartesian coordinates.
/// Coordinates are relative to 0,0 of the angle base vertex
/// </summary>
public Point Point
{
get
{
int x = (int)(m_R * Math.Cos(m_Theta));
int y = (int)(m_R * Math.Sin(m_Theta));
return new Point(x, y);
}
}
#endregion
#region Operators
public static bool operator ==(PolarPoint left, PolarPoint right)
{
return (left.m_R == right.m_R) && (left.m_Theta == right.m_Theta);
}
public static bool operator !=(PolarPoint left, PolarPoint right)
{
return (left.m_R != right.m_R) || (left.m_Theta != right.m_Theta);
}
#endregion
#region Object overrides
public override bool Equals(object obj)
{
if (obj is PolarPoint)
return this==((PolarPoint)obj);
else
return false;
}
public override int GetHashCode()
{
return m_R.GetHashCode() + m_Theta.GetHashCode();
}
public override string ToString()
{
return string.Format("Distance: {0}; Angle: {1} rad.", m_R, m_Theta);
}
#endregion
}
}
I eventually got it down to this function (where cameraYR is the angle that the point A is rotated at, cameraX is A.x, cameraY is A.y, x is B.x and y is B.y):
float cameraAngle = PI + cameraYR;
float angle = PI / 2 + atan2f(cameraY - y, cameraX - x);
float anglediff = fmodf(angle - cameraAngle + PI, PI * 2) - PI;
return (anglediff <= visibleAngle && anglediff >= -visibleAngle) || (anglediff <= -PI * 2 + visibleAngle && angleDiff >= -PI * 2 - visibleAngle);
I just answered a similar question. Find if an angle is between 2 angles (which also solves your problem). Check below
Is angle in between two angles
Hope it helps
I am trying to implement a UI like this one..
http://www.shrenikvikam.com/wp-content/uploads/2011/04/214e422a43E11S3.png-150x134.png
But i am having some trouble implementing this.. Could someone tell me mistakes in this...
public class Meter extends View{
static final int ORBIT_COLOR = Color.argb(255, 66, 66, 66);
static final double RAD_CIRCLE = 2*Math.PI; // Number radians in a circle
private Paint paint; // Paint object controlling format of screen draws
private ShapeDrawable planet; // Planet symbol
private int planetRadius = 7; // Radius of spherical planet (pixels)
private int sunRadius = 12; // Radius of Sun (pixels)
private float X0 = 0; // X offset from center (pixels)
private float Y0 = 0; // Y offset from center (pixels)
private float X; // Current X position of planet (pixels)
private float Y; // Current Y position of planet (pixels)
private float centerX; // X for center of display (pixels)
private float centerY; // Y for center of display (pixels)
private float R0; // Radius of circular orbit (pixels)
private int nsteps = 120; // Number animation steps around circle
private double theta; // Angle around orbit (radians)
private double dTheta; // Angular increment each step (radians)
private double direction = -1; // Direction: counter-clockwise -1; clockwise +1
private float lastTouchX; // x coordinate of symbol i at last touch
private float lastTouchY; // x coordinate of symbol i at last touch
private int divisions = 120; // Since it requires temperature change from 0 -120
private double oneSegmentLength = (2 * Math.PI * R0)/(double)120;
public Meter(Context context) {
super(context);
// Initialize angle and angle step (in radians)
theta = 30;
//dTheta = RAD_CIRCLE/((double) nsteps); // Angle increment in radians
dTheta = ((360-60)/(double)divisions);
planet = new ShapeDrawable(new OvalShape());
planet.getPaint().setColor(Color.WHITE);
planet.setBounds(0, 0, 2*planetRadius, 2*planetRadius);
paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(14);
paint.setStrokeWidth(1);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
// MotionEvent class constant signifying a finger-down event
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
lastTouchX = x;
lastTouchY = y;
newXY();
break;
}
// MotionEvent class constant signifying a finger-drag event
case MotionEvent.ACTION_MOVE: {
float newX = ev.getX();
float newY = ev.getY();
float dx = newX - lastTouchX;
float dy = newY - lastTouchY;
int diff = (int) (Math.abs(ev.getX()) % Math.abs(oneSegmentLength));
if(diff == 0){
if(Math.abs(dx) > Math.abs(dy)) {
if(dx>0) direction = 1;
else direction = -1;
newXY();
} else {
newXY();
}
Log.d("MOVE", "dx ->" + dx + " one seg->" + oneSegmentLength);
invalidate();
}
break;
}
// MotionEvent class constant signifying a finger-up event
case MotionEvent.ACTION_UP: {
Log.d("ACTION MOVE","Value ->");
final float x = ev.getX();
final float y = ev.getY();
lastTouchX = x;
lastTouchY = y;
newXY();
break;
}
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(paint, canvas);
canvas.save();
canvas.translate(X + X0, Y + Y0);
planet.draw(canvas);
canvas.restore();
}
// Called by onDraw to draw the background
private void drawBackground(Paint paint, Canvas canvas){
paint.setColor(Color.YELLOW);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(centerX + X0, centerY + Y0, sunRadius, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(ORBIT_COLOR);
canvas.drawCircle(centerX + X0, centerY + Y0, R0, paint);
}
//Method to increment angle theta and compute the new X and Y .
private void newXY(){
theta += dTheta;
Log.d("THETA VAL", "->" + theta);
//if(theta > RAD_CIRCLE) theta -= RAD_CIRCLE; // For convenience, keep angle 0-2pi
if(theta > 150)theta = 30;
if(theta > 30 && theta <120){
X = (float)(R0*Math.sin(direction*theta)) + centerX - planetRadius;
Y = centerY - (float)(R0*Math.cos(direction*theta)) - planetRadius;
}
//Log.i("ANIMATOR", "X="+X+" Y="+Y);
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh){
// Coordinates for center of screen
centerX = w/2;
centerY = h/2;
// Make orbital radius a fraction of minimum of width and height of display
R0 = (float) (0.90*Math.min(centerX, centerY));
oneSegmentLength = (2 * Math.PI * R0)/(double)120;
// Set the initial position of the planet (translate by planetRadius so center of planet
// is at this position)
X = centerX - planetRadius ;
Y = centerY - R0 - planetRadius;
}
}
I am referring this code to do this implementation...
http://eagle.phys.utk.edu/guidry/android/animatorDemo.html
I am just drawing a circle and trying to implement the same motion between 0 -120 degrees..