From f659d2b5176f16ba1ab39f58d4176d1252be0d14 Mon Sep 17 00:00:00 2001 From: Kanging Li Date: Sun, 27 Jan 2019 18:56:27 -0500 Subject: [PATCH 1/2] fix ground polygon rectangle --- CHANGES.md | 1 + Source/Core/PolygonGeometry.js | 94 ++++++++++++++++++++++++++++--- Specs/Core/PolygonGeometrySpec.js | 61 ++++++++++++++++++++ 3 files changed, 149 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b19c37bda54..2fe239f0c3e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ Change Log * Fixed an issue where setting `scene.globe.cartographicLimitRectangle` to `undefined` would cause a crash. [#7477](https://github.com/AnalyticalGraphicsInc/cesium/issues/7477) * Fixed `PrimitiveCollection.removeAll` to no longer `contain` removed primitives. [#7491](https://github.com/AnalyticalGraphicsInc/cesium/pull/7491) * Fixed `GeoJsonDataSource` to use polygons and polylines that use rhumb lines. [#7492](https://github.com/AnalyticalGraphicsInc/cesium/pull/7492) +* Fixed an issue where some ground polygons would be cut off along circles of latitude. [#7507](https://github.com/AnalyticalGraphicsInc/cesium/issues/7507) ### 1.53 - 2019-01-02 diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index b6cb3d35a7e..0264578965b 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -13,6 +13,8 @@ define([ './defineProperties', './DeveloperError', './Ellipsoid', + './EllipsoidGeodesic', + './EllipsoidRhumbLine', './EllipsoidTangentPlane', './Geometry', './GeometryAttribute', @@ -44,6 +46,8 @@ define([ defineProperties, DeveloperError, Ellipsoid, + EllipsoidGeodesic, + EllipsoidRhumbLine, EllipsoidTangentPlane, Geometry, GeometryAttribute, @@ -372,18 +376,84 @@ define([ return geometry; } - function computeRectangle(positions, ellipsoid, result) { + var startCartographicScratch = new Cartographic(); + var endCartographicScratch = new Cartographic(); + var idlCross = { + west : 0.0, + east : 0.0 + }; + function computeRectangle(positions, ellipsoid, arcType, granularity, result) { + result = defaultValue(result, new Rectangle()); if (!defined(positions) || positions.length < 3) { - if (!defined(result)) { - return new Rectangle(); - } result.west = 0.0; result.north = 0.0; result.south = 0.0; result.east = 0.0; return result; } - return Rectangle.fromCartesianArray(positions, ellipsoid, result); + + result.west = Number.POSITIVE_INFINITY; + result.east = Number.NEGATIVE_INFINITY; + result.south = Number.POSITIVE_INFINITY; + result.north = Number.NEGATIVE_INFINITY; + + idlCross.west = Number.POSITIVE_INFINITY; + idlCross.east = Number.NEGATIVE_INFINITY; + + var inverseChordLength = 1.0 / CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + var positionsLength = positions.length; + var endCartographic = ellipsoid.cartesianToCartographic(positions[0], endCartographicScratch); + var startCartographic = startCartographicScratch; + var swap; + + for (var i = 1; i < positionsLength; i++) { + swap = startCartographic; + startCartographic = endCartographic; + endCartographic = ellipsoid.cartesianToCartographic(positions[i], swap); + interpolateAndGrowRectangle(startCartographic, endCartographic, ellipsoid, arcType, inverseChordLength, result, idlCross); + } + + swap = startCartographic; + startCartographic = endCartographic; + endCartographic = ellipsoid.cartesianToCartographic(positions[0], swap); + interpolateAndGrowRectangle(startCartographic, endCartographic, ellipsoid, arcType, inverseChordLength, result, idlCross); + + if (result.east - result.west > idlCross.west - idlCross.east) { + result.east = idlCross.east; + result.west = idlCross.west; + } + + return result; + } + + var interpolatedCartographicScratch = new Cartographic(); + function interpolateAndGrowRectangle(startCartographic, endCartographic, ellipsoid, arcType, inverseChordLength, result, idlCross) { + var segmentPath; + if (arcType === ArcType.GEODESIC) { + segmentPath = new EllipsoidGeodesic(startCartographic, endCartographic, ellipsoid); + } else { + segmentPath = new EllipsoidRhumbLine(startCartographic, endCartographic, ellipsoid); + } + var segmentLength = segmentPath.surfaceDistance; + + var numPoints = Math.ceil(segmentLength * inverseChordLength); + var subsegmentDistance = numPoints > 0 ? segmentLength / (numPoints - 1) : Number.POSITIVE_INFINITY; + var interpolationDistance = 0.0; + + for (var i = 0; i < numPoints; i++) { + var interpolatedCartographic = segmentPath.interpolateUsingSurfaceDistance(interpolationDistance, interpolatedCartographicScratch); + interpolationDistance += subsegmentDistance; + var longitude = interpolatedCartographic.longitude; + var latitude = interpolatedCartographic.latitude; + + result.west = Math.min(result.west, longitude); + result.east = Math.max(result.east, longitude); + result.south = Math.min(result.south, latitude); + result.north = Math.max(result.north, latitude); + + idlCross.west = longitude > 0.0 ? Math.min(longitude, idlCross.west) : idlCross.west; + idlCross.east = longitude < 0.0 ? Math.max(longitude, idlCross.east) : idlCross.east; + } } var createGeometryFromPositionsExtrudedPositions = []; @@ -800,6 +870,8 @@ define([ * * @param {Object} options Object with the following properties: * @param {PolygonHierarchy} options.polygonHierarchy A polygon hierarchy that can include holes. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions sampled. + * @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}. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. * @param {Rectangle} [result] An object in which to store the result. * @@ -811,10 +883,18 @@ define([ Check.typeOf.object('options.polygonHierarchy', options.polygonHierarchy); //>>includeEnd('debug'); + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var arcType = defaultValue(options.arcType, ArcType.GEODESIC); + //>>includeStart('debug', pragmas.debug); + if (arcType !== ArcType.GEODESIC && arcType !== ArcType.RHUMB) { + throw new DeveloperError('Invalid arcType. Valid options are ArcType.GEODESIC and ArcType.RHUMB.'); + } + //>>includeEnd('debug'); + var polygonHierarchy = options.polygonHierarchy; var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - return computeRectangle(polygonHierarchy.positions, ellipsoid, result); + return computeRectangle(polygonHierarchy.positions, ellipsoid, arcType, granularity, result); }; /** @@ -1000,7 +1080,7 @@ define([ get : function() { if (!defined(this._rectangle)) { var positions = this._polygonHierarchy.positions; - this._rectangle = computeRectangle(positions, this._ellipsoid); + this._rectangle = computeRectangle(positions, this._ellipsoid, this._arcType, this._granularity); } return this._rectangle; diff --git a/Specs/Core/PolygonGeometrySpec.js b/Specs/Core/PolygonGeometrySpec.js index 93547332422..e4b5db219e5 100644 --- a/Specs/Core/PolygonGeometrySpec.js +++ b/Specs/Core/PolygonGeometrySpec.js @@ -1055,6 +1055,67 @@ defineSuite([ expect(CesiumMath.toDegrees(r.west)).toEqualEpsilon(-100.5, CesiumMath.EPSILON13); }); + it('computes rectangle according to arctype', function() { + var pGeodesic = new PolygonGeometry({ + vertexFormat : VertexFormat.POSITION_AND_ST, + polygonHierarchy: { + positions : Cartesian3.fromDegreesArrayHeights([ + -90.0, 30.0, 0, + -80.0, 30.0, 0, + -80.0, 40.0, 0, + -90.0, 40.0, 0 + ])}, + granularity: CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.GEODESIC + }); + + var boundingGeodesic = pGeodesic.rectangle; + expect(CesiumMath.toDegrees(boundingGeodesic.north)).toBeGreaterThan(40.0); + expect(CesiumMath.toDegrees(boundingGeodesic.south)).toEqualEpsilon(30.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingGeodesic.east)).toEqualEpsilon(-80.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingGeodesic.west)).toEqualEpsilon(-90.0, CesiumMath.EPSILON10); + + var pRhumb = new PolygonGeometry({ + vertexFormat : VertexFormat.POSITION_AND_ST, + polygonHierarchy: { + positions : Cartesian3.fromDegreesArrayHeights([ + -90.0, 30.0, 0, + -80.0, 30.0, 0, + -80.0, 40.0, 0, + -90.0, 40.0, 0 + ])}, + granularity: CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.RHUMB + }); + + var boundingRhumb = pRhumb.rectangle; + expect(CesiumMath.toDegrees(boundingRhumb.north)).toEqualEpsilon(40.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingRhumb.south)).toEqualEpsilon(30.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingRhumb.east)).toEqualEpsilon(-80.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingRhumb.west)).toEqualEpsilon(-90.0, CesiumMath.EPSILON10); + }); + + it('computes rectangles that cross the IDL', function() { + var pRhumb = new PolygonGeometry({ + vertexFormat : VertexFormat.POSITION_AND_ST, + polygonHierarchy: { + positions : Cartesian3.fromDegreesArray([ + 175, 30, + -170, 30, + -170, 40, + 175, 40 + ])}, + granularity: CesiumMath.RADIANS_PER_DEGREE, + arcType : ArcType.RHUMB + }); + + var boundingRhumb = pRhumb.rectangle; + expect(CesiumMath.toDegrees(boundingRhumb.north)).toEqualEpsilon(40.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingRhumb.south)).toEqualEpsilon(30.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingRhumb.east)).toEqualEpsilon(-170.0, CesiumMath.EPSILON10); + expect(CesiumMath.toDegrees(boundingRhumb.west)).toEqualEpsilon(175.0, CesiumMath.EPSILON10); + }); + it('computeRectangle', function() { var options = { vertexFormat : VertexFormat.POSITION_AND_ST, From 8c680e95db85a89f554ea24f2322d63c7eeb1a5c Mon Sep 17 00:00:00 2001 From: Kanging Li Date: Mon, 28 Jan 2019 20:05:16 -0500 Subject: [PATCH 2/2] use simple rectangle around lat/long for rhumb polygons --- Source/Core/PolygonGeometry.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 0264578965b..596f38e9bbc 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -382,6 +382,7 @@ define([ west : 0.0, east : 0.0 }; + var ellipsoidGeodesic = new EllipsoidGeodesic(); function computeRectangle(positions, ellipsoid, arcType, granularity, result) { result = defaultValue(result, new Rectangle()); if (!defined(positions) || positions.length < 3) { @@ -392,6 +393,14 @@ define([ return result; } + if (arcType === ArcType.RHUMB) { + return Rectangle.fromCartesianArray(positions, ellipsoid, result); + } + + if (!ellipsoidGeodesic.ellipsoid.equals(ellipsoid)) { + ellipsoidGeodesic = new EllipsoidGeodesic(undefined, undefined, ellipsoid); + } + result.west = Number.POSITIVE_INFINITY; result.east = Number.NEGATIVE_INFINITY; result.south = Number.POSITIVE_INFINITY; @@ -410,13 +419,15 @@ define([ swap = startCartographic; startCartographic = endCartographic; endCartographic = ellipsoid.cartesianToCartographic(positions[i], swap); - interpolateAndGrowRectangle(startCartographic, endCartographic, ellipsoid, arcType, inverseChordLength, result, idlCross); + ellipsoidGeodesic.setEndPoints(startCartographic, endCartographic); + interpolateAndGrowRectangle(ellipsoidGeodesic, inverseChordLength, result, idlCross); } swap = startCartographic; startCartographic = endCartographic; endCartographic = ellipsoid.cartesianToCartographic(positions[0], swap); - interpolateAndGrowRectangle(startCartographic, endCartographic, ellipsoid, arcType, inverseChordLength, result, idlCross); + ellipsoidGeodesic.setEndPoints(startCartographic, endCartographic); + interpolateAndGrowRectangle(ellipsoidGeodesic, inverseChordLength, result, idlCross); if (result.east - result.west > idlCross.west - idlCross.east) { result.east = idlCross.east; @@ -427,21 +438,15 @@ define([ } var interpolatedCartographicScratch = new Cartographic(); - function interpolateAndGrowRectangle(startCartographic, endCartographic, ellipsoid, arcType, inverseChordLength, result, idlCross) { - var segmentPath; - if (arcType === ArcType.GEODESIC) { - segmentPath = new EllipsoidGeodesic(startCartographic, endCartographic, ellipsoid); - } else { - segmentPath = new EllipsoidRhumbLine(startCartographic, endCartographic, ellipsoid); - } - var segmentLength = segmentPath.surfaceDistance; + function interpolateAndGrowRectangle(ellipsoidGeodesic, inverseChordLength, result, idlCross) { + var segmentLength = ellipsoidGeodesic.surfaceDistance; var numPoints = Math.ceil(segmentLength * inverseChordLength); var subsegmentDistance = numPoints > 0 ? segmentLength / (numPoints - 1) : Number.POSITIVE_INFINITY; var interpolationDistance = 0.0; for (var i = 0; i < numPoints; i++) { - var interpolatedCartographic = segmentPath.interpolateUsingSurfaceDistance(interpolationDistance, interpolatedCartographicScratch); + var interpolatedCartographic = ellipsoidGeodesic.interpolateUsingSurfaceDistance(interpolationDistance, interpolatedCartographicScratch); interpolationDistance += subsegmentDistance; var longitude = interpolatedCartographic.longitude; var latitude = interpolatedCartographic.latitude;