React native SVG building pie chart with Path - react-native

I have a code that builds a SVG figure for a pie chart, using this function:
export const generateArc = (percentage: number, radius: number) => {
const a = (percentage * 2 * Math.PI) / 100 // angle (in radian) depends on percentage
const r = radius // radius of the circle
const rx = r
const ry = r
const xAxisRotation = 0
let largeArcFlag = 1
const sweepFlag = 1
const x = r + r * Math.sin(a)
const y = r - r * Math.cos(a)
if (percentage <= 50) {
largeArcFlag = 0
} else {
largeArcFlag = 1
}
return `A${rx} ${ry} ${xAxisRotation} ${largeArcFlag} ${sweepFlag} ${x} ${y}`
}
Everything works fine, except when percentage is 100, the chart disappears, you can look here
I feel like that there's some miscalculation, but i don't have much experience in such things with SVGS, and i can't figure it out.
The workarounds I've found are:
iOS: check if percentage is 100, then use 99.9, but that doesn't work for android (i've tested on real device, there's a little gap that's not filled, but on some emulators 99.9999 gives the full circle), you can check it in a snack
For both platform just use a full circle SVG instead, if percentage is 100, but i don't like that workaround

Thanks to #Robert Longson I've modified the function that generates arc a bit, so now it generates 2 arcs if percentage is 100
const generatePath = (percentage: number, radius: number) => {
if (percentage === 100) {
const x = radius
const y = radius
// in path x has to be 0 = starting point
return `M ${0}, ${y}
a ${x},${y} 0 1,1 ${radius * 2},0
a ${x},${y} 0 1,1 -${radius * 2},0
`
}
const a = (percentage * 2 * Math.PI) / 100 // angle (in radian) depends on percentage
const r = radius // radius of the circle
const rx = r
const ry = r
const xAxisRotation = 0
let largeArcFlag = 1
const sweepFlag = 1
const x = r + r * Math.sin(a)
const y = r - r * Math.cos(a)
if (percentage <= 50) {
largeArcFlag = 0
} else {
largeArcFlag = 1
}
return `M${radius} ${radius} L${radius} 0 A${rx} ${ry} ${xAxisRotation} ${largeArcFlag} ${sweepFlag} ${x} ${y} Z`
}

Related

Rounded corners for Arc Progress in react-native-svg

I've created an Arc Progress based on a tutorial (v. https://www.youtube.com/watch?v=UMvw20nRZls) and worked fine
But, since i have no experience using svg path, i'm struggling with my Arc trying to get it's corners rounded
Here is how the Arc's result so far
const { PI, cos, sin } = Math;
const { width } = Dimensions.get('window');
const size = width - 32;
const strokeWidth = 20;
const AnimatedPath = Animated.createAnimatedComponent(Path);
const r = (size - strokeWidth) / 2;
const cx = size / 2;
const cy = size / 2;
const A = PI + PI * 0.4;
const startAngle = PI + PI * 0.2;
const endAngle = 2 * PI - PI * 0.2;
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
const x1 = cx - r * cos(startAngle);
const y1 = -r * sin(startAngle) + cy;
const x2 = cx - r * cos(endAngle);
const y2 = -r * sin(endAngle) + cy;
const d = `M ${x1} ${y1} A ${r} ${r} 0 1 0 ${x2} ${y2}`;
<Path
stroke="#FFF"
fill="none"
strokeDasharray={`${circumference}, ${circumference}`}
{...{ d, strokeWidth }}
/>
And i expect to have the corners like this:
ps: i'm using an expo app, so , i needed to install react-native-svg with expo to work
You could set the strokeLinecap prop on the Path to round:
<Path
stroke="black"
fill="none"
strokeLinecap="round"
strokeDasharray={`${circumference}, ${circumference}`}
{...{d, strokeWidth}}
/>
The strokeLinecap prop specifies the shape to be used at the end of open subpaths when they are stroked. Can be either 'butt', 'square' or 'round'.
Source: https://github.com/react-native-community/react-native-svg#common-props.

How calulate the area of a polygon in react-native-maps?

I am passing the array of co-ordinates to the polygon and want to find the area of the polygon through that co-ordinates. I have checked the documentation of the react-native-maps but there is no function provided.
Is there is any way to calculate the area.
Thanks in advance.
Library will not give you this functionality.
Try this
function calcArea(locations) {
if (!locations.length) {
return 0;
}
if (locations.length < 3) {
return 0;
}
let radius = 6371000;
const diameter = radius * 2;
const circumference = diameter * Math.PI;
const listY = [];
const listX = [];
const listArea = [];
// calculate segment x and y in degrees for each point
const latitudeRef = locations[0].latitude;
const longitudeRef = locations[0].longitude;
for (let i = 1; i < locations.length; i++) {
let latitude = locations[i].latitude;
let longitude = locations[i].longitude;
listY.push(this.calculateYSegment(latitudeRef, latitude, circumference));
listX.push(this.calculateXSegment(longitudeRef, longitude, latitude, circumference));
}
// calculate areas for each triangle segment
for (let i = 1; i < listX.length; i++) {
let x1 = listX[i - 1];
let y1 = listY[i - 1];
let x2 = listX[i];
let y2 = listY[i];
listArea.push(this.calculateAreaInSquareMeters(x1, x2, y1, y2));
}
// sum areas of all triangle segments
let areasSum = 0;
listArea.forEach(area => areasSum = areasSum + area)
// get abolute value of area, it can't be negative
let areaCalc = Math.abs(areasSum);// Math.sqrt(areasSum * areasSum);
return areaCalc;
}
function calculateAreaInSquareMeters(x1, x2, y1, y2) {
return (y1 * x2 - x1 * y2) / 2;
}
function calculateYSegment(latitudeRef, latitude, circumference) {
return (latitude - latitudeRef) * circumference / 360.0;
}
function calculateXSegment(longitudeRef, longitude, latitude, circumference) {
return (longitude - longitudeRef) * circumference * Math.cos((latitude * (Math.PI / 180))) / 360.0;
}
Reference

Calculate vertical bearing between two GPS coordinates with altitudes

I am planning to build an antenna tracker. I need to get bearing and tilt from GPS point A with altitude and GPS point B with altitude.
This is the example points:
latA = 39.099912
lonA = -94.581213
altA = 273.543
latB = 38.627089
lonB = -90.200203
altB = 1380.245
I've already got the formula for horizontal bearing and it gives me 97.89138167122422
This is the code:
function toRadian(num) {
return num * (Math.PI / 180);
}
function toDegree(num) {
return num * (180 / Math.PI);
}
function getHorizontalBearing(fromLat, fromLon, toLat, toLon) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let dLon = toLon - fromLon;
let x = Math.tan(toLat / 2 + Math.PI / 4);
let y = Math.tan(fromLat / 2 + Math.PI / 4);
let dPhi = Math.log(x / y);
if (Math.abs(dLon) > Math.PI) {
if (dLon > 0.0) {
dLon = -(2 * Math.PI - dLon);
} else {
dLon = (2 * Math.PI + dLon);
}
}
return (toDegree(Math.atan2(dLon, dPhi)) + 360) % 360;
}
let n = getHorizontalBearing(39.099912, -94.581213, 38.627089, -90.200203);
console.info(n);
But I don't know how to find the tilt angle. Anyone could help me?
I think I got the answer after searching around.
This is the complete code, if you think this is wrong, feel free to correct me.
function toRadian(num) {
return num * (Math.PI / 180);
}
function toDegree(num) {
return num * (180 / Math.PI);
}
// North is 0 degree, South is 180 degree
function getHorizontalBearing(fromLat, fromLon, toLat, toLon, currentBearing) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let dLon = toLon - fromLon;
let x = Math.tan(toLat / 2 + Math.PI / 4);
let y = Math.tan(fromLat / 2 + Math.PI / 4);
let dPhi = Math.log(x / y);
if (Math.abs(dLon) > Math.PI) {
if (dLon > 0.0) {
dLon = -(2 * Math.PI - dLon);
} else {
dLon = (2 * Math.PI + dLon);
}
}
let targetBearing = (toDegree(Math.atan2(dLon, dPhi)) + 360) % 360;
return targetBearing - currentBearing;
}
// Horizon is 0 degree, Up is 90 degree
function getVerticalBearing(fromLat, fromLon, fromAlt, toLat, toLon, toAlt, currentElevation) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let fromECEF = getECEF(fromLat, fromLon, fromAlt);
let toECEF = getECEF(toLat, toLon, toAlt);
let deltaECEF = getDeltaECEF(fromECEF, toECEF);
let d = (fromECEF[0] * deltaECEF[0] + fromECEF[1] * deltaECEF[1] + fromECEF[2] * deltaECEF[2]);
let a = ((fromECEF[0] * fromECEF[0]) + (fromECEF[1] * fromECEF[1]) + (fromECEF[2] * fromECEF[2]));
let b = ((deltaECEF[0] * deltaECEF[0]) + (deltaECEF[2] * deltaECEF[2]) + (deltaECEF[2] * deltaECEF[2]));
let elevation = toDegree(Math.acos(d / Math.sqrt(a * b)));
elevation = 90 - elevation;
return elevation - currentElevation;
}
function getDeltaECEF(from, to) {
let X = to[0] - from[0];
let Y = to[1] - from[1];
let Z = to[2] - from[2];
return [X, Y, Z];
}
function getECEF(lat, lon, alt) {
let radius = 6378137;
let flatteningDenom = 298.257223563;
let flattening = 0.003352811;
let polarRadius = 6356752.312106893;
let asqr = radius * radius;
let bsqr = polarRadius * polarRadius;
let e = Math.sqrt((asqr-bsqr)/asqr);
// let eprime = Math.sqrt((asqr-bsqr)/bsqr);
let N = getN(radius, e, lat);
let ratio = (bsqr / asqr);
let X = (N + alt) * Math.cos(lat) * Math.cos(lon);
let Y = (N + alt) * Math.cos(lat) * Math.sin(lon);
let Z = (ratio * N + alt) * Math.sin(lat);
return [X, Y, Z];
}
function getN(a, e, latitude) {
let sinlatitude = Math.sin(latitude);
let denom = Math.sqrt(1 - e * e * sinlatitude * sinlatitude);
return a / denom;
}
let n = getHorizontalBearing(39.099912, -94.581213, 39.099912, -94.588032, 0.00);
console.info("Horizontal bearing:\t", n);
let m = getVerticalBearing(39.099912, -94.581213, 273.543, 39.099912, -94.588032, 873.543, 0.0);
console.info("Vertical bearing:\t", m);
Don Cross's javascript code produces good results. It takes into consideration the curvature of the earth plus the fact that the earth is oblate.
Example:
var elDegrees = calculateElevationAngleCosineKitty(
{latitude: 35.346257, longitude: -97.863801, altitudeMetres: 10},
{latitude: 34.450545, longitude: -96.500167, altitudeMetres: 9873}
);
console.log("El: " + elDegrees);
/***********************************
Code by Don Cross at cosinekitty.com
http://cosinekitty.com/compass.html
************************************/
function calculateElevationAngleCosineKitty(source, target)
{
var oblate = true;
var a = {'lat':source.latitude, 'lon':source.longitude, 'elv':source.altitudeMetres};
var b = {'lat':target.latitude, 'lon':target.longitude, 'elv':target.altitudeMetres};
var ap = LocationToPoint(a, oblate);
var bp = LocationToPoint(b, oblate);
var bma = NormalizeVectorDiff(bp, ap);
var elevation = 90.0 - (180.0 / Math.PI)*Math.acos(bma.x*ap.nx + bma.y*ap.ny + bma.z*ap.nz);
return elevation;
}
function NormalizeVectorDiff(b, a)
{
// Calculate norm(b-a), where norm divides a vector by its length to produce a unit vector.
var dx = b.x - a.x;
var dy = b.y - a.y;
var dz = b.z - a.z;
var dist2 = dx*dx + dy*dy + dz*dz;
if (dist2 == 0) {
return null;
}
var dist = Math.sqrt(dist2);
return { 'x':(dx/dist), 'y':(dy/dist), 'z':(dz/dist), 'radius':1.0 };
}
function EarthRadiusInMeters (latitudeRadians) // latitude is geodetic, i.e. that reported by GPS
{
// http://en.wikipedia.org/wiki/Earth_radius
var a = 6378137.0; // equatorial radius in meters
var b = 6356752.3; // polar radius in meters
var cos = Math.cos (latitudeRadians);
var sin = Math.sin (latitudeRadians);
var t1 = a * a * cos;
var t2 = b * b * sin;
var t3 = a * cos;
var t4 = b * sin;
return Math.sqrt ((t1*t1 + t2*t2) / (t3*t3 + t4*t4));
}
function GeocentricLatitude(lat)
{
// Convert geodetic latitude 'lat' to a geocentric latitude 'clat'.
// Geodetic latitude is the latitude as given by GPS.
// Geocentric latitude is the angle measured from center of Earth between a point and the equator.
// https://en.wikipedia.org/wiki/Latitude#Geocentric_latitude
var e2 = 0.00669437999014;
var clat = Math.atan((1.0 - e2) * Math.tan(lat));
return clat;
}
function LocationToPoint(c, oblate)
{
// Convert (lat, lon, elv) to (x, y, z).
var lat = c.lat * Math.PI / 180.0;
var lon = c.lon * Math.PI / 180.0;
var radius = oblate ? EarthRadiusInMeters(lat) : 6371009;
var clat = oblate ? GeocentricLatitude(lat) : lat;
var cosLon = Math.cos(lon);
var sinLon = Math.sin(lon);
var cosLat = Math.cos(clat);
var sinLat = Math.sin(clat);
var x = radius * cosLon * cosLat;
var y = radius * sinLon * cosLat;
var z = radius * sinLat;
// We used geocentric latitude to calculate (x,y,z) on the Earth's ellipsoid.
// Now we use geodetic latitude to calculate normal vector from the surface, to correct for elevation.
var cosGlat = Math.cos(lat);
var sinGlat = Math.sin(lat);
var nx = cosGlat * cosLon;
var ny = cosGlat * sinLon;
var nz = sinGlat;
x += c.elv * nx;
y += c.elv * ny;
z += c.elv * nz;
return {'x':x, 'y':y, 'z':z, 'radius':radius, 'nx':nx, 'ny':ny, 'nz':nz};
}
/***********************
END cosinekitty.com code
************************/

Answer to converting lat/long to x/y on mercator not working for me when changing location from Germany to US

I'm using an answer from Raphael from this post (https://stackoverflow.com/a/10401734/3321095) to convert lat/long to xy coordinates plotted on a mercator map. Raphael's example uses an area in Hamburg, Germany. I tested it and it does work. I then changed it to find a point within the United States but the coordinates are always beyond the size of the image. Can someone help?
<script type="text/javascript">
var mapWidth = 749; //1500;
var mapHeight = 462; //1577;
var mapLonLeft = 125; //9.8;
var mapLonRight = 65 //10.2;
var mapLonDelta = mapLonRight - mapLonLeft;
var mapLatBottom = 25 //53.45;
var mapLatBottomDegree = mapLatBottom * Math.PI / 180;
function convertGeoToPixel(lat, lon)
{
var position = new Array(2);
var x = (lon - mapLonLeft) * (mapWidth / mapLonDelta);
var lat = lat * Math.PI / 180;
var worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
var mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree))));
var y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY);
position[0] = x;
position[1] = y;
return position;
}
var coordinates = convertGeoToPixel(30.274333164300643, -97.74064064025879); //convertGeoToPixel(53.7, 9.95);
alert("x: " + coordinates[0] + " y: " + coordinates[1]);
</script>
Hope you figured this out in the last year. Your code helped me with a similar project. Your code is missing a minus sign and should look like this:
var mapLonLeft = -125; //9.8;
var mapLonRight = -65 //10.2;
Longitude is negative in the USA.

Stopping at a point

I have a space ship that I want to turn to a destination angle. Currently it works like 90% of the time, but sometimes, it 'jumps' to the destination angle rather than moving smoothly. Here is my code:
a = System.Math.Sin(.destStoppingAngle + System.Math.PI)
b = System.Math.Cos(.destStoppingAngle + System.Math.PI)
c = System.Math.Sin(.msngFacing)
d = System.Math.Cos(.msngFacing)
det = a * d - b * c
If det > 0 Then
.msngFacing = .msngFacing - .ROTATION_RATE * TV.TimeElapsed
If det < 0.1 Then
.msngFacing = .destStoppingAngle
.turning = False
End If
Else
.msngFacing = .msngFacing + .ROTATION_RATE * TV.TimeElapsed
If det > 0.1 Then
.msngFacing = .destStoppingAngle
.turning = False
End If
End If
I would do it like this. First you need a function to lerp an angle (C code, port it yourself):
float lerpangle(float from, float to, float frac) {
float a;
if ( to - from > 180 ) {
to -= 360;
}
if ( to - from < -180 ) {
to += 360;
}
a = from + frac * (to - from);
return a;
}
Then, when starting the rotation you have the duration and stoppingangle as your own parameters. Get the startingangle from your object and startingtime (in something decently precise, milliseconds) and save them. The rotation then goes like this:
current_rotation = lerpangle(startingangle, stoppingangle,
(time.now - startingtime) / duration)