11import {
2- degreesToRadians ,
32 polygon ,
43 isObject ,
54 isNumber ,
65 Coord ,
76 Units ,
7+ point ,
8+ radiansToDegrees ,
89} from "@turf/helpers" ;
9- import { rhumbDestination } from "@turf/rhumb- destination" ;
10+ import { destination } from "@turf/destination" ;
1011import { transformRotate } from "@turf/transform-rotate" ;
1112import { getCoord } from "@turf/invariant" ;
12- import { GeoJsonProperties , Feature , Polygon } from "geojson" ;
13+ import { GeoJsonProperties , Feature , Polygon , Position } from "geojson" ;
1314
1415/**
1516 * Takes a {@link Point} and calculates the ellipse polygon given two semi-axes expressed in variable units and steps for precision.
@@ -47,12 +48,11 @@ function ellipse(
4748) : Feature < Polygon > {
4849 // Optional params
4950 options = options || { } ;
50- const steps = options . steps || 64 ;
51+ let steps = options . steps || 64 ;
5152 const units = options . units || "kilometers" ;
52- const angle = options . angle || 0 ;
53+ let angle = options . angle || 0 ;
5354 const pivot = options . pivot || center ;
5455 const properties = options . properties || { } ;
55-
5656 // validation
5757 if ( ! center ) throw new Error ( "center is required" ) ;
5858 if ( ! xSemiAxis ) throw new Error ( "xSemiAxis is required" ) ;
@@ -61,62 +61,99 @@ function ellipse(
6161 if ( ! isNumber ( steps ) ) throw new Error ( "steps must be a number" ) ;
6262 if ( ! isNumber ( angle ) ) throw new Error ( "angle must be a number" ) ;
6363
64- const centerCoords = getCoord ( center ) ;
65- if ( units !== "degrees" ) {
66- const xDest = rhumbDestination ( center , xSemiAxis , 90 , { units } ) ;
67- const yDest = rhumbDestination ( center , ySemiAxis , 0 , { units } ) ;
68- xSemiAxis = getCoord ( xDest ) [ 0 ] - centerCoords [ 0 ] ;
69- ySemiAxis = getCoord ( yDest ) [ 1 ] - centerCoords [ 1 ] ;
70- }
64+ const centerCoords = getCoord (
65+ transformRotate ( point ( getCoord ( center ) ) , angle , { pivot } )
66+ ) ;
67+
68+ angle = - 90 + angle ;
69+
70+ // Divide steps by 4 for one quadrant
71+ steps = Math . ceil ( steps / 4 ) ;
72+
73+ let quadrantParameters = [ ] ;
74+ let parameters = [ ] ;
75+
76+ const a = xSemiAxis ;
77+ const b = ySemiAxis ;
78+
79+ // Gradient x intersect
80+ const c = b ;
81+
82+ // Gradient of line
83+ const m = ( a - b ) / ( Math . PI / 2 ) ;
84+
85+ // Area under line
86+ const A = ( ( a + b ) * Math . PI ) / 4 ;
87+
88+ // Weighting function
89+ const v = 0.5 ;
90+
91+ const k = steps ;
7192
72- const coordinates : number [ ] [ ] = [ ] ;
73- for ( let i = 0 ; i < steps ; i += 1 ) {
74- const stepAngle = ( i * - 360 ) / steps ;
75- let x =
76- ( xSemiAxis * ySemiAxis ) /
77- Math . sqrt (
78- Math . pow ( ySemiAxis , 2 ) +
79- Math . pow ( xSemiAxis , 2 ) * Math . pow ( getTanDeg ( stepAngle ) , 2 )
80- ) ;
81- let y =
82- ( xSemiAxis * ySemiAxis ) /
83- Math . sqrt (
84- Math . pow ( xSemiAxis , 2 ) +
85- Math . pow ( ySemiAxis , 2 ) / Math . pow ( getTanDeg ( stepAngle ) , 2 )
86- ) ;
87-
88- if ( stepAngle < - 90 && stepAngle >= - 270 ) x = - x ;
89- if ( stepAngle < - 180 && stepAngle >= - 360 ) y = - y ;
90- if ( units === "degrees" ) {
91- const angleRad = degreesToRadians ( angle ) ;
92- const newx = x * Math . cos ( angleRad ) + y * Math . sin ( angleRad ) ;
93- const newy = y * Math . cos ( angleRad ) - x * Math . sin ( angleRad ) ;
94- x = newx ;
95- y = newy ;
93+ let w = 0 ;
94+ let x = 0 ;
95+
96+ for ( let i = 0 ; i < steps ; i ++ ) {
97+ x += w ;
98+
99+ if ( m === 0 ) {
100+ // It's a circle, so use simplified c*w - A/k == 0
101+ w = A / k / c ;
102+ } else {
103+ // Otherwise, use full (v*m)*w^2 + (m*x+c)*w - A/k == 0
104+ // Solve as quadratic ax^2 + bx + c = 0
105+ w =
106+ ( - ( m * x + c ) +
107+ Math . sqrt ( Math . pow ( m * x + c , 2 ) - 4 * ( v * m ) * - ( A / k ) ) ) /
108+ ( 2 * ( v * m ) ) ;
109+ }
110+ if ( x != 0 ) {
111+ // easier to add it later to avoid having twice the same point
112+ quadrantParameters . push ( x ) ;
96113 }
114+ }
97115
98- coordinates . push ( [ x + centerCoords [ 0 ] , y + centerCoords [ 1 ] ] ) ;
116+ //NE
117+ parameters . push ( 0 ) ;
118+ for ( let i = 0 ; i < quadrantParameters . length ; i ++ ) {
119+ parameters . push ( quadrantParameters [ i ] ) ;
99120 }
100- coordinates . push ( coordinates [ 0 ] ) ;
101- if ( units === "degrees" ) {
102- return polygon ( [ coordinates ] , properties ) ;
103- } else {
104- return transformRotate ( polygon ( [ coordinates ] , properties ) , angle , {
105- pivot,
106- } ) ;
121+ //NW
122+ parameters . push ( Math . PI / 2 ) ;
123+ for ( let i = 0 ; i < quadrantParameters . length ; i ++ ) {
124+ parameters . push (
125+ Math . PI - quadrantParameters [ quadrantParameters . length - i - 1 ]
126+ ) ;
107127 }
108- }
128+ //SW
129+ parameters . push ( Math . PI ) ;
130+ for ( let i = 0 ; i < quadrantParameters . length ; i ++ ) {
131+ parameters . push ( Math . PI + quadrantParameters [ i ] ) ;
132+ }
133+ //SE
134+ parameters . push ( ( 3 * Math . PI ) / 2 ) ;
135+ for ( let i = 0 ; i < quadrantParameters . length ; i ++ ) {
136+ parameters . push (
137+ 2 * Math . PI - quadrantParameters [ quadrantParameters . length - i - 1 ]
138+ ) ;
139+ }
140+ parameters . push ( 0 ) ;
109141
110- /**
111- * Get Tan Degrees
112- *
113- * @private
114- * @param {number } deg Degrees
115- * @returns {number } Tan Degrees
116- */
117- function getTanDeg ( deg : number ) {
118- const rad = ( deg * Math . PI ) / 180 ;
119- return Math . tan ( rad ) ;
142+ // We can now construct the ellipse
143+ const coords : Position [ ] = [ ] ;
144+ for ( const param of parameters ) {
145+ const theta = Math . atan2 ( b * Math . sin ( param ) , a * Math . cos ( param ) ) ;
146+ const r = Math . sqrt (
147+ ( Math . pow ( a , 2 ) * Math . pow ( b , 2 ) ) /
148+ ( Math . pow ( a * Math . sin ( theta ) , 2 ) + Math . pow ( b * Math . cos ( theta ) , 2 ) )
149+ ) ;
150+ coords . push (
151+ destination ( centerCoords , r , angle + radiansToDegrees ( theta ) , {
152+ units : units ,
153+ } ) . geometry . coordinates
154+ ) ;
155+ }
156+ return polygon ( [ coords ] , properties ) ;
120157}
121158
122159export { ellipse } ;
0 commit comments