-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add EllipsoidRhumb class similar to EllipsoidGeodesic #7484
Conversation
Thanks for the pull request @shehzan10!
Reviewers, don't forget to make sure that:
I am a bot who helps you make Cesium awesome! Contributions to my configuration are welcome. 🌍 🌎 🌏 |
@likangning93 Can you do the initial review? |
Are you sure about the name? Why isn't this |
I think most dictionaries consider the term On the other hand the Wikipedia entry argues for |
Please double check how the term is used in our field; in the past 15 years, I have only heard people say "rhumb line", not "rhumb" - it's OK if it really is commonly called "rhumb" - just be sure please. |
@pjcozzi This was my exact reasoning behind the name, that's geodesic is I'm seeing uses of both in API references (I'm guessing others chose the shorter name for the same reason). One notable use is STK Components which uses Should I change it to |
Seems that way even though it does not have perfect symmetry with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't say I was able to parse much of the math, but some comments in other places:
ebbde06
to
86c23bc
Compare
@likangning93 Updated. |
…to EllipsoidRhumbLine
@likangning93 I added 2 new functions to compute intersections. |
I threw together a crude toy to play with rhumbs vs. geodesics in Sandcastle, and it looks like the lines work as advertised: var viewer = new Cesium.Viewer('cesiumContainer', {
mapProjection : new Cesium.WebMercatorProjection()
});
var entities = viewer.entities;
var drawing = false;
var startPoint;
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(function(event) {
if (drawing) {
drawing = false;
return;
}
var earthPosition = viewer.camera.pickEllipsoid(event.position);
if (Cesium.defined(earthPosition)) {
drawing = true;
startPoint = Cesium.Cartographic.fromCartesian(earthPosition);
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
handler.setInputAction(function(event) {
if (!drawing) {
return;
}
var newPosition = viewer.camera.pickEllipsoid(event.endPosition);
if (Cesium.defined(newPosition)) {
var endPoint = Cesium.Cartographic.fromCartesian(newPosition);
if (!Cesium.Cartographic.equalsEpsilon(endPoint, startPoint, Cesium.Math.EPSILON3)) {
makeLines(startPoint, endPoint);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
function makeLines(start, end) {
entities.removeAll();
// create regular line
entities.add({
name : 'Geodesic',
polyline : {
positions : Cesium.Cartesian3.fromRadiansArray([start.longitude, start.latitude,
end.longitude, end.latitude]),
width : 5,
material : Cesium.Color.RED
}
});
var rhumb = new Cesium.EllipsoidRhumbLine(start, end);
var radians = [];
var positionsCount = 100;
for (var i = 0; i <= positionsCount; i++) {
var interpolated = rhumb.interpolateUsingFraction(i / positionsCount);
radians.push(interpolated.longitude);
radians.push(interpolated.latitude);
}
entities.add({
name : 'Rhumbline',
polyline : {
positions : Cesium.Cartesian3.fromRadiansArray(radians),
width : 5,
material : Cesium.Color.BLUE
}
});
} |
result.height = 0; | ||
return result; | ||
} else if (CesiumMath.equalsEpsilon(Math.abs(CesiumMath.PI_OVER_TWO - absHeading), CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON8)) { | ||
if (CesiumMath.equalsEpsilon(intersectionLongitude, start.longitude, CesiumMath.EPSILON12)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be !CesiumMath.equalsEpsilon(...
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this is correct. It's else if
is checking if the line is N-S, and the nested if
is checking if the longitude is the same, in which case there are infinite intersections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm ok. So this case is "infinite intersections" and returns undefined, and the path below returns the intersection at the pole when considering the rhumbline to be an infinite spiral defined by a start and a heading? If so, that might be worth noting in the doc.
var eSinPhi = ellipticity * Math.sin(phi); | ||
var numerator = (1 + eSinPhi) / (1 - eSinPhi); | ||
newPhi = 2 * Math.atan(leftComponent * Math.pow(numerator / denominator, ellipticity / 2)) - CesiumMath.PI_OVER_TWO; | ||
} while (!CesiumMath.equalsEpsilon(newPhi, phi, CesiumMath.EPSILON12)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know if there's a situation where this wouldn't converge, and we would want this to break after some number of iterations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will converge and it's converging pretty quickly in all the tests I did. In the test cases I ran, it took less than 20 iterations.
expect(pointUsingInterpolation.longitude).toEqualEpsilon(pointUsingIntersection.longitude, CesiumMath.EPSILON12); | ||
expect(pointUsingInterpolation.latitude).toEqualEpsilon(pointUsingIntersection.latitude, CesiumMath.EPSILON12); | ||
|
||
pointUsingInterpolation = rhumb.interpolateUsingFraction(1.1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the expected behavior here? I guess does it clamp to the endpoint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will interpolate beyond the endpoint.
@likangning93 Updated. |
Maybe document the "infinite spiral from start+heading" behavior, since that might change expectations for N-S rhumb/longitude intersection and interpolation beyond length. Otherwise that should be it for me, this looks good @shehzan10! |
Thanks @likangning93. I added the comment. @mramato Do you want to take a look? |
Will do later today or first thing tomorrow. |
heading : { | ||
get : function() { | ||
//>>includeStart('debug', pragmas.debug); | ||
Check.defined('distance', this._distance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is what EllipsoidGeodesic
has, but this is a really odd check to me, especially since there is no public property called distance
. Would it be better for us to update this and EllipsoidGeodesic
to just be undefined? On the other hand, it may never happen in practice so I could be bikeshedding. I'm okay with just punting, but since EllipsoidGeodesic
is so old, it's good to revisit things once in a while.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was just a way to assure that heading and distance are both computed since the constructor allows using undefined
as start
and end
.
We could simply change this to check whether heading is defined, which will make more sense here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since EllipsoidGeodescic has been like this forever, I'm probably bike shedding, let's just leave it as-is and we can worry about it if it ever comes up.
Source/Core/EllipsoidRhumbLine.js
Outdated
* @param {EllipsoidRhumbLine} [result] The object in which to store the result. | ||
* @returns {EllipsoidRhumbLine} The EllipsoidRhumbLine object. | ||
*/ | ||
EllipsoidRhumbLine.fromStartAndEnd = function(start, end, ellipsoid, result) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this function needed, it just matches the constructor, doesn't it??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it because it allows using a scratch result object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like overkill, can't you just call setEndPoints
on an existing object? I doubt someone would need to change the ellipsoid on an existing instance. If we have a valid use case, then maybe it's worth it, but we try not to add code "just because it might be useful" since that introduces more code to maintain.
Source/Core/EllipsoidRhumbLine.js
Outdated
result._heading = CesiumMath.negativePiToPi(heading); | ||
result._distance = distance; | ||
|
||
result._end = result.interpolateUsingSurfaceDistance(distance, new Cartographic()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole function is kind of an odd implementation to me. Why not break out interpolateUsingSurfaceDistance
into a standalone local private function that can be called with start, heading, distance, ellipsoid
, us that to compute the end point then just create the fully contructed result. Using a half-initialized instance like this seems like something that could bite us.
Obviously we still expose the prototype version of interpolateUsingSurfaceDistance
that just called the local one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this could work better. I'll make the change.
result.latitude = start.latitude; | ||
result.height = 0; | ||
return result; | ||
} else if (CesiumMath.equalsEpsilon(Math.abs(CesiumMath.PI_OVER_TWO - absHeading), CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON8)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume these epsilons were from the ported code? (if not, how did we choose them?) Same for the rest of the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was chosen as the epsilon at which Math.cos()
returns 1.0
similar to http://help.agi.com/AGIComponents/html/F_AGI_Foundation_Trig_AngleEpsilon.htm.
Plese merge in master when you get a chance. |
Thanks @shehzan10, tests look comprehensive (but I can't run coverage right now since I'm on Linux). Those few comments are all I have, bump when ready and I'll merge. Looking forward to the follow-up PR for geometries! |
|
||
/** | ||
* Initializes a rhumb line on the ellipsoid connecting the two provided planetodetic points. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should document behavior when start and end are on opposite meridians (returns E-W line). Should there be a mechanism to get the opposite line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GatorScott This check does not allow points to be on opposite ends meridians (same limitation as EllipsoidGeodesic
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this limitation/restriction be documented so the developer can protect themselves from this DeveloperError: "Expected value to be greater than or equal to 0.0125, actual value was 0.0"? Secondarily, is 0.716197 degrees the right epsilon?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the documentation for the developer error. I'm not entirely certain about how 0.0125 came to be, which is from the STK Components code. @fstoner anything you can add?
@mramato Updated. |
Looks good, just document the exception that @GatorScott mentioned and this should be good to go. |
…llipsoidRhumbLine
Thanks again, @shehzan10. |
As the title suggest, this PR adds an
EllipsoidRhumb
class similar toEllipsoidGeodesic
. This is the first step in adding rhumb line support for geometry (#4000).