public COVERAGE_TYPE polygonGeometry() { /////////////////////////////////////////////////////////////////////// // get the mean polygon location and the polygon area // convert all vertices to UTM // get the UTM zone that will be used // get recangular bounding box // determine the orientation of the point-scatter covariance ellipse /////////////////////////////////////////////////////////////////////// utm = new UTM2Geodetic(); //class for LL to UTM conversion for (int i = 0; i < polygons.Count; i++) { //local copy of the polygon -- re-inserted into the polygon list at the end of this procedure Polygon p = polygons[i]; int numLatLonPoints = p.latitudeDeg.Count; //mean Northing and Easting from mean lat/lon -- also use this to get the UTMZone that will be held fixed for this polygon p.meanNorthing = 0.0; p.meanEasting = 0.0; //NOTE: get the UTMZone here (for the polygon mean location) that will be used for the whole project utm.LLtoUTM(p.meanLatDeg * Deg2Rad, p.meanLonDeg * Deg2Rad, ref p.meanNorthing, ref p.meanEasting, ref p.UTMZone, false); //the UTM zone will be held constant across the polygon points -- deals with polys that cross UTM zones //get the Northing and Easting from the lat/lon poly points double northing = 0.0, easting = 0.0; p.maxNorthing = -9999999.0; p.maxEasting = -999999999.0; p.minNorthing = 999999999.0; p.minEasting = 99999999999.0; p.maxLatDeg = -99999.0; p.maxLonDeg = -9999999.0; p.minLatDeg = 99999.0; p.minLonDeg = 999999.0; double CovXX = 0.0, CovYY = 0.0, CovXY = 0.0; //covariance of the UTM poly points for (int ipnts = 0; ipnts < numLatLonPoints; ipnts++) { utm.LLtoUTM(p.latitudeDeg[ipnts] * Deg2Rad, p.longitudeDeg[ipnts] * Deg2Rad, ref northing, ref easting, ref p.UTMZone, true); p.Northing.Add(northing); p.Easting.Add(easting); if (northing > p.maxNorthing) p.maxNorthing = northing; if (northing < p.minNorthing) p.minNorthing = northing; if (easting > p.maxEasting) p.maxEasting = easting; if (easting < p.minEasting) p.minEasting = easting; if (p.latitudeDeg[ipnts] > p.maxLatDeg) p.maxLatDeg = p.latitudeDeg[ipnts]; if (p.latitudeDeg[ipnts] < p.minLatDeg) p.minLatDeg = p.latitudeDeg[ipnts]; if (p.longitudeDeg[ipnts] > p.maxLonDeg) p.maxLonDeg = p.longitudeDeg[ipnts]; if (p.longitudeDeg[ipnts] < p.minLonDeg) p.minLonDeg = p.longitudeDeg[ipnts]; //covariance elements used to compute the principal axes of the polygon //here we are assuming a north (X), east (Y), down (Z) corrdinatate system CovYY += (easting - p.meanEasting) * (easting - p.meanEasting); CovXX += (northing - p.meanNorthing) * (northing - p.meanNorthing); CovXY += (easting - p.meanEasting) * (northing - p.meanNorthing); } CovYY /= 1000*numLatLonPoints; CovXX /= 1000*numLatLonPoints; CovXY /= 1000*numLatLonPoints; //Compute the orientation of the principal axis of the covariance ellipse //this is used to determine the best flying direction //eigenvalues of covariance matrix -- see below site for equations for eigenvalues/vectors for 2D matrix // people.csail.mit.edu/bkph/articles/Eigenvectors.pdf double det = Math.Sqrt( (CovXX - CovYY)*(CovXX - CovYY) + 4.0 * CovXY * CovXY ); double lambda1 = ( CovXX + CovYY + det )/ 2.0; double lambda2 = ( CovXX + CovYY - det )/ 2.0; //the larger of these is the length of the major axis //smaller is length of the minor axis //the angle associated with the larger eigenvalue is the angle of the major axis //eigenvectors should be 90 deg apart if (lambda1 > lambda2) { //eigenvectors of covariance matrix double eigen1X = 2.0 * CovXY; double eigen1Y = CovYY - CovXX + det; double ang1 = Math.Atan(eigen1Y / eigen1X) * Constants.Rad2Deg; p.rotationAngleDeg = ang1; } else { //eigenvectors of covariance matrix double eigen2X = 2.0 * CovXY; double eigen2Y = CovYY - CovXX - det; double ang2 = Math.Atan(eigen2Y / eigen2X) * Constants.Rad2Deg; p.rotationAngleDeg = ang2; } //reflect a negative major axis direction by 180 deg (major axis can go either direction) if (p.rotationAngleDeg < 0) p.rotationAngleDeg += 180.0; //this must be done after creating the UTM Northing & Easting from the geodetic coordinates polygonMath polyMath = new polygonMath(p.Easting, p.Northing, datasetFolder); //compute rectangle bounds using the principal axes double maxAlongPA = -9999999999, minAlongPA = 9999999999, distanceA = 0.0; double maxOrthoPA = -9999999999, minOrthoPA = 9999999999, distanceO = 0.0; for (int ipnts = 0; ipnts < numLatLonPoints; ipnts++) { //note below that the X components is Northing and the Y components are Easting //The "line" is defined by its origin and rotation angle (positive east of north) //the "point" is the ipnts vertex of the polygon distanceO = polyMath.distanceFromPoint2Line( //distance orthogonal to the pricipal axis new PointD(p.meanNorthing, p.meanEasting), p.rotationAngleDeg, new PointD(p.Northing[ipnts], p.Easting[ipnts])); distanceA = polyMath.distanceFromPoint2Line( //distance orthogonal to the pricipal axis new PointD(p.meanNorthing, p.meanEasting), p.rotationAngleDeg + 90, new PointD(p.Northing[ipnts], p.Easting[ipnts])); if (distanceA > maxAlongPA) maxAlongPA = distanceA; if (distanceA < minAlongPA) minAlongPA = distanceA; if (distanceO > maxOrthoPA) maxOrthoPA = distanceO; if (distanceO < minOrthoPA) minOrthoPA = distanceO; } //length and width of a bounding box with long-side along principal axis //length should always be larger than the width double lengthKm = (maxAlongPA - minAlongPA) / 1000.0; double widthKm = (maxOrthoPA - minOrthoPA) / 1000.0; //Constants.Log("Principal Axis bounding box: " + lengthKm.ToString("F1") + " X " + widthKm.ToString("F1")); //if (lengthKm < widthKm) // MessageBox.Show("Principal axes of polygon not longer than minor axis"); //principal axis direction is defined by p.rotationAngleDeg //set the corner of the principal axes bounding box to be negative-going along the major and minor axis //this corner will also become the origion of the flight line reference coordinate system //this will correspond to a south-east corner for a north-going princpal axis p.boundingBoxCornerNorthing = Math.Cos(p.rotationAngleDeg * Constants.Deg2Rad) * (p.meanNorthing + minAlongPA) + Math.Sin(p.rotationAngleDeg * Constants.Deg2Rad) * (p.meanEasting + minOrthoPA); p.boundingBoxCornerEasting = -Math.Sin(p.rotationAngleDeg * Constants.Deg2Rad) * (p.meanNorthing + minAlongPA) + Math.Cos(p.rotationAngleDeg * Constants.Deg2Rad) * (p.meanEasting + minOrthoPA); //TODO: should just return the polygon points and fill the p structure after the return p.areasqkm = polyMath.areaOfPolygon(); p.areasqmi = p.areasqkm * 1000000.0 / ((0.3048 * 5280.0) * (0.3048 * 5280.0)); Constants.Log(""); if (p.polyCoverageType == COVERAGE_TYPE.polygon) { Constants.Log("Polygon area: " + p.areasqmi.ToString("F3") + " (sqmi) " + p.areasqkm.ToString("F3") + " (sqkm)"); } else if (p.polyCoverageType == COVERAGE_TYPE.linearFeature) { double pathRange = polyMath.pathLength(); Constants.Log("Path length: " + (pathRange/0.3048/5280.0).ToString("F2") + " (mi) " + (pathRange/1000.0).ToString("F2") + " (km)"); } Constants.Log(""); /////////////////////////////////////////////////////////////////////////////////////////////// //write the summary information for this polygon //outfile.WriteLine(); outfile.WriteLine("PolygonName: {0} PolyPoints= {1,4} Area= {2,9:####.00} (kmsq) {3,9:####.00} (sqmi)", p.PolygonName, numLatLonPoints.ToString(), p.areasqkm, p.areasqmi); //foreach (airport apt in apts) // outfile.WriteLine(" {0,50} distance (mi) {1,8:####.00}", apt.name, apt.distanceToPolyCenterMi); ///////////////////////////////////////////////////////////////////////////////////////////////// //we have modified a copy of the polygon object -- re-insert it into the polygon list polygons[i] = p; } //NOTE: here we are assuming that all the coverage types are identical //generally we only use a single coverage for a mission plan session return polygons[0].polyCoverageType; }
public void createBackgroundMapsForLinearFeature(List<LINEAR_FEATURE> linearFeatures, String UTMZone, bool downloadMap) { /////////////////TODO -- put in a input file //////////////////////////////////////////////////////////////////////////// double maxIncrementAlonPathMeters = 5.0 * 5280.0 * 0.3048; // 5.0 miles //////////////////////////////////////////////////////////////////////////// for (int iFeat = 0; iFeat < linearFeatures.Count; iFeat++) { LINEAR_FEATURE linearFeature = linearFeatures[iFeat]; polygonMath polyMath = new polygonMath(linearFeature.EastingProNavS, linearFeature.NorthingProNavS, datasetFolder); UTM2Geodetic utm = new UTM2Geodetic(); linearFeature.NWMapCorner = new List<PointD>(); linearFeature.SEMapCorner = new List<PointD>(); linearFeature.mapNames = new List<string>(); //find a deltaRange along the path < maxIncrementAlonPathMeters such that deltaRange * N = totalPathLength int nMaps = (int)(linearFeature.pathLength / maxIncrementAlonPathMeters); //above gives a number of increments such that one additional increment will fall outside the endpoint. double incrementAlongPath = linearFeature.pathLength / (nMaps + 1); //this gives an incerment that is < maxIncrementAlonPathMeters such that //there will be nMaps+1 increments that end exactly on the end of the path //We will need nMaps + 2 maps with one map at the beginning and one map at the end int totalMaps = nMaps + 2; //this lets us put N equispaced 10mix10mi maps along the path. for (int i = 0; i < totalMaps; i++) { ///////////////////// //get a map ///////////////////// //build up the URL to retrieve the Google Map -- see the above model for the syntax //this zoom level gives ~ 10mi X 10mi int zoom = 12; //increment the path PointD pap = polyMath.pointAlongPathFromStart(i * incrementAlongPath); double latitude = 0.0, longitude = 0.0; utm.UTMtoLL(pap.Y, pap.X, UTMZone, ref latitude, ref longitude); setMap(zoom, new PointD(longitude, latitude)); /* below used only to get the width & height of the returned map with zoom = 12 double NWNorthing = 0.0, NWEasting = 0.0; double SENorthing = 0.0, SEEasting = 0.0; utm.LLtoUTM(mapNWCornerCordinates.Y * Constants.Deg2Rad, mapNWCornerCordinates.X * Constants.Deg2Rad, ref NWNorthing, ref NWEasting, ref UTMZone, true); utm.LLtoUTM(mapSECornerCordinates.Y * Constants.Deg2Rad, mapSECornerCordinates.X * Constants.Deg2Rad, ref SENorthing, ref SEEasting, ref UTMZone, true); double mapNSDimensionMi = (NWNorthing - SENorthing) / 0.3048 / 5280.0; double mapEWDimensionMi = (SEEasting - NWEasting) / 0.3048 / 5280.0; * */ linearFeature.NWMapCorner.Add(mapNWCornerCordinates); linearFeature.SEMapCorner.Add(mapSECornerCordinates); String GoogleMapsURL = "http://maps.googleapis.com/maps/api/staticmap?center=" + latitude.ToString() + "," + longitude.ToString() + "&zoom=" + zoom.ToString() + "&size=640x480&sensor=false"; //this downloads a map that can be opened --- need to figure out the scaling... //GoogleMapsURL = "http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/Routes?wp.0=Seattle,WA;64;1&wp.1=Redmond,WA;66;2&key=" + // Constants.bingKey; //set the file storage location String backgroundImageFolder = datasetFolder + "\\" + polygonName + "_Background"; if (!Directory.Exists(backgroundImageFolder)) Directory.CreateDirectory(backgroundImageFolder); String SaveToFile = backgroundImageFolder + "\\linearMap_" + iFeat.ToString("D2") +"_" + i.ToString("D3") + ".png"; if (downloadMap) { //get the file from the web using a webClient getFileFromWeb(GoogleMapsURL, SaveToFile); } linearFeature.mapNames.Add(SaveToFile); } } }
public void expandPolygonVertices(double border) { for (int ipoly = 0; ipoly < polygons.Count; ipoly++) { Polygon p = polygons[ipoly]; polygonMath polyMath = new polygonMath(p.Easting, p.Northing, datasetFolder); //////////////////////////////////////////////////////////////////////////////////////////////////////// //expand the client polygon with a border //these will become the expanded polygon points //note the origonal points are destroyed -- should save them List<double> EastingExp = new List<double>(); List<double> NorthingExp = new List<double>(); p.OriginalLatitudeDeg = new List<double>(); p.OriginalLongitudeDeg = new List<double>(); //double border = 500.0; //added border in meters polyMath.expandedPoly(ref EastingExp, ref NorthingExp, border); //preserve the original polygon latitude and longitude for (int i = 0; i < EastingExp.Count; i++) { p.OriginalLatitudeDeg.Add(p.latitudeDeg[i]); p.OriginalLongitudeDeg.Add(p.longitudeDeg[i]); } //destroy the old lat-lon points and replace with the new expanded points p.latitudeDeg.Clear(); p.longitudeDeg.Clear(); p.Easting.Clear(); p.Northing.Clear(); for (int i = 0; i < EastingExp.Count; i++) { if (NorthingExp[i] > p.maxNorthing) p.maxNorthing = NorthingExp[i]; if (NorthingExp[i] < p.minNorthing) p.minNorthing = NorthingExp[i]; if (EastingExp[i] > p.maxEasting) p.maxEasting = EastingExp[i]; if (EastingExp[i] < p.minEasting) p.minEasting = EastingExp[i]; p.Easting.Add(EastingExp[i]); //replace original with expanded p.Northing.Add(NorthingExp[i]); double lat = 0.0; double lon = 0.0; utm.UTMtoLL(NorthingExp[i], EastingExp[i], p.UTMZone, ref lat, ref lon); p.latitudeDeg.Add(lat); p.longitudeDeg.Add(lon); if (lat > p.maxLatDeg) p.maxLatDeg = lat; if (lat < p.minLatDeg) p.minLatDeg = lat; if (lon > p.maxLonDeg) p.maxLonDeg = lon; if (lon < p.minLonDeg) p.minLonDeg = lon; } //////////////////////////////////////////////////////////////////////////////////////////////////// Constants.Log("Expanded the client polygon by 500m " + p.PolygonName); //we have modified a copy of the polygon -- new re-insert it into the list polygons[ipoly] = p; } }
public void generateSmoothTrajectoryWithProNav(String DataSetFolder, String UTMZone, bool computeProjections, ref double maxBankDeg, double ProNavGain, double deltaTime, double DistanceToTarget, double vehicleVelocity, double altitude, double swathWidth, List<double> NorthingIn, List<double> EastingIn, List<double> NorthingOut, List<double> EastingOut, List<double> NorthingProjected, List<double> EastingProjected, List<double> NorthingIP1, List<double> EastingIP1, List<double> NorthingIP2, List<double> EastingIP2) { ////////////////////////////////////////////////////////////////////////////////////////// //using an input set of path points generated by Google earth, create a smoothed //trajectory flyable by an aircraft that passes near the points. ////////////////////////////////////////////////////////////////////////////////////////// //initialize the state at first vertex with heading along first segment double X = NorthingIn[0]; double Y = EastingIn[0]; //heading from 0th point towards the 1th point -- heading is positive CCW from north double heading = Math.Atan2(EastingIn[1] - EastingIn[0], NorthingIn[1] - NorthingIn[0]); //add the initial position to the output trajectory NorthingOut.Add(X); EastingOut.Add(Y); //stop a segment when the ProNav acceleration command results in a bank greater than some max bank threshold bool stopThisSegment = false; polygonMath polyMath = new polygonMath(EastingIn, NorthingIn, datasetFolder); UTM2Geodetic utm = new UTM2Geodetic(); int lastXYVertex = 0; double distanceToNextVertex = 0; int iTime = 0; double bankCommand = 0.0; double maxBank = -999999.0; //integrate the state in the while loop while (!stopThisSegment) { ////////////////////////////// //state: X Y heading ////////////////////////////// //get a point on the path that is orthognal to the velocity vector PointD pca = polyMath.pointOnPathOrthogonalToVelocityVector(heading, new PointD(X, Y), ref lastXYVertex, ref distanceToNextVertex, EastingIn, NorthingIn); //if (lastXYVertex == 1) break; //stopping condition -- have gone beyond last vertex in the path if (-distanceToNextVertex > 0) { Console.WriteLine(" stopping on distanceToNextVertex " + distanceToNextVertex.ToString()); stopThisSegment = true; } //locate a point ahead of (X,Y) along the path by a prescribed distance //this becomes the target for pro nav guidance //placing the target farther ahead reduces the max bank but smooths the points PointD pAhead = polyMath.FuturePointOnLineFeature( pca, lastXYVertex, distanceToNextVertex, DistanceToTarget, EastingIn, NorthingIn); //X is north and Y is east double velX = vehicleVelocity * Math.Cos(heading); double velY = vehicleVelocity * Math.Sin(heading); //compute the Line-Of-Sight rate between the platform and the target point //the heading_dot will be commanded to -3*LOSRate per the ProNav // X -- North Y -- East -- causes heading to be positive CCW rotation for Right Hand coordinate system double delX = pAhead.X - X; double delY = pAhead.Y - Y; double LOSAngle = Math.Atan2(delY, delX); double LOSRate = Math.Cos(LOSAngle) * Math.Cos(LOSAngle) * (-velY / delX + delY * velX / (delX * delX)); X += velX * deltaTime; Y += velY * deltaTime; //////////////////////////////////////////////////// //generate the output file for the trajectory //////////////////////////////////////////////////// NorthingOut.Add(X); EastingOut.Add(Y); double accelCommand = ProNavGain * LOSRate * vehicleVelocity; //units: m/s/s //pursuit navigation //double accelCommand = -0.10 * (heading - LOSAngle) * vehicleVelocity; //bank command necessary to maintain level flight and achieve command lateral acceleration bankCommand = Math.Atan(accelCommand / Constants.Gravity); // degrees if (Math.Abs(bankCommand) > maxBank) maxBank = Math.Abs(bankCommand); heading += (accelCommand / vehicleVelocity) * deltaTime; if (accelCommand / Constants.Gravity > 100.0) { stopThisSegment = true; Console.WriteLine(" stopping on the bank command threshold "); } if (iTime%3 == 0 && computeProjections) { double Xproj = X + altitude * Math.Tan(bankCommand) * velY / vehicleVelocity; double Yproj = Y - altitude * Math.Tan(bankCommand) * velX / vehicleVelocity; NorthingProjected.Add(Xproj); EastingProjected.Add(Yproj); //creat endpoints for an image taken at this location NorthingIP1.Add(Xproj + swathWidth * velY / vehicleVelocity / 2.0 ); EastingIP1.Add( Yproj - swathWidth * velX / vehicleVelocity / 2.0 ); NorthingIP2.Add(Xproj - swathWidth * velY / vehicleVelocity / 2.0 ); EastingIP2.Add( Yproj + swathWidth * velX / vehicleVelocity / 2.0 ); //predicted miss distance //if we assume the future point doesnt move, we can compute the miss distance //that would occur if the continue with pronav until the point-of-closest-approach double tgo = DistanceToTarget / vehicleVelocity; double Xmiss = 3.0 * (X + velX * tgo) / tgo * tgo; double Ymiss = 3.0 * (Y + velY * tgo) / tgo * tgo; //miss vector orthogonal to flight direction } iTime++; if (iTime > 5000) { stopThisSegment = true; Console.WriteLine("stopping on count"); } } //end of trajectory generator maxBankDeg = maxBank*Constants.Rad2Deg; Console.WriteLine(" Max bank command = " + maxBankDeg.ToString("F2") + " deg"); }
List<LINEAR_FEATURE> linearFeatureCoverage(int numberParallelPaths, LINEAR_PATH_CENTERING centering, double altAGLMeters, double swathWidth, double vehicleVelocityMperSec) { ///////////////////////////////////////////////////////////////////////////////////////////////////// //This procedure creates a mission plan from a kml linear path created by the Google Earth Path tool //The geodetic path points (vertices) must have been generated using "InspectKMLforMissions()" //In this procedure, the input path is "flown" using a 2D simulation and proportional navigation //Capability is provided to allow multiple side-by-side parallel paths //either centered over the inoput path of to the right of this path. Separate flight lines //may be required for a single path if the path turns are too sharp. ///////////////////////////////////////////////////////////////////////////////////////////////////// //TODO: get these from the input file //pronav numerical integration parameters double proNavGain = 3.0; //proportional Navigation Gain -- usually assumed to be 3.0 double deltaTime = 2.0; //integration step size along the trajectory for the pro nav simulation double distanceToTarget = 2500.0; //diatance ahead along the path the "rabbit" is placed double altitudeAGL = altAGLMeters; double vehicleVelocity = vehicleVelocityMperSec; List<LINEAR_FEATURE> linearFeatures = new List<LINEAR_FEATURE>(); //the following trajectories are constant for all parallel paths //these represent the smoothed trajectory for the input path //The parallel paths are parallel t the smoothed input path List<double> EastingTrajectoryF = new List<double>(); List<double> NorthingTrajectoryF = new List<double>(); List<double> EastingTrajectoryB = new List<double>(); List<double> NorthingTrajectoryB = new List<double>(); List<double> EastingTrajectoryS = new List<double>(); List<double> NorthingTrajectoryS = new List<double>(); List<double> placeHolder = new List<double>(); //polyMat provides polygon and line path math tools polygonMath polyMath = new polygonMath(polys[0].Easting, polys[0].Northing, datasetFolder); String UTMZone = polys[0].UTMZone; double maxBankDeg = 0.0; //generate the pro nav smoothed forward trajectory polyMath.generateSmoothTrajectoryWithProNav(datasetFolder, UTMZone, false, ref maxBankDeg, proNavGain, deltaTime, distanceToTarget, vehicleVelocity, altitudeAGL, swathWidth, polys[0].Northing, polys[0].Easting, NorthingTrajectoryF, EastingTrajectoryF, placeHolder, placeHolder, placeHolder, placeHolder, placeHolder, placeHolder); //create a reversed set of path points so we can fly the path backwards List<double> NorthingB = new List<double>(); List<double> EastingB = new List<double>(); for (int i = polys[0].Northing.Count - 1; i >= 0; i--) { NorthingB.Add(polys[0].Northing[i]); EastingB.Add(polys[0].Easting[i]); } //generate the pro nav smoothed backward trajectory polyMath.generateSmoothTrajectoryWithProNav(datasetFolder, UTMZone, false, ref maxBankDeg, proNavGain, deltaTime, distanceToTarget, vehicleVelocity, altitudeAGL, swathWidth, NorthingB, EastingB, NorthingTrajectoryB, EastingTrajectoryB, placeHolder, placeHolder, placeHolder, placeHolder, placeHolder, placeHolder); List<double> NorthingOutBrev = new List<double>(); List<double> EastingOutBrev = new List<double>(); //reverse this trajectory so we can match it to the forward trajectory for (int i = NorthingTrajectoryB.Count - 1; i >= 0; i--) { NorthingOutBrev.Add(NorthingTrajectoryB[i]); EastingOutBrev.Add(EastingTrajectoryB[i]); } // //average the two paths to get a smoothed path that can be flown both ways int numRecs = NorthingTrajectoryF.Count; double distanceToNextVertex = 0; int index = 0; if (NorthingTrajectoryF.Count > NorthingTrajectoryB.Count) numRecs = NorthingTrajectoryB.Count; //go through all points and do the averaging for (int i = 0; i < numRecs; i++) { //increment through points in forward trajectory //locate point-of-closest-approach on the backward trajectorty //average these two points to get the smoothed trajectory PointD pca = polyMath.distanceFromPoint2Linefeature( new PointD(NorthingTrajectoryF[i], EastingTrajectoryF[i]), ref index, ref distanceToNextVertex, NorthingOutBrev, EastingOutBrev); NorthingTrajectoryS.Add((NorthingTrajectoryF[i] + pca.X) / 2.0); EastingTrajectoryS.Add((EastingTrajectoryF[i] + pca.Y) / 2.0); } /* //the following diagnostic kml files are constant for each path prepareKmlForUTMPointList(datasetFolder, "TrajectoryF", NorthingTrajectoryF, EastingTrajectoryF, UTMZone); prepareKmlForUTMPointList(datasetFolder, "TrajectoryB", NorthingTrajectoryB, EastingTrajectoryB, UTMZone); prepareKmlForUTMPointList(datasetFolder, "TrajectoryS ", NorthingTrajectoryS, EastingTrajectoryS, UTMZone); * */ //prepare the trajectory information for the offset parallel paths //NOTE: path 0 is always assumed to be the initial path flown from start-to-end as defined by the input data // successive even paths are flown end-to-start (reversed direction) // successive odd paths are flown start-to-end for (int iPar = 0; iPar < numberParallelPaths; iPar++) { LINEAR_FEATURE linearFeature = new LINEAR_FEATURE(); linearFeature.proNavGain = proNavGain; linearFeature.rabbitAheadDistance = distanceToTarget; linearFeature.EastingProNavS = new List<double>(); linearFeature.NorthingProNavS = new List<double>(); linearFeature.EastingProjection = new List<double>(); linearFeature.NorthingProjection = new List<double>(); linearFeature.EastingImage1 = new List<double>(); linearFeature.NorthingImage1 = new List<double>(); linearFeature.EastingImage2 = new List<double>(); linearFeature.NorthingImage2 = new List<double>(); linearFeature.alongPathAltitude = new List<double>(); //this where we offset the input path depending on numberParallelPaths & LINEAR_PATH_CENTERING //generate an offset trajectory to the smoothed trajectory polygonMath polyMath2 = new polygonMath(EastingTrajectoryS, NorthingTrajectoryS, datasetFolder); double offset = 0.0; if ( centering == LINEAR_PATH_CENTERING.centeredOnInputPath) offset = -(numberParallelPaths - 1) * swathWidth / 2.0 + iPar * swathWidth; else if (centering == LINEAR_PATH_CENTERING.offsetToLeftOfInputPath) offset = iPar * swathWidth; else if (centering == LINEAR_PATH_CENTERING.offsetToRightOfInputPath) offset = -iPar * swathWidth; offset *= 0.70; ///this accounts for a 30% sidelap between paths //create the current path when multiple paths are requested List<double> EastingParallel = new List<double>(); List<double> NorthingParallel = new List<double>(); //return a parallel path from the input smoothed path polyMath2.parallelPath(ref EastingParallel, ref NorthingParallel, offset); //should this be polyMath2 ??? linearFeature.pathLength = polyMath.pathLength(); //create a reversed set of path points so we can fly the path backwards List<double> NorthingRev = new List<double>(); List<double> EastingRev = new List<double>(); if (iPar % 2 != 0) //locate the odd paths (1, 3, 5, 7 ... { for (int i = EastingTrajectoryS.Count - 1; i >= 0; i--) { NorthingRev.Add(NorthingParallel[i]); EastingRev.Add(EastingParallel[i]); } for (int i = 0; i < EastingTrajectoryS.Count; i++) { NorthingParallel[i] = NorthingRev[i]; EastingParallel[i] = EastingRev[i]; } } //generate the pro nav solution for the smoothed forward/backward trajectory //this is the trajectory that is expected from the actual flight mission polyMath.generateSmoothTrajectoryWithProNav(datasetFolder, UTMZone, true, ref maxBankDeg, proNavGain, deltaTime, distanceToTarget/2.0, vehicleVelocity, altitudeAGL, swathWidth, NorthingParallel, EastingParallel, linearFeature.NorthingProNavS, linearFeature.EastingProNavS, linearFeature.NorthingProjection, linearFeature.EastingProjection, linearFeature.NorthingImage1, linearFeature.EastingImage1, linearFeature.NorthingImage2, linearFeature.EastingImage2); String parallelPathNotation= ""; if (numberParallelPaths > 0) parallelPathNotation = iPar.ToString("D2"); /* //the following kml output files vary with each parallel path prepareKmlForUTMPointList(datasetFolder, "ParallelPath" + parallelPathNotation, NorthingParallel, EastingParallel, UTMZone); prepareKmlForUTMPointList(datasetFolder, "ProNavS" + parallelPathNotation, linearFeature.NorthingProNavS, linearFeature.EastingProNavS, UTMZone); prepareKmlForUTMPointList(datasetFolder, "Projection" + parallelPathNotation, linearFeature.NorthingProjection, linearFeature.EastingProjection, UTMZone); prepareKmlForUTMPointPairs(datasetFolder, "ImageEndpoints" + parallelPathNotation, UTMZone, linearFeature.NorthingImage1, linearFeature.EastingImage1, linearFeature.NorthingImage2, linearFeature.EastingImage2); * */ linearFeature.maximumMeasuredBank = maxBankDeg; //add the linear feature just prepared fo the list of linear features //there will be one prepared linear feature for each of the parallel paths linearFeatures.Add(linearFeature); } //return the list of linear features prepared in this routine. return linearFeatures; }
private COVERAGE_SUMMARY getPolygonFlightPathCoverage(double rotationRad, Polygon p, double swathWidthKm, double maxFLLengthKm, double minLineLength, double DRImageHeightKm, ref int numSets, ref int numFlightLines, ref double[,,] XAllSets, ref double[,,] YAllSets) { ///////////////////////////////////////////////////////////////////////// //this procedure defines a set of flight lines that cover a polygon //The flight lines are returned on the arrays XallSets, YAllSets ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////// //For coverages other than North-South, //rotate the input easting, Northing polygon vertices by angle about the polygon centroid //cover the rotated polygon with a North-South set of flight lines //This will result in a YAllSets[i,k,j], XAllsets[i,k,j] set of flight line endpoints //where i=0,1 to represent the start or end // j= the set number // k= flightline number within the set //now rotate the flight line endpoints by -theta about the polygon centroid. //now YAllSets, XAlSets are rotated vectors //go through the set-based flight lines, tossing zero-length lines //and to produce a 1D (non-set-based) list of flight lines //The flightline-length computation must use the generalized UTM flight line endpoints. ////////////////////////////////////////////////////////////////////////////////////////////////////////// // //rotate the input polygon List<double> originalPolygonEasting = new List<double>(); List<double> originalPolygonNorthing = new List<double>(); for (int i = 0; i < p.Northing.Count; i++) { originalPolygonNorthing.Add(p.Northing[i]); originalPolygonEasting.Add(p.Easting[i]); } //rotate the original coordinates p.maxEasting = -999999999999.0; p.maxNorthing = -9999999999999.0; p.minEasting = 999999999999.0; p.minNorthing = 9999999999999.0; //double rotation = (-p.rotationAngleDeg) * Constants.Deg2Rad; for (int i = 0; i < p.Northing.Count; i++) { double delEasting = originalPolygonEasting[i] - p.meanEasting; double delNorthing = originalPolygonNorthing[i] - p.meanNorthing; p.Easting[i] = p.meanEasting + Math.Cos(rotationRad) * delEasting + Math.Sin(rotationRad) * delNorthing; p.Northing[i] = p.meanNorthing - Math.Sin(rotationRad) * delEasting + Math.Cos(rotationRad) * delNorthing; //recompute the maximums for the rotated vertices if (p.Easting[i] > p.maxEasting) p.maxEasting = p.Easting[i]; if (p.Northing[i] > p.maxNorthing) p.maxNorthing = p.Northing[i]; if (p.Easting[i] < p.minEasting) p.minEasting = p.Easting[i]; if (p.Northing[i] < p.minNorthing) p.minNorthing = p.Northing[i]; } //compute the north-to-south distance wherein we will space the flightlines double NSDstanceKm = (p.maxNorthing - p.minNorthing) / 1000.0; double EWDstanceKm = (p.maxEasting - p.minEasting) / 1000.0; double NSDstanceMi = (p.maxNorthing - p.minNorthing) / 0.3048 / 5280.0; double EWDstanceMi = (p.maxEasting - p.minEasting) / 0.3048 / 5280.0; //must also recompute the maxNorthing and maxEasting for the rotated vertices //int numFlightLines = 0, numSets = 0; double maxFLLengthMet = 0.0; double E2WKm = 0.0; numFlightLines = Convert.ToInt32(Math.Floor(EWDstanceKm / swathWidthKm)); //flight lines should always be inside the polygon //input flight line length is reduced to cause an integer number of equal-length flight lines to span the NS extent numSets = Convert.ToInt32(Math.Floor(NSDstanceKm / maxFLLengthKm)) + 1; maxFLLengthKm = NSDstanceKm / numSets; //make maxFLLength equal-to or smaller-than max so each set has constant NS length double maxFLLengthMi = maxFLLengthKm * 1000.0 / 0.3048 / 5280.0; maxFLLengthMet = maxFLLengthKm * 1000.0; E2WKm = swathWidthKm * numFlightLines; //redefine the E2W coverage distance so that flightline spacing fixed by camera FOV and sidelap //this is the final UTM set of flight line endpointd //XAllSets[k,i,j], YAllSets[k,i,j] whete k=0,1 (start,end of flight line), i=Set, j=FlightlineInSet //X is the Easting and Y is the Northing //the Start is always below (to the south) from the end for a flight line //double[, ,] XAllSets; //double[, ,] YAllSets; //set up for the polygon math polygonMath polyMath = new polygonMath(p.Easting, p.Northing, datasetFolder); //these arrays store the endpoints of the flight lines not-terminated by max line length constraint double[,] XintAllFL = new double[2, numFlightLines + 1]; //beginning and end of each flight line double[,] YintAllFL = new double[2, numFlightLines + 1]; double EastingStart = 0, EastingEnd = 0, NorthingStart = 0, NorthingEnd = 0; //set the first NS flight line on the eastmost side -- no more than 1/2 swathwidth inside the polygon //because the flight lines are N-S, the eastings for the endpoints will be the same //NOTE: EWDstanceKm is the max extent of the polygon rectangular boundary //E2WKm will be <= EWDstanceKm EastingStart = p.minEasting + 1000.0 * (EWDstanceKm - E2WKm) / 2.0; //fight lines will always be inside the polygon EastingEnd = EastingStart; // all NS flight lines have the same EW coordinates (they are exactly NS) //initialize the initial flight line ends as the max of the polygon rectangular envelope NorthingStart = p.maxNorthing; NorthingEnd = p.minNorthing; outfile.WriteLine(); outfile.WriteLine(" Precompute the flight line lengths without maxLength consideration"); ///////////////////////////////////////////////////////////////////////////////////////////////////// //without the below statement, the last flight line will be too far inside the right max extent //something fishy about the above integer math!!!!! ///////////////////////////////////////////////////////////////////////////////////////////////////// numFlightLines++; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //pre compute all the flightline intersections with the polygon without consideration of Sets that will act to reduce the flight line length //this initial step computes the flightlines as though they can have infinite length // Sets: vertically divide the Project polygon into equal-distance regions where we will look for non-zero flight lines ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// double maxUnterminatedLineLength = -999.0; for (int i = 0; i < numFlightLines; i++) { //flight line intersections wthout consideration of line length double[] Xint = new double[2]; double[] Yint = new double[2]; //compute the north-most and south-most flightline ends by the intersection of the flightline with the polygon //here we only return two intersections -- internal to this procedure, we compute the most-north and most-south of all intersections //note the polygons may contain concave areas that would cause more than two intersections int intersectionCounter = polyMath.intersectionOfLineWithPolygon(EastingStart, NorthingStart, EastingEnd, NorthingEnd, ref Xint, ref Yint); XintAllFL[0, i] = Xint[0]; //west-most line intersection with poly for Flightline i XintAllFL[1, i] = Xint[1]; //east-most line intersection with poly YintAllFL[0, i] = Yint[0]; //south-most line intersection with poly YintAllFL[1, i] = Yint[1]; //north-most line intersection with poly double lineLength = 0.0; lineLength = (YintAllFL[1, i] - YintAllFL[0, i]); outfile.WriteLine(String.Format("line number: {0,2} non-terminated linelength = {1,12:#.00} (km) {2,12:#.00} (Mi)", i, lineLength / 1000.0, lineLength / 0.3048 / 5280.0)); outfile.Flush(); // if (lineLength > maxUnterminatedLineLength) maxUnterminatedLineLength = lineLength; //increment the NS flight line by the swath width -- assume North-South flightline that moves to the east EastingStart += (swathWidthKm * 1000.0); EastingEnd = EastingStart; } ///////////////////////////////////////////////////////////////////////////////////////////////////// //completed locating the coverage flight lines without consideration of maximum flight line length ///////////////////////////////////////////////////////////////////////////////////////////////////// outfile.WriteLine(); outfile.WriteLine(); //input flight line length is reduced to cause an integer number of equal-length flight lines to span the NS extent outfile.WriteLine(" Break up the non-terminated lines using the adjusted flightline maxLength: " + maxFLLengthMi.ToString("F2") + " (mi)"); outfile.WriteLine(" Number of Sets: " + numSets.ToString()); outfile.WriteLine(); outfile.WriteLine(); outfile.Flush(); //these will hold the flight line endpoints that are broken into sets to reduce flight line length XAllSets = new double[2, numSets, numFlightLines + 1]; YAllSets = new double[2, numSets, numFlightLines + 1]; ///////////////////////////////////////////////////////////// //takes care of the case when there is only a single set ///////////////////////////////////////////////////////////// //handle the case where the non-terminated line length is less than the client-requested max line length //if (maxUnterminatedLineLength < maxFLLengthMet) numSets = 1; for (int i = 0; i < numSets; i++) for (int j = 0; j < numFlightLines; j++) for (int k = 0; k < 2; k++) { XAllSets[k, i, j] = XintAllFL[k, j]; YAllSets[k, i, j] = YintAllFL[k, j]; } FLSum = new FlightlineSummary[numSets * numFlightLines + 1]; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //now break up the non-terminated (too long) NS flight lines into the subsets defined by the user-set max flight line length // the subset regions will have equal vertical (NS) height if lines end at a set boundary //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //cycle through the number of sets as determined by the max flight line length for (int i = 0; i < numSets; i++) //numsets breaks the too-long flight lines to be less than the max flight line { outfile.WriteLine(); outfile.WriteLine(" Set: " + i.ToString()); outfile.WriteLine(); //define flight line candidate endpoints based on only the set start points //these are adjusted based on line-end intersections with the client polygon double startSetY = 0, endSetY = 0, startSetX = 0, endSetX = 0; //define the vertical locations of the Set endpoints -- starts at the north-most set and moves south startSetY = p.maxNorthing - i * maxFLLengthKm * 1000.0; //test subset flight line Ystart at the Set start endSetY = startSetY - maxFLLengthKm * 1000.0; //test subset flight line Yend at the Set end //cycle through the number of flight lines spaced across the plan pattern for each along-line set for (int j = 0; j < numFlightLines; j++) { //start & end X (easting) values are equal and increment to the east with flight line counter startSetX = p.minEasting + 1000.0 * (EWDstanceKm - E2WKm) / 2.0 + j * swathWidthKm * 1000.0; //EW coordinates of NS flightlines are identical endSetX = startSetX; //at this point we have a set of flight lines defined by the along-line set and across-line FL spacing //based solely on a rectangular region defined by the max/min coverage boundaries //we need to terminate these lines based on their intersections with the client polygon //the below arrays are the intersections of the flight line with the client polygon double[] Xint = new double[2]; double[] Yint = new double[2]; //compute the number of intersections of the per-set line segment with the client polygon //this can be zero, 1, 2, .... int numIntersects = polyMath.intersectionOfLineWithPolygon(startSetX, startSetY, endSetX, endSetY, ref Xint, ref Yint); //test to see if both ends of the test flight line are outside the total Project polygon external hull -- if so skip remainder of this loop -- //NOTE: endSetY will always be to the North of startSetY -- End and Start have a geographic order //endsetY is south of startSetY -- YintAllFL[1, j] is south of YintAllFL[0, j] //YintAllFL[0, i] south-most line intersection with poly //YintAllFL[1, i] north-most line intersection with poly if (endSetY > YintAllFL[1, j] || startSetY < YintAllFL[0, j]) { outfile.WriteLine("both ends of FL= " + j.ToString() + " outside set " + i.ToString()); //force this line length for this set to zero -- else it will be counted again on another set YAllSets[1, i, j] = YintAllFL[0, j]; YAllSets[0, i, j] = YintAllFL[0, j]; continue; } //if we get here -- at least one line end is inside the current set boundary extent // numIntersects is the number of intersections of the set-terminated flight line with the client polygon //there can be zero intersects even with both ends outside or inside the polygon if (numIntersects == 0) { //test to see if one end is inside the polygon -- since there are zero intersects, this is the case where both ends are inside the polygon if (polyMath.pointInsidePolygon(p.Easting.Count, endSetX, endSetY, p.Easting, p.Northing)) //both endpoints are inside the polygon { // set the desired flight line ends to fall on the set boundaries YAllSets[1, i, j] = startSetY; YAllSets[0, i, j] = endSetY; outfile.WriteLine(" both line ends are inside the client poly "); } //the below "continue" is redundant with the first test above -- should never get here //there are zero set-line intersections and neither end is inside the client poly else { outfile.WriteLine("set line has at least one intersections with client poly -- but neither end is inside the poly"); continue; //this is the case where the poly is concave so that the complete terminated line is outside the polygon } } else if (numIntersects % 2 == 1) //one of the set-boundary-terminated flight lines is outside the client polygon { outfile.WriteLine(" one line end is inside the client poly "); //test to see if the endpoint is inside the client polygon //if so, then this end is on the desired flight line //the other end is at the set boundary if (polyMath.pointInsidePolygon(p.Easting.Count, endSetX, endSetY, p.Easting, p.Northing)) { YAllSets[1, i, j] = Yint[1]; YAllSets[0, i, j] = endSetY; } else //the startpoint is inside the client polygon { YAllSets[1, i, j] = startSetY; YAllSets[0, i, j] = Yint[0]; } } //here we have an even number of intersctions else //either both of the unterminated lines are outside the polygon or there is a concave polygon section // must also consider a jut-out where both ends are outside --- { if (!polyMath.pointInsidePolygon(p.Easting.Count, endSetX, endSetY, p.Easting, p.Northing)) { outfile.WriteLine("even number of intersections -- both ends are outside the client poly "); //both ends must be outside the polygon YAllSets[1, i, j] = Yint[1]; YAllSets[0, i, j] = Yint[0]; } else //both ends must be inside the polygon { outfile.WriteLine("even number of intersections -- both ends are inside the client poly "); YAllSets[1, i, j] = startSetY; YAllSets[0, i, j] = endSetY; } } double lineLengthKm = 0, lineLengthMi = 0; //this becomes a list of the flight lines on a Set grouping that cover the polygon //need to keep the order of the line within the set ... e.g., track those on the ends XAllSets[0, i, j] = startSetX; //start of the line (this is to the South for a NS flightline) XAllSets[1, i, j] = endSetX; //end of the line lineLengthKm = (YAllSets[1, i, j] - YAllSets[0, i, j]) / 1000.0; lineLengthMi = (YAllSets[1, i, j] - YAllSets[0, i, j]) / 0.3048 / 5280.0; outfile.WriteLine(String.Format("lines terminated at Set boundaries: set= {0,2} FL= {1,2} length: {2,10:#.00} Km {3,10:#.00} Mi intersects = {4}", i, j, lineLengthKm, lineLengthMi, numIntersects)); outfile.Flush(); //below we do the following: //if a line is too short, add the segment to either the next or last extension of the line //if the short line is at the bottom of this Set, add to the top of the next set //if the short line is at the top of this Set, add to the bottom of the last set } } outfile.WriteLine(); outfile.WriteLine(); outfile.WriteLine("Extend the flight lines slightly to account for the descretization resulting from the swath width"); /////////////////////////////////////////////////////////////////////////////////////////// //we have a set of flight lines that cover the polygon spaced per the swath width //However, if we use these flight lines, we will end up with gaps at the polygon edges //due to the discretization of the flight lines. A short flight line prior to a //longer flight line must be extended so that its edge covers the polygon. //this should really be done by locating the polygon intersections at the halfway points between //each of the flightline. We use the 0.75 below to account for this approximation. //if the polygon edges were linear between the endpoints, we would use 0.5 //if the edge bewteen the endpoint has a vertices with convex shape it should be bigger than 0.5 ////////////////////////////////////////////////////////////////////////////////////////// //save the old endpoints double[, ,] YAllSetsTemp = new double[2, numSets, numFlightLines]; for (int i = 0; i < numSets; i++) for (int j = 0; j < numFlightLines; j++) for (int k = 0; k < 2; k++) YAllSetsTemp[k, i, j] = YAllSets[k, i, j]; double[, ,] XAllSetsTemp = new double[2, numSets, numFlightLines]; for (int i = 0; i < numSets; i++) for (int j = 0; j < numFlightLines; j++) for (int k = 0; k < 2; k++) XAllSetsTemp[k, i, j] = XAllSets[k, i, j]; for (int i = 0; i < numSets; i++) { for (int j = 0; j < numFlightLines; j++) { double lineLengthKm = lineLengthKm = (YAllSets[1, i, j] - YAllSets[0, i, j]) / 1000.0; //discard zero-length lines if (lineLengthKm == 0.0) { outfile.WriteLine(" discard set {0} FL {1} due to zero length", i, j); continue; } else { outfile.WriteLine(" keep set {0,2} FL {1,2} length = {2,8:#.00} (km) {3,8:#.00} (mi) ", i, j, lineLengthKm, lineLengthKm * 1000.0 / 0.3048 / 5280.0); } if (j < (numFlightLines - 2)) { double lineLengthKm2 = 0; lineLengthKm2 = (YAllSetsTemp[1, i, j + 1] - YAllSetsTemp[0, i, j + 1]) / 1000.0; if (lineLengthKm2 == 0.0) continue; //test to see if the next flight line end is above the current flight line end //if so, then extend this flight line halfway to the next line end if (YAllSetsTemp[1, i, j] < YAllSetsTemp[1, i, j + 1]) YAllSets[1, i, j] += 0.75 * (YAllSetsTemp[1, i, j + 1] - YAllSets[1, i, j]); //test to see if the next flight line start is below the current flight line start //if so, then extend this flight line start point halfway to the next line startpoint if (YAllSetsTemp[0, i, j] > YAllSetsTemp[0, i, j + 1]) YAllSets[0, i, j] -= 0.75 * (YAllSetsTemp[0, i, j] - YAllSetsTemp[0, i, j + 1]); } if (j > 0) { double lineLengthKm2 = lineLengthKm2 = (YAllSetsTemp[1, i, j - 1] - YAllSetsTemp[0, i, j - 1]) / 1000.0; if (lineLengthKm2 == 0.0) continue; //test to see if the prior flight line end is above the current flight line end //if so, then extend this flight line end point halfway to the prior line end point if (YAllSetsTemp[1, i, j] < YAllSetsTemp[1, i, j - 1]) YAllSets[1, i, j] += 0.75 * (YAllSetsTemp[1, i, j - 1] - YAllSetsTemp[1, i, j]); //test to see if the prior flight line start is below the current flight line start //if so, then extend this flight line start point halfway to the prior line start point if (YAllSetsTemp[0, i, j] > YAllSetsTemp[0, i, j - 1]) YAllSets[0, i, j] -= 0.75 * (YAllSetsTemp[0, i, j] - YAllSetsTemp[0, i, j - 1]); } } } //output a summary of the above computations for (int i = 0; i < numSets; i++) { outfile.WriteLine("Set = " + i.ToString("D2")); for (int j = 0; j < numFlightLines; j++) { double newlineLengthKm = (YAllSets[1, i, j] - YAllSets[0, i, j]) / 1000.0; double oldlineLengthKm = (YAllSetsTemp[1, i, j] - YAllSetsTemp[0, i, j]) / 1000.0; outfile.WriteLine("FL = {0,2} oldLength = {1,8:#.00} (km) newLength = {2,8:#.00} ", j, oldlineLengthKm, newlineLengthKm); } } outfile.WriteLine(); outfile.WriteLine(); outfile.WriteLine("Go back through the terminated flight lines and add too short lines to prior set "); outfile.Flush(); ////////////////////////////////////////////////////////////////////////////////////////////////////////////// //go back through the flight line segments, ordered by set & flightline, and cull the "zero-length" segments //also add the too-short segments to the adjoining (to the South) Set //Caveat: adding the too-short segments to the South Set can result in some odd outlier flightline groupings!!! //Resolution: at the start of a set: once we "keep" a segment, dont adjoin anymore short lines to the south // but at the end of a Set, once we adjoin a segment, must also adjoin the remainder // thus we have to look for short flightlines going forward -- terminating after we find a sufficiently long line // and then go backward, again stopping after we have found a sufficiently long line ////////////////////////////////////////////////////////////////////////////////////////////////////////////// int numTooShortLines = 0; for (int i = 0; i < numSets - 2; i++) //TEMPORARY ??? { //define the vertical locations of the Set endpoints double startSetY = p.maxNorthing - i * maxFLLengthKm * 1000.0; //test subset flight line Ystart at the Set start double endSetY = startSetY - maxFLLengthKm * 1000.0; //test subset flight line Yend at the Set end int jStart = 0, jEnd = numFlightLines, jDelta = 1; //conditions that cause going forward for each set for (int k = 0; k < 2; k++) //causes going backward and forward through the flightlines in a Set -- see above Caveat. { if (k == 1) //going backward -- lines go from largest to smallest { jStart = numFlightLines - 1; jEnd = 0; jDelta = -1; } //conditions that cause going backward //multiplying by "-1" on the back pass flips the equality!! -- so start at the end going down to "0" (j>-1) for (int j = jStart; (jDelta * j) < (jDelta * jEnd - 1); j += jDelta) { //test to see if both ends of the test flight line are outside the polygon -- if so skip remainder of this loop -- if (endSetY > YintAllFL[1, j] || startSetY < YintAllFL[0, j]) continue; double lineLengthKm = (YAllSets[1, i, j] - YAllSets[0, i, j]) / 1000.0; double lineLengthMi = (YAllSets[1, i, j] - YAllSets[0, i, j]) / 0.3048 / 5280.0; outfile.WriteLine(String.Format("set {0} FL {1} linelength = {2,15:#.00} Km {3,15:#.00} Mi", i, j, lineLengthKm, lineLengthMi)); outfile.Flush(); if (lineLengthKm == 0) continue; //if zero length -- continue //if a line is too short, add the segment to either the next or last extension of the line if (lineLengthKm < minLineLength) { numTooShortLines++; //if the short line is at the bottom of this Set, add to the top of the next set if (YAllSets[0, i, j] == endSetY && i < (numSets - 1)) //bottom of this short line is at the bottom of the Set { outfile.WriteLine(String.Format("remove and add to top of next set:: {0} {1} {2,15:#.00} Km", i, j, lineLengthKm)); outfile.Flush(); YAllSets[1, i + 1, j] += lineLengthKm * 1000.0; //bottom this segment added to top of next segment YAllSets[0, i, j] = 0; //set old short segment to zero YAllSets[1, i, j] = 0; //set old short segment to zero } //if the short line is at the top of this Set, add to the bottom of the last set else if (YAllSets[1, i, j] == startSetY && i > 0) ////top of this short line is at the top of the Set { outfile.WriteLine(String.Format("remove and add to bottom of last set:: {0} {1} {2,15:#.00} Km", i, j, lineLengthKm)); outfile.Flush(); YAllSets[0, i - 1, j] -= lineLengthKm * 1000.0; //top this segment added to bottom of last segment YAllSets[0, i, j] = 0; //set old short segment to zero YAllSets[1, i, j] = 0; //set old short segment to zero } else { //if both ends of the short line are on the polygon, then do nothing. if (k == 0) outfile.WriteLine(String.Format("forward Pass: short line contained within the polygon:: {0} {1} {2,15:#.00} Km", i, j, lineLengthKm)); if (k == 1) outfile.WriteLine(String.Format("backward Pass: short line contained within the polygon:: {0} {1} {2,15:#.00} Km", i, j, lineLengthKm)); outfile.Flush(); } } //below break causes the short flight lies to be merged with the Southern set either at the start or end of a Set else break; //this breaks after we have found the first occurrence of a sufficient long fliight line } } //end of the k-look --- k=0 is forward and k=1 is backward } outfile.WriteLine("Number of moved too-short flight lines = {0,2}", numTooShortLines); ///////////////////////////////////////////////////////////////////////////////////////// //adjust the photocenters so that they fall on a rectangular grid //grid origin is always the NW corner of the bounding rectangle //note that the rotated flight lines are still North-South //make adjustments to the UTM north and south flight line endpoints /////////////////////////////////////////////////////////////////////////////// outfile.WriteLine(); outfile.WriteLine("Adjust the flight line endoints so they fall on a rectangular grid"); COVERAGE_SUMMARY covSummary = new COVERAGE_SUMMARY(); covSummary.numTotalFlightLines = 0; covSummary.totalFlightLineLength = 0.0; for (int i = 0; i < numSets; i++) { outfile.WriteLine("Set= {0,2} ", i); for (int j = 0; j < numFlightLines; j++) { double distanceBetweenTriggersMeters = DRImageHeightKm * 1000.0; double originalFLlength = YAllSets[1, i, j] - YAllSets[0, i, j]; //make FL start fall on a grid with downrange spacing of distanceBetweenTriggers //the start end is always below the end (to the south for a NS flightline) //compute the number of grid cells from the origin -- the "+1" ensures it covers (is above) the non-gridded flight line int numGridsFromMaxNorthingS = (int)((p.maxNorthing - YAllSets[0, i, j]) / distanceBetweenTriggersMeters); //the "+1" below always ensures the quantized start points are outside (below) the polygon. //this will result in a single frame duplication between sets. YAllSets[0, i, j] = p.maxNorthing - (numGridsFromMaxNorthingS + 1) * distanceBetweenTriggersMeters; //make FL end latitude fall on a grid with downrange spacing of distanceBetweenTriggers //compute the number of grid cells from the origin -- the "-1" ensures it covers the non-gridded flight line int numGridsFromMaxNorthingE = (int)((p.maxNorthing - YAllSets[1, i, j]) / distanceBetweenTriggersMeters) + 1; //the "-1" below always ensures the quantized endponts points are outside (above) the polygon (note the double negative. //this will result in a songle frame duplication between sets. YAllSets[1, i, j] = p.maxNorthing - (numGridsFromMaxNorthingE - 1) * distanceBetweenTriggersMeters; double finalFLlength = YAllSets[1, i, j] - YAllSets[0, i, j]; outfile.WriteLine("FL = {0,2} original length = {1,8:#.00} (km) adjusted length = {2,8:#.00} ", j, originalFLlength, finalFLlength); if (finalFLlength > 0) { covSummary.numTotalFlightLines++; covSummary.totalFlightLineLength += finalFLlength; } } } covSummary.totalFlightLineLength /= 1000.0; covSummary.averageFlightLineLength = covSummary.totalFlightLineLength / covSummary.numTotalFlightLines; ///////////////////////////////////////////////////////////////////////////////////////////////////////////// //all the above was performed assuming the input polygon was rotated to a North-South orientation //NS flightlines were computer to cover the polygon //we must now counter-rotate the resulting flight lines back to their original geometry //this approach preseves the flightline relationship with the original polygon //The resulting flight lines are now arbitrarily rotated relative to north ///////////////////////////////////////////////////////////////////////////////////////////////////////////// //retrieve the original non-rotated UTM input coordinates for (int i = 0; i < p.Northing.Count; i++) { p.Easting[i] = originalPolygonEasting[i]; p.Northing[i] = originalPolygonNorthing[i]; } //rotate the flight line endpoints back to the original non-rotated coordinate frame //X corresponds to Eastng, Y to Northing -- //note that in the initial rotation, the variable "rotation" was set to p.rotationDeg //below we rotation with -rotation to get back to the original coordinates for (int i = 0; i < numSets; i++) { for (int j = 0; j < numFlightLines; j++) { for (int k = 0; k < 2; k++) { double delEasting = XAllSets[k, i, j] - p.meanEasting; double delNorthing = YAllSets[k, i, j] - p.meanNorthing; XAllSets[k, i, j] = p.meanEasting + Math.Cos(-rotationRad) * delEasting + Math.Sin(-rotationRad) * delNorthing; YAllSets[k, i, j] = p.meanNorthing - Math.Sin(-rotationRad) * delEasting + Math.Cos(-rotationRad) * delNorthing; XAllSets[k, i, j] = p.meanEasting + Math.Cos(-rotationRad) * delEasting + Math.Sin(-rotationRad) * delNorthing; YAllSets[k, i, j] = p.meanNorthing - Math.Sin(-rotationRad) * delEasting + Math.Cos(-rotationRad) * delNorthing; } } } //return sufficient information to compare various coverage options return covSummary; }
////////////////////////////////////// //all the action occurs here ////////////////////////////////////// private void prepareKML() { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // this procedure is called immediately after we have clicked the BROWSE button and selected the input file ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// //filestream to create the files where we will write some summary output (debug: KMLDebugFile) FileStream fs1; try { //debug output data fs1 = new FileStream(datasetFolder + "\\KMLDebugFile.txt", FileMode.Create, FileAccess.Write, FileShare.None); outfile = new StreamWriter(fs1); outfile.AutoFlush = true; } catch { MessageBox.Show("Cant open the KMLDebugFile file "); } //Constants.Log -- writes the the RTF on the main page of the GUI Constants.Log("File opened for writing log information: \n" + datasetFolder + "\\KMLDebugFile.txt"); //First determine whether the mission is a linear or area mission //In Google Earth -- use the Polygon tool for the polygon and the Path tool for the linear feature //can detect these by inspecting the input KML //Path has only a LineString tag and coordonate tag //Polygon has a Polygon tag, OuterBoundaryIs tag, linearRing tag, coordinates tag //get the lat/lon client data and other info from the input kml file InspectKMLforMissions kml = new InspectKMLforMissions(kmlInputFilename, datasetFolder, outfile); //generate polygon or linearFeature geometry information and further populate the job definition structure //convert geodetic to UTM coordinates, compute area, bounding box, etc ... COVERAGE_TYPE polyCoverageType_polygonOrLinear = kml.polygonGeometry(); //expand the poly boundary for insurance -- destroying the original polygon if (polyCoverageType_polygonOrLinear == COVERAGE_TYPE.polygon) { //original lat-lon poonts saved in originalLatitudeDeg, originalLongitudeDeg within poly description double borderWidth = 10; //probably should be adaptive to 1/2 the swath width !! kml.expandPolygonVertices(borderWidth); //expand the client poly vertices with a border for insurance } //get the airports that are close to the Job polygons kml.getAirports(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// //We plan to allow three levels of naming: job, project, polygon //the original idea was that job = the overall job containing projects and individual polygons in a job //an example would be //job: agricultureFieldsForDate //Project: FarmerJohn, FarmerJoe //polygon: field#1, field#2, etc //However, the current setup is that we have a single polygon in the input kml file //we will use the polygon.name for the output kml file --- that becomes the flight plan for the flight computer ///////////////////////////////////////////////////////////////////////////////////////////////////////////// //retrieve a local copy of the Project Polygons from the client-input kml //shared for the polygon and linear feature coverage polys = new List<Polygon>(); //do we need this statement ??? polys = kml.getPolygonsFromKML(); //must see if the input kml file and the polygon.name are the same. //if polygon name and inut polygon name are the same, the input file will be deastroyed (over-written) //if names are the same: add "_input" to the original name DirectoryInfo kmlInfo = new DirectoryInfo(kmlInputFilename); String inputFilenameWOextension = Path.GetFileNameWithoutExtension(kmlInputFilename); if (inputFilenameWOextension == polys[0].PolygonName) { MessageBox.Show("Google Earth placemark name same as input kml filename. \n" + "Input kml name changed by appending _input at the end"); String newName = Path.GetDirectoryName(kmlInputFilename) + "\\" + inputFilenameWOextension + "_input.kml"; System.IO.File.Move(kmlInputFilename, newName); Constants.Log(""); Constants.Log("The input kml fileName and the Google Earth Placemark name are the same"); Constants.Log("The input kml filename has been changed by adding _input to its fileName"); Constants.Log("The prepared mission plan will have the name of the original kml file"); Constants.Log("Use the new kml filename (with _input) on any new flight planning exercise"); } //////////////////////////////////////////////////////////////////////////////////// //at this point, we have the polygon selected as well as the nearby airports //////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////// //Cycle through all the Project Polygons // for now -- we only allow a single poly at a time //however, the kml read program allows mpre than one at a time //this would be useful for clients with a lot of polygon areas //////////////////////////////////////////////////////////////// //for (int totIt=0; totIt<4; totIt++) { //disable the browse button after the input kml selection has been completed button1.Visible = false; button1.Enabled = false; Polygon p = polys[0]; //we need to display the airports and allow the user to select a project airport. //show the airport list in a datGridView and wait for the user to select the correct airport. foreach (airport apt in p.airports) this.dataGridView1.Rows.Add(apt.name, apt.distanceToPolyCenterMi.ToString("F1")); Constants.Log(" "); Constants.Log("Please select a takeoff airport from the dropdown list "); this.dataGridView1.Enabled = true; this.dataGridView1.Visible = true; this.dataGridView1.CurrentCell.Selected = false; this.label2.Enabled = true; this.label2.Visible = true; //wait here while the user selects the takeoff airport //the selected airport is given: selectedAirportIndex while (!airportSelected) { Application.DoEvents(); } Constants.Log("Selected airport: " + p.airports[selectedAirportIndex].name); Constants.Log(" "); Constants.Log("Please select a camera .... "); this.listBox2.Enabled = true; this.listBox2.Visible = true; this.label1.Enabled = true; this.label1.Visible = true; //wait here for the user to select a camera while (!cameraSelected) { Application.DoEvents(); //wait for user inputs } String cameraType = this.listBox2.SelectedItem.ToString(); Constants.Log(" "); Constants.Log("Please select a resolution (cm) .... "); this.listBox3.Enabled = true; this.listBox3.Visible = true; this.label3.Enabled = true; this.label3.Visible = true; //wait here for the user to select a resolution while (!ResolutionSelected) { Application.DoEvents(); //wait for user inputs } //no need to select max flight line for a linear feature path coverage if (p.polyCoverageType == COVERAGE_TYPE.polygon) { Constants.Log(" "); Constants.Log("Please select a max Flightline Length (nm) .... "); this.listBox4.Enabled = true; this.listBox4.Visible = true; this.label4.Enabled = true; this.label4.Visible = true; //wait here for the user to select a max flight line length while (!maxFLSelected) { Application.DoEvents(); //wait for user inputs } //no need to select max mission time for a linear feature path coverage //assume the user will make the linear features of flyable lengths Constants.Log(" "); Constants.Log("Large coverages may require multiple flight missions. "); Constants.Log("Please select a maximum per-mission ferry+overtarget flight time (hrs) .... "); this.listBox6.Enabled = true; this.listBox6.Visible = true; this.label6.Enabled = true; this.label6.Visible = true; //wait here for the user to select a max mission time while (!maxMissionTimeSelected) { Application.DoEvents(); //wait for user inputs } } else { this.panel1.Enabled = true; this.panel1.Visible = true; radioButton1.Checked = false; radioButton2.Checked = false; radioButton3.Checked = false; //wait here for the user to select the number of prlallel paths while (!linearFeaturePathsSelected || (radioButton1.Checked == false && radioButton2.Checked == false && radioButton3.Checked == false) ) { Application.DoEvents(); //wait for user inputs } if (radioButton1.Checked == true) {linearPathCentering = LINEAR_PATH_CENTERING.centeredOnInputPath; } else if (radioButton2.Checked == true) {linearPathCentering = LINEAR_PATH_CENTERING.offsetToLeftOfInputPath; } else if (radioButton3.Checked == true) {linearPathCentering = LINEAR_PATH_CENTERING.offsetToRightOfInputPath; } } Constants.Log(" "); Constants.Log("Please select a nominal aircraft speed (knots) .... "); this.listBox5.Enabled = true; this.listBox5.Visible = true; this.label5.Enabled = true; this.label5.Visible = true; //wait here for the user to select a aircraft speed while (!nominalSpeedSelected) { Application.DoEvents(); //wait for user inputs } p.selectedAirportIndex = selectedAirportIndex; ////////////////////////////////////////////////////////////////////////// //at this point, we have completed all the user's initial inputs. //next we compute the performance for the three coverage types ////////////////SETUP PARAMETERS/////////////////////////////////////////////////////////////////// /// specific setup for the Canon 5D rotated 90 deg (long side downrange) //double flightAlt = 3500.0; //5,000 provides about 6" resolution -- !!!!!!!!!!this is assumed to be agl!!!!!!!!!!! //compute the flight line spacing from the camera FOV and altitude double CRFOV = 27.5; //degrees crossrange FOV (rotated 5D with 50m lenses -- GeoPod) double DRFOV = 39.0; //degrees downrange FOV (rotated 5D with 50mm lens) double downlap = 60; //percent downlap X 100 -- 60 double sideLap = 30.0; //percent sidelap X 100 int cameraPixels = 3744; //pixels in crossrange (rotated canon 5D) //max FL length is in nautical miles double maxFLLengthMi = maxFLLengthNM * 1.15078; //convert nautical miles to statute miles double timeToTurn = 1.0 / 60.0; //hours -- about 2 min -- this is conservative 1.5 is typical double aircraftSpeedKnots = nominalSpeed; // 100.0; double takeoffTime = 5.0 / 60.0; //time to roll/run-up on the runway turing the takeoff event -- added to the ferry time double landingTime = 5.0 / 60.0; //time in pattern & taxi roll on the runway turing the landing event -- added to the ferry time //we attempt to merge small lines (below minLineLength) with prior lines double minLineLength = 8.0; //minimum length of a flight line segment (units km) /////////////////////////////////////////////////////////////////////////////////////////////////////////// //derived setup parameters///////////////////////////////////////////////////////////////////////////////////////////////// //double swathWidth = 2.0 * Math.Tan(CRFOV * Constants.Deg2Rad / 2.0) * flightAlt; //in feet //double GSD = swathWidth / cameraPixels * 0.3048; //GSD im meters double swathWidthMeters = resolutionCm * cameraPixels / 100.0; double flightAlt = swathWidthMeters / (2.0 * Math.Tan(CRFOV * Constants.Deg2Rad / 2.0)) / 0.3048; double swathWidthWithSidelap = (1.0 - sideLap / 100.0) * swathWidthMeters; //in meters double swathWidthKm = swathWidthWithSidelap / 1000.0; //Km //set the max flight line length double maxFLLengthKm = maxFLLengthMi * 5280.0 * 0.3048 / 1000.0; outfile.WriteLine(); outfile.WriteLine("Input flight line length is: " + maxFLLengthMi.ToString("F1") + " (mi) " + maxFLLengthKm.ToString("F1") + "( km)" ); //downrange image spacing including downlap //below used to compute the number of images per flight line double DRImageHeightWithDownlap = (1.0 - downlap / 100.0) * 2.0 * Math.Tan(DRFOV * Constants.Deg2Rad / 2.0) * flightAlt; //in feet //same as above but in km -- distance downrange between each image trigger double DRImageHeightKm = DRImageHeightWithDownlap * 0.3048 / 1000.0; //Km double aircraftSpeedKPH = aircraftSpeedKnots * 0.514 / 1000 * 3600.0; //0.514 m/sec per knot -- 100 knots = 51.4 m/s double forwardSpeedInClimb = 0.70 * aircraftSpeedKPH; //limit the forward speed when the aircraft is climbing to altitude double avgClimbRate = 500.0; //ft per minute -- climb rate double minimumFerryTime = (flightAlt / avgClimbRate) / 60.0; //time to climb to altitude double distanceDuringClimb = forwardSpeedInClimb * minimumFerryTime; //horizontal distance traveled during the climb to altitude //note -- this can make a flight line exceed the max length -- but not by too much. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //at this point, we have the camera parameters //and all the coordinates of the polygon or linear path /////////////////////////////////////////////////////////////////////////////// //compute the LinearFeature coverage --- The below procedure will terminate the Planner if (p.polyCoverageType == COVERAGE_TYPE.linearFeature) { LinearFeaturePlanner(p, flightAlt, swathWidthKm, aircraftSpeedKnots, aircraftSpeedKPH, DRImageHeightKm); } ////////////////////////////////////////////////////////////////////////// //we will get to the below functionality only for the polygon planner ////////////////////////////////////////////////////////////////////////// //now determine if we prefere a NS or EW mission plan //compute the north-to-south distance wherein we will space the flightlines double NSDstanceKm = (p.maxNorthing - p.minNorthing) / 1000.0; double EWDstanceKm = (p.maxEasting - p.minEasting) / 1000.0; double NSDstanceMi = (p.maxNorthing - p.minNorthing) / 0.3048 / 5280.0; double EWDstanceMi = (p.maxEasting - p.minEasting) / 0.3048 / 5280.0; btnNS_EW.Visible = true; btnNS_EW.Enabled = true; int numSets = 0; int numFlightLines = 0; int maxSets = 20; int maxFlightLines = 200; double[, ,] XAllSets = new double[2, maxSets, maxFlightLines]; double[, ,] YAllSets = new double[2, maxSets, maxFlightLines]; /////////////////////////////////////////////////////////////////////////////////////////// //get the polygon coverage for three types of coverage and present the results to the user /////////////////////////////////////////////////////////////////////////////////////////// COVERAGE_SUMMARY covSummary1 = getPolygonFlightPathCoverage(0.0, //North-South case p, swathWidthKm, maxFLLengthKm, minLineLength, DRImageHeightKm, ref numSets, ref numFlightLines, ref XAllSets, ref YAllSets); COVERAGE_SUMMARY covSummary2 = getPolygonFlightPathCoverage(90.0 * Constants.Deg2Rad, //East-West case p, swathWidthKm, maxFLLengthKm, minLineLength, DRImageHeightKm, ref numSets, ref numFlightLines, ref XAllSets, ref YAllSets); COVERAGE_SUMMARY covSummary3 = //principal axis case getPolygonFlightPathCoverage((180.0-p.rotationAngleDeg) * Constants.Deg2Rad, p, swathWidthKm, maxFLLengthKm, minLineLength, DRImageHeightKm, ref numSets, ref numFlightLines, ref XAllSets, ref YAllSets); double flightTime1 = covSummary1.totalFlightLineLength / aircraftSpeedKPH + (covSummary1.numTotalFlightLines - 1) * timeToTurn; double flightTime2 = covSummary2.totalFlightLineLength / aircraftSpeedKPH + (covSummary2.numTotalFlightLines - 1) * timeToTurn; double flightTime3 = covSummary3.totalFlightLineLength / aircraftSpeedKPH + (covSummary3.numTotalFlightLines - 1) * timeToTurn; //display the results to the user and allow user to select from among the types of coverage Constants.Log(""); Constants.Log(" Coverage Options: "); Constants.Log(" Type #FL AvgFL (km) TotalLength (km) Time (hrs) "); String fmt1 = String.Format("NorthSouth \t{0,5} \t{1,8:##.00} \t{2,10:###.00} \t{3,6:#.00}", covSummary1.numTotalFlightLines, covSummary1.averageFlightLineLength, covSummary1.totalFlightLineLength, flightTime1); Constants.Log(fmt1); String fmt2 = String.Format("EastWest \t{0,5} \t{1,8:##.00} \t{2,10:###.00} \t{3,6:#.00}", covSummary2.numTotalFlightLines, covSummary2.averageFlightLineLength, covSummary2.totalFlightLineLength, flightTime2); Constants.Log(fmt2); String fmt3 = String.Format("PrincipalAxis \t{0,5} \t{1,8:##.00} \t{2,10:###.00} \t{3,6:#.00}", covSummary3.numTotalFlightLines, covSummary3.averageFlightLineLength, covSummary3.totalFlightLineLength, flightTime3); Constants.Log(fmt3); outfile.WriteLine(); outfile.WriteLine("Results of three types of coverage "); outfile.WriteLine(fmt1); outfile.WriteLine(fmt2); outfile.WriteLine(fmt3); //select the recommended coverage option from the three candidates if (flightTime1 < flightTime2 && flightTime1 < flightTime3) { btnNS_EW.Text = "North-South"; polyCoverageType = PolygonCoverageType.South2North; }; if (flightTime2 < flightTime1 && flightTime2 < flightTime3) { btnNS_EW.Text = "East-West"; polyCoverageType = PolygonCoverageType.East2West; }; if (flightTime3 < flightTime1 && flightTime3 < flightTime2) { btnNS_EW.Text = "PrincipleAxis"; polyCoverageType = PolygonCoverageType.PrincipalAxis; }; //enable the missionPlan startup button. button3.Visible = true; button3.Enabled = true; /////////////////////////////////////////////////////////////////////////////////// // wait here til user clicks considers the coverage options and clicks: "PLAN" /////////////////////////////////////////////////////////////////////////////////// while (!MissionPlanningInitiated) { Application.DoEvents(); //wait for user inputs } //redo the polygon coverage based on the user selection -- //need to save the old plans so we dont have to redo!! if ( polyCoverageType == PolygonCoverageType.South2North) getPolygonFlightPathCoverage(0.0, //North-South case p, swathWidthKm, maxFLLengthKm, minLineLength, DRImageHeightKm, ref numSets, ref numFlightLines, ref XAllSets, ref YAllSets); else if (polyCoverageType == PolygonCoverageType.East2West) getPolygonFlightPathCoverage(90.0 * Constants.Deg2Rad, //East-West case p, swathWidthKm, maxFLLengthKm, minLineLength, DRImageHeightKm, ref numSets, ref numFlightLines, ref XAllSets, ref YAllSets); else if (polyCoverageType == PolygonCoverageType.PrincipalAxis) getPolygonFlightPathCoverage((180.0-p.rotationAngleDeg) * Constants.Deg2Rad, p, swathWidthKm, maxFLLengthKm, minLineLength, DRImageHeightKm, ref numSets, ref numFlightLines, ref XAllSets, ref YAllSets); //this is placed here just in case the user has changed his mind and re-clicked a different airport p.selectedAirportIndex = selectedAirportIndex; GoogleMaps gglmaps = new GoogleMaps(datasetFolder, p.PolygonName); gglmaps.getGoogleMapToCoverProject(ref p, downloadMap); //insert the Google map from the web into this polygon ///////////////////////////////////////////////////////////////////////////////// bool replannerTool = false; //set true for a replanner tool ///////////////////////////////////////////////////////////////////////////////// //this is for a replanner tool // what does this do ????? //supposed to allow visual editing of the Quick-Look and replanning the mission to cover clouds & shadows List<endPoints> reflyLines = null; ReflyPlanner refly; if (replannerTool) { refly = new ReflyPlanner(@"E:\Melbourne\Melbourne_NEW_005\QLData\refly_005.kml", p); MessageBox.Show("refly tool is in effect"); //should have a way to indicate which photocenters are to be reflown reflyLines = refly.getReflySegments(); } ///////////////////////////////////////////////////////////////////////////////// //open the file to write the kml Project results kmlOutputFilename = datasetFolder + "\\" + p.PolygonName + ".kml"; //NOTE: this will fail if the input client polygon (.kml) is the same as the desired output kml FileStream fs2 = null; try { fs2 = new FileStream(kmlOutputFilename, FileMode.Create, FileAccess.Write, FileShare.None); } catch { MessageBox.Show("cant open the kml Project results file -- input file cannot be same as output file "); } kmlFlightLines = new StreamWriter(fs2); //kml file where we will write kmlFlightLines.AutoFlush = true; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //at this point, we have a 2D table YAllSets[numsets][numFlightLines] that describes the intended flightlines and lengths //however, some of these have zero length because of the irregular shape of the polygon //Moreover, we have not considered the max endurance -- so we must collect sequentual flightlines into flyable missions //Below we convert the 2D structure to a 1D structure containing only flightlines to be flown //we also assign the flightlines to individual missions ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// outfile.WriteLine(); outfile.WriteLine("Now have the set of flight lines by set"); outfile.WriteLine("Next break up these flight lines into time-constrained groupings to get a mission"); outfile.WriteLine(); Constants.Log("Accumulate flight lines into missions based on max mission length"); //go through the flightlines for a set and determine the total time-over-the-covered area for the complete Set //If this is > 2 hrs, then break the set into ~equal Area Of Interest (AOI) flight sessions "AOITimeMax". //find the start-end flightline for AOIs so the accumulated AOITime < AOITimeMax -- except the last AOI may be larger List<MissionSummary> MissionSumA = new List<MissionSummary>(); //set up for the polygon math polygonMath polyMath = new polygonMath(p.Easting, p.Northing, datasetFolder); double totalFlightlineDistance = 0; double flightlineDistanceThisMission = 0; int missionNumber = 0; int totalImages = 0; int totalImagesThisMission = 0; int startFlightlineThisMission = 0; double sumMissionLat = 0.0; //used to compute the average mission center (average lat/lon) double sumMissionLon = 0.0; double totalFerryTime = 0.0; double totalTimeToTurn = 0.0; double totalTimeOverTarget = 0.0; double latStart = 0, lonStart = 0; double latEnd = 0, lonEnd = 0; int jApt = 0; //airport counter double ferryTime = 0.0; double oldFerryTime = 0.0; int numberFlightlines = 0; outfile.Flush(); double avgMissionLat=0.0; double avgMissionLon=0.0; int numFL = 0; //numFL becomes the sequential flight line counter for the 1D array that will persist across the project for (int i = 0; i < numSets; i++) { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //compute the max allowable AOITime for this Set double maxAOITime = maxMissionTimeHrs; //maxAOITimeGlobal; outfile.WriteLine("Input Maximum Mission time is set to " + maxMissionTimeHrs.ToString()); double totalFlightlineDistancePerSet = 0; int numFLthisSet=0; int lastNonZeroFLThisSet = 0; //precompute the total time to cover the flight lines in this set for (int j = 0; j < numFlightLines; j++) { double FLLength = 0; double delEasting = XAllSets[1, i, j] - XAllSets[0, i, j]; double delNorthing = YAllSets[1, i, j] - YAllSets[0, i, j]; FLLength = Math.Sqrt(delEasting*delEasting + delNorthing * delNorthing); if (FLLength > 0) { numFLthisSet++; totalFlightlineDistancePerSet += FLLength; lastNonZeroFLThisSet = j; } } //total time for this set double totalAOITime = (totalFlightlineDistancePerSet / 1000.0) / aircraftSpeedKPH + (numFLthisSet - 1) * timeToTurn; double numberOfAOIsThisSet = Math.Floor(totalAOITime / maxAOITime) + 1.0; //adjust the maxAOITime so that it is <= to the input maxAOITime but equal for all missions within this set maxAOITime = totalAOITime / numberOfAOIsThisSet; outfile.WriteLine(); outfile.WriteLine("Number of missions this set = " + numberOfAOIsThisSet.ToString()); outfile.WriteLine("Adjusted Maximum Mission time to enforce equal-size missions this set " + maxAOITime.ToString("F1")); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //max allowable is now <= the initial max allowable -- reduced to have neary equal sized AOIs within the set. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int AOIsThisSet = 0; //////////////////////////////////////////////////////////////////////////////////// //now go back through the flight lines and break into groupings of flight lines //this will define the flyable missions with a prescribed time-over-target threshold //////////////////////////////////////////////////////////////////////////////////// for (int j = 0; j < numFlightLines; j++) { //compute the flight line length using the north and south intersection extents //YAllSets[1, i, j] is the flight line "end" -- the larger Northing value (i.e., northmost end) double flightLineDistanceKm = 0; double delEasting = XAllSets[1, i, j] - XAllSets[0, i, j]; double delNorthing = YAllSets[1, i, j] - YAllSets[0, i, j]; flightLineDistanceKm = Math.Sqrt(delEasting * delEasting + delNorthing * delNorthing) / 1000.0; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (flightLineDistanceKm == 0 ) continue; //skip the below computations id the terminated flight line has zero length //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //number of flightlines this mission is current flight line (numFL) minus start flight line numberFlightlines = numFL - startFlightlineThisMission + 1; //num of flight lines this mission //sum the total flight line distances //images this flight line based on the downrange image extent int imagesThisFlightline = Convert.ToInt32(Math.Floor(flightLineDistanceKm / DRImageHeightKm) + 1.0); //per-mission and total travel distances and num of images totalFlightlineDistance += flightLineDistanceKm; flightlineDistanceThisMission += flightLineDistanceKm; totalImages += imagesThisFlightline; totalImagesThisMission += imagesThisFlightline; //transfer the start/end UTM coordinates back to the LatLon for use in the kml file //Y is northing and X is easting utm.UTMtoLL(YAllSets[0, i, j], XAllSets[0, i, j], p.UTMZone, ref latStart, ref lonStart); utm.UTMtoLL(YAllSets[1, i, j], XAllSets[1, i, j], p.UTMZone, ref latEnd, ref lonEnd); FLSum[numFL].planned.startUTMX = XAllSets[0, i, j]; FLSum[numFL].planned.startUTMY = YAllSets[0, i, j]; FLSum[numFL].planned.endUTMX = XAllSets[1, i, j]; FLSum[numFL].planned.endUTMY = YAllSets[1, i, j]; //fill the flightline summary structure FLSum[numFL].planned.startLat = latStart; FLSum[numFL].planned.startLon = lonStart; FLSum[numFL].planned.endLat = latEnd; FLSum[numFL].planned.endLon = lonEnd; FLSum[numFL].numImages = imagesThisFlightline; FLSum[numFL].lineNumberInPolygon = numFL; FLSum[numFL].flightlineLengthMi = flightLineDistanceKm * 1000.0 / 0.3048 / 5280.0; FLSum[numFL].FLstat = fightlineStatus.plan; //sums to be used to compute the average mission center sumMissionLat += (FLSum[numFL].planned.startLat + FLSum[numFL].planned.endLat) / 2.0; sumMissionLon += (FLSum[numFL].planned.startLon + FLSum[numFL].planned.endLon) / 2.0; ///////////////////////////////Closest Airport/////////////////////////////////////////////////////////////////// //average mission lat/lon for use in getting the ferry distance //we keep a running sum to get the avg as we add flight lines to the mission avgMissionLat = sumMissionLat / numberFlightlines; avgMissionLon = sumMissionLon / numberFlightlines; /* //closest airport and ferry time to average mission location //test all candidate airports and use the closest to the mission center double minAptDistance = 99999999999999.0; for (int jA = 0; jA < p.airports.Count; jA++) { double delLat = p.airports[jA].lat - avgMissionLat; double delLon = p.airports[jA].lon - avgMissionLon; double dist = Math.Sqrt(delLat * delLat + delLon * delLon); if (dist < minAptDistance) { minAptDistance = dist; jApt = jA; } //store the index of the closest airport } */ double aptNorthing = 0.0, aptEasting = 000, avgNorthing = 0.0, avgEasting = 0.0; //convert the airport lat/Long (LL) coordinates to UTM for distance measurement utm.LLtoUTM(p.airports[p.selectedAirportIndex].lat * Constants.Deg2Rad, p.airports[p.selectedAirportIndex].lon * Constants.Deg2Rad, ref aptNorthing, ref aptEasting, ref p.UTMZone, true); utm.LLtoUTM(avgMissionLat * Constants.Deg2Rad, avgMissionLon * Constants.Deg2Rad, ref avgNorthing, ref avgEasting, ref p.UTMZone, true); //delta N and E distances in Miles double dn = (aptNorthing - avgNorthing) / 1000.0; double de = (aptEasting - avgEasting) / 1000.0; //ferry distance one-way -- approximation cause the ferry time really to the start of the first line of this mission double ferryDistanceKm = Math.Sqrt(dn * dn + de * de); oldFerryTime = ferryTime; // save the last ferry time --- new ferry time assuming we keep the current mission is below if (ferryDistanceKm > distanceDuringClimb) //account for the travel portion while climbing to altitude ferryTime = minimumFerryTime + (ferryDistanceKm - distanceDuringClimb) / aircraftSpeedKPH + ferryDistanceKm / aircraftSpeedKPH + takeoffTime + landingTime; else //closest airport is inside the mission area -- climb time is purely addidive to the flight time ferryTime = minimumFerryTime + ferryDistanceKm / aircraftSpeedKPH + takeoffTime + landingTime; /////////////////////////////////Closest AIRPORT//////////////////////////////////////////////////////////////// numFL++; //number of flight lines that have been investigated ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //separate the flight lines into multiple missions by asking: If we add this flight line do we exceed the aircraft endurance? //also do this for the last flightline in each Set -- and the very last flight line. //note -- on the first entry, we havent computed a ferry time (its zero) but its computed below double expectedAOITime = (flightlineDistanceThisMission) / aircraftSpeedKPH + (numberFlightlines - 1) * timeToTurn; outfile.WriteLine(String.Format(" New flightline: {0,2} length {1,10:#.00} (mi) accumulated Time = {2,10:#.00}", numFL, flightLineDistanceKm * 1000.0 / 0.3048 / 5280.0, expectedAOITime)); outfile.Flush(); //NOTE: expectedAOITime includes the current line being tested //will we allow each AOI to be slightly larger than maxAOITime (by one flight line) or force each AOI to be smaller by one flight line? //for the first case, we will end up with a short AOI at the end -- for the second case, we end up with a long AOI at the end. //at the very end, we always want to set the prior lines in this Set as an AOI //first part of the "if" test tests to see if adding this flight line exceeds the maxAOITime. //note that this test is removed if we are at the last AOI within a set. This allows the last AOI to be established //even though the AOITime is below the maxAOITime. The second part of the "if" test triggers the AOI at the very last nonzero flightline. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if ((expectedAOITime > maxAOITime && AOIsThisSet != (numberOfAOIsThisSet-1) ) || (j == lastNonZeroFLThisSet)) { AOIsThisSet++; //numFL is the total number of flight lines that have been processed below //numFL-1 is the flightline ID of the last fight line that was processed //numFL will be the next flight line process so is the start line of the next mission //below logic accounts for the potential staggered start and ends of flight lines //these computations assume you start at the west headed north and turn back south after each north-going line, alternating north/south directions double extendedTravelToNextLine = 0; // accounts for the distance to travel to the start of the next flight line for (int k = startFlightlineThisMission; k < (numFL-2) ; k += 2) { //account for extended line going north and turning south (end is at the north) extendedTravelToNextLine += Math.Abs(FLSum[k].planned.endUTMX - FLSum[k + 1].planned.endUTMX) / 1000.0; //account for extended line going south and turning north (start is at the south) extendedTravelToNextLine += Math.Abs(FLSum[k + 1].planned.startUTMX - FLSum[k + 2].planned.startUTMX) / 1000.0; } flightlineDistanceThisMission += extendedTravelToNextLine; totalTimeToTurn += (numberFlightlines - 1) * timeToTurn; //Store the mission summary data into a structure and add to the ArrayList Collection MissionSummary missionSum = new MissionSummary(); missionSum.missionPolygon = new List<PointD>(); // this is a geodetic polygon formed from the flight line endpoints missionSum.missionNumber = missionNumber; //incremental index of the missions missionSum.startFlightLine = startFlightlineThisMission; //starting flight line for this mission missionSum.FerryTime = ferryTime; //two-way ferry --- computed below missionSum.LengthMi = flightlineDistanceThisMission * 1000.0 / 0.3048 / 5280.0; missionSum.numberFlightlines = numberFlightlines; // missionSum.TimeOverTarget = flightlineDistanceThisMission / aircraftSpeedKPH + (numberFlightlines - 1) * timeToTurn; totalTimeOverTarget += missionSum.TimeOverTarget; missionSum.MissionStat = MissionStatus.plan; missionSum.takeoffAirport = p.airports[jApt]; missionSum.totalImages = totalImagesThisMission; missionSum.backgroundImageNWCorner = new PointD(0.0, 0.0); missionSum.backgroundImageSECorner = new PointD(0.0, 0.0); outfile.WriteLine(String.Format(" new misson: {0,2} startFlightLine {1,3} numFlightLines {2,3} flightlineDistance {3,10:#.00} (Mi) ferryTime {4,5:#.00} (hr) ", missionSum.missionNumber, missionSum.startFlightLine, missionSum.numberFlightlines, missionSum.LengthMi, missionSum.FerryTime)); missionSum.backgroundImageFilename = "NA"; missionSum.setNumber = i; if (!replannerTool) { //form a polygon of the mission envelope based upon the flight line endpoints. //I dont see where these UTM endpoints are used!!! List<double> EastingMsn = new List<double>(); List<double> NorthingMsn = new List<double>(); //go around the flight line endpoints beginning at the south ends and moving counterclockwise for (int ifl = startFlightlineThisMission; ifl < (startFlightlineThisMission + numberFlightlines); ifl++) { EastingMsn.Add(FLSum[ifl].planned.startUTMX); NorthingMsn.Add(FLSum[ifl].planned.startUTMY); missionSum.missionPolygon.Add(new PointD(FLSum[ifl].planned.startLon, FLSum[ifl].planned.startLat)); } //add the north ends starting at the westmost lines so the poly forms a sequential set of points counterclockwise for (int ifl = (startFlightlineThisMission + numberFlightlines - 1); ifl > startFlightlineThisMission - 1; ifl--) { EastingMsn.Add(FLSum[ifl].planned.endUTMX); NorthingMsn.Add(FLSum[ifl].planned.endUTMY); missionSum.missionPolygon.Add(new PointD(FLSum[ifl].planned.endLon, FLSum[ifl].planned.endLat)); } //add one more point back at the start to create a linesegment to close the polygon missionSum.missionPolygon.Add(new PointD(FLSum[startFlightlineThisMission].planned.startLon, FLSum[startFlightlineThisMission].planned.startLat)); } else { //form the mission polygon from the flight line bounding boxes double maxLat = -99999.0, maxLon = -99999.0; double minLat = 99999.0, minLon = 99999.0; foreach (endPoints ep in reflyLines) { if (ep.startLat > maxLat) maxLat = ep.startLat; if (ep.endLat > maxLat) maxLat = ep.endLat; if (ep.startLon > maxLon) maxLon = ep.startLon; if (ep.endLon > maxLon) maxLon = ep.endLon; if (ep.startLat < minLat) minLat = ep.startLat; if (ep.endLat < minLat) minLat = ep.endLat; if (ep.startLon < minLon) minLon = ep.startLon; if (ep.endLon < minLon) minLon = ep.endLon; } missionSum.missionPolygon.Add(new PointD(minLon-0.01, maxLat+0.01)); missionSum.missionPolygon.Add(new PointD(maxLon+0.01, maxLat+0.01)); missionSum.missionPolygon.Add(new PointD(maxLon+0.01, minLat-0.01)); missionSum.missionPolygon.Add(new PointD(minLon-0.01, minLat-0.01)); missionSum.missionPolygon.Add(new PointD(minLon-0.01, maxLat+0.01)); } //mission center/average lat/lon is computed below this statement missionSum.missionCenter = new PointD(avgMissionLat, avgMissionLon); outfile.Flush(); //////////// add mission summary to the ArrayList collection of missions/////////////////////////////// MissionSumA.Add(missionSum); totalFerryTime += ferryTime; //total ferry times across the complete project -- used for the average ferry per mission flightlineDistanceThisMission = 0; //reset the mission distance summation totalImagesThisMission = 0; //reset the total images this mission missionNumber++; //increment the mission couinter startFlightlineThisMission = numFL; //set the start flight line for the next mission sumMissionLat = 0.0; //reset the sums used for the current mission lat/lon average sumMissionLon = 0.0; } } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //cycle back through the missions and get the maps and the average altitude //do this in a batch rather than in the per-mission segment above //in order to better batch-submit requests to the Google Maps API ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// List<PointD> totalPointsInPoly = new List<PointD>(); //a list of Npts points randomly spaced withi each polygon; int Npts = 100; //number of random-spaced points from each mission polygon used to form the average elevation in the mission polygon // MissionSumA is the collection of all missions -- this includes the mission polygon that is determined above for (int i = 0; i < MissionSumA.Count; i++) { //////////////////////////////////////////////////////////// // not quite sure why we do the intermediate storage !!! //////////////////////////////////////////////////////////// MissionSummary missionSum = MissionSumA[i]; //get the map of the mission area gglmaps.createBackgroundMaps(ref missionSum, FLSum, p.UTMZone, downloadMap); MissionSumA[i] = missionSum; //get a list of all the accumulated Npts points used for all the averages //this is done so we can request the elevation using Google Maps API with a minimum of requests // getPointsInPoly returns a list of Npts PointD points -- so the below call adds Npts on each call ??? for (int ips = 0; ips < Npts; ips++) { totalPointsInPoly.Add(polyMath.getPointsInPoly(Npts, missionSum.missionPolygon)[ips]); } } //this procedure gets the elevations for the totalPointsInPoly using the Google Elevation API //the points are requested using the polyline request method to minimize the number of requests. //we pack as many elevation points into each request as possible within the constraints of the URL length //The elevation API limits requests to 2500/day and a total of 25000 elevation points per day. //so 2500 requests with 100 points per request will hit the limit. List<double> heights = polyMath.getTerrainHeightsFromPointList(totalPointsInPoly); //go back through the total list of elevations and break the list into missions and get the elevation stats per mission for (int i = 0; i < MissionSumA.Count; i++) { MissionSummary missionSum = MissionSumA[i]; List<double> heightsThisMission = new List<double>(); for (int ips = i * Npts; ips < (i + 1) * Npts; ips++) heightsThisMission.Add(heights[ips]); heightsThisMission.Sort(); //used to get the 90 percentile height missionSum.ts.avgTerrainHeight = heightsThisMission.Average(); missionSum.ts.terrain90percentileHeight = heightsThisMission[Convert.ToInt32(0.90 * Npts)]; missionSum.ts.maxTerrainHeight = heightsThisMission.Max(); missionSum.ts.minTerrainHeight = heightsThisMission.Min(); ////flight altitude in feet for the pilot display missionSum.FlightAltMSLft = flightAlt + missionSum.ts.terrain90percentileHeight / 0.3048; //flightAlt is AGL in ft; ////round to the nearest 100 ft missionSum.FlightAltMSLft = (int)(missionSum.FlightAltMSLft / 100.0 + 0.50) * 100.00; MissionSumA[i] = missionSum; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //now we have all the data for the missions. //we need to write out the xml text lines that will be inserted into the original kml file. //we insert beneath the original Placemark element (after the <Placemark> and form a <Folder> kml structure //each element in the folders will contain one mission //also each mission folder will contain multiple Placemark elements that represent the flightlines //note the <description> <!CDATA[ ..... ]]> .... this is inserted HTML code to format the presentation ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Constants.Log(" Write the mission plan .kml"); //////////////////////////////// //write the kml file //////////////////////////////// writeKmlPolygonMissonPlan(p, DRImageHeightKm, missionNumber, flightAlt, swathWidthKm, totalFlightlineDistance, totalFerryTime, aircraftSpeedKPH, totalTimeOverTarget, numFL, totalImages, MissionSumA); /////////////////////////////////////////////////////////////// //the above comprises a complete closed set of .kml elements /////////////////////////////////////////////////////////////// kmlFlightLines.Flush(); Constants.Log(""); Constants.Log(""); Constants.Log("Normal Termination "); Constants.Log(""); ////////////////////////////////////////////// //show the END button button2.Visible = true; button2.Enabled = true; ////////////////////////////////////////////// } //Application.Exit(); }
void LinearFeaturePlanner(Polygon p, double flightAlt, double swathWidthKm, double aircraftSpeedKnots, double aircraftSpeedKPH, double DRImageHeightKm) { //if (p.polyCoverageType == COVERAGE_TYPE.linearFeature) //{ //show the "PLAN" button button3.Visible = true; button3.Enabled = true; /////////////////////////////////////////////////////////////////////////////////// // wait here til user clicks considers the coverage options and clicks: "PLAN" /////////////////////////////////////////////////////////////////////////////////// while (!MissionPlanningInitiated) { Application.DoEvents(); //wait for user inputs } //TODO //get the flyable aircraft paths that will cover the input linear feature path //there will be one linearFeature object for each of the parallel paths List<LINEAR_FEATURE> linearFeatures = linearFeatureCoverage(numParallelPaths, linearPathCentering, flightAlt, swathWidthKm * 1000.0, aircraftSpeedKnots * 0.514); //the above procedure writes kml files for forward, backward, & smoothed trajectory //also writes out the image boresight projection and the image endpoints as kml files GoogleMaps ggl = new GoogleMaps(datasetFolder, p.PolygonName); //get the project map for the path -- shows complete extent of the path and takeoff airport ggl.getGoogleMapToCoverProject(ref p, downloadMap); //get the path-segment maps that will be used to form a aircraft-centered sliding window map //get rectangular maps sized 10mi x 10mi at 5mi intervals along the path ggl.createBackgroundMapsForLinearFeature(linearFeatures, p.UTMZone, downloadMap); double totalPathDistance = 0.0; polygonMath polymath = new polygonMath(linearFeatures[0].EastingProNavS, linearFeatures[0].NorthingProNavS, datasetFolder); //get the along-path altitudes for (int iLF = 0; iLF < linearFeatures.Count; iLF++) { totalPathDistance += linearFeatures[iLF].pathLength; List<PointD> pointsAlongPath = new List<PointD>(); UTM2Geodetic utm = new UTM2Geodetic(); //create a sequence of points along path based on the smoothed trajectory for (int i = 0; i < linearFeatures[iLF].NorthingProNavS.Count; i++) { double lat = 0.0, lon = 0.0; utm.UTMtoLL(linearFeatures[iLF].NorthingProNavS[i], linearFeatures[iLF].EastingProNavS[i], p.UTMZone, ref lat, ref lon); pointsAlongPath.Add(new PointD(lon, lat)); } //get the terrain heights using the Google Elevation API linearFeatures[iLF].alongPathAltitude = polymath.getTerrainHeightsFromPointList(pointsAlongPath); //number of triggered images along the path linearFeatures[iLF].numImages = (int)(linearFeatures[iLF].pathLength / (DRImageHeightKm * 1000.0)); //get stats of the along-path altitudes double maxAlt = -99999.0, minAlt = 99999.0, meanAlt = 0.0; foreach (double alt in linearFeatures[iLF].alongPathAltitude) { if (alt > maxAlt) maxAlt = alt; if (alt < minAlt) minAlt = alt; meanAlt += alt; } meanAlt /= linearFeatures[iLF].alongPathAltitude.Count; linearFeatures[iLF].maxAltitude = maxAlt; linearFeatures[iLF].minAltitude = minAlt; linearFeatures[iLF].meanAltitude = meanAlt; linearFeatures[iLF].pathNumber = iLF; } //get the 2way ferry distance double total2WayFerryDistance = 0.0; //start: always start for 0th path to the selected airport //distance from selected airport to start of first line double delNS = linearFeatures[0].NorthingProNavS[0] - p.airports[selectedAirportIndex].northing; double delES = linearFeatures[0].EastingProNavS[0] - p.airports[selectedAirportIndex].easting; double distanceToAptS = Math.Sqrt(delNS * delNS + delES * delES); total2WayFerryDistance += distanceToAptS; //always return to the airport from the end of the last path int endIndex = linearFeatures[linearFeatures.Count - 1].NorthingProNavS.Count - 1; //distance from selected airport to start of first line delNS = linearFeatures[linearFeatures.Count - 1].NorthingProNavS[endIndex] - p.airports[selectedAirportIndex].northing; delES = linearFeatures[linearFeatures.Count - 1].EastingProNavS[endIndex] - p.airports[selectedAirportIndex].easting; distanceToAptS = Math.Sqrt(delNS * delNS + delES * delES); //final 2-way ferry distance is the sum of the to- and from- ferry distances total2WayFerryDistance += distanceToAptS; double totalPathTime = totalPathDistance / (aircraftSpeedKPH * 1000.0); int totalLFImages = (int)(totalPathDistance / (DRImageHeightKm * 1000.0)); writeKmlLinearFeatureMissonPlan(p, DRImageHeightKm, numParallelPaths, flightAlt, swathWidthKm, totalPathDistance, total2WayFerryDistance, aircraftSpeedKPH, totalPathTime, totalLFImages, linearFeatures); ////////////////////////////////////////////// //show the END button button2.Visible = true; button2.Enabled = true; ////////////////////////////////////////////// //wait here to see if the user clicks the "END" button while (true) { Application.DoEvents(); } //Environment.Exit(-1); //} //////////////////////// end of linear feature Planner ////////////////// }