/// <summary> /// Create sample polyline feature using the geometries from the point feature layer. /// </summary> /// <param name="polylineLayer">Polyline geometry feature layer used to add the new features.</param> /// <param name="pointLayer">The geometries from the point layer are used as vertices for the new line features.</param> /// <returns></returns> private Task <bool> constructSamplePolylines(FeatureLayer polylineLayer, FeatureLayer pointLayer) { // execute the fine grained API calls on the CIM main thread return(QueuedTask.Run(() => { // get the underlying feature class for each layer var polylineFeatureClass = polylineLayer.GetTable() as FeatureClass; var pointFeatureClass = pointLayer.GetTable() as FeatureClass; // retrieve the feature class schema information for the feature classes var polylineDefinition = polylineFeatureClass.GetDefinition() as FeatureClassDefinition; var pointDefinition = pointFeatureClass.GetDefinition() as FeatureClassDefinition; // construct a cursor for all point features, since we want all feature there is no // QueryFilter required var pointCursor = pointFeatureClass.Search(null, false); // initialize a counter variable int pointCounter = 0; // initialize a list to hold 5 coordinates that are used as vertices for the polyline var lineCoordinates = new List <Coordinate>(5); // set up the edit operation for the feature creation var createOperation = new EditOperation(); createOperation.Name = "Create polylines"; createOperation.SelectNewFeatures = false; // set up the datum transformation to be used in the projection ProjectionTransformation transformation = ProjectionTransformation.CreateFromEnvironment(pointDefinition.GetSpatialReference(), polylineDefinition.GetSpatialReference()); // loop through the point features while (pointCursor.MoveNext()) { pointCounter++; var pointFeature = pointCursor.Current as Feature; // add the feature point geometry as a coordinate into the vertex list of the line // - ensure that the projection of the point geometry is converted to match the spatial reference of the line // with a datum transformation considering the different spheroids lineCoordinates.Add(((MapPoint)GeometryEngine.ProjectEx(pointFeature.GetShape(), transformation)).Coordinate); // for every 5 geometries, construct a new polyline and queue a feature create if (pointCounter % 5 == 0) { // construct a new polyline by using the 5 point coordinate in the current list var newPolyline = PolylineBuilder.CreatePolyline(lineCoordinates, polylineDefinition.GetSpatialReference()); // queue the create operation as part of the edit operation createOperation.Create(polylineLayer, newPolyline); // reset the list of coordinates lineCoordinates = new List <Coordinate>(5); } } // execute the edit (create) operation return createOperation.ExecuteAsync(); })); }
/// <summary> /// Creates a new camera offset from the provided camera around an ellipse. /// </summary> /// <param name="camera">The starting camera.</param> /// <param name="ellipse">The ellipse around which the camera will rotate.</param> /// <param name="centerPoint">The center point of the ellipse.</param> /// <param name="percentAlong">The percentage around the ellipse to create the camera.</param> private Camera OffsetCamera(Camera camera, Polyline ellipse, MapPoint centerPoint, double percentAlong) { camera = CloneCamera(camera); var fromPoint = GeometryEngine.MovePointAlongLine(ellipse, percentAlong, true, 0); var segment = LineBuilder.CreateLineSegment(new Coordinate2D(centerPoint.X, centerPoint.Y), new Coordinate2D(fromPoint.X, centerPoint.Y), centerPoint.SpatialReference); var difX = GeometryEngine.GeodesicLength(PolylineBuilder.CreatePolyline(segment, segment.SpatialReference)); if (centerPoint.X - fromPoint.X < 0) { difX *= -1; } segment = LineBuilder.CreateLineSegment(new Coordinate2D(centerPoint.X, centerPoint.Y), new Coordinate2D(centerPoint.X, fromPoint.Y), centerPoint.SpatialReference); var difY = GeometryEngine.GeodesicLength(PolylineBuilder.CreatePolyline(segment, segment.SpatialReference)); if (centerPoint.Y - fromPoint.Y < 0) { difY *= -1; } var radian = Math.Atan2(difX, difY); var heading = radian * -180 / Math.PI; camera.Heading = heading; var difZ = centerPoint.Z - (camera.Z * ((camera.SpatialReference.IsGeographic) ? 1.0 : camera.SpatialReference.Unit.ConversionFactor)); var hypotenuse = GeometryEngine.GeodesicDistance(fromPoint, centerPoint); radian = Math.Atan2(difZ, hypotenuse); var pitch = radian * 180 / Math.PI; camera.Pitch = pitch; if (fromPoint.SpatialReference.Wkid != camera.SpatialReference.Wkid) { var transformation = ProjectionTransformation.Create(fromPoint.SpatialReference, camera.SpatialReference); fromPoint = GeometryEngine.ProjectEx(fromPoint, transformation) as MapPoint; } camera.X = fromPoint.X; camera.Y = fromPoint.Y; return(camera); }
/// <summary> /// Creates keyframes along the path using the user defined settings. /// </summary> /// <param name="line">The geometry of the line to fly along.</param> /// <param name="verticalUnit">The elevation unit of the 3D layer</param> internal Task CreateKeyframesAlongPath(Polyline line, Unit verticalUnit) { return(QueuedTask.Run(() => { var mapView = MapView.Active; if (mapView == null) { return; } //Get the camera track from the active map's animation. //There will always be only one camera track in the animation. var cameraTrack = mapView.Map.Animation.Tracks.OfType <CameraTrack>().First(); //Get some of the user settings for constructing the keyframes alone the path. var densifyDistance = Animation.Settings.KeyEvery; var verticalOffset = Animation.Settings.HeightAbove / ((mapView.Map.SpatialReference.IsGeographic) ? 1.0 : mapView.Map.SpatialReference.Unit.ConversionFactor); //1 meter double currentTimeSeconds = GetInsertTime(mapView.Map.Animation); //We need to project the line to a projected coordinate system to calculate the line's length in 3D //as well as more accurately calculated heading and pitch along the path. if (line.SpatialReference.IsGeographic) { if (mapView.Map.SpatialReference.IsGeographic) { var transformation = ProjectionTransformation.Create(line.SpatialReference, SpatialReferences.WebMercator, line.Extent); line = GeometryEngine.ProjectEx(line, transformation) as Polyline; } else { var transformation = ProjectionTransformation.Create(line.SpatialReference, mapView.Map.SpatialReference, line.Extent); line = GeometryEngine.ProjectEx(line, transformation) as Polyline; } } //If the user has specified to create keyframes at additional locations than just the vertices //we will densify the line by the distance the user specified. if (!Animation.Settings.VerticesOnly) { line = GeometryEngine.DensifyByLength(line, densifyDistance / line.SpatialReference.Unit.ConversionFactor) as Polyline; } //To maintain a constant speed we need to divide the total time we want the animation to take by the length of the line. var duration = Animation.Settings.Duration; var secondsPerUnit = duration / line.Length3D; Camera prevCamera = null; //Loop over each vertex in the line and create a new keyframe at each. for (int i = 0; i < line.PointCount; i++) { #region Camera MapPoint cameraPoint = line.Points[i]; //If the point is not in the same spatial reference of the map we need to project it. if (cameraPoint.SpatialReference.Wkid != mapView.Map.SpatialReference.Wkid) { var transformation = ProjectionTransformation.Create(cameraPoint.SpatialReference, mapView.Map.SpatialReference); cameraPoint = GeometryEngine.Project(cameraPoint, mapView.Map.SpatialReference) as MapPoint; } //Construct a new camera from the point. var camera = new Camera(cameraPoint.X, cameraPoint.Y, cameraPoint.Z, Animation.Settings.Pitch, 0.0, cameraPoint.SpatialReference, CameraViewpoint.LookFrom); //Convert the Z unit to meters if the camera is not in a geographic coordinate system. if (!camera.SpatialReference.IsGeographic) { camera.Z /= camera.SpatialReference.Unit.ConversionFactor; } //Convert the Z to the unit of the layer's elevation unit and then add the user defined offset from the line. camera.Z *= verticalUnit.ConversionFactor; camera.Z += verticalOffset; //If this is the last point in the collection use the same heading and pitch from the previous camera. if (i + 1 == line.Points.Count) { camera.Heading = prevCamera.Heading; camera.Pitch = prevCamera.Pitch; } else { var currentPoint = line.Points[i]; var nextPoint = line.Points[i + 1]; #region Heading //Calculate the heading from the current point to the next point in the path. var difX = nextPoint.X - currentPoint.X; var difY = nextPoint.Y - currentPoint.Y; var radian = Math.Atan2(difX, difY); var heading = radian * -180 / Math.PI; camera.Heading = heading; #endregion #region Pitch //If the user doesn't want to hardcode the pitch, calculate the pitch based on the current point to the next point. if (Animation.Settings.UseLinePitch) { var hypotenuse = Math.Sqrt(Math.Pow(difX, 2) + Math.Pow(difY, 2)); var difZ = nextPoint.Z - currentPoint.Z; //If the line's unit is not the same as the elevation unit of the layer we need to convert the Z so they are in the same unit. if (line.SpatialReference.Unit.ConversionFactor != verticalUnit.ConversionFactor) { difZ *= (verticalUnit.ConversionFactor / line.SpatialReference.Unit.ConversionFactor); } radian = Math.Atan2(difZ, hypotenuse); var pitch = radian * 180 / Math.PI; camera.Pitch = pitch; } else { camera.Pitch = Animation.Settings.Pitch; } #endregion } #endregion #region Time //The first point will have a time of 0 seconds, after that we need to set the time based on the 3D distance between the points. if (i > 0) { var lineSegment = PolylineBuilder.CreatePolyline(new List <MapPoint>() { line.Points[i - 1], line.Points[i] }, line.SpatialReference); var length = lineSegment.Length3D; currentTimeSeconds += length * secondsPerUnit; } #endregion //Create a new keyframe using the camera and the time. cameraTrack.CreateKeyframe(camera, TimeSpan.FromSeconds(currentTimeSeconds), AnimationTransition.Linear); prevCamera = camera; } })); }
/// <summary> /// Create keyframes centered around a point. /// </summary> /// <param name="point">The center point around which the keyframes are created.</param> internal Task CreateKeyframesAroundPoint(MapPoint point) { return(QueuedTask.Run(() => { var mapView = MapView.Active; var degrees = Animation.Settings.Degrees; if (mapView == null || degrees == 0) { return; } //Get the camera track from the active map's animation. //There will always be only one camera track in the animation. var cameraTrack = mapView.Map.Animation.Tracks.OfType <CameraTrack>().First(); var camera = mapView.Camera; //Calculate the number of keys to create. var keyEvery = (degrees < 0) ? -10 : 10; //10 degrees var numOfKeys = Math.Floor(degrees / keyEvery); var remainder = degrees % keyEvery; //To maintain a constant speed we need to divide the total time we want the animation to take by the number of degrees of rotation. var duration = Animation.Settings.Duration; double timeInterval = duration / Math.Abs(degrees); double currentTimeSeconds = GetInsertTime(mapView.Map.Animation); //Get the distance from the current location to the point we want to rotate around to get the radius. var cameraPoint = MapPointBuilder.CreateMapPoint(camera.X, camera.Y, camera.SpatialReference); var radius = GeometryEngine.GeodesicDistance(cameraPoint, point); var radian = ((camera.Heading - 90) / 180.0) * Math.PI; //If the spatial reference of the point is projected and the unit is not in meters we need to convert the Z values to meters. if (!point.SpatialReference.IsGeographic && point.SpatialReference.Unit.ConversionFactor != 1.0) { point = MapPointBuilder.CreateMapPoint(point.X, point.Y, point.Z * point.SpatialReference.Unit.ConversionFactor, point.SpatialReference); } //For all geodesic calculations we will use WGS84 so we will project the point if it is not already. if (point.SpatialReference.Wkid != SpatialReferences.WGS84.Wkid) { var transformation = ProjectionTransformation.Create(point.SpatialReference, SpatialReferences.WGS84); point = GeometryEngine.ProjectEx(point, transformation) as MapPoint; } //Create an ellipse around the center point. var parameter = new GeometryEngine.GeodesicEllipseParameter(); parameter.Center = point.Coordinate; parameter.SemiAxis1Length = radius; parameter.SemiAxis2Length = radius; parameter.AxisDirection = radian; parameter.LinearUnit = LinearUnit.Meters; parameter.OutGeometryType = GeometryType.Polyline; parameter.VertexCount = 36; var ellipse = GeometryEngine.GeodesicEllipse(parameter, point.SpatialReference) as Polyline; //For each key we will progressively rotate around the ellipse and calculate the camera position at each. for (int i = 0; i <= numOfKeys; i++) { var percentAlong = ((Math.Abs(keyEvery) * i) % 360) / 360.0; if (keyEvery > 0) { percentAlong = 1 - percentAlong; } //Get the camera at the position around the ellipse. camera = OffsetCamera(camera, ellipse, point, percentAlong); //Increment the time by the amount of time per key. if (i != 0) { currentTimeSeconds += (timeInterval * Math.Abs(keyEvery)); } //Create a new keyframe for the camera. cameraTrack.CreateKeyframe(camera, TimeSpan.FromSeconds(currentTimeSeconds), AnimationTransition.FixedArc); } //For any degree rotation left over create a keyframe. For example 155, would have a keyframe every 10 degrees and then one for the final 5 degrees. if (remainder != 0.0) { var percentAlong = ((Math.Abs(keyEvery) * numOfKeys + Math.Abs(remainder)) % 360) / 360.0; if (remainder > 0) { percentAlong = 1 - percentAlong; } OffsetCamera(camera, ellipse, point, percentAlong); //Increment the time and create the keyframe. currentTimeSeconds += (timeInterval * Math.Abs(remainder)); cameraTrack.CreateKeyframe(camera, TimeSpan.FromSeconds(currentTimeSeconds), AnimationTransition.FixedArc); } })); }