コード例 #1
0
ファイル: GoogleMaps.cs プロジェクト: jekain314/KMLReader
        public void createBackgroundMaps(ref MissionSummary msn, FlightlineSummary[] FLSum, String UTMZone, bool downloadMap)
        {
            //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            //create the background map that will be used for Waldo_FCS
            //also use this map as an icon when selecting the mission from the mission selection screen

            //procedure:::::::
            //get the bounding rectangle for the set of flight lines for each mission
            //establish a border around this bounding box based on the percent
            //between the bounding box extents -- currently set to minimum of 25% on each side (NS or EW)
            //next calculate the GE Maps zoom level that will establish the Google Earth minimum-size map to encompass this area
            //we initialize at zoom = 7 (large map) and increase until the map coverage doesnt encompass the desired extent
            //we always assume a 640 X 480 Google Earth map because thats the largest size that is free.
            //Google Maps for Business allows larger maps!!!
            //We determine the NW and SE corners of the downloaded Google Earth map for use in flight management
            //We download the map using the Google Map API (just pass a URL to a .net web client)
            //Next we draw the flight lines on this map using the .net drawing tools
            //the store the map to a BackgroundImages folder for later use by the GeoScanner.
            //in another location of this code, we write the .kml to allow the background map to be viewed by the mission planner kml
            //you can bundle the .kml file and the BackgroundImages folder into a zip and rename to kmz for a consolidated mission plan
            ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            double maxLat = -999.0, maxLon = -999.0, minLat = 999.0, minLon = 999.0;
            double maxUTMX = -99999999999.0;
            double maxUTMY = -99999999999.0;
            double minUTMX =  99999999999.0;
            double minUTMY =  99999999999.0;

            //get the bounding box for the mission polygons (formed by ends of the FLs)
            //extend the flight lines by 4 mi at each end.
            double extendDistanceMet = 4.0 * 5280.0 * 0.3048;  // 4 miles in meters
            for (int j = msn.startFlightLine; j < (msn.startFlightLine + msn.numberFlightlines); j++)
            {
                //form a unit vector along each flight line -- start-to-end
                double delX = FLSum[j].planned.endUTMX - FLSum[j].planned.startUTMX;
                double delY = FLSum[j].planned.endUTMY - FLSum[j].planned.startUTMY;
                double mag = Math.Sqrt(delX * delX + delY * delY);
                delX /= mag;
                delY /= mag;
                //project 4 mi beyond the end
                double extendedEndX = FLSum[j].planned.endUTMX + delX * extendDistanceMet;
                double extendedEndY = FLSum[j].planned.endUTMY + delY * extendDistanceMet;
                double extendedStartX = FLSum[j].planned.startUTMX - delX * extendDistanceMet;
                double extendedStartY = FLSum[j].planned.startUTMY - delY * extendDistanceMet;

                if (extendedEndX   > maxUTMX) maxUTMX = extendedEndX;
                if (extendedEndY   > maxUTMY) maxUTMY = extendedEndY;
                if (extendedStartX > maxUTMX) maxUTMX = extendedStartX;
                if (extendedStartY > maxUTMY) maxUTMY = extendedStartY;
                if (extendedEndX   < minUTMX) minUTMX = extendedEndX;
                if (extendedEndY   < minUTMY) minUTMY = extendedEndY;
                if (extendedStartX < minUTMX) minUTMX = extendedStartX;
                if (extendedStartY < minUTMY) minUTMY = extendedStartY;
            }
            utm.UTMtoLL(maxUTMY, minUTMX, UTMZone, ref maxLat, ref minLon);
            utm.UTMtoLL(minUTMY, maxUTMX, UTMZone, ref minLat, ref maxLon);

            //compute the desired map center from the lat/lon max and min
            PointD mapCenter = new PointD((maxLon + minLon) / 2.0, (maxLat + minLat) / 2.0);

            //set a buffer around the map size that will contain the mission flight lines plus a border
            //make this buffer a fixed size (rather than percent) to allow for very small bounding boxes
            double bufferPercent = 0.00;  //this bufgfer has been removed and replaced with the end-line extensions
            double latBuffer = bufferPercent * (maxLat - minLat);
            double lonBuffer = bufferPercent * (maxLon - minLon);

            //  get the google maps zoom level that will encompass this map within a 640X480 pixel size
            int selectedZoom = 1;  //initial guess at the zoom
            //note: the returned image coverage/size is reduced as the zooom is increased
            //so increase til the the image no longer covers the bounding box -- image size assumed to be 640X480
            //640 X 480 is largest image that can be retreived frn Google Maps for Free.
            for (int zoom = 7; zoom < 20; zoom++)
            {
                setMap(zoom, mapCenter);  //gets the map bounding box given mapcenter and zoom level

                PointD mapNWCordinates = getNWMapCoordinates();
                //all we need to test is one corner -- coverage reduces about the center
                //not sure about this!! -- may need to check all the corners!!
                if (mapNWCordinates.X > (minLon - lonBuffer) || mapNWCordinates.Y < (maxLat + latBuffer))
                {
                    //test to see if the desired boundary rect is no longer contained in the Google Maps image
                    //set the desired zoom level to the last good zoom level
                    selectedZoom = zoom - 1;
                    break;
                }
            }

            //this URL returns a valid image
            //http://maps.googleapis.com/maps/api/staticmap?center=40.714728,-73.998672&zoom=12&size=400x400&sensor=false

            //build up the URL to retrieve the Google Map -- see the above model for the syntax
            String GoogleMapsURL = "http://maps.googleapis.com/maps/api/staticmap?center=" +
                        mapCenter.Y.ToString() + "," +
                        mapCenter.X.ToString() + "&zoom=" + selectedZoom.ToString() + "&size=640x480&sensor=false";

            //set the file storage location
            String backgroundImageFolder = datasetFolder + "\\" + polygonName + "_Background";
            if (!Directory.Exists(backgroundImageFolder)) Directory.CreateDirectory(backgroundImageFolder);

            String SaveToFile = backgroundImageFolder + "\\Background_" + msn.missionNumber.ToString("D2") + ".png";

            //enforce the Google API Maps interface to make requests slower than 10 requests/second (See Google API Service Agreement)
            //measure elapsed time since the last request
            int elapsedMillisecs = (DateTime.Now - timeSinceLastMapRequest).Milliseconds;  //elapsed time since last request
            //if time between this and last request is too fast, then Sleep
            //below we ensure we dont Make requests faster than 2/sec  -- 0.5 secs between requests
            if (elapsedMillisecs < 500)  //if elapsed time < 0.5 secs sleep long enough to make the elapsed time 0.5 secs
            {
                Thread.Sleep(500 - elapsedMillisecs);
            }
            timeSinceLastMapRequest = DateTime.Now;

            //get the file from the web using a webClient
            if (downloadMap)
            {
                getFileFromWeb(GoogleMapsURL, SaveToFile);
            }

            setMap(selectedZoom, mapCenter);
            msn.backgroundImageNWCorner = getNWMapCoordinates();
            msn.backgroundImageSECorner = getSEMapCoordinates();
            msn.backgroundImageFilename = polygonName + "_Background\\Background_" + msn.missionNumber.ToString("D2") + ".png";

            /////////////////////////////////////////////////////////////////////////////////
            //below material generates a map presentation with the flight lines overlayed
            /////////////////////////////////////////////////////////////////////////////////
            //Image img = Image.FromFile(SaveToFile); //get an image object from the stored file
            ////must convert this image into a non-indexed image in order to draw on it -- saved file PixelFormat is "Format8bppindexed"
            //Bitmap bm = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            //Graphics g = Graphics.FromImage(bm);  //create a graphics object
            //g.DrawImage(img, 0, 0); //now draw the original indexed image (from the file) to the non-indexed image

            //for (int j = msn.startFlightLine; j < (msn.startFlightLine + msn.numberFlightlines); j++)
            //{
            //    Point startFLPix = FromCoordinatesToLocalMapPixels(new PointD(FLSum[j].planned.startLon, FLSum[j].planned.startLat));  //first convert center to Google Map pixels
            //    Point endFLPix = FromCoordinatesToLocalMapPixels(new PointD(FLSum[j].planned.endLon, FLSum[j].planned.endLat));  //first convert center to Google Map pixels
            //    g.DrawLine(new Pen(Color.Red, 3), startFLPix, endFLPix);
            //}

            //String missionImageFolder = datasetFolder + "\\" + polygonName + "_Summary";
            //if (!Directory.Exists(missionImageFolder)) Directory.CreateDirectory(missionImageFolder);
            //bm.Save(datasetFolder + "\\" + msn.backgroundImageFilename);
        }
コード例 #2
0
ファイル: Form1.cs プロジェクト: jekain314/KMLReader
        //////////////////////////////////////
        //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();
        }