Skip to content

Commit 9892e1d

Browse files
author
Hannah
authoredJan 24, 2019
Merge pull request #7492 from shehzan10/rhumb-lines
Add Rhumb Line Support to Polygon and Polyline Geometries
2 parents 99717a8 + 5f7a7ac commit 9892e1d

33 files changed

+1514
-103
lines changed
 

‎Apps/Sandcastle/gallery/Polygon.html

+15
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,21 @@
125125
}
126126
});
127127

128+
var purplePolygonUsingRhumbLines = viewer.entities.add({
129+
name : 'Purple polygon using rhumb lines with outline',
130+
polygon : {
131+
hierarchy : Cesium.Cartesian3.fromDegreesArray([-120.0, 45.0,
132+
-80.0, 45.0,
133+
-80.0, 55.0,
134+
-120.0, 55.0]),
135+
extrudedHeight: 50000,
136+
material : Cesium.Color.PURPLE,
137+
outline : true,
138+
outlineColor : Cesium.Color.MAGENTA,
139+
arcType : Cesium.ArcType.RHUMB
140+
}
141+
});
142+
128143
viewer.zoomTo(viewer.entities);//Sandcastle_End
129144
Sandcastle.finishedLoading();
130145
}

‎Apps/Sandcastle/gallery/Polyline.html

+12-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@
4040
}
4141
});
4242

43+
var greenRhumbLine = viewer.entities.add({
44+
name : 'Green rhumb line',
45+
polyline : {
46+
positions : Cesium.Cartesian3.fromDegreesArray([-75, 35,
47+
-125, 35]),
48+
width : 5,
49+
arcType : Cesium.ArcType.RHUMB,
50+
material : Cesium.Color.GREEN
51+
}
52+
});
53+
4354
var glowingLine = viewer.entities.add({
4455
name : 'Glowing blue line on the surface',
4556
polyline : {
@@ -73,7 +84,7 @@
7384
positions : Cesium.Cartesian3.fromDegreesArrayHeights([-75, 43, 500000,
7485
-125, 43, 500000]),
7586
width : 10,
76-
followSurface : false,
87+
arcType : Cesium.ArcType.NONE,
7788
material : new Cesium.PolylineArrowMaterialProperty(Cesium.Color.PURPLE)
7889
}
7990
});

‎Apps/Sandcastle/gallery/development/Ground Primitive.html

+21
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
terrainProvider: Cesium.createWorldTerrain()
3232
});
3333
var scene = viewer.scene;
34+
viewer.extend(Cesium.viewerCesiumInspectorMixin);
3435

3536
function offsetPositions(positions, degreeOffset) {
3637
positions = scene.globe.ellipsoid.cartesianArrayToCartographicArray(positions);
@@ -325,6 +326,26 @@
325326
}),
326327
classificationType : Cesium.ClassificationType.TERRAIN
327328
}));
329+
330+
// Rhumb line polygon geometry
331+
scene.groundPrimitives.add(new Cesium.GroundPrimitive({
332+
geometryInstances : new Cesium.GeometryInstance({
333+
geometry : new Cesium.PolygonGeometry({
334+
polygonHierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray([
335+
-130, 55,
336+
-100, 55,
337+
-100, 45,
338+
-130, 45
339+
])),
340+
arcType : Cesium.ArcType.RHUMB
341+
}),
342+
attributes: {
343+
color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(1.0, 1.0, 0.0, 0.5))
344+
},
345+
id : 'rhumbPolygon'
346+
}),
347+
classificationType : Cesium.ClassificationType.TERRAIN
348+
}));
328349
});
329350

330351
Sandcastle.reset = function() {

‎Apps/Sandcastle/gallery/development/Polylines.html

+14
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@
119119
})
120120
});
121121
Sandcastle.declare(fadingPolyline); // For highlighting on mouseover in Sandcastle.
122+
123+
// A rhumb line with two points.
124+
var rhumbLine = polylines.add({
125+
positions : Cesium.PolylinePipeline.generateCartesianRhumbArc({
126+
positions : Cesium.Cartesian3.fromDegreesArray([-130.0, 30.0,
127+
-75.0, 30.0])
128+
}),
129+
width: 5,
130+
material : Cesium.Material.fromType('Color', {
131+
color : new Cesium.Color(0.0, 1.0, 0.0, 1.0)
132+
})
133+
});
134+
Sandcastle.declare(rhumbLine); // For highlighting on mouseover in Sandcastle.
135+
122136
}
123137

124138
var viewer = new Cesium.Viewer('cesiumContainer');

‎CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Change Log
99

1010
##### Deprecated :hourglass_flowing_sand:
1111
* `Scene.clampToHeight` now takes an optional `width` argument before the `result` argument. The previous function definition will no longer work in 1.56. [#7287](https://github.com/AnalyticalGraphicsInc/cesium/pull/7287)
12+
* `PolylineGeometry.followSurface` has been superceded by `PolylineGeometry.arcType`. The previous definition will no longer work in 1.57. Replace `followSurface: false` with `arcType: Cesium.ArcType.NONE` and `followSurface: true` with `arcType: Cesium.ArcType.GEODESIC`. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492)
13+
* `SimplePolylineGeometry.followSurface` has been superceded by `SimplePolylineGeometry.arcType`. The previous definition will no longer work in 1.57. Replace `followSurface: false` with `arcType: Cesium.ArcType.NONE` and `followSurface: true` with `arcType: Cesium.ArcType.GEODESIC`. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492)
1214

1315
##### Additions :tada:
1416
* Added support for textured ground entities (entities with unspecified `height`) and `GroundPrimitives` on 3D Tiles. [#7434](https://github.com/AnalyticalGraphicsInc/cesium/pull/7434)
@@ -17,6 +19,7 @@ Change Log
1719
* Added the ability to specify the width of the intersection volume for `Scene.sampleHeight`, `Scene.clampToHeight`, `Scene.sampleHeightMostDetailed`, and `Scene.clampToHeightMostDetailed`. [#7287](https://github.com/AnalyticalGraphicsInc/cesium/pull/7287)
1820
* Added a [new Sandcastle example](https://cesiumjs.org/Cesium/Build/Apps/Sandcastle/?src=Time%20Dynamic%20Wheels.html) on using `nodeTransformations` to rotate a model's wheels based on its velocity. [#7361](https://github.com/AnalyticalGraphicsInc/cesium/pull/7361)
1921
* Added `EllipsoidRhumbLine` class as a rhumb line counterpart to `EllipsoidGeodesic`. [#7484](https://github.com/AnalyticalGraphicsInc/cesium/pull/7484)
22+
* Added rhumb line support to `PolygonGeometry`, `PolygonOutlineGeometry`, `PolylineGeometry`, `GroundPolylineGeometry`, and `SimplePolylineGeometry`. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492)
2023

2124
##### Fixes :wrench:
2225
* Fixed 3D Tiles performance regression. [#7482](https://github.com/AnalyticalGraphicsInc/cesium/pull/7482)
@@ -28,6 +31,7 @@ Change Log
2831
* Fixed Sandcastle's "Open in New Window" button not displaying imagery due to blob URI limitations. [#7250](https://github.com/AnalyticalGraphicsInc/cesium/pull/7250)
2932
* Fixed an issue where setting `scene.globe.cartographicLimitRectangle` to `undefined` would cause a crash. [#7477](https://github.com/AnalyticalGraphicsInc/cesium/issues/7477)
3033
* Fixed `PrimitiveCollection.removeAll` to no longer `contain` removed primitives. [#7491](https://github.com/AnalyticalGraphicsInc/cesium/pull/7491)
34+
* Fixed `GeoJsonDataSource` to use polygons and polylines that use rhumb lines. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492)
3135

3236
### 1.53 - 2019-01-02
3337

‎Source/Core/ArcType.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
define([
2+
'./freezeObject'
3+
], function(
4+
freezeObject) {
5+
'use strict';
6+
7+
/**
8+
* ArcType defines the path that should be taken connecting vertices.
9+
*
10+
* @exports ArcType
11+
*/
12+
var ArcType = {
13+
/**
14+
* Straight line that does not conform to the surface of the ellipsoid.
15+
*
16+
* @type {Number}
17+
* @constant
18+
*/
19+
NONE : 0,
20+
21+
/**
22+
* Follow geodesic path.
23+
*
24+
* @type {Number}
25+
* @constant
26+
*/
27+
GEODESIC : 1,
28+
29+
/**
30+
* Follow rhumb or loxodrome path.
31+
*
32+
* @type {Number}
33+
* @constant
34+
*/
35+
RHUMB : 2
36+
};
37+
38+
return freezeObject(ArcType);
39+
});

‎Source/Core/GroundPolylineGeometry.js

+73-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
define([
22
'./ApproximateTerrainHeights',
3+
'./ArcType',
34
'./arrayRemoveDuplicates',
45
'./BoundingSphere',
56
'./Cartesian3',
@@ -13,6 +14,7 @@ define([
1314
'./defineProperties',
1415
'./Ellipsoid',
1516
'./EllipsoidGeodesic',
17+
'./EllipsoidRhumbLine',
1618
'./EncodedCartesian3',
1719
'./GeographicProjection',
1820
'./Geometry',
@@ -25,6 +27,7 @@ define([
2527
'./WebMercatorProjection'
2628
], function(
2729
ApproximateTerrainHeights,
30+
ArcType,
2831
arrayRemoveDuplicates,
2932
BoundingSphere,
3033
Cartesian3,
@@ -38,6 +41,7 @@ define([
3841
defineProperties,
3942
Ellipsoid,
4043
EllipsoidGeodesic,
44+
EllipsoidRhumbLine,
4145
EncodedCartesian3,
4246
GeographicProjection,
4347
Geometry,
@@ -80,6 +84,7 @@ define([
8084
* @param {Number} [options.width=1.0] The screen space width in pixels.
8185
* @param {Number} [options.granularity=9999.0] The distance interval in meters used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation.
8286
* @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
87+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
8388
*
8489
* @exception {DeveloperError} At least two positions are required.
8590
*
@@ -104,6 +109,9 @@ define([
104109
if ((!defined(positions)) || (positions.length < 2)) {
105110
throw new DeveloperError('At least two positions are required.');
106111
}
112+
if (defined(options.arcType) && options.arcType !== ArcType.GEODESIC && options.arcType !== ArcType.RHUMB) {
113+
throw new DeveloperError('Valid options for arcType are ArcType.GEODESIC and ArcType.RHUMB.');
114+
}
107115
//>>includeEnd('debug');
108116

109117
/**
@@ -130,6 +138,13 @@ define([
130138
*/
131139
this.loop = defaultValue(options.loop, false);
132140

141+
/**
142+
* The type of path the polyline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
143+
* @type {ArcType}
144+
* @default ArcType.GEODESIC
145+
*/
146+
this.arcType = defaultValue(options.arcType, ArcType.GEODESIC);
147+
133148
this._ellipsoid = Ellipsoid.WGS84;
134149

135150
// MapProjections can't be packed, so store the index to a known MapProjection.
@@ -150,7 +165,7 @@ define([
150165
*/
151166
packedLength: {
152167
get: function() {
153-
return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0;
168+
return 1.0 + this._positions.length * 3 + 1.0 + 1.0 + 1.0 + Ellipsoid.packedLength + 1.0 + 1.0;
154169
}
155170
}
156171
});
@@ -195,12 +210,19 @@ define([
195210
var interpolatedBottomScratch = new Cartesian3();
196211
var interpolatedTopScratch = new Cartesian3();
197212
var interpolatedNormalScratch = new Cartesian3();
198-
function interpolateSegment(start, end, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray) {
213+
function interpolateSegment(start, end, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray) {
199214
if (granularity === 0.0) {
200215
return;
201216
}
202-
var ellipsoidGeodesic = new EllipsoidGeodesic(start, end, ellipsoid);
203-
var surfaceDistance = ellipsoidGeodesic.surfaceDistance;
217+
218+
var ellipsoidLine;
219+
if (arcType === ArcType.GEODESIC) {
220+
ellipsoidLine = new EllipsoidGeodesic(start, end, ellipsoid);
221+
} else if (arcType === ArcType.RHUMB) {
222+
ellipsoidLine = new EllipsoidRhumbLine(start, end, ellipsoid);
223+
}
224+
225+
var surfaceDistance = ellipsoidLine.surfaceDistance;
204226
if (surfaceDistance < granularity) {
205227
return;
206228
}
@@ -214,7 +236,7 @@ define([
214236
var pointsToAdd = segments - 1;
215237
var packIndex = normalsArray.length;
216238
for (var i = 0; i < pointsToAdd; i++) {
217-
var interpolatedCartographic = ellipsoidGeodesic.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch);
239+
var interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(distanceFromStart, interpolatedCartographicScratch);
218240
var interpolatedBottom = getPosition(ellipsoid, interpolatedCartographic, minHeight, interpolatedBottomScratch);
219241
var interpolatedTop = getPosition(ellipsoid, interpolatedCartographic, maxHeight, interpolatedTopScratch);
220242

@@ -266,6 +288,7 @@ define([
266288

267289
array[index++] = value.granularity;
268290
array[index++] = value.loop ? 1.0 : 0.0;
291+
array[index++] = value.arcType;
269292

270293
Ellipsoid.pack(value._ellipsoid, array, index);
271294
index += Ellipsoid.packedLength;
@@ -299,6 +322,7 @@ define([
299322

300323
var granularity = array[index++];
301324
var loop = array[index++] === 1.0;
325+
var arcType = array[index++];
302326

303327
var ellipsoid = Ellipsoid.unpack(array, index);
304328
index += Ellipsoid.packedLength;
@@ -311,6 +335,7 @@ define([
311335
positions : positions,
312336
granularity : granularity,
313337
loop : loop,
338+
arcType : arcType,
314339
ellipsoid : ellipsoid
315340
});
316341
geometry._projectionIndex = projectionIndex;
@@ -321,6 +346,7 @@ define([
321346
result._positions = positions;
322347
result.granularity = granularity;
323348
result.loop = loop;
349+
result.arcType = arcType;
324350
result._ellipsoid = ellipsoid;
325351
result._projectionIndex = projectionIndex;
326352
result._scene3DOnly = scene3DOnly;
@@ -384,6 +410,9 @@ define([
384410
var nextBottomScratch = new Cartesian3();
385411
var vertexNormalScratch = new Cartesian3();
386412
var intersectionScratch = new Cartesian3();
413+
var cartographicScratch0 = new Cartographic();
414+
var cartographicScratch1 = new Cartographic();
415+
var cartographicIntersectionScratch = new Cartographic();
387416
/**
388417
* Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere.
389418
* Vertices are "fat," packing all the data needed in each volume to describe a line on terrain or 3D Tiles.
@@ -397,6 +426,7 @@ define([
397426
var loop = groundPolylineGeometry.loop;
398427
var ellipsoid = groundPolylineGeometry._ellipsoid;
399428
var granularity = groundPolylineGeometry.granularity;
429+
var arcType = groundPolylineGeometry.arcType;
400430
var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](ellipsoid);
401431

402432
var minHeight = WALL_INITIAL_MIN_HEIGHT;
@@ -417,7 +447,12 @@ define([
417447
// may get split by the plane of IDL + Prime Meridian.
418448
var p0;
419449
var p1;
450+
var c0;
451+
var c1;
452+
var rhumbLine = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
420453
var intersection;
454+
var intersectionCartographic;
455+
var intersectionLongitude;
421456
var splitPositions = [positions[0]];
422457
for (i = 0; i < positionsLength - 1; i++) {
423458
p0 = positions[i];
@@ -426,7 +461,21 @@ define([
426461
if (defined(intersection) &&
427462
!Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
428463
!Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
429-
splitPositions.push(Cartesian3.clone(intersection));
464+
if (groundPolylineGeometry.arcType === ArcType.GEODESIC) {
465+
splitPositions.push(Cartesian3.clone(intersection));
466+
} else if (groundPolylineGeometry.arcType === ArcType.RHUMB) {
467+
intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude;
468+
c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
469+
c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
470+
rhumbLine.setEndPoints(c0, c1);
471+
intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch);
472+
intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch);
473+
if (defined(intersection) &&
474+
!Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
475+
!Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
476+
splitPositions.push(Cartesian3.clone(intersection));
477+
}
478+
}
430479
}
431480
splitPositions.push(p1);
432481
}
@@ -438,7 +487,21 @@ define([
438487
if (defined(intersection) &&
439488
!Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
440489
!Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
441-
splitPositions.push(Cartesian3.clone(intersection));
490+
if (groundPolylineGeometry.arcType === ArcType.GEODESIC) {
491+
splitPositions.push(Cartesian3.clone(intersection));
492+
} else if (groundPolylineGeometry.arcType === ArcType.RHUMB) {
493+
intersectionLongitude = ellipsoid.cartesianToCartographic(intersection, cartographicScratch0).longitude;
494+
c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
495+
c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
496+
rhumbLine.setEndPoints(c0, c1);
497+
intersectionCartographic = rhumbLine.findIntersectionWithLongitude(intersectionLongitude, cartographicIntersectionScratch);
498+
intersection = ellipsoid.cartographicToCartesian(intersectionCartographic, intersectionScratch);
499+
if (defined(intersection) &&
500+
!Cartesian3.equalsEpsilon(intersection, p0, CesiumMath.EPSILON7) &&
501+
!Cartesian3.equalsEpsilon(intersection, p1, CesiumMath.EPSILON7)) {
502+
splitPositions.push(Cartesian3.clone(intersection));
503+
}
504+
}
442505
}
443506
}
444507
var cartographicsLength = splitPositions.length;
@@ -495,7 +558,7 @@ define([
495558
cartographicsArray.push(startCartographic.latitude);
496559
cartographicsArray.push(startCartographic.longitude);
497560

498-
interpolateSegment(startCartographic, nextCartographic, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
561+
interpolateSegment(startCartographic, nextCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
499562

500563
// All inbetween points
501564
for (i = 1; i < cartographicsLength - 1; ++i) {
@@ -514,7 +577,7 @@ define([
514577
cartographicsArray.push(vertexCartographic.latitude);
515578
cartographicsArray.push(vertexCartographic.longitude);
516579

517-
interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
580+
interpolateSegment(cartographics[i], cartographics[i + 1], minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
518581
}
519582

520583
// Last point - either loop or attach a normal "perpendicular" to the wall.
@@ -542,7 +605,7 @@ define([
542605
cartographicsArray.push(endCartographic.longitude);
543606

544607
if (loop) {
545-
interpolateSegment(endCartographic, startCartographic, minHeight, maxHeight, granularity, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
608+
interpolateSegment(endCartographic, startCartographic, minHeight, maxHeight, granularity, arcType, ellipsoid, normalsArray, bottomPositionsArray, topPositionsArray, cartographicsArray);
546609
index = normalsArray.length;
547610
for (i = 0; i < 3; ++i) {
548611
normalsArray[index + i] = normalsArray[i];

‎Source/Core/PolygonGeometry.js

+25-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
define([
2+
'./ArcType',
23
'./arrayFill',
34
'./BoundingRectangle',
45
'./BoundingSphere',
@@ -29,6 +30,7 @@ define([
2930
'./VertexFormat',
3031
'./WindingOrder'
3132
], function(
33+
ArcType,
3234
arrayFill,
3335
BoundingRectangle,
3436
BoundingSphere,
@@ -386,14 +388,14 @@ define([
386388

387389
var createGeometryFromPositionsExtrudedPositions = [];
388390

389-
function createGeometryFromPositionsExtruded(ellipsoid, polygon, granularity, hierarchy, perPositionHeight, closeTop, closeBottom, vertexFormat) {
391+
function createGeometryFromPositionsExtruded(ellipsoid, polygon, granularity, hierarchy, perPositionHeight, closeTop, closeBottom, vertexFormat, arcType) {
390392
var geos = {
391393
walls : []
392394
};
393395
var i;
394396

395397
if (closeTop || closeBottom) {
396-
var topGeo = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat);
398+
var topGeo = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat, arcType);
397399

398400
var edgePoints = topGeo.attributes.position.values;
399401
var indices = topGeo.indices;
@@ -455,7 +457,7 @@ define([
455457
outerRing = outerRing.slice().reverse();
456458
}
457459

458-
var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight);
460+
var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight, arcType);
459461
geos.walls.push(new GeometryInstance({
460462
geometry : wallGeo
461463
}));
@@ -472,7 +474,7 @@ define([
472474
hole = hole.slice().reverse();
473475
}
474476

475-
wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity);
477+
wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity, perPositionHeight, arcType);
476478
geos.walls.push(new GeometryInstance({
477479
geometry : wallGeo
478480
}));
@@ -498,6 +500,7 @@ define([
498500
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
499501
* @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
500502
* @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
503+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polygon edges must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
501504
*
502505
* @see PolygonGeometry#createGeometry
503506
* @see PolygonGeometry#fromPositions
@@ -578,6 +581,9 @@ define([
578581
if (defined(options.perPositionHeight) && options.perPositionHeight && defined(options.height)) {
579582
throw new DeveloperError('Cannot use both options.perPositionHeight and options.height');
580583
}
584+
if (defined(options.arcType) && options.arcType !== ArcType.GEODESIC && options.arcType !== ArcType.RHUMB) {
585+
throw new DeveloperError('Invalid arcType. Valid options are ArcType.GEODESIC and ArcType.RHUMB.');
586+
}
581587
//>>includeEnd('debug');
582588

583589
var polygonHierarchy = options.polygonHierarchy;
@@ -610,6 +616,7 @@ define([
610616
this._shadowVolume = defaultValue(options.shadowVolume, false);
611617
this._workerName = 'createPolygonGeometry';
612618
this._offsetAttribute = options.offsetAttribute;
619+
this._arcType = defaultValue(options.arcType, ArcType.GEODESIC);
613620

614621
this._rectangle = undefined;
615622
this._textureCoordinateRotationPoints = undefined;
@@ -618,7 +625,7 @@ define([
618625
* The number of elements used to pack the object into an array.
619626
* @type {Number}
620627
*/
621-
this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + VertexFormat.packedLength + 11;
628+
this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + VertexFormat.packedLength + 12;
622629
}
623630

624631
/**
@@ -635,6 +642,7 @@ define([
635642
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
636643
* @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
637644
* @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
645+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polygon edges must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
638646
* @returns {PolygonGeometry}
639647
*
640648
*
@@ -673,7 +681,8 @@ define([
673681
perPositionHeight : options.perPositionHeight,
674682
closeTop : options.closeTop,
675683
closeBottom : options.closeBottom,
676-
offsetAttribute : options.offsetAttribute
684+
offsetAttribute : options.offsetAttribute,
685+
arcType : options.arcType
677686
};
678687
return new PolygonGeometry(newOptions);
679688
};
@@ -713,6 +722,7 @@ define([
713722
array[startingIndex++] = value._closeBottom ? 1.0 : 0.0;
714723
array[startingIndex++] = value._shadowVolume ? 1.0 : 0.0;
715724
array[startingIndex++] = defaultValue(value._offsetAttribute, -1);
725+
array[startingIndex++] = value._arcType;
716726
array[startingIndex] = value.packedLength;
717727

718728
return array;
@@ -760,6 +770,7 @@ define([
760770
var closeBottom = array[startingIndex++] === 1.0;
761771
var shadowVolume = array[startingIndex++] === 1.0;
762772
var offsetAttribute = array[startingIndex++];
773+
var arcType = array[startingIndex++];
763774
var packedLength = array[startingIndex];
764775

765776
if (!defined(result)) {
@@ -779,6 +790,7 @@ define([
779790
result._closeBottom = closeBottom;
780791
result._shadowVolume = shadowVolume;
781792
result._offsetAttribute = offsetAttribute === -1 ? undefined : offsetAttribute;
793+
result._arcType = arcType;
782794
result.packedLength = packedLength;
783795
return result;
784796
};
@@ -820,6 +832,7 @@ define([
820832
var perPositionHeight = polygonGeometry._perPositionHeight;
821833
var closeTop = polygonGeometry._closeTop;
822834
var closeBottom = polygonGeometry._closeBottom;
835+
var arcType = polygonGeometry._arcType;
823836

824837
var outerPositions = polygonHierarchy.positions;
825838
if (outerPositions.length < 3) {
@@ -856,7 +869,8 @@ define([
856869
bottom: false,
857870
top: true,
858871
wall: false,
859-
extrude: false
872+
extrude: false,
873+
arcType: arcType
860874
};
861875

862876
var i;
@@ -868,7 +882,7 @@ define([
868882
options.shadowVolume = polygonGeometry._shadowVolume;
869883
options.offsetAttribute = polygonGeometry._offsetAttribute;
870884
for (i = 0; i < polygons.length; i++) {
871-
var splitGeometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight, closeTop, closeBottom, vertexFormat);
885+
var splitGeometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight, closeTop, closeBottom, vertexFormat, arcType);
872886

873887
var topAndBottom;
874888
if (closeTop && closeBottom) {
@@ -901,7 +915,7 @@ define([
901915
} else {
902916
for (i = 0; i < polygons.length; i++) {
903917
var geometryInstance = new GeometryInstance({
904-
geometry : PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight, vertexFormat)
918+
geometry : PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight, vertexFormat, arcType)
905919
});
906920
geometryInstance.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometryInstance.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
907921
options.geometry = geometryInstance.geometry;
@@ -962,7 +976,8 @@ define([
962976
extrudedHeight : minHeight,
963977
height : maxHeight,
964978
vertexFormat : VertexFormat.POSITION_ONLY,
965-
shadowVolume: true
979+
shadowVolume: true,
980+
arcType : polygonGeometry._arcType
966981
});
967982
};
968983

‎Source/Core/PolygonGeometryLibrary.js

+72-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
define([
2+
'./ArcType',
23
'./arrayRemoveDuplicates',
34
'./Cartesian2',
45
'./Cartesian3',
6+
'./Cartographic',
57
'./ComponentDatatype',
68
'./defaultValue',
79
'./defined',
10+
'./DeveloperError',
811
'./Ellipsoid',
12+
'./EllipsoidRhumbLine',
913
'./Geometry',
1014
'./GeometryAttribute',
1115
'./GeometryAttributes',
@@ -19,13 +23,17 @@ define([
1923
'./Queue',
2024
'./WindingOrder'
2125
], function(
26+
ArcType,
2227
arrayRemoveDuplicates,
2328
Cartesian2,
2429
Cartesian3,
30+
Cartographic,
2531
ComponentDatatype,
2632
defaultValue,
2733
defined,
34+
DeveloperError,
2835
Ellipsoid,
36+
EllipsoidRhumbLine,
2937
Geometry,
3038
GeometryAttribute,
3139
GeometryAttributes,
@@ -141,7 +149,20 @@ define([
141149
PolygonGeometryLibrary.subdivideLineCount = function(p0, p1, minDistance) {
142150
var distance = Cartesian3.distance(p0, p1);
143151
var n = distance / minDistance;
144-
var countDivide = Math.max(0, Math.ceil(Math.log(n) / Math.log(2)));
152+
var countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n)));
153+
return Math.pow(2, countDivide);
154+
};
155+
156+
var scratchCartographic0 = new Cartographic();
157+
var scratchCartographic1 = new Cartographic();
158+
var scratchCartographic2 = new Cartographic();
159+
var scratchCartesian0 = new Cartesian3();
160+
PolygonGeometryLibrary.subdivideRhumbLineCount = function(ellipsoid, p0, p1, minDistance) {
161+
var c0 = ellipsoid.cartesianToCartographic(p0, scratchCartographic0);
162+
var c1 = ellipsoid.cartesianToCartographic(p1, scratchCartographic1);
163+
var rhumb = new EllipsoidRhumbLine(c0, c1, ellipsoid);
164+
var n = rhumb.surfaceDistance / minDistance;
165+
var countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n)));
145166
return Math.pow(2, countDivide);
146167
};
147168

@@ -168,6 +189,35 @@ define([
168189
return positions;
169190
};
170191

192+
PolygonGeometryLibrary.subdivideRhumbLine = function(ellipsoid, p0, p1, minDistance, result) {
193+
var c0 = ellipsoid.cartesianToCartographic(p0, scratchCartographic0);
194+
var c1 = ellipsoid.cartesianToCartographic(p1, scratchCartographic1);
195+
var rhumb = new EllipsoidRhumbLine(c0, c1, ellipsoid);
196+
197+
var n = rhumb.surfaceDistance / minDistance;
198+
var countDivide = Math.max(0, Math.ceil(CesiumMath.log2(n)));
199+
var numVertices = Math.pow(2, countDivide);
200+
var distanceBetweenVertices = rhumb.surfaceDistance / numVertices;
201+
202+
if (!defined(result)) {
203+
result = [];
204+
}
205+
206+
var positions = result;
207+
positions.length = numVertices * 3;
208+
209+
var index = 0;
210+
for ( var i = 0; i < numVertices; i++) {
211+
var c = rhumb.interpolateUsingSurfaceDistance(i * distanceBetweenVertices, scratchCartographic2);
212+
var p = ellipsoid.cartographicToCartesian(c, scratchCartesian0);
213+
positions[index++] = p.x;
214+
positions[index++] = p.y;
215+
positions[index++] = p.z;
216+
}
217+
218+
return positions;
219+
};
220+
171221
var scaleToGeodeticHeightN1 = new Cartesian3();
172222
var scaleToGeodeticHeightN2 = new Cartesian3();
173223
var scaleToGeodeticHeightP1 = new Cartesian3();
@@ -403,7 +453,7 @@ define([
403453
return result;
404454
};
405455

406-
PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat) {
456+
PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat, arcType) {
407457
var indices = PolygonPipeline.triangulate(polygon.positions2D, polygon.holes);
408458

409459
/* If polygon is completely unrenderable, just use the first three vertices */
@@ -442,14 +492,18 @@ define([
442492
return geometry;
443493
}
444494

445-
return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity);
495+
if (arcType === ArcType.GEODESIC) {
496+
return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity);
497+
} else if (arcType === ArcType.RHUMB) {
498+
return PolygonPipeline.computeRhumbLineSubdivision(ellipsoid, positions, indices, granularity);
499+
}
446500
};
447501

448502
var computeWallIndicesSubdivided = [];
449503
var p1Scratch = new Cartesian3();
450504
var p2Scratch = new Cartesian3();
451505

452-
PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight) {
506+
PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight, arcType) {
453507
var edgePositions;
454508
var topEdgeLength;
455509
var i;
@@ -463,8 +517,14 @@ define([
463517
var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
464518

465519
var numVertices = 0;
466-
for (i = 0; i < length; i++) {
467-
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
520+
if (arcType === ArcType.GEODESIC) {
521+
for (i = 0; i < length; i++) {
522+
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
523+
}
524+
} else if (arcType === ArcType.RHUMB) {
525+
for (i = 0; i < length; i++) {
526+
numVertices += PolygonGeometryLibrary.subdivideRhumbLineCount(ellipsoid, positions[i], positions[(i + 1) % length], minDistance);
527+
}
468528
}
469529

470530
topEdgeLength = (numVertices + length) * 3;
@@ -473,7 +533,12 @@ define([
473533
p1 = positions[i];
474534
p2 = positions[(i + 1) % length];
475535

476-
var tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided);
536+
var tempPositions;
537+
if (arcType === ArcType.GEODESIC) {
538+
tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided);
539+
} else if (arcType === ArcType.RHUMB) {
540+
tempPositions = PolygonGeometryLibrary.subdivideRhumbLine(ellipsoid, p1, p2, minDistance, computeWallIndicesSubdivided);
541+
}
477542
var tempPositionsLength = tempPositions.length;
478543
for (var j = 0; j < tempPositionsLength; ++j, ++index) {
479544
edgePositions[index] = tempPositions[j];

‎Source/Core/PolygonOutlineGeometry.js

+47-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
define([
2+
'./ArcType',
23
'./arrayFill',
34
'./arrayRemoveDuplicates',
45
'./BoundingSphere',
@@ -24,6 +25,7 @@ define([
2425
'./Queue',
2526
'./WindingOrder'
2627
], function(
28+
ArcType,
2729
arrayFill,
2830
arrayRemoveDuplicates,
2931
BoundingSphere,
@@ -52,7 +54,7 @@ define([
5254
var createGeometryFromPositionsPositions = [];
5355
var createGeometryFromPositionsSubdivided = [];
5456

55-
function createGeometryFromPositions(ellipsoid, positions, minDistance, perPositionHeight) {
57+
function createGeometryFromPositions(ellipsoid, positions, minDistance, perPositionHeight, arcType) {
5658
var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid);
5759
var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions);
5860

@@ -70,12 +72,23 @@ define([
7072

7173
if (!perPositionHeight) {
7274
var numVertices = 0;
73-
for (i = 0; i < length; i++) {
74-
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
75+
if (arcType === ArcType.GEODESIC) {
76+
for (i = 0; i < length; i++) {
77+
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
78+
}
79+
} else if (arcType === ArcType.RHUMB) {
80+
for (i = 0; i < length; i++) {
81+
numVertices += PolygonGeometryLibrary.subdivideRhumbLineCount(ellipsoid, positions[i], positions[(i + 1) % length], minDistance);
82+
}
7583
}
7684
subdividedPositions = new Float64Array(numVertices * 3);
7785
for (i = 0; i < length; i++) {
78-
var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
86+
var tempPositions;
87+
if (arcType === ArcType.GEODESIC) {
88+
tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
89+
} else if (arcType === ArcType.RHUMB) {
90+
tempPositions = PolygonGeometryLibrary.subdivideRhumbLine(ellipsoid, positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
91+
}
7992
var tempPositionsLength = tempPositions.length;
8093
for (var j = 0; j < tempPositionsLength; ++j) {
8194
subdividedPositions[index++] = tempPositions[j];
@@ -121,7 +134,7 @@ define([
121134
});
122135
}
123136

124-
function createGeometryFromPositionsExtruded(ellipsoid, positions, minDistance, perPositionHeight) {
137+
function createGeometryFromPositionsExtruded(ellipsoid, positions, minDistance, perPositionHeight, arcType) {
125138
var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid);
126139
var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions);
127140

@@ -140,14 +153,25 @@ define([
140153

141154
if (!perPositionHeight) {
142155
var numVertices = 0;
143-
for (i = 0; i < length; i++) {
144-
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
156+
if (arcType === ArcType.GEODESIC) {
157+
for (i = 0; i < length; i++) {
158+
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
159+
}
160+
} else if (arcType === ArcType.RHUMB) {
161+
for (i = 0; i < length; i++) {
162+
numVertices += PolygonGeometryLibrary.subdivideRhumbLineCount(ellipsoid, positions[i], positions[(i + 1) % length], minDistance);
163+
}
145164
}
146165

147166
subdividedPositions = new Float64Array(numVertices * 3 * 2);
148167
for (i = 0; i < length; ++i) {
149168
corners[i] = index / 3;
150-
var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
169+
var tempPositions;
170+
if (arcType === ArcType.GEODESIC) {
171+
tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
172+
} else if (arcType === ArcType.RHUMB) {
173+
tempPositions = PolygonGeometryLibrary.subdivideRhumbLine(ellipsoid, positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
174+
}
151175
var tempPositionsLength = tempPositions.length;
152176
for (var j = 0; j < tempPositionsLength; ++j) {
153177
subdividedPositions[index++] = tempPositions[j];
@@ -218,6 +242,7 @@ define([
218242
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
219243
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
220244
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
245+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of path the outline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
221246
*
222247
* @see PolygonOutlineGeometry#createGeometry
223248
* @see PolygonOutlineGeometry#fromPositions
@@ -297,13 +322,17 @@ define([
297322
if (options.perPositionHeight && defined(options.height)) {
298323
throw new DeveloperError('Cannot use both options.perPositionHeight and options.height');
299324
}
325+
if (defined(options.arcType) && options.arcType !== ArcType.GEODESIC && options.arcType !== ArcType.RHUMB) {
326+
throw new DeveloperError('Invalid arcType. Valid options are ArcType.GEODESIC and ArcType.RHUMB.');
327+
}
300328
//>>includeEnd('debug');
301329

302330
var polygonHierarchy = options.polygonHierarchy;
303331
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
304332
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
305333
var perPositionHeight = defaultValue(options.perPositionHeight, false);
306334
var perPositionHeightExtrude = perPositionHeight && defined(options.extrudedHeight);
335+
var arcType = defaultValue(options.arcType, ArcType.GEODESIC);
307336

308337
var height = defaultValue(options.height, 0.0);
309338
var extrudedHeight = defaultValue(options.extrudedHeight, height);
@@ -318,6 +347,7 @@ define([
318347
this._granularity = granularity;
319348
this._height = height;
320349
this._extrudedHeight = extrudedHeight;
350+
this._arcType = arcType;
321351
this._polygonHierarchy = polygonHierarchy;
322352
this._perPositionHeight = perPositionHeight;
323353
this._perPositionHeightExtrude = perPositionHeightExtrude;
@@ -328,7 +358,7 @@ define([
328358
* The number of elements used to pack the object into an array.
329359
* @type {Number}
330360
*/
331-
this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + 7;
361+
this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + 8;
332362
}
333363

334364
/**
@@ -358,6 +388,7 @@ define([
358388
array[startingIndex++] = value._granularity;
359389
array[startingIndex++] = value._perPositionHeightExtrude ? 1.0 : 0.0;
360390
array[startingIndex++] = value._perPositionHeight ? 1.0 : 0.0;
391+
array[startingIndex++] = value._arcType;
361392
array[startingIndex++] = defaultValue(value._offsetAttribute, -1);
362393
array[startingIndex] = value.packedLength;
363394

@@ -396,6 +427,7 @@ define([
396427
var granularity = array[startingIndex++];
397428
var perPositionHeightExtrude = array[startingIndex++] === 1.0;
398429
var perPositionHeight = array[startingIndex++] === 1.0;
430+
var arcType = array[startingIndex++];
399431
var offsetAttribute = array[startingIndex++];
400432
var packedLength = array[startingIndex];
401433

@@ -410,6 +442,7 @@ define([
410442
result._granularity = granularity;
411443
result._perPositionHeight = perPositionHeight;
412444
result._perPositionHeightExtrude = perPositionHeightExtrude;
445+
result._arcType = arcType;
413446
result._offsetAttribute = offsetAttribute === -1 ? undefined : offsetAttribute;
414447
result.packedLength = packedLength;
415448

@@ -426,6 +459,7 @@ define([
426459
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
427460
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
428461
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
462+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of path the outline must follow. Valid options are {@link LinkType.GEODESIC} and {@link ArcType.RHUMB}.
429463
* @returns {PolygonOutlineGeometry}
430464
*
431465
*
@@ -460,6 +494,7 @@ define([
460494
ellipsoid : options.ellipsoid,
461495
granularity : options.granularity,
462496
perPositionHeight : options.perPositionHeight,
497+
arcType: options.arcType,
463498
offsetAttribute : options.offsetAttribute
464499
};
465500
return new PolygonOutlineGeometry(newOptions);
@@ -476,6 +511,7 @@ define([
476511
var granularity = polygonGeometry._granularity;
477512
var polygonHierarchy = polygonGeometry._polygonHierarchy;
478513
var perPositionHeight = polygonGeometry._perPositionHeight;
514+
var arcType = polygonGeometry._arcType;
479515

480516
var polygons = PolygonGeometryLibrary.polygonOutlinesFromHierarchy(polygonHierarchy, !perPositionHeight, ellipsoid);
481517

@@ -494,7 +530,7 @@ define([
494530
var i;
495531
if (extrude) {
496532
for (i = 0; i < polygons.length; i++) {
497-
geometryInstance = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], minDistance, perPositionHeight);
533+
geometryInstance = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], minDistance, perPositionHeight, arcType);
498534
geometryInstance.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(geometryInstance.geometry, height, extrudedHeight, ellipsoid, perPositionHeight);
499535
if (defined(polygonGeometry._offsetAttribute)) {
500536
var size = geometryInstance.geometry.attributes.position.values.length / 3;
@@ -516,7 +552,7 @@ define([
516552
}
517553
} else {
518554
for (i = 0; i < polygons.length; i++) {
519-
geometryInstance = createGeometryFromPositions(ellipsoid, polygons[i], minDistance, perPositionHeight);
555+
geometryInstance = createGeometryFromPositions(ellipsoid, polygons[i], minDistance, perPositionHeight, arcType);
520556
geometryInstance.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometryInstance.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
521557

522558
if (defined(polygonGeometry._offsetAttribute)) {

‎Source/Core/PolygonPipeline.js

+154
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ define([
22
'../ThirdParty/earcut-2.1.1',
33
'./Cartesian2',
44
'./Cartesian3',
5+
'./Cartographic',
56
'./Check',
67
'./ComponentDatatype',
78
'./defaultValue',
@@ -16,6 +17,7 @@ define([
1617
earcut,
1718
Cartesian2,
1819
Cartesian3,
20+
Cartographic,
1921
Check,
2022
ComponentDatatype,
2123
defaultValue,
@@ -227,6 +229,158 @@ define([
227229
});
228230
};
229231

232+
var subdivisionC0Scratch = new Cartographic();
233+
var subdivisionC1Scratch = new Cartographic();
234+
var subdivisionC2Scratch = new Cartographic();
235+
var subdivisionCart2Scratch0 = new Cartesian2();
236+
var subdivisionCart2Scratch1 = new Cartesian2();
237+
var subdivisionCart2Scratch2 = new Cartesian2();
238+
var subdivisionMidCart2Scratch = new Cartesian2();
239+
240+
/**
241+
* Subdivides positions on rhumb lines and raises points to the surface of the ellipsoid.
242+
*
243+
* @param {Ellipsoid} ellipsoid The ellipsoid the polygon in on.
244+
* @param {Cartesian3[]} positions An array of {@link Cartesian3} positions of the polygon.
245+
* @param {Number[]} indices An array of indices that determines the triangles in the polygon.
246+
* @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
247+
*
248+
* @exception {DeveloperError} At least three indices are required.
249+
* @exception {DeveloperError} The number of indices must be divisable by three.
250+
* @exception {DeveloperError} Granularity must be greater than zero.
251+
*/
252+
PolygonPipeline.computeRhumbLineSubdivision = function(ellipsoid, positions, indices, granularity) {
253+
granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE);
254+
255+
//>>includeStart('debug', pragmas.debug);
256+
Check.typeOf.object('ellipsoid', ellipsoid);
257+
Check.defined('positions', positions);
258+
Check.defined('indices', indices);
259+
Check.typeOf.number.greaterThanOrEquals('indices.length', indices.length, 3);
260+
Check.typeOf.number.equals('indices.length % 3', '0', indices.length % 3, 0);
261+
Check.typeOf.number.greaterThan('granularity', granularity, 0.0);
262+
//>>includeEnd('debug');
263+
264+
// triangles that need (or might need) to be subdivided.
265+
var triangles = indices.slice(0);
266+
267+
// New positions due to edge splits are appended to the positions list.
268+
var i;
269+
var length = positions.length;
270+
var subdividedPositions = new Array(length * 3);
271+
var q = 0;
272+
for (i = 0; i < length; i++) {
273+
var item = positions[i];
274+
subdividedPositions[q++] = item.x;
275+
subdividedPositions[q++] = item.y;
276+
subdividedPositions[q++] = item.z;
277+
}
278+
279+
var subdividedIndices = [];
280+
281+
// Used to make sure shared edges are not split more than once.
282+
var edges = {};
283+
284+
var granularitySqrd = granularity * granularity;
285+
286+
while (triangles.length > 0) {
287+
var i2 = triangles.pop();
288+
var i1 = triangles.pop();
289+
var i0 = triangles.pop();
290+
291+
var v0 = Cartesian3.fromArray(subdividedPositions, i0 * 3, subdivisionV0Scratch);
292+
var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch);
293+
var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch);
294+
295+
var c0 = ellipsoid.cartesianToCartographic(v0, subdivisionC0Scratch);
296+
var c1 = ellipsoid.cartesianToCartographic(v1, subdivisionC1Scratch);
297+
var c2 = ellipsoid.cartesianToCartographic(v2, subdivisionC2Scratch);
298+
299+
var c0Cart2 = Cartesian2.fromElements(c0.longitude, c0.latitude, subdivisionCart2Scratch0);
300+
var c1Cart2 = Cartesian2.fromElements(c1.longitude, c1.latitude, subdivisionCart2Scratch1);
301+
var c2Cart2 = Cartesian2.fromElements(c2.longitude, c2.latitude, subdivisionCart2Scratch2);
302+
303+
var g0 = Cartesian2.distanceSquared(c0Cart2, c1Cart2);
304+
var g1 = Cartesian2.distanceSquared(c1Cart2, c2Cart2);
305+
var g2 = Cartesian2.distanceSquared(c2Cart2, c0Cart2);
306+
307+
var max = Math.max(g0, g1, g2);
308+
var edge;
309+
var mid;
310+
var midHeight;
311+
var midCartesian3;
312+
313+
// if the max length squared of a triangle edge is greater than squared granularity, subdivide the triangle
314+
if (max > granularitySqrd) {
315+
if (g0 === max) {
316+
edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1);
317+
318+
i = edges[edge];
319+
if (!defined(i)) {
320+
mid = Cartesian2.add(c0Cart2, c1Cart2, subdivisionMidCart2Scratch);
321+
Cartesian2.multiplyByScalar(mid, 0.5, mid);
322+
midHeight = (c0.height + c1.height) * 0.5;
323+
midCartesian3 = Cartesian3.fromRadians(mid.x, mid.y, midHeight, ellipsoid, subdivisionMidScratch);
324+
subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z);
325+
i = subdividedPositions.length / 3 - 1;
326+
edges[edge] = i;
327+
}
328+
329+
triangles.push(i0, i, i2);
330+
triangles.push(i, i1, i2);
331+
} else if (g1 === max) {
332+
edge = Math.min(i1, i2) + ' ' + Math.max(i1, i2);
333+
334+
i = edges[edge];
335+
if (!defined(i)) {
336+
mid = Cartesian2.add(c1Cart2, c2Cart2, subdivisionMidCart2Scratch);
337+
Cartesian2.multiplyByScalar(mid, 0.5, mid);
338+
midHeight = (c1.height + c2.height) * 0.5;
339+
midCartesian3 = Cartesian3.fromRadians(mid.x, mid.y, midHeight, ellipsoid, subdivisionMidScratch);
340+
subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z);
341+
i = subdividedPositions.length / 3 - 1;
342+
edges[edge] = i;
343+
}
344+
345+
triangles.push(i1, i, i0);
346+
triangles.push(i, i2, i0);
347+
} else if (g2 === max) {
348+
edge = Math.min(i2, i0) + ' ' + Math.max(i2, i0);
349+
350+
i = edges[edge];
351+
if (!defined(i)) {
352+
mid = Cartesian2.add(c2Cart2, c0Cart2, subdivisionMidCart2Scratch);
353+
Cartesian2.multiplyByScalar(mid, 0.5, mid);
354+
midHeight = (c2.height + c0.height) * 0.5;
355+
midCartesian3 = Cartesian3.fromRadians(mid.x, mid.y, midHeight, ellipsoid, subdivisionMidScratch);
356+
subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z);
357+
i = subdividedPositions.length / 3 - 1;
358+
edges[edge] = i;
359+
}
360+
361+
triangles.push(i2, i, i1);
362+
triangles.push(i, i0, i1);
363+
}
364+
} else {
365+
subdividedIndices.push(i0);
366+
subdividedIndices.push(i1);
367+
subdividedIndices.push(i2);
368+
}
369+
}
370+
371+
return new Geometry({
372+
attributes : {
373+
position : new GeometryAttribute({
374+
componentDatatype : ComponentDatatype.DOUBLE,
375+
componentsPerAttribute : 3,
376+
values : subdividedPositions
377+
})
378+
},
379+
indices : subdividedIndices,
380+
primitiveType : PrimitiveType.TRIANGLES
381+
});
382+
};
383+
230384
/**
231385
* Scales each position of a geometry's position attribute to a height, in place.
232386
*

‎Source/Core/PolylineGeometry.js

+51-21
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
define([
2+
'./ArcType',
23
'./arrayRemoveDuplicates',
34
'./BoundingSphere',
45
'./Cartesian3',
56
'./Color',
67
'./ComponentDatatype',
78
'./defaultValue',
89
'./defined',
10+
'./deprecationWarning',
911
'./DeveloperError',
1012
'./Ellipsoid',
1113
'./Geometry',
@@ -18,13 +20,15 @@ define([
1820
'./PrimitiveType',
1921
'./VertexFormat'
2022
], function(
23+
ArcType,
2124
arrayRemoveDuplicates,
2225
BoundingSphere,
2326
Cartesian3,
2427
Color,
2528
ComponentDatatype,
2629
defaultValue,
2730
defined,
31+
deprecationWarning,
2832
DeveloperError,
2933
Ellipsoid,
3034
Geometry,
@@ -87,8 +91,8 @@ define([
8791
* @param {Number} [options.width=1.0] The width in pixels.
8892
* @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors.
8993
* @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices.
90-
* @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc.
91-
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer.
94+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow.
95+
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.arcType is not ArcType.NONE. Determines the number of positions in the buffer.
9296
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
9397
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
9498
*
@@ -136,7 +140,15 @@ define([
136140
this._width = width;
137141
this._colorsPerVertex = colorsPerVertex;
138142
this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
143+
139144
this._followSurface = defaultValue(options.followSurface, true);
145+
if (defined(options.followSurface)) {
146+
deprecationWarning('PolylineGeometry.followSurface', 'PolylineGeometry.followSurface is deprecated and will be removed in Cesium 1.55. Use PolylineGeometry.arcType instead.');
147+
options.arcType = options.followSurface ? ArcType.GEODESIC : ArcType.NONE;
148+
}
149+
this._arcType = defaultValue(options.arcType, ArcType.GEODESIC);
150+
this._followSurface = (this._arcType !== ArcType.NONE);
151+
140152
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
141153
this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
142154
this._workerName = 'createPolylineGeometry';
@@ -198,7 +210,7 @@ define([
198210

199211
array[startingIndex++] = value._width;
200212
array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0;
201-
array[startingIndex++] = value._followSurface ? 1.0 : 0.0;
213+
array[startingIndex++] = value._arcType;
202214
array[startingIndex] = value._granularity;
203215

204216
return array;
@@ -213,7 +225,7 @@ define([
213225
vertexFormat : scratchVertexFormat,
214226
width : undefined,
215227
colorsPerVertex : undefined,
216-
followSurface : undefined,
228+
arcType : undefined,
217229
granularity : undefined
218230
};
219231

@@ -258,15 +270,15 @@ define([
258270

259271
var width = array[startingIndex++];
260272
var colorsPerVertex = array[startingIndex++] === 1.0;
261-
var followSurface = array[startingIndex++] === 1.0;
273+
var arcType = array[startingIndex++];
262274
var granularity = array[startingIndex];
263275

264276
if (!defined(result)) {
265277
scratchOptions.positions = positions;
266278
scratchOptions.colors = colors;
267279
scratchOptions.width = width;
268280
scratchOptions.colorsPerVertex = colorsPerVertex;
269-
scratchOptions.followSurface = followSurface;
281+
scratchOptions.arcType = arcType;
270282
scratchOptions.granularity = granularity;
271283
return new PolylineGeometry(scratchOptions);
272284
}
@@ -277,7 +289,7 @@ define([
277289
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
278290
result._width = width;
279291
result._colorsPerVertex = colorsPerVertex;
280-
result._followSurface = followSurface;
292+
result._arcType = arcType;
281293
result._granularity = granularity;
282294

283295
return result;
@@ -299,7 +311,7 @@ define([
299311
var vertexFormat = polylineGeometry._vertexFormat;
300312
var colors = polylineGeometry._colors;
301313
var colorsPerVertex = polylineGeometry._colorsPerVertex;
302-
var followSurface = polylineGeometry._followSurface;
314+
var arcType = polylineGeometry._arcType;
303315
var granularity = polylineGeometry._granularity;
304316
var ellipsoid = polylineGeometry._ellipsoid;
305317

@@ -316,27 +328,36 @@ define([
316328
return undefined;
317329
}
318330

319-
if (followSurface) {
331+
if (arcType === ArcType.GEODESIC || arcType === ArcType.RHUMB) {
332+
var subdivisionSize;
333+
var numberOfPointsFunction;
334+
if (arcType === ArcType.GEODESIC) {
335+
subdivisionSize = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
336+
numberOfPointsFunction = PolylinePipeline.numberOfPoints;
337+
} else {
338+
subdivisionSize = granularity;
339+
numberOfPointsFunction = PolylinePipeline.numberOfPointsRhumbLine;
340+
}
341+
320342
var heights = PolylinePipeline.extractHeights(positions, ellipsoid);
321-
var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
322343

323344
if (defined(colors)) {
324345
var colorLength = 1;
325346
for (i = 0; i < positionsLength - 1; ++i) {
326-
colorLength += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance);
347+
colorLength += numberOfPointsFunction(positions[i], positions[i + 1], subdivisionSize);
327348
}
328349

329350
var newColors = new Array(colorLength);
330351
var newColorIndex = 0;
331352

332353
for (i = 0; i < positionsLength - 1; ++i) {
333354
var p0 = positions[i];
334-
var p1 = positions[i+1];
355+
var p1 = positions[i + 1];
335356
var c0 = colors[i];
336357

337-
var numColors = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
358+
var numColors = numberOfPointsFunction(p0, p1, subdivisionSize);
338359
if (colorsPerVertex && i < colorLength) {
339-
var c1 = colors[i+1];
360+
var c1 = colors[i + 1];
340361
var interpolatedColors = interpolateColors(p0, p1, c0, c1, numColors);
341362
var interpolatedColorsLength = interpolatedColors.length;
342363
for (j = 0; j < interpolatedColorsLength; ++j) {
@@ -349,18 +370,27 @@ define([
349370
}
350371
}
351372

352-
newColors[newColorIndex] = Color.clone(colors[colors.length-1]);
373+
newColors[newColorIndex] = Color.clone(colors[colors.length - 1]);
353374
colors = newColors;
354375

355376
scratchInterpolateColorsArray.length = 0;
356377
}
357378

358-
positions = PolylinePipeline.generateCartesianArc({
359-
positions: positions,
360-
minDistance: minDistance,
361-
ellipsoid: ellipsoid,
362-
height: heights
363-
});
379+
if (arcType === ArcType.GEODESIC) {
380+
positions = PolylinePipeline.generateCartesianArc({
381+
positions: positions,
382+
minDistance: subdivisionSize,
383+
ellipsoid: ellipsoid,
384+
height: heights
385+
});
386+
} else {
387+
positions = PolylinePipeline.generateCartesianRhumbArc({
388+
positions: positions,
389+
granularity: subdivisionSize,
390+
ellipsoid: ellipsoid,
391+
height: heights
392+
});
393+
}
364394
}
365395

366396
positionsLength = positions.length;

‎Source/Core/PolylinePipeline.js

+164
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ define([
66
'./DeveloperError',
77
'./Ellipsoid',
88
'./EllipsoidGeodesic',
9+
'./EllipsoidRhumbLine',
910
'./IntersectionTests',
1011
'./isArray',
1112
'./Math',
@@ -19,6 +20,7 @@ define([
1920
DeveloperError,
2021
Ellipsoid,
2122
EllipsoidGeodesic,
23+
EllipsoidRhumbLine,
2224
IntersectionTests,
2325
isArray,
2426
CesiumMath,
@@ -36,6 +38,11 @@ define([
3638
return Math.ceil(distance / minDistance);
3739
};
3840

41+
PolylinePipeline.numberOfPointsRhumbLine = function(p0, p1, granularity) {
42+
var radiansDistanceSquared = Math.pow((p0.longitude - p1.longitude), 2) + Math.pow((p0.latitude - p1.latitude), 2);
43+
return Math.ceil(Math.sqrt(radiansDistanceSquared / (granularity * granularity)));
44+
};
45+
3946
var cartoScratch = new Cartographic();
4047
PolylinePipeline.extractHeights = function(positions, ellipsoid) {
4148
var length = positions.length;
@@ -87,6 +94,7 @@ define([
8794
var scaleFirst = new Cartesian3();
8895
var scaleLast = new Cartesian3();
8996
var ellipsoidGeodesic = new EllipsoidGeodesic();
97+
var ellipsoidRhumb = new EllipsoidRhumbLine();
9098

9199
//Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2.
92100
//Result includes p1, but not include p2. This function is called for a sequence of line segments,
@@ -119,6 +127,41 @@ define([
119127
return index;
120128
}
121129

130+
//Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2.
131+
//Result includes p1, but not include p2. This function is called for a sequence of line segments,
132+
//and this prevents duplication of end point.
133+
function generateCartesianRhumbArc(p0, p1, granularity, ellipsoid, h0, h1, array, offset) {
134+
var first = ellipsoid.scaleToGeodeticSurface(p0, scaleFirst);
135+
var last = ellipsoid.scaleToGeodeticSurface(p1, scaleLast);
136+
var start = ellipsoid.cartesianToCartographic(first, carto1);
137+
var end = ellipsoid.cartesianToCartographic(last, carto2);
138+
139+
var numPoints = PolylinePipeline.numberOfPointsRhumbLine(start, end, granularity);
140+
var heights = subdivideHeights(numPoints, h0, h1);
141+
142+
if (!ellipsoidRhumb.ellipsoid.equals(ellipsoid)) {
143+
ellipsoidRhumb = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
144+
}
145+
ellipsoidRhumb.setEndPoints(start, end);
146+
var surfaceDistanceBetweenPoints = ellipsoidRhumb.surfaceDistance / numPoints;
147+
148+
var index = offset;
149+
start.height = h0;
150+
var cart = ellipsoid.cartographicToCartesian(start, cartesian);
151+
Cartesian3.pack(cart, array, index);
152+
index += 3;
153+
154+
for (var i = 1; i < numPoints; i++) {
155+
var carto = ellipsoidRhumb.interpolateUsingSurfaceDistance(i * surfaceDistanceBetweenPoints, carto2);
156+
carto.height = heights[i];
157+
cart = ellipsoid.cartographicToCartesian(carto, cartesian);
158+
Cartesian3.pack(cart, array, index);
159+
index += 3;
160+
}
161+
162+
return index;
163+
}
164+
122165
/**
123166
* Breaks a {@link Polyline} into segments such that it does not cross the &plusmn;180 degree meridian of an ellipsoid.
124167
*
@@ -287,6 +330,97 @@ define([
287330
return newPositions;
288331
};
289332

333+
var scratchCartographic0 = new Cartographic();
334+
var scratchCartographic1 = new Cartographic();
335+
336+
/**
337+
* Subdivides polyline and raises all points to the specified height using Rhumb lines. Returns an array of numbers to represent the positions.
338+
* @param {Object} options Object with the following properties:
339+
* @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
340+
* @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
341+
* @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
342+
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
343+
* @returns {Number[]} A new array of positions of type {Number} that have been subdivided and raised to the surface of the ellipsoid.
344+
*
345+
* @example
346+
* var positions = Cesium.Cartesian3.fromDegreesArray([
347+
* -105.0, 40.0,
348+
* -100.0, 38.0,
349+
* -105.0, 35.0,
350+
* -100.0, 32.0
351+
* ]);
352+
* var surfacePositions = Cesium.PolylinePipeline.generateRhumbArc({
353+
* positons: positions
354+
* });
355+
*/
356+
PolylinePipeline.generateRhumbArc = function(options) {
357+
if (!defined(options)) {
358+
options = {};
359+
}
360+
var positions = options.positions;
361+
//>>includeStart('debug', pragmas.debug);
362+
if (!defined(positions)) {
363+
throw new DeveloperError('options.positions is required.');
364+
}
365+
//>>includeEnd('debug');
366+
367+
var length = positions.length;
368+
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
369+
var height = defaultValue(options.height, 0);
370+
var hasHeightArray = isArray(height);
371+
372+
if (length < 1) {
373+
return [];
374+
} else if (length === 1) {
375+
var p = ellipsoid.scaleToGeodeticSurface(positions[0], scaleFirst);
376+
height = hasHeightArray ? height[0] : height;
377+
if (height !== 0) {
378+
var n = ellipsoid.geodeticSurfaceNormal(p, cartesian);
379+
Cartesian3.multiplyByScalar(n, height, n);
380+
Cartesian3.add(p, n, p);
381+
}
382+
383+
return [p.x, p.y, p.z];
384+
}
385+
386+
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
387+
388+
var numPoints = 0;
389+
var i;
390+
391+
var c0 = ellipsoid.cartesianToCartographic(positions[0], scratchCartographic0);
392+
var c1;
393+
for (i = 0; i < length - 1; i++) {
394+
c1 = ellipsoid.cartesianToCartographic(positions[i + 1], scratchCartographic1);
395+
numPoints += PolylinePipeline.numberOfPointsRhumbLine(c0, c1, granularity);
396+
c0 = Cartographic.clone(c1, scratchCartographic0);
397+
}
398+
399+
var arrayLength = (numPoints + 1) * 3;
400+
var newPositions = new Array(arrayLength);
401+
var offset = 0;
402+
403+
for (i = 0; i < length - 1; i++) {
404+
var p0 = positions[i];
405+
var p1 = positions[i + 1];
406+
407+
var h0 = hasHeightArray ? height[i] : height;
408+
var h1 = hasHeightArray ? height[i + 1] : height;
409+
410+
offset = generateCartesianRhumbArc(p0, p1, granularity, ellipsoid, h0, h1, newPositions, offset);
411+
}
412+
413+
subdivideHeightsScratchArray.length = 0;
414+
415+
var lastPoint = positions[length - 1];
416+
var carto = ellipsoid.cartesianToCartographic(lastPoint, carto1);
417+
carto.height = hasHeightArray ? height[length - 1] : height;
418+
var cart = ellipsoid.cartographicToCartesian(carto, cartesian);
419+
Cartesian3.pack(cart, newPositions, arrayLength - 3);
420+
421+
return newPositions;
422+
};
423+
290424
/**
291425
* Subdivides polyline and raises all points to the specified height. Returns an array of new {Cartesian3} positions.
292426
* @param {Object} options Object with the following properties:
@@ -317,5 +451,35 @@ define([
317451
return newPositions;
318452
};
319453

454+
/**
455+
* Subdivides polyline and raises all points to the specified height using Rhumb Lines. Returns an array of new {Cartesian3} positions.
456+
* @param {Object} options Object with the following properties:
457+
* @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
458+
* @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
459+
* @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
460+
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
461+
* @returns {Cartesian3[]} A new array of cartesian3 positions that have been subdivided and raised to the surface of the ellipsoid.
462+
*
463+
* @example
464+
* var positions = Cesium.Cartesian3.fromDegreesArray([
465+
* -105.0, 40.0,
466+
* -100.0, 38.0,
467+
* -105.0, 35.0,
468+
* -100.0, 32.0
469+
* ]);
470+
* var surfacePositions = Cesium.PolylinePipeline.generateCartesianRhumbArc({
471+
* positons: positions
472+
* });
473+
*/
474+
PolylinePipeline.generateCartesianRhumbArc = function(options) {
475+
var numberArray = PolylinePipeline.generateRhumbArc(options);
476+
var size = numberArray.length/3;
477+
var newPositions = new Array(size);
478+
for (var i = 0; i < size; i++) {
479+
newPositions[i] = Cartesian3.unpack(numberArray, i*3);
480+
}
481+
return newPositions;
482+
};
483+
320484
return PolylinePipeline;
321485
});

‎Source/Core/SimplePolylineGeometry.js

+44-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
define([
2+
'./ArcType',
23
'./BoundingSphere',
34
'./Cartesian3',
45
'./Color',
56
'./ComponentDatatype',
67
'./defaultValue',
78
'./defined',
9+
'./deprecationWarning',
810
'./DeveloperError',
911
'./Ellipsoid',
1012
'./Geometry',
@@ -15,12 +17,14 @@ define([
1517
'./PolylinePipeline',
1618
'./PrimitiveType'
1719
], function(
20+
ArcType,
1821
BoundingSphere,
1922
Cartesian3,
2023
Color,
2124
ComponentDatatype,
2225
defaultValue,
2326
defined,
27+
deprecationWarning,
2428
DeveloperError,
2529
Ellipsoid,
2630
Geometry,
@@ -83,8 +87,8 @@ define([
8387
* @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the positions in the polyline as a line strip.
8488
* @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors.
8589
* @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices.
86-
* @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc.
87-
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer.
90+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow.
91+
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.arcType is not ArcType.NONE. Determines the number of positions in the buffer.
8892
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
8993
*
9094
* @exception {DeveloperError} At least two positions are required.
@@ -121,7 +125,15 @@ define([
121125
this._positions = positions;
122126
this._colors = colors;
123127
this._colorsPerVertex = colorsPerVertex;
128+
124129
this._followSurface = defaultValue(options.followSurface, true);
130+
if (defined(options.followSurface)) {
131+
deprecationWarning('PolylineGeometry.followSurface', 'PolylineGeometry.followSurface is deprecated and will be removed in Cesium 1.55. Use PolylineGeometry.arcType instead.');
132+
options.arcType = options.followSurface ? ArcType.GEODESIC : ArcType.NONE;
133+
}
134+
this._arcType = defaultValue(options.arcType, ArcType.GEODESIC);
135+
this._followSurface = this._arcType === ArcType.NONE;
136+
125137
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
126138
this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
127139
this._workerName = 'createSimplePolylineGeometry';
@@ -179,7 +191,7 @@ define([
179191
startingIndex += Ellipsoid.packedLength;
180192

181193
array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0;
182-
array[startingIndex++] = value._followSurface ? 1.0 : 0.0;
194+
array[startingIndex++] = value._arcType;
183195
array[startingIndex] = value._granularity;
184196

185197
return array;
@@ -222,7 +234,7 @@ define([
222234
startingIndex += Ellipsoid.packedLength;
223235

224236
var colorsPerVertex = array[startingIndex++] === 1.0;
225-
var followSurface = array[startingIndex++] === 1.0;
237+
var arcType = array[startingIndex++];
226238
var granularity = array[startingIndex];
227239

228240
if (!defined(result)) {
@@ -231,7 +243,7 @@ define([
231243
colors : colors,
232244
ellipsoid : ellipsoid,
233245
colorsPerVertex : colorsPerVertex,
234-
followSurface : followSurface,
246+
arcType : arcType,
235247
granularity : granularity
236248
});
237249
}
@@ -240,7 +252,7 @@ define([
240252
result._colors = colors;
241253
result._ellipsoid = ellipsoid;
242254
result._colorsPerVertex = colorsPerVertex;
243-
result._followSurface = followSurface;
255+
result._arcType = arcType;
244256
result._granularity = granularity;
245257

246258
return result;
@@ -252,7 +264,8 @@ define([
252264
positions : scratchArray1,
253265
height: scratchArray2,
254266
ellipsoid: undefined,
255-
minDistance : undefined
267+
minDistance : undefined,
268+
granularity : undefined
256269
};
257270

258271
/**
@@ -265,7 +278,7 @@ define([
265278
var positions = simplePolylineGeometry._positions;
266279
var colors = simplePolylineGeometry._colors;
267280
var colorsPerVertex = simplePolylineGeometry._colorsPerVertex;
268-
var followSurface = simplePolylineGeometry._followSurface;
281+
var arcType = simplePolylineGeometry._arcType;
269282
var granularity = simplePolylineGeometry._granularity;
270283
var ellipsoid = simplePolylineGeometry._ellipsoid;
271284

@@ -281,16 +294,34 @@ define([
281294
var color;
282295
var offset = 0;
283296

284-
if (followSurface) {
297+
if (arcType === ArcType.GEODESIC || arcType === ArcType.RHUMB) {
298+
var subdivisionSize;
299+
var numberOfPointsFunction;
300+
var generateArcFunction;
301+
if (arcType === ArcType.GEODESIC) {
302+
subdivisionSize = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
303+
numberOfPointsFunction = PolylinePipeline.numberOfPoints;
304+
generateArcFunction = PolylinePipeline.generateArc;
305+
} else {
306+
subdivisionSize = granularity;
307+
numberOfPointsFunction = PolylinePipeline.numberOfPointsRhumbLine;
308+
generateArcFunction = PolylinePipeline.generateRhumbArc;
309+
}
310+
285311
var heights = PolylinePipeline.extractHeights(positions, ellipsoid);
312+
286313
var generateArcOptions = generateArcOptionsScratch;
287-
generateArcOptions.minDistance = minDistance;
314+
if (arcType === ArcType.GEODESIC) {
315+
generateArcOptions.minDistance = minDistance;
316+
} else {
317+
generateArcOptions.granularity = granularity;
318+
}
288319
generateArcOptions.ellipsoid = ellipsoid;
289320

290321
if (perSegmentColors) {
291322
var positionCount = 0;
292323
for (i = 0; i < length - 1; i++) {
293-
positionCount += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance) + 1;
324+
positionCount += numberOfPointsFunction(positions[i], positions[i+1], subdivisionSize) + 1;
294325
}
295326

296327
positionValues = new Float64Array(positionCount * 3);
@@ -307,7 +338,7 @@ define([
307338
scratchArray2[0] = heights[i];
308339
scratchArray2[1] = heights[i + 1];
309340

310-
var pos = PolylinePipeline.generateArc(generateArcOptions);
341+
var pos = generateArcFunction(generateArcOptions);
311342

312343
if (defined(colors)) {
313344
var segLen = pos.length / 3;
@@ -326,7 +357,7 @@ define([
326357
} else {
327358
generateArcOptions.positions = positions;
328359
generateArcOptions.height= heights;
329-
positionValues = new Float64Array(PolylinePipeline.generateArc(generateArcOptions));
360+
positionValues = new Float64Array(generateArcFunction(generateArcOptions));
330361

331362
if (defined(colors)) {
332363
colorValues = new Uint8Array(positionValues.length / 3 * 4);

‎Source/DataSources/Entity.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ define([
678678
/**
679679
* Checks if the given Scene supports polylines clamped to terrain or 3D Tiles.
680680
* If this feature is not supported, Entities with PolylineGraphics will be rendered with vertices at
681-
* the provided heights and using the `followSurface` parameter instead of clamped to the ground.
681+
* the provided heights and using the `arcType` parameter instead of clamped to the ground.
682682
*
683683
* @param {Scene} scene The current scene.
684684
* @returns {Boolean} Whether or not the current scene supports polylines on terrain or 3D TIles.

‎Source/DataSources/GeoJsonDataSource.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
define([
2+
'../Core/ArcType',
23
'../Core/Cartesian3',
34
'../Core/Color',
45
'../Core/createGuid',
@@ -27,6 +28,7 @@ define([
2728
'./PolygonGraphics',
2829
'./PolylineGraphics'
2930
], function(
31+
ArcType,
3032
Cartesian3,
3133
Color,
3234
createGuid,
@@ -365,6 +367,7 @@ define([
365367
polylineGraphics.material = material;
366368
polylineGraphics.width = widthProperty;
367369
polylineGraphics.positions = new ConstantProperty(coordinatesArrayToCartesianArray(coordinates, crsFunction));
370+
polylineGraphics.arcType = ArcType.RHUMB;
368371
}
369372

370373
function processLineString(dataSource, geoJson, geometry, crsFunction, options) {
@@ -434,6 +437,7 @@ define([
434437
polygon.outlineColor = outlineColorProperty;
435438
polygon.outlineWidth = widthProperty;
436439
polygon.material = material;
440+
polygon.arcType = ArcType.RHUMB;
437441

438442
var holes = [];
439443
for (var i = 1, len = coordinates.length; i < len; i++) {

‎Source/DataSources/PolygonGeometryUpdater.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
define([
22
'../Core/ApproximateTerrainHeights',
3+
'../Core/ArcType',
34
'../Core/Cartesian2',
45
'../Core/Cartesian3',
56
'../Core/Check',
@@ -33,6 +34,7 @@ define([
3334
'./Property'
3435
], function(
3536
ApproximateTerrainHeights,
37+
ArcType,
3638
Cartesian2,
3739
Cartesian3,
3840
Check,
@@ -88,6 +90,7 @@ define([
8890
this.granularity = undefined;
8991
this.stRotation = undefined;
9092
this.offsetAttribute = undefined;
93+
this.arcType = undefined;
9194
}
9295

9396
/**
@@ -275,6 +278,7 @@ define([
275278
!Property.isConstant(polygon.closeTop) || //
276279
!Property.isConstant(polygon.closeBottom) || //
277280
!Property.isConstant(polygon.zIndex) || //
281+
!Property.isConstant(polygon.arcType) || //
278282
(this._onTerrain && !Property.isConstant(this._materialProperty));
279283
};
280284

@@ -322,6 +326,7 @@ define([
322326
options.closeBottom = Property.getValueOrDefault(polygon.closeBottom, Iso8601.MINIMUM_VALUE, true);
323327
options.offsetAttribute = offsetAttribute;
324328
options.height = heightValue;
329+
options.arcType = Property.getValueOrDefault(polygon.arcType, Iso8601.MINIMUM_VALUE, ArcType.GEODESIC);
325330

326331
extrudedHeightValue = GroundGeometryUpdater.getGeometryExtrudedHeight(extrudedHeightValue, extrudedHeightReferenceValue);
327332
if (extrudedHeightValue === GroundGeometryUpdater.CLAMP_TO_GROUND) {
@@ -399,6 +404,7 @@ define([
399404
options.closeBottom = Property.getValueOrDefault(polygon.closeBottom, time, true);
400405
options.offsetAttribute = offsetAttribute;
401406
options.height = heightValue;
407+
options.arcType = Property.getValueOrDefault(polygon.arcType, time, ArcType.GEODESIC);
402408

403409
extrudedHeightValue = GroundGeometryUpdater.getGeometryExtrudedHeight(extrudedHeightValue, extrudedHeightReferenceValue);
404410
if (extrudedHeightValue === GroundGeometryUpdater.CLAMP_TO_GROUND) {

‎Source/DataSources/PolygonGraphics.js

+13
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ define([
4444
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polygon casts or receives shadows from each light source.
4545
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polygon will be displayed.
4646
* @param {Property} [options.classificationType=ClassificationType.BOTH] An enum Property specifying whether this polygon will classify terrain, 3D Tiles, or both when on the ground.
47+
* @param {Property} [options.arcType=ArcType.GEODESIC] The type of line the polygon edges must follow.
4748
* @param {ConstantProperty} [options.zIndex=0] A property specifying the zIndex used for ordering ground geometry. Only has an effect if the polygon is constant and neither height or extrudedHeight are specified.
4849
*
4950
* @see Entity
@@ -88,6 +89,8 @@ define([
8889
this._distanceDisplayConditionSubscription = undefined;
8990
this._classificationType = undefined;
9091
this._classificationTypeSubscription = undefined;
92+
this._arcType = undefined;
93+
this._arcTypeSubscription = undefined;
9194
this._zIndex = undefined;
9295
this._zIndexSubscription = undefined;
9396
this._definitionChanged = new Event();
@@ -260,6 +263,14 @@ define([
260263
*/
261264
classificationType : createPropertyDescriptor('classificationType'),
262265

266+
/**
267+
* Gets or sets the {@link ArcType} Property specifying the type of lines the polygon edges use.
268+
* @memberof PolygonGraphics.prototype
269+
* @type {Property}
270+
* @default ArcType.GEODESIC
271+
*/
272+
arcType : createPropertyDescriptor('arcType'),
273+
263274
/**
264275
* Gets or sets the zIndex Prperty specifying the ordering of ground geometry. Only has an effect if the polygon is constant and neither height or extrudedHeight are specified.
265276
* @memberof PolygonGraphics.prototype
@@ -298,6 +309,7 @@ define([
298309
result.shadows = this.shadows;
299310
result.distanceDisplayCondition = this.distanceDisplayCondition;
300311
result.classificationType = this.classificationType;
312+
result.arcType = this.arcType;
301313
result.zIndex = this.zIndex;
302314

303315
return result;
@@ -335,6 +347,7 @@ define([
335347
this.shadows = defaultValue(this.shadows, source.shadows);
336348
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
337349
this.classificationType = defaultValue(this.classificationType, source.classificationType);
350+
this.arcType = defaultValue(this.arcType, source.arcType);
338351
this.zIndex = defaultValue(this.zIndex, source.zIndex);
339352
};
340353

‎Source/DataSources/PolylineGeometryUpdater.js

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
define([
2+
'../Core/ArcType',
23
'../Core/BoundingSphere',
34
'../Core/Check',
45
'../Core/Color',
@@ -31,6 +32,7 @@ define([
3132
'./MaterialProperty',
3233
'./Property'
3334
], function(
35+
ArcType,
3436
BoundingSphere,
3537
Check,
3638
Color,
@@ -81,12 +83,15 @@ define([
8183
this.positions = undefined;
8284
this.width = undefined;
8385
this.followSurface = undefined;
86+
this.arcType = undefined;
8487
this.granularity = undefined;
8588
}
8689

8790
function GroundGeometryOptions() {
8891
this.positions = undefined;
8992
this.width = undefined;
93+
this.arcType = undefined;
94+
this.granularity = undefined;
9095
}
9196

9297
/**
@@ -309,6 +314,19 @@ define([
309314
}
310315
},
311316

317+
/**
318+
* Gets a value indicating if the path of the line.
319+
* @memberof PolylineGeometryUpdater.prototype
320+
*
321+
* @type {ArcType}
322+
* @readonly
323+
*/
324+
arcType : {
325+
get : function() {
326+
return this._arcType;
327+
}
328+
},
329+
312330
/**
313331
* Gets a value indicating if the geometry is clamped to the ground.
314332
* Returns false if polylines on terrain is not supported.
@@ -498,11 +516,12 @@ define([
498516

499517
var width = polyline.width;
500518
var followSurface = polyline.followSurface;
519+
var arcType = polyline.arcType;
501520
var clampToGround = polyline.clampToGround;
502521
var granularity = polyline.granularity;
503522

504523
if (!positionsProperty.isConstant || !Property.isConstant(width) ||
505-
!Property.isConstant(followSurface) || !Property.isConstant(granularity) ||
524+
!Property.isConstant(followSurface) || !Property.isConstant(arcType) || !Property.isConstant(granularity) ||
506525
!Property.isConstant(clampToGround) || !Property.isConstant(zIndex)) {
507526
if (!this._dynamic) {
508527
this._dynamic = true;
@@ -533,11 +552,14 @@ define([
533552
geometryOptions.positions = positions;
534553
geometryOptions.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined;
535554
geometryOptions.followSurface = defined(followSurface) ? followSurface.getValue(Iso8601.MINIMUM_VALUE) : undefined;
555+
geometryOptions.arcType = defined(arcType) ? arcType.getValue(Iso8601.MINIMUM_VALUE) : undefined;
536556
geometryOptions.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
537557

538558
var groundGeometryOptions = this._groundGeometryOptions;
539559
groundGeometryOptions.positions = positions;
540560
groundGeometryOptions.width = geometryOptions.width;
561+
groundGeometryOptions.arcType = geometryOptions.arcType;
562+
groundGeometryOptions.granularity = geometryOptions.granularity;
541563

542564
this._clampToGround = defined(clampToGround) ? clampToGround.getValue(Iso8601.MINIMUM_VALUE) : false;
543565

@@ -626,6 +648,8 @@ define([
626648
geometryUpdater._clampToGround = Property.getValueOrDefault(polyline._clampToGround, time, false);
627649
geometryUpdater._groundGeometryOptions.positions = positions;
628650
geometryUpdater._groundGeometryOptions.width = Property.getValueOrDefault(polyline._width, time, 1);
651+
geometryUpdater._groundGeometryOptions.arcType = Property.getValueOrDefault(polyline._arcType, time, ArcType.GEODESIC);
652+
geometryUpdater._groundGeometryOptions.granularity = Property.getValueOrDefault(polyline._granularity, time, 9999);
629653

630654
var groundPrimitives = this._groundPrimitives;
631655

@@ -682,9 +706,15 @@ define([
682706
return;
683707
}
684708

685-
var followSurface = Property.getValueOrDefault(polyline._followSurface, time, true);
709+
var followSurface = Property.getValueOrUndefined(polyline._followSurface, time);
710+
var arcType = ArcType.GEODESIC;
711+
if (defined(followSurface)) {
712+
arcType = followSurface ? ArcType.GEODESIC : ArcType.NONE;
713+
}
714+
arcType = Property.getValueOrDefault(polyline._arcType, time, arcType);
715+
686716
var globe = geometryUpdater._scene.globe;
687-
if (followSurface && defined(globe)) {
717+
if (arcType !== ArcType.NONE && defined(globe)) {
688718
generateCartesianArcOptions.ellipsoid = globe.ellipsoid;
689719
generateCartesianArcOptions.positions = positions;
690720
generateCartesianArcOptions.granularity = Property.getValueOrUndefined(polyline._granularity, time);

‎Source/DataSources/PolylineGraphics.js

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
define([
2+
'../Core/ArcType',
23
'../Core/defaultValue',
34
'../Core/defined',
45
'../Core/defineProperties',
@@ -7,6 +8,7 @@ define([
78
'./createMaterialPropertyDescriptor',
89
'./createPropertyDescriptor'
910
], function(
11+
ArcType,
1012
defaultValue,
1113
defined,
1214
defineProperties,
@@ -26,13 +28,13 @@ define([
2628
*
2729
* @param {Object} [options] Object with the following properties:
2830
* @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions that define the line strip.
29-
* @param {Property} [options.followSurface=true] A boolean Property specifying whether the line segments should be great arcs or linearly connected.
31+
* @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow.
3032
* @param {Property} [options.clampToGround=false] A boolean Property specifying whether the Polyline should be clamped to the ground.
3133
* @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels.
3234
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the polyline.
3335
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the polyline.
3436
* @param {MaterialProperty} [options.depthFailMaterial] A property specifying the material used to draw the polyline when it is below the terrain.
35-
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if followSurface is true.
37+
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if arcType is not ArcType.NONE.
3638
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polyline casts or receives shadows from each light source.
3739
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polyline will be displayed.
3840
* @param {Property} [options.classificationType=ClassificationType.BOTH] An enum Property specifying whether this polyline will classify terrain, 3D Tiles, or both when on the ground.
@@ -52,6 +54,8 @@ define([
5254
this._positionsSubscription = undefined;
5355
this._followSurface = undefined;
5456
this._followSurfaceSubscription = undefined;
57+
this._arcType = undefined;
58+
this._arcTypeSubscription = undefined;
5559
this._clampToGround = undefined;
5660
this._clampToGroundSubscription = undefined;
5761
this._granularity = undefined;
@@ -136,10 +140,20 @@ define([
136140
* should be great arcs or linearly connected.
137141
* @memberof PolylineGraphics.prototype
138142
* @type {Property}
143+
* @deprecated This property has been deprecated. Use {@link PolylineGraphics#arcType} instead.
139144
* @default true
140145
*/
141146
followSurface : createPropertyDescriptor('followSurface'),
142147

148+
/**
149+
* Gets or sets the {@link ArcType} Property specifying whether the line segments should be great arcs, rhumb lines or linearly connected.
150+
* @memberof PolylineGraphics.prototype
151+
* @type {Property}
152+
* @deprecated This property has been deprecated. Use {@link PolylineGraphics#arcType} instead.
153+
* @default ArcType.GEODESIC
154+
*/
155+
arcType : createPropertyDescriptor('arcType'),
156+
143157
/**
144158
* Gets or sets the boolean Property specifying whether the polyline
145159
* should be clamped to the ground.
@@ -150,7 +164,7 @@ define([
150164
clampToGround : createPropertyDescriptor('clampToGround'),
151165

152166
/**
153-
* Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if followSurface is true and clampToGround is false.
167+
* Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if arcType is not ArcType.NONE and clampToGround is false.
154168
* @memberof PolylineGraphics.prototype
155169
* @type {Property}
156170
* @default Cesium.Math.RADIANS_PER_DEGREE
@@ -206,6 +220,7 @@ define([
206220
result.positions = this.positions;
207221
result.width = this.width;
208222
result.followSurface = this.followSurface;
223+
result.arcType = this.arcType;
209224
result.clampToGround = this.clampToGround;
210225
result.granularity = this.granularity;
211226
result.shadows = this.shadows;
@@ -235,6 +250,7 @@ define([
235250
this.positions = defaultValue(this.positions, source.positions);
236251
this.width = defaultValue(this.width, source.width);
237252
this.followSurface = defaultValue(this.followSurface, source.followSurface);
253+
this.arcType = defaultValue(this.arcType, source.arcType);
238254
this.clampToGround = defaultValue(this.clampToGround, source.clampToGround);
239255
this.granularity = defaultValue(this.granularity, source.granularity);
240256
this.shadows = defaultValue(this.shadows, source.shadows);

‎Source/Scene/DebugModelMatrixPrimitive.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
define([
2+
'../Core/ArcType',
23
'../Core/Cartesian3',
34
'../Core/Color',
45
'../Core/defaultValue',
@@ -10,6 +11,7 @@ define([
1011
'./PolylineColorAppearance',
1112
'./Primitive'
1213
], function(
14+
ArcType,
1315
Cartesian3,
1416
Color,
1517
defaultValue,
@@ -142,7 +144,7 @@ define([
142144
Color.RED,
143145
Color.RED
144146
],
145-
followSurface: false
147+
arcType: ArcType.NONE
146148
}),
147149
modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
148150
id : this.id,
@@ -160,7 +162,7 @@ define([
160162
Color.GREEN,
161163
Color.GREEN
162164
],
163-
followSurface: false
165+
arcType: ArcType.NONE
164166
}),
165167
modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
166168
id : this.id,
@@ -178,7 +180,7 @@ define([
178180
Color.BLUE,
179181
Color.BLUE
180182
],
181-
followSurface: false
183+
arcType: ArcType.NONE
182184
}),
183185
modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
184186
id : this.id,

‎Specs/Core/GroundPolylineGeometrySpec.js

+57-2
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
defineSuite([
22
'Core/GroundPolylineGeometry',
33
'Core/ApproximateTerrainHeights',
4+
'Core/ArcType',
45
'Core/arraySlice',
56
'Core/Cartesian3',
67
'Core/Cartographic',
7-
'Core/Math',
88
'Core/Ellipsoid',
99
'Core/GeographicProjection',
10+
'Core/Math',
1011
'Core/WebMercatorProjection',
1112
'Specs/createPackableSpecs'
1213
], function(
1314
GroundPolylineGeometry,
1415
ApproximateTerrainHeights,
16+
ArcType,
1517
arraySlice,
1618
Cartesian3,
1719
Cartographic,
18-
CesiumMath,
1920
Ellipsoid,
2021
GeographicProjection,
22+
CesiumMath,
2123
WebMercatorProjection,
2224
createPackableSpecs) {
2325
'use strict';
@@ -364,6 +366,58 @@ defineSuite([
364366
expect(geometry.attributes.position.values.length).toEqual(24 * 3);
365367
});
366368

369+
it('interpolates long polyline segments for rhumb lines', function() {
370+
// rhumb distance = 289020, geodesic distance = 288677
371+
var positions = Cartesian3.fromDegreesArray([
372+
10, 75,
373+
20, 75
374+
]);
375+
376+
var rhumbGroundPolylineGeometry = new GroundPolylineGeometry({
377+
positions : positions,
378+
granularity : 2890.0,
379+
arcType: ArcType.RHUMB
380+
});
381+
var geodesicGroundPolylineGeometry = new GroundPolylineGeometry({
382+
positions : positions,
383+
granularity : 2890.0,
384+
arcType: ArcType.GEODESIC
385+
});
386+
387+
var rhumbGeometry = GroundPolylineGeometry.createGeometry(rhumbGroundPolylineGeometry);
388+
var geodesicGeometry = GroundPolylineGeometry.createGeometry(geodesicGroundPolylineGeometry);
389+
390+
expect(rhumbGeometry.indices.length).toEqual(3636);
391+
expect(geodesicGeometry.indices.length).toEqual(3600);
392+
expect(geodesicGeometry.attributes.position.values.length).toEqual(2400);
393+
expect(rhumbGeometry.attributes.position.values.length).toEqual(2424);
394+
395+
// Interpolate one segment but not the other
396+
positions = Cartesian3.fromDegreesArray([
397+
10, 75,
398+
20, 75,
399+
20.01, 75
400+
]);
401+
rhumbGroundPolylineGeometry = new GroundPolylineGeometry({
402+
positions : positions,
403+
granularity : 2890.0,
404+
arcType: ArcType.RHUMB
405+
});
406+
geodesicGroundPolylineGeometry = new GroundPolylineGeometry({
407+
positions : positions,
408+
granularity : 2890.0,
409+
arcType: ArcType.GEODESIC
410+
});
411+
412+
rhumbGeometry = GroundPolylineGeometry.createGeometry(rhumbGroundPolylineGeometry);
413+
geodesicGeometry = GroundPolylineGeometry.createGeometry(geodesicGroundPolylineGeometry);
414+
415+
expect(rhumbGeometry.indices.length).toEqual(3636 + 36);
416+
expect(geodesicGeometry.indices.length).toEqual(3600 + 36);
417+
expect(geodesicGeometry.attributes.position.values.length).toEqual(2400 + 24);
418+
expect(rhumbGeometry.attributes.position.values.length).toEqual(2424 + 24);
419+
});
420+
367421
it('loops when there are enough positions and loop is specified', function() {
368422
var groundPolylineGeometry = new GroundPolylineGeometry({
369423
positions : Cartesian3.fromDegreesArray([
@@ -564,6 +618,7 @@ defineSuite([
564618
Cartesian3.pack(positions[2], packedInstance, packedInstance.length);
565619
packedInstance.push(polyline.granularity);
566620
packedInstance.push(polyline.loop ? 1.0 : 0.0);
621+
packedInstance.push(polyline.arcType);
567622

568623
Ellipsoid.pack(Ellipsoid.WGS84, packedInstance, packedInstance.length);
569624

‎Specs/Core/PolygonGeometrySpec.js

+127-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defineSuite([
22
'Core/PolygonGeometry',
3+
'Core/ArcType',
34
'Core/arrayFill',
45
'Core/BoundingSphere',
56
'Core/Cartesian3',
@@ -12,6 +13,7 @@ defineSuite([
1213
'Specs/createPackableSpecs'
1314
], function(
1415
PolygonGeometry,
16+
ArcType,
1517
arrayFill,
1618
BoundingSphere,
1719
Cartesian3,
@@ -59,6 +61,17 @@ defineSuite([
5961
}))).toBeUndefined();
6062
});
6163

64+
it('throws if arcType is not valid', function() {
65+
expect(function() {
66+
return new PolygonGeometry({
67+
positions : [Cartesian3.fromDegrees(0, 0),
68+
Cartesian3.fromDegrees(1, 0),
69+
Cartesian3.fromDegrees(1, 1)],
70+
arcType: ArcType.NONE
71+
});
72+
}).toThrowDeveloperError();
73+
});
74+
6275
it('createGeometry returns undefined due to duplicate positions', function() {
6376
var geometry = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({
6477
positions : Cartesian3.fromDegreesArray([
@@ -176,6 +189,82 @@ defineSuite([
176189
expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 3)).height).toEqualEpsilon(0, CesiumMath.EPSILON6);
177190
});
178191

192+
it('create geometry creates with rhumb lines', function() {
193+
var p = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({
194+
vertexFormat : VertexFormat.POSITION_ONLY,
195+
positions : Cartesian3.fromDegreesArray([
196+
-1.0, -1.0,
197+
1.0, -1.0,
198+
1.0, 1.0,
199+
-1.0, 1.0
200+
]),
201+
granularity : CesiumMath.RADIANS_PER_DEGREE,
202+
arcType : ArcType.RHUMB
203+
}));
204+
205+
expect(p.attributes.position.values.length).toEqual(13 * 3); // 8 around edge + 5 in the middle
206+
expect(p.indices.length).toEqual(16 * 3); //4 squares * 4 triangles per square
207+
});
208+
209+
it('create geometry throws if arcType is STRAIGHT', function() {
210+
expect(function() {
211+
PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({
212+
vertexFormat: VertexFormat.POSITION_ONLY,
213+
positions: Cartesian3.fromDegreesArray([
214+
-1.0, -1.0,
215+
1.0, -1.0,
216+
1.0, 1.0,
217+
-1.0, 1.0
218+
]),
219+
granularity: CesiumMath.RADIANS_PER_DEGREE,
220+
arcType: ArcType.NONE
221+
}));
222+
}).toThrowDeveloperError();
223+
});
224+
225+
it('create geometry creates with lines with different number of subdivisions for geodesic and rhumb', function() {
226+
var positions = Cartesian3.fromDegreesArray([
227+
-30.0, -30.0,
228+
30.0, -30.0,
229+
30.0, 30.0,
230+
-30.0, 30.0
231+
]);
232+
var geodesic = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({
233+
vertexFormat : VertexFormat.POSITION_ONLY,
234+
positions : positions,
235+
granularity : CesiumMath.RADIANS_PER_DEGREE,
236+
arcType : ArcType.GEODESIC
237+
}));
238+
var rhumb = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({
239+
vertexFormat : VertexFormat.POSITION_ONLY,
240+
positions : positions,
241+
granularity : CesiumMath.RADIANS_PER_DEGREE,
242+
arcType : ArcType.RHUMB
243+
}));
244+
245+
expect(geodesic.attributes.position.values.length).not.toEqual(rhumb.attributes.position.values.length);
246+
expect(geodesic.indices.length).not.toEqual(rhumb.indices.length);
247+
});
248+
249+
it('computes positions with per position heights for rhumb lines', function() {
250+
var ellipsoid = Ellipsoid.WGS84;
251+
var height = 100.0;
252+
var positions = Cartesian3.fromDegreesArrayHeights([
253+
-1.0, -1.0, height,
254+
1.0, -1.0, 0.0,
255+
1.0, 1.0, 0.0,
256+
-1.0, 1.0, 0.0
257+
]);
258+
var p = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({
259+
positions : positions,
260+
perPositionHeight : true,
261+
arcType : ArcType.RHUMB
262+
}));
263+
264+
expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 0)).height).toEqualEpsilon(height, CesiumMath.EPSILON6);
265+
expect(ellipsoid.cartesianToCartographic(Cartesian3.fromArray(p.attributes.position.values, 3)).height).toEqualEpsilon(0, CesiumMath.EPSILON6);
266+
});
267+
179268
it('computes all attributes', function() {
180269
var p = PolygonGeometry.createGeometry(PolygonGeometry.fromPositions({
181270
vertexFormat : VertexFormat.ALL,
@@ -233,6 +322,43 @@ defineSuite([
233322
expect(p.indices.length).toEqual(10 * 3);
234323
});
235324

325+
it('creates a polygon from hierarchy with rhumb lines', function() {
326+
var hierarchy = {
327+
positions : Cartesian3.fromDegreesArray([
328+
-124.0, 35.0,
329+
-110.0, 35.0,
330+
-110.0, 40.0,
331+
-124.0, 40.0
332+
]),
333+
holes : [{
334+
positions : Cartesian3.fromDegreesArray([
335+
-122.0, 36.0,
336+
-122.0, 39.0,
337+
-112.0, 39.0,
338+
-112.0, 36.0
339+
]),
340+
holes : [{
341+
positions : Cartesian3.fromDegreesArray([
342+
-120.0, 36.5,
343+
-114.0, 36.5,
344+
-114.0, 38.5,
345+
-120.0, 38.5
346+
])
347+
}]
348+
}]
349+
};
350+
351+
var p = PolygonGeometry.createGeometry(new PolygonGeometry({
352+
vertexFormat : VertexFormat.POSITION_ONLY,
353+
polygonHierarchy : hierarchy,
354+
granularity : CesiumMath.PI_OVER_THREE,
355+
arcType : ArcType.RHUMB
356+
}));
357+
358+
expect(p.attributes.position.values.length).toEqual(12 * 3); // 4 points * 3 rectangles
359+
expect(p.indices.length).toEqual(10 * 3);
360+
});
361+
236362
it('removes duplicates in polygon hierarchy', function() {
237363
var hierarchy = {
238364
positions : Cartesian3.fromDegreesArray([
@@ -1067,6 +1193,6 @@ defineSuite([
10671193
addPositions(packedInstance, holePositions1);
10681194
packedInstance.push(Ellipsoid.WGS84.radii.x, Ellipsoid.WGS84.radii.y, Ellipsoid.WGS84.radii.z);
10691195
packedInstance.push(1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
1070-
packedInstance.push(0.0, 0.0, CesiumMath.PI_OVER_THREE, 0.0, 0.0, 1.0, 0, 1, 0, -1, 53);
1196+
packedInstance.push(0.0, 0.0, CesiumMath.PI_OVER_THREE, 0.0, 0.0, 1.0, 0, 1, 0, -1, ArcType.GEODESIC, 54);
10711197
createPackableSpecs(PolygonGeometry, polygon, packedInstance);
10721198
});

0 commit comments

Comments
 (0)
Please sign in to comment.