top of page

Java Dynamic Google Earth Tweet and WMS Mapper

Here I share some details on an "end of term" project for a software development course at the university of Salzburg.

The aim was to design a programme that downloads a web map service and a twitter.csv file, converts both into kml structures, and launches them in Google Earth. In the process the tweets are transformed into 3D polygons that are colour-coded according to the time of the tweet, with a time series to allow the user to interact and with pop-up boxes.  In addition to this, various user interactions were added into the programme to enhance user experience. For the same reason, a kml tour was created and integrated into the programme as well. A key concept during this project was modularity. 

Here's a video demo of the completed project:

 

Aim 1:  Modularity

Since this assignment contains a variety of steps, it would lead to an extremely long and complex code if we designed it so that all the code was contained in a single class. Instead, after spending some time discussing our plan of action, we quickly opted for a more modular approach. This way, we can easily and intuitively dissect the programme into its sub-components. This division of sub-tasks makes it much easier for us to keep a clear structure in the programme and to easily jump into any of the sub-components if we need to work on that part. In this manner, we created one main executable class (GoogleEarthTweetMapper.java) that will bring all the individual steps together.

Aim 2: User Input and Experience

Although it was not technically required by the assignment instructions, we wanted to allow the user to interact with the programme in a few key steps. With this, we also had the aim of enhancing the user experience. We tried to come up with simple and friendly texts that would inform the user of what is happening. For example, the programme starts with a greeting and an explanation of what will happen.

Specifically, we wanted to allow user input for these decisions:

  1. Allow the user to either accept the default directory or set a new one.

  2. Ask the user if they would like to see additional information on the WMS while the programme is running.

  3. Ask the user if they would like to proceed to launch Google Earth.

Concept 

 

We aimed to make the whole programme run smoothly without the user having to figure out which classes to execute and in which order. We hope that it may demonstrate how we applied our newly obtained knowledge about modularity, to make the whole programme run smoothly and without the user having to figure our which classes to execute and in which order. To see the main class GoogleEarthTweetMapper.java in the correct order.

SWD1.png
icon.png

The Main Executable Class: GoogleEarthTweetMapper.java


We aimed to make the whole programme run smoothly without the user having to figure out which classes to execute and in which order. All relevant method are therefore called from the main executable class GoogleEarthTweetMapper.java in the correct order:

package eot_Sahinovic_Woehs_Zorenboehmer;

public class GoogleEarthTweetMapper {

        // method to ask user for directory called at start, since subsequent variables require the "directory"
       public static String directory = User_input.askUser1();
       public static String google_earth_filepath = "C:\\Program Files\\Google\\Google Earth Pro\\client\\googleearth.exe";
       
       public static String wms_url ="http://maps.heigit.org/osm-wms/service?service=WMS&request=GetCapabilities&version=1.1.0";
       public static String wms_png = directory + "\\boston.png";
       public static String wms_kml = directory + "\\boston.kml";    

       public static String twitter_url = "http://www.berndresch.com/work/twitter.csv";
       public static String twitter_csv = directory + "\\tweets.csv";
       public static String twitter_kml = directory + "\\tweets_polygons.kml";
       
       public static String tour_kml = directory + "\\A Little Tour of our Project.kml";
       

        public static void main(String[] args) {
       
           // Get WMS Map and store locally
           WMS_GetMap.getMap();
           System.out.println(" --- Step 1 completed. ---\n");
           
           // Turn WMS into KML
           WMS_ImageToKML.wmsTOkml();
           System.out.println(" --- Step 2 completed. --- \n");
           
           // Download tweets.csv from web
           Tweets_download.downloadCSV();
           System.out.println(" --- Step 3 completed. --- \n");
           
           // Access local tweets.csv file, convert and store as KML file
           Tweets_polygons.tweetsToPolygons();
           System.out.println(" --- Step 4 completed. --- \n");
           
           // Write and store .kml file of tour through Google Earth
           GoogleEarth_tour.createTour();
           System.out.println(" --- Step 5 completed. --- \n");
           
           // Ask to proceed with launch
           User_input.askUser2();
           System.out.println("Continued.\n");
           
           // Launch
           GoogleEarth_launch.launchGoogleEarth();
           System.out.println(" --- Step 6 completed. --- \n");
           
       } // main()
} // class

User Input 1

The .askUser1() method is called at the very beginning of the main class to set the directory. The directory needs to be declared at the very beginning since multiple other variables incorporate the directory, and all the following classes and methods build upon it. In this method, we included a default directory, to make the programme works either way. We set the default directory to "C:/Users/Public/Documents", which is given on any PC. Nonetheless, we wanted to let the user specify a different directory if they prefer that. 

Mediamodifier-Design-Template (29).png

public class User_input {
   
 
 public static String directory;
   
   
static String askUser1() { 
       
       Scanner askUser =
new Scanner(System.in);   // warning coming from non-closed scanner. Note: if we close it, subsequent scanners fail to work.
        String answer;
       

        // welcome and information for user
        System.out.println("Hello! Welcome to this Google Earth Tweet and WMS Mapper!\n\n"
                + "This programme will:\n"
                + "1: download an image of Boston from a WMS\n"
                + "2: convert it into a KML structure\n"
                + "3: download a tweets.csv file from the web\n"
                + "4: convert it into a KML structure\n"
                + "5: create a KML file for a tour in Google Earth\n"
                + "6: launch all kml files in Google Earth\n\n"
                + "By default, the downloaded files will be stored at: C:\\Users\\Public\\Documents"
                + "\nIf this is fine, please enter Y below and hit enter to continue! If not, enter anything else to set a new directory.");
       
       answer = askUser.next(); 
       String newDirectory =
null;
       

        // if yes, set directory to the default and return directory
        if(answer.contains("Y") || answer.contains("y") || answer.contains("yes") || answer.contains("Yes")) {
           System.out.println(
"Great! Lets get started!\n");
           
directory = "C:\\Users\\Public\\Documents";
           
return directory;
           

        // else, allow user to enter directory and check if it exists. Otherwise keep asking for a valid directory.
        } else {
           System.
out.println("Please enter a new directory below and hit enter: ");
           Scanner askNewDirectory = new Scanner(System.
in);
           
boolean exists = false;                              // by default, exists is set to "false"
            while (exists != true) {                             // while-loop keeps asking for user input until exists is "true"
                newDirectory = askNewDirectory.nextLine();       // use .nextLine() to make sure  path-files with a space are read
                File tempDir = new File(newDirectory);    
               
boolean exists2 = tempDir.exists();              // check if entered directory exists
                if (exists2 == true) {                           // if directory exists, set exists2 to "true"
                    exists = true;                               // if exists2 is "true", exists becomes "true" and exits while-loop
                    directory = newDirectory;
                   System.
out.println("\nNew directory set to: " + directory + "\n");
               }
else {
                   System.
out.println("\nWrong file path. Please try again.");
               }
           }
           
return directory;
       }
   }
// askUser1()

Picture3.png

Connecting to WMS
and
Downloading Image

Here, we created a non-executable wms_GetMap.java class which contains a method getMap() that:

  1. creates a WebMapServer object

  2. tests the URL

  3. sends a fully configured GetMapRequest

  4. asks the user if they would like to see information on the service (and either prints infos to the console or moves on)

  5. writes the image in .png to the local directory

package eot_Sahinovic_Woehs_Zorenboehmer;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Scanner;

import javax.imageio.ImageIO;

import org.geotools.ows.ServiceException;
import org.geotools.ows.wms.WMSCapabilities;
import org.geotools.ows.wms.WebMapServer;
import org.geotools.ows.wms.request.GetMapRequest;
import org.geotools.ows.wms.response.GetMapResponse;

public class WMS_GetMap {
   
   static void getMap() {
   
       String wms_url = GoogleEarthTweetMapper.wms_url;
       String wms_png = GoogleEarthTweetMapper.wms_png;
               
       // Check if WMS URL is valid
       URL url = null;
       try {
             System.out.print("Testing Heigit's wms service URL... ");
             url = new URL(wms_url);
           } catch (MalformedURLException e) {
               System.out.println("Error related to URL.");
           }
   
       // Construct WMS object
       WebMapServer wms = null;
       try {
             wms = new WebMapServer(url);
           } catch (IOException e) {
             System.out.println("There was an error communicating with the server, e.g. the server is down.");
           } catch (ServiceException e) {
             System.out.println("The server returned a ServiceException (unusual in this case).");
           } 
       System.out.println("No errors in communicating with the server.");
           
       // Get Map Request: ask client to create a GetMapRequest object
       GetMapRequest request = wms.createGetMapRequest();
       
       // Configure the request object
       request.addLayer("osm_auto:all", "default"); 
       request.setFormat("image/png");
       request.setDimensions("1000", "1000");
       request.setTransparent(true);
       request.setSRS("EPSG:4326");
       request.setVersion("1.1.1");
       request.setBBox("-71.13,42.32,-71.03,42.42");
       
       // user interaction: offer additional information
       Scanner askServiceInfo = new Scanner(System.in);  // warning coming from non-closed scanner. Note: if scanner is closed subsequent scanners fail to work
       String answer1;
       String answer2;
       System.out.println("\nWould you like additional information on the WMS service while the programme runs? Type Y for yes or anything else for no and hit enter.");
       answer1 = askServiceInfo.next();
       // askServiceInfo.close();
       
       if(answer1.contains("Y") || answer1.contains("y") || answer1.contains("yes") || answer1.contains("Yes")) {
           // Access WMS Capabilities directly from the WMS
           WMSCapabilities capabilities = wms.getCapabilities();
           System.out.println("\tThe WMS capabilities are being retreived from a server called: " + capabilities.getService().getName() + "\n\t"  // method to access service infos
                   + "The server's title is: " + capabilities.getService().getTitle() +"\n\t" 
                   + "The requested image will be stored as a .png with dimensions of 1000 x 1000 pixels.\n\t" 
                   + "The coordinate reference system is set to ESPG:4326.\n\t"
                   + "The requested GetMap URL is: " + request.getFinalURL());
           
           System.out.println("\nPlease let us know when you are done with this step. Type any keyword and press enter:");
              Scanner askNextStep = new Scanner(System.in);
              answer2 = askNextStep.next();
                   // if(answer2.length() != 0){
                   // }else {
                   // }

            } else {
               System.out.println("Alright.");  // lowkey annoyed answer hehe ;)
           }
       
       // get response and store as .png locally
       try {
       GetMapResponse response = (GetMapResponse) wms.issueRequest(request);           
       BufferedImage image = ImageIO.read(response.getInputStream());
       ImageIO.write(image, "png", new File(wms_png));
           } catch (IOException e) {
               System.out.println("\nThere was an error writing the image.");
           } catch (ServiceException e) {
               e.printStackTrace();
           }
           System.out.println("\nImage was successfully saved at: " + wms_png);  // Inform
           
   } // getMap()
} // class

Converting WMS Image into .KML

Here, we created a non-executable wms_ImageToKML class which contains a method wmsTOkml() that:

  1. finds the local boston.png image

  2. writes a kml file (containing the image) to a local wms_kml_structure.kml

package eot_Sahinovic_Woehs_Zorenboehmer;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class WMS_ImageToKML {

    static void wmsTOkml() {

    String wms_kml = GoogleEarthTweetMapper.wms_kml
   String wms_png = GoogleEarthTweetMapper.wms_png;
   
   System.out.println("Turning local .png of Boston into kml format ... ");  // Inform

    // find stored boston.png image
   File wms_image = new File(wms_png);
   if(wms_image.exists()) {
       System.out.println("Image found...");
       
   }else {
       System.out.println("Image not found.");
   }
   
   // KML body
   String [] kmlArray = {"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
                       ,"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\r\n"
                       ,"        <Folder>    "
                       ,"        <name>Ground Overlays</name>\r\n"
                       ,"        <description>Examples of ground overlays</description>\r\n"
                       ,"        <GroundOverlay>\r\n"
                       ,"            <name>Boston WMS image</name>\r\n"
                       ,"            <description>Output WMS image</description>\r\n"
                       ,"            <color>B3ffffff</color>\r\n"
                       ,"            <Icon>\r\n"
                       ,"                <href>" + wms_png + "</href>\r\n"          // integrate boston.png
                       ,"            </Icon>\r\n"
                       ,"            <LatLonBox>\r\n"
                       ,                "<north>42.42</north>\r\n"                 // BBOX: -71.13,42.32,-71.03,42.42
                       ,                "<south>42.32</south>\r\n"
                       ,                "<east>-71.03</east>\r\n"
                       ,                "<west>-71.13</west>\r\n"
                       ,                "<rotation>0</rotation>\r\n"
                       ,          "</LatLonBox>\r\n"
                       ,        "</GroundOverlay>\r\n"
                       ,        "</Folder>"
                       ,    "</kml>\r\n"        
                       };
   
        // write and store file locally
       try {
           File kml_file = new File(wms_kml);
           kml_file.createNewFile();
           
           FileWriter writer = new FileWriter(kml_file);
           for(String i : kmlArray){
               writer.write(i);
           }
           writer.close();
   
       }catch(IOException e) {
        System.out.println("Error while writing the boston.kml file.");    
       }
       System.out.println("Image converted to kml and stored at: " + wms_kml);

    } // wmsTOkml()    
} // class

Downloading Twitter.csv File

Although this step was not included in the assignment instructions, we decided it would be nice for the user to not worry about having to download the .csv file themselves. Therefore we created a non-executable tweets_download.java class that contains the methods saveFileFromUrlWithCommonsIO() and downloadCSV() that access and download the .csv file from this web-URL: http://www.berndresch.com/work/twitter.csv 

package eot_Sahinovic_Woehs_Zorenboehmer;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.commons.io.FileUtils;

public class Tweets_download {
   
   // method for downloading .csv file from web and saving locally
   public static void saveFileFromUrlWithCommonsIO(String fileName,
            String fileUrl) throws MalformedURLException, IOException {
            FileUtils.copyURLToFile(new URL(fileUrl), new File(fileName));
            }
   
   public static void downloadCSV() {
       
       String twitter_url = GoogleEarthTweetMapper.twitter_url;
       String twitter_csv = GoogleEarthTweetMapper.twitter_csv;
       
       System.out.println("Downloading online tweets.csv and storing locally ... "); // Inform
       
       try {
       
        // call method with relevant parameters
        saveFileFromUrlWithCommonsIO(
        twitter_csv,
        twitter_url);

         } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        System.out.println("CSV file downloaded and stored at :" + twitter_csv); // Inform
        
   } // downloadCSV()
} // class

Picture4.png

This step proved to be the most complex one. In the end we used two methods within the non-executable tweets_polygons.java class. The first method dynamically determines a colour-code for the placemarks depending on the time of the tweet. The second method creates a kml file that contains 3D polygons for the point locations, with pop-up boxes, time-stamps, and a colour-coded visualisation of the time of the tweet.

In the process of creating this class we had to deal with a few challenges:

Time Stamp: After some initial research into typical kml-conform time stamps and comparing them to "created_at" column in the tweets.csv file, we chose to aim for a time stamp that was already mostly given in the .csv, but with a few differences:

         kml time stamp: 1997-07-16T10:30:15+03:00

         tweets.csv time format: 2012-11-05 15:47:16+01

We therefore concluded that we needed to add in the "T" where the blank space is and add ":00" at the end. We split the content of “column[6]” of the String array, which is the “created_at” column by “ “ (a blank space) and inserted a “T” in its place, and added the ending “:00”.

Creating Polygons from Points: To solve this, we perfomed some simple math calculations on the lat and lng coordinates of the point locations. This required a few conversions of String to Double and then back into String, to insert the new coordinates into the kml body.

Colouring the Polygons according to the Time of Tweet: Here we decided to work with the difference between the first tweet’s time and every other tweet’s time to dynamically adjust the green colour value in a hexadecimal colour code for each placemark to give us a colour-ramp from yellow (= earliest) to red (= latest). The value for green was calculated with the two values “secondsFirstDate” (= the time of the first tweet in seconds) and “seconds” (= the time of the current tweet in seconds). By parsing the time, we are initially given milliseconds. We divided these values by 1000 to arrive at seconds, and then again by 6 to ensure all values are within a range of 256, which is required for the colour values. We then simply calculated the difference between the first and the current tweet and used that difference to set the green colour value.

package eot_Sahinovic_Woehs_Zorenboehmer;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;

public class Tweets_polygons {
   
   // method for setting the colour according to the time
   public static String dateToColor(Date date, Date firstDate) {
       
       // .getTime converts to milliseconds, divide with 1000 to get seconds, additionally divide by 6 to get results within a range of 256 (required for the colouring)
       long seconds = date.getTime() / 1000 / 6;                 // time of current csv row
       long secondsFirstDate = firstDate.getTime() / 1000 / 6;   // first time in tweets.csv
       
       // convert to int and calculate time difference since "First Date"
       int difference = (int) (seconds - secondsFirstDate);
       
       // subtract difference from 255 (255 = maximum green colour value)
       // this allows us to assign brighter shades to earlier times and darker shades to later times

       int invertColor = 255 - difference%256;
       
       // convert int into hexadecimal string
       String changeColor = Integer.toHexString(invertColor);
       
       // if the conversion results in a String with only 1 character, we add another character to fit the required 8-character hexadecimal string
       if(changeColor.length() == 1) {
           changeColor = "0" + changeColor;
       }
       
       // return the formatted colour which is then passed to the kml_color variable to insert into the kml body
       // hexadecimal order = alpha(opacity) + blue + green + red
       // we dynamically set green according to the tweet time. Opacity and red are at maximum (FF). Blue is 00. This gives dynamic colour ramp from yellow to red.

       return "FF"+ "00" + changeColor + "FF";
       
   } //dateToColor()
   
   
    // method to create .kml file
    static void tweetsToPolygons() {
           
           String twitter_csv = GoogleEarthTweetMapper.twitter_csv;             
           String twitter_kml = GoogleEarthTweetMapper.twitter_kml;          
           ArrayList<String> kmlcontent = new ArrayList<String>();
           String kmlcontentString = null;
           String kml_head = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                             + "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\" xmlns:kml=\"http://www.opengis.net/kml/2.2\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n"
                             + "<Document>\n";
           String kml_end =  "</Document>\n"
                             + "</kml>";
           String line = "";
           String splitBy = ";";
           int count = 0;
           
           System.out.println("Accessing local tweets.csv file and converting into kml file ...");  // Inform
           Date firstDate = null;
   
           // CSV columns: id; lng; lat; hashtags; place_id; tweet; created_at; user_id
           
       try {
           BufferedReader br = new BufferedReader(new FileReader(twitter_csv));  
           while((line = br.readLine()) != null) {         // while-loop to iterate through lines 
               if(count != 0) {                            // != 0 to skip the first row, which contains the column names
               String[] column = line.split(splitBy);      // split row by ;
               String[] time = column[6].split(" ");       // split the "created_at" column by blank space, add in the missing "T"
               
               // Strings for the triangle coordinates 
               String lower_right;
               String lower_left;
               String above;
               
               // building blocks for the triangles based on the point coordinate in the CSV file
               // we need "Double" data type here to perform some simple math

               Double lower_right_lng = (Double.valueOf(column[1]) - 0.00075);
               Double lower_right_lat = (Double.valueOf(column[2]) - 0.00025);
               Double lower_left_lng = (Double.valueOf(column[1]) + 0.00075);
               Double lower_left_lat = (Double.valueOf(column[2]) - 0.00025);
               Double upper_lng = (Double.valueOf(column[1]));
               Double upper_lat = (Double.valueOf(column[2]) + 0.00025);
               
               // convert back into "String" data type for the KML file
               above = Double.toString(upper_lng) + "," + Double.toString(upper_lat) + ",100\n";
               lower_right = Double.toString(lower_right_lng) + "," + Double.toString(lower_right_lat) + ",100\n";
               lower_left = Double.toString(lower_left_lng) + "," + Double.toString(lower_left_lat) + ",100\n";
               
               // set dynamic colour value depending on time of tweet (see method above)
               // parse time[1], which contains the time, to get a java-readable date format

               String timestamp = time[1].substring(0, 8);  // only read characters 0 - 8, since the last 3 aren't used in java
               SimpleDateFormat date_format = new SimpleDateFormat("HH:mm:ss");
               
               Date date = null;
               try {
                   date = date_format.parse(timestamp);
               } catch (ParseException e) {
                   System.err.println("There was problem with the date format.");
                   e.printStackTrace();
               }
               
               // The CSV file is sorted by 'created_at' column, therefore we can fetch the first (starting) date of 
               // tweets and use it to reference the seconds passed in all other tweets

               if(count == 1) {
                   firstDate = date;
               }
               
               // pass date and first date to dateToColor() method, return colour value according to time passed since "firstDate"
               String kml_color = dateToColor(date, firstDate);
               
               // KML Body for each placemark
               kmlcontent.add("<Style id=\"transBluePoly\">\n"
                       + "<LineStyle>\n"
                       + "<width>1</width>\n"
                       + "</LineStyle>\n"
                       + "<PolyStyle>\n"
                       + "<color>"+ kml_color + "</color>\n"        // insert dynamic colour value
                       + "</PolyStyle>\n"
                       + "</Style>\n"
                       + "<Placemark>\n"
                       + "<description><![CDATA[\n"                 // pop-up box with some descriptions
                       + "<h1>Tweet Details</h1>\n"
                       + "<span style='color:blue'>Additional Infos:</span>\n"
                       + "<table border=1>\n" 
                       + "<tr><td>Hashtags  </td><td>" + column[3] + "</td></tr>\n" 
                       + "<tr><td>Tweet Content</td><td>" + column[5] + "</td></tr>\n" 
                       + "<tr><td>Time of Tweet</td><td>" + column[6] + "</td></tr>\n" 
                       + "</table>\n"
                       + "]]></description>\n" 
                       + "<TimeStamp>\n" 
                       + "<when>" + time[0] + "T" + time[1] + ":00</when>\n"    // KML conform formatting 
                       + "</TimeStamp>\n"
                       + "<styleUrl>#transBluePoly</styleUrl>\n"
                       + "<Polygon>\n" 
                       + "<extrude>1</extrude>\n" 
                       + "<altitudeMode>relativeToGround</altitudeMode>\n" 
                       + "<outerBoundaryIs>\n" 
                       + "<LinearRing>\n" 
                       + "<coordinates>" + above       // triangle
                       + lower_left
                       + lower_right
                       + above
                       + "</coordinates>\n"
                       + "</LinearRing>\n"
                       + "</outerBoundaryIs>\n"
                       + "</Polygon>" 
                       + "</Placemark>\n");
                   }
                
                   count++;  // count to keep track of how many placemarks are processed
               }
               br.close();
           } catch (IOException e) {
                   e.printStackTrace();
                   }
       
           // turn String array into one single String to then write it into a .kml file
           for(int i=1; i < kmlcontent.size(); i++) {
               kmlcontentString = String.join(",", kmlcontent);
           }
               System.out.println(count + " KML placemarks have been generated.");   // Inform
           
            // write and store file
            try {
               final FileWriter fw = new FileWriter(twitter_kml);
               fw.write(kml_head + kmlcontentString + kml_end);
               fw.close();
               }catch (IOException e) {
                   System.out.println("error");
               }                       
                 
       System.out.println("All " + count + " placemarks have been integrated into a single KML file, which is saved at " + twitter_kml);    // Inform

     } // tweetsToPolygons()
} // class

Converting Tweets.csv into Tweets.kml

Creating a KML Tour

Although not technically required, we though it would improve the user experience to be guided through the results in Google Earth. We therefore created our own KML tour and integrated it into a non-executable googleEarth_Tour.java class that stores the tour.kml locally. We created a 1 minute 22 second long tour that automatically starts upon the launch of Google Earth: it first zooms to Boston and ends in New York City where the user can then interact with the time slider or click on the polygons for the pop-up boxes.

package eot_Sahinovic_Woehs_Zorenboehmer;

import java.io.FileWriter;
import java.io.IOException;

public class GoogleEarth_tour {
       
   static void createTour() {
   
       String tour_kml = GoogleEarthTweetMapper.tour_kml;
       
    // write and store kml file for the tour
    try {
           final FileWriter fw = new FileWriter(tour_kml);
           fw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                   + "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\" xmlns:kml=\"http://www.opengis.net/kml/2.2\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n"
                   + "<gx:Tour>\n"
                   + "    <name>A Little Tour of our Project</name>\n"
                   + "    <gx:Playlist>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-63.12290854007514</longitude>\n"
                   + "                <latitude>39.71944819955333</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>0.9939864428085535</heading>\n"
                   + "                <tilt>0.3786868214997998</tilt>\n"
                   + "                <range>9358161.741512068</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:Wait><gx:duration>2</gx:duration>\n"
                   + "</gx:Wait>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>5</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-71.00888853810095</longitude>\n"
                   + "                <latitude>42.42768383617462</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>11.16425128020962</heading>\n"
                   + "                <tilt>75.09594378664738</tilt>\n"
                   + "                <range>33468.75153062727</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>7</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-71.05756219764805</longitude>\n"
                   + "                <latitude>42.36381827829531</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>10.46224084333792</heading>\n"
                   + "                <tilt>73.15957923404167</tilt>\n"
                   + "                <range>16764.66828564515</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:Wait><gx:duration>0</gx:duration>\n"
                   + "</gx:Wait>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>7</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-71.11151196070301</longitude>\n"
                   + "                <latitude>42.34727871857214</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>-114.9206190297224</heading>\n"
                   + "                <tilt>79.41729683363144</tilt>\n"
                   + "                <range>13060.02547246438</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>7</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-71.11254908336019</longitude>\n"
                   + "                <latitude>42.34841147160513</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>-114.9213187114234</heading>\n"
                   + "                <tilt>79.41734701430147</tilt>\n"
                   + "                <range>13065.70842019121</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:Wait><gx:duration>0</gx:duration>\n"
                   + "</gx:Wait>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>7</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-73.86458392265166</longitude>\n"
                   + "                <latitude>40.86813179276373</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>-116.7537623780953</heading>\n"
                   + "                <tilt>75.03497476257493</tilt>\n"
                   + "                <range>27679.03348690332</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:Wait><gx:duration>0</gx:duration>\n"
                   + "</gx:Wait>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>8</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-73.95117991181387</longitude>\n"
                   + "                <latitude>40.78966963325912</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>-134.23974677664</heading>\n"
                   + "                <tilt>57.28506199682491</tilt>\n"
                   + "                <range>3118.651189787366</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:Wait><gx:duration>0</gx:duration>\n"
                   + "</gx:Wait>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>8</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-73.99118107526316</longitude>\n"
                   + "                <latitude>40.75405954432178</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>-159.7455532781321</heading>\n"
                   + "                <tilt>74.69366745565868</tilt>\n"
                   + "                <range>2796.849892250283</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:Wait><gx:duration>0</gx:duration>\n"
                   + "</gx:Wait>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>14</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-73.99779113834319</longitude>\n"
                   + "                <latitude>40.71720996130342</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>20.88076637360208</heading>\n"
                   + "                <tilt>74.3499982894479</tilt>\n"
                   + "                <range>3941.452446446386</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:Wait><gx:duration>0</gx:duration>\n"
                   + "</gx:Wait>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>12</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-74.01685810703967</longitude>\n"
                   + "                <latitude>40.69122646445487</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>20.87436197040366</heading>\n"
                   + "                <tilt>54.36565733205216</tilt>\n"
                   + "                <range>20329.19846682225</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "        <gx:FlyTo>\n"
                   + "            <gx:duration>5</gx:duration>\n"
                   + "            <gx:flyToMode>smooth</gx:flyToMode>\n"
                   + "            <LookAt>\n"
                   + "                <gx:horizFov>59.99999999999999</gx:horizFov>\n"
                   + "                <longitude>-74.01429117929963</longitude>\n"
                   + "                <latitude>40.68700202906096</latitude>\n"
                   + "                <altitude>0</altitude>\n"
                   + "                <heading>20.87603598047493</heading>\n"
                   + "                <tilt>54.36574872906584</tilt>\n"
                   + "                <range>20341.71715068461</range>\n"
                   + "                <gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>\n"
                   + "            </LookAt>\n"
                   + "        </gx:FlyTo>\n"
                   + "    </gx:Playlist>\n"
                   + "</gx:Tour>\n"
                   + "</kml>\n");

           fw.close();
           
           }catch (IOException e) {
               System.out.println("Error writing KML file.");
           } 
    
            System.out.println("KML Tour created and stored at " + tour_kml); // inform
            
   } // createTour()
} // class

 

User Input 2

Here, we call the class user_input.java again, but we call a different method than the one in step 2 (then: .askUser1(), now: .askUser2()). Before launching the Google Earth application, we wanted to ask the user to confirm that they want to launch the programme. For this, we simply used a scanner to prompt the user to enter some form of "yes" to launch the programme. If something else is entered, the programme ends. We called the askUser2() method from the non-executable user_Input.java class.

static void askUser2() {
       

       // ask user to proceed with google earth launch
       Scanner askContinue = new Scanner(System.in);
       String answer1;
       
       System.
out.println("We have created a small tour to show you the WMS and Tweets in Google Earth. It will automatically begin when Google Earth is launched.\n"
               + "Would you like to proceed to launch Google Earth now? Type Y for yes or anything else for no and hit enter.");
       
       answer1 = askContinue.next(); 
       askContinue.close();
       

       // if yes, proceed with programme execution. Else, exit programme.
       if(answer1.contains("Y") || answer1.contains("y") || answer1.contains("yes") || answer1.contains("Yes")) {
           System.
out.println("Proceeding... "); 
           } else {
               System.
out.println("We've ended the programme for you. If you'd like to try again, run the programme again. :)");  // Inform
               System.exit(0); 
           }
       
   }
// askUer2()
} // class

ybzcff85xnyuiw3mqhok.png

package eot_Sahinovic_Woehs_Zorenboehmer;

import java.io.IOException;

public class GoogleEarth_launch {

    static void launchGoogleEarth() {
   
       System.out.println("Launching Google Earth... ");  // Inform
    
       String command = GoogleEarthTweetMapper.google_earth_filepath;
       String wms_kml = GoogleEarthTweetMapper.wms_kml;
       String twitter_kml = GoogleEarthTweetMapper.twitter_kml;
       String tour_kml = GoogleEarthTweetMapper.tour_kml;
       
        // open googleearth.exe, both KML files, and the tour
       try {
           Runtime.getRuntime().exec(new String[] {
                   command,
                   wms_kml,
                   twitter_kml,
                   tour_kml
                   });
       }
       catch (IOException e2) {
           e2.printStackTrace();
       }
       System.out.println("Launch complete.\n");  // Inform
    
   } //launchGoogleEarth()
} // class

 

Here, we call the class user_input.java again, but we call a different method than the one in step 2 (then: .askUser1(), now: .askUser2()). Before launching the Google Earth application, we wanted to ask the user to confirm that they want to launch the programme. For this, we simply used a scanner to prompt the user to enter some form of "yes" to launch the programme. If something else is entered, the programme ends. We called the askUser2() method from the non-executable user_Input.java class.

Launch Google Earth

bottom of page