Arrays - Puerto Rico Map

main1.png main2.png

Arrays help us store and work with groups of data of the same type. The data is stored in consecutive memory spaces which can be accessed by using the name of the array and indexes or subscripts that indicate the position where the data is stored. Repetition structures provide us a simple way of accessing the data within an array.

An object is an entity that it is used in many programming languages to integrate the data and the code that operates on it, simplifying the modification of large programs. A common task in programming using C++ is working with arrays of objects. In today's laboratory experience you will be working with georeferenced data of cities in Puerto Rico, where you will have attributes, such as name of the city, the latitude and longitude of its location, that you will use to illustrate properties in a map.

Objectives:

  1. Dynamically create and manipulate an array of objects.

  2. Code functions that process arrays of objects.

  3. Practice passing arrays of objects as parameters in a function.

  4. Practice the sequential reading of data from a file.

  5. Use modular programming.

  6. Use repetition and decision structures.

Pre-Lab:

Before coming to the laboratory you should have:

  1. Reviewed the concepts related to arrays of objects.

  2. Reviewed the concepts related to functions that use arrays of objects.

  3. Reviewed how to read data from a file.

  4. Studied the concepts and instructions for the laboratory session.

  5. Taken the Pre-Lab quiz, available in Moodle.



Georeferenced Data

Working with arrays of objects is a very common task in programming with C++. Once you have read the object's information from a file or as user input, you should use your algorithmic skills and knowledge of C++ to invoke the adequate methods and functions to process the data correctly.

In this laboratory experience, you will be working with georeferenced data of the cities in Puerto Rico. When data is georeferenced it simply means that it has an associated location in physical space. Typically this location is defined in latitude and longitude coordinates. For example, the following is part of a file that contains georeferenced data for some Puerto Rican cities:


Arroyo 17.9658 -66.0614
Bayamon 18.3833 -66.15
Caguas 18.2342 -66.0486
Dorado 18.4589 -66.2678
Fajardo 18.3258 -65.6525

Figure 1. Part of the content of a file with georeferenced data of cities in Puerto Rico; contains name of the city, coordinates for latitude and longitude.


Orthodromic Distance

To calculate the distance between two points in the Euclidean plane, you to trace the straight line segment that joins these points and compute its length using the distance formula you studied in your Pre-Calculus course. To calculate the distance between two points in the surface of a sphere you don't use the straight line segment that joins them, you use the shortest distance between these points measured over the sphere. This distance is called the orthodromic distance. To calculate the orthodromic distance between two points in the earth globe, you have to use the latitude and longitude coordinates.

The GPOI Class

The most common way for C++ programmers to encapsulate data to an entity is by using classes. For example, in the case of the georeferenced data, a practical way to encapsulate the information about each city would be to implement a GeoreferencedPointOfInterest class that contains at least data members (or attributes) for: the name of the city, its latitude and longitude. The GPOI class would also need to implement methods to access, modify, and perform computations on its attributes.

In this laboratory experience, you are provided a GPOI class with the following interface methods:

  • GISPOI(): the default constructor

  • GISPOI(QString s, double latitude, double longitude): constructor that receives name, longitude and latitude.

  • double getLat(), double getLon(): getters for the latitude, longitude.

  • QString getName(): getter for the name.

  • void setAll(string s, double a, double b): setter for all the properties (at once)

  • double odDistance(const GISPOI &B) const: given B, another GPOI object, returns the orthodromic distance (or the closest distance) between the invoking GPOI and B.



Assume that we define an array of string objects as follows: string S[5] and we assign the string "chicken" to each object. Which of the following would change the object at index 3 to "chickan"? S[4] = "a"; S[3][4] = 'a'; S.at(3) = "a"; S.replace(3,4, 'a'); The correct option is S[3][5] = 'a'. This instruction performs the following task: *change the letter at index 5 of object with index 3 to 'a'.
The option S[3] = "a" changes the whole content of object with index 3 to "a"
The intention of S.at(3) = "a" is changing the content of the object with index 3 to "a". However, the instruction is not valid because the result of the at() method is a string constant, whose value cannot be changed.
The option S.replace(3,4,'a'); is not valid because the replace() method works on strings, not on arrays.

Assume that we define an array of string objects as follows: string S[10] and that we assign strings to each of the objects. Which of the following prints the length of element 7 of array S? cout << S[7].length(); cout << S.length()[7]; cout << S.length(7); cout << S.at(7).length(); The following options print the length of element 7: cout << S[7].length();, cout << S.at(7).length();. In both, the length() method is preceded by an expression can be evaluated to element 7 of array S. Both S.at(7) and S[7] are expressions that refer to element 7.
The option cout << S.length()[7]; is invalid because it is invoking the method length() on an array of strings. The length() method can be invoked on any string object but not an array of objects.
The option cout << S.length(7) is invalid because it is trying to invoke the length() method on an array of strings. Furthermore, the length() method does not require arguments.

Suppose that we create an array of QPoint objects as follows: QPoint P[7]; and that we assign the x and y values to the objects. Which of the following can be use to compute the sum of the y coordinates of all points? The beta option is the correct answer because the instruction inside the loop correctly accesses the y coordinate for each of the points.
The alpha option includes a P.y() in the expression inside the loop. P.y() is not a valid instruction because P is an array, not a QPoint object, thus getter y() cannot be invoked on P.
The gamma option contains a[i] in the expression that is inside the loop. a[i] is not valid because a is not array, it is an int.



Reading Data from Text Files in C++

This laboratory experience requires you to read data from a text file. You can skip to the next section if you feel that your file reading skills are competent. Otherwise, read on...

C++ provides functions to read and write data to/from files. In this laboratory experience you will be using one of the most rudimentary file input/output schemes provided in C++ to read/write from text files. Text files consist exclusively of ASCII characters which represent data in any of the primitive types provided by C++. Typically, the values are separated by spaces. For instance let's assume that the file nameAge.txt contains some data about names and ages.

Tomas 34
Marta 55
Remigio 88
Andrea 43

To read a text file in C++, we need to have a sense of how it is organized and what type of data you would like to read. The example nameAge.txt file contains four lines, each consisting of a string and an integer. Here is a simple program to read that file entirely while printing its content. Read the comments to understand the various parts.

#include <iostream>

// fstream is the header file that contains classes, functions and 
// objects to deal with file input and output.
#include <fstream>  

using namespace std;

int main(){

    // We shall use these two variables to assign the values read
    // from each line in the file.
    string name;
    int age;

    // This is the object that will represent the file.
    ifstream inFile;

    // We call the open function to open the input file `nameAge.txt` 
    inFile.open("nameAge.txt");


    // We check if the file was correctly opened
    if (!inFile.is_open()) {
        cout << "Error openning file nameAge.txt\n";
        exit(1);
    }

    // While there is data in the file, read a string and an int.
    // Notice how the `>>` symbol is used, similar to when using cin

    while (inFile  >> name >> age) {
        cout << name << " : " << age << endl;
    }

    // Close the file. 
    inFile.close();

    return 0;
}

The ifstream object is used for reading a text file sequentially. It keeps track of the next position in the file that should be read. Each time that a data is read from the file (using inFile >> ____) it advances its position so that the next inFile >> ___ reads the next data and so forth.

Notice the line inFile >> name >> age. This instruction accomplishes several tasks:

  • It reads a string and an int from the file (if available) and assigns them to the variables name and age.
  • If both data were read, the expression evaluates to true, thus entering the while block.
  • If both data could not be read, the expression evaluates to false thus ending the while block.

Here are some code snippets for common reading tasks. Observe that all of them:

  1. Create an ifstream object, call the open function and check if the file is opened correctly.
  2. Create one or more variables to assign the values that are read from the file.
  3. Implement a loop which repeats until no more data is available in the file.
  4. close the file at the end.

Example 1: Read a file that consists only of integers, accumulate their values into a sum.

    ifstream inFile;
    int n;
    int accum = 0;

    inFile.open("nums.txt");

    if (!inFile.is_open()) {
        cout << "Error openning file nums.txt\n";
        exit(1);
    }

    while (inFile  >> n) {
        accum = accum + n;
    }

    cout << "Total: "  << accum << endl;

    inFile.close();

Example 2: Count the number of lines in a file that consists of names. Then choose the name at the center line.

    ifstream inFile;
    string name;
    int ctr = 0;

    inFile.open("names.txt");

    if (!inFile.is_open()) {
        cout << "Error openning file names.txt\n";
        exit(1);
    }

    while (inFile  >> name) {
        ctr++;
    }

    cout << "Total number of lines: " << ctr << endl;

    // These two commands "rewind" the file so that we can start
    // reading again from the beginning. 
    inFile.clear();
    inFile.seekg(0);

    for (int i = 0; i <= ctr / 2; i++) {
        inFile >> name;
    }

    cout << "The name at the position " << ctr / 2 << ": " << name << endl;

    inFile.close();


Laboratory Session:

Exercise 1 - Download and Understand the Code

Instructions

  1. Load the project prMap into QtCreator. There are two ways to do this:

    • Using the virtual machine: Double click the file prMap.pro located in the folder /home/eip/labs/arrays-prmap of your virtual machine.
    • Downloading the project�s folder from Bitbucket: Use a terminal and write the command git clone http:/bitbucket.org/eip-uprrp/arrays-prmap to download the folder arrays-prmap from Bitbucket. Double click the file prMap.pro located in the folder that you downloaded to your computer.
  2. Compile and run the program. In its current state, the program simply displays a map of Puerto Rico. This map is provided so that you can visualize the results of your program. You may see some warnings which are due to the fact that some of the functions are incomplete. You will complete them throughout this laboratory experience.

  3. Open the main.cpp file. This is the file where you will be writing your code. This file contains the following functions:

    1. void printArrayOfCities(GISPOI A[], int size): Given A, an array of GISPOI objects and its size, prints all the cities in the array. You may use this function as part of your debugging process.

    2. int countLinesInFile(ifstream &file): Given a reference to the object that represents a file, this function counts and returns the number of lines in the file.

    3. void readFileToArray(ifstream &file, GISPOI A[], int numOfCities): Given the ifstream object of a file, an array of cities and the number of records to read from the file, this function reads the values from the file and populates the array with objects. This is a function you will implement.

    4. void maxDistances(GISPOI A[], int size, int &idxCityA, int &idxCityB) : Given A, an array of cities, determines the farthest two cities. Remember that the distance you will calculate is the orthodromic distance. The function returns (by reference) the indices of these cities in the array. This is a function you will implement.

    5. void minDistances(GISPOI A[], int size, int &idxCityA, int &idxCityB): Given A, an array of cities, determines the closest two cities. Remember that the distance you will compute is the orthodromic distance. The function returns (by reference) the indices of these cities in the array. This is a function you will implement.

    6. double cycleDistance(GISPOI A[], int size, int P[]): Given an array of cities A, the size of the array, and an array P with a permutation of the integers in [0, size-1], computes and returns the distance to travel the cycle of cities A[P[0]] A[P[1]] A[P[size-1]]. Remember that the distance you will calculate is the orthodromic distance.

      For example, if the cities read from the file where Mayag�ez, Ponce, Yauco and San Juan (in that order) and the permutation P is , the function should compute the distance of a cycle from San Juan Ponce Mayag�ez Yauco San Juan. This is a function you will implement.

There are two additional functions that you need to know:

  1. void MainWindow::drawLine(const GISPOI &city01, const GISPOI &city02): Given a reference to two GISPOI objects, the function draws a line between them.

  2. void drawPoints(GISPOI* gisLocations, unsigned int size);: Given an array of GISPOI objects and their size, displays their locations as points in the map.

Exercise 2 - Read the Georeferenced Points into an Array

Remember that you will only be changing code in the main.cpp file. Your first task will be to add code to read the entire contents of a file into an array of GISPOI objects.

  1. In the main() function, add the necessary instructions to open the file that contains the georeferenced city information. The file that you will use first is pr10.txt that is in the data directory. You need to provide the complete path to the file as a parameter to the open() method of your ifstream object. As always, when using files you should verify if the entered name is a file that can be successfully opened for reading.

  2. Invoke the int countLinesInFile(ifstream &inFile) function to obtain the number of lines in the file. You may print out the number obtained so that you can validate if your program is working correctly.

  3. Dynamically create an array as big as the number of lines in the file.

  4. Modify the void readFileToArray(ifstream &file, GISPOI A[], int numOfCities) function so that it reads all the lines in the file to the objects in the array.

  5. In the main() function invoke the readFileToArray function, passing the reference to the file, the array you created in step 3, and its size.

  6. After invoking readFileToArray you may invoke void printArrayOfCities(GISPOI A[], int size) to print the names and georeferences of the points read from the file.

  7. Invoke the method drawPoints(GISPOI* gisLocations, unsigned int size) on the w object so that a point will be shown in the map for each city: w.drawPoints(A, size) (assuming that A is the name of your array). You should obtain something similar to the next figure.

    main1.png

Exercise 3 - Max and Min Functions

Once you have the information of georeferenced cities in the array of objects, you can start processing them in many interesting ways. We will start with some basic operations.

  1. Read the documentation and implement the function void maxDistances(GISPOI A[], int size, int &idxCityA, int &idxCityB). Invoke the function from main().

  2. Use the void drawLine(const GISPOI &city01, const GISPOI &city02) method of the w object to draw a line connecting the two farthest cities. Notice that the second and third parameters of this method are references to the objects that represent the cities (not their indices in the array).

  3. Read the documentation and implement the function void minDistances(GISPOI A[], int size, int &idxCityA, int &idxCityB). Invoke the function from main().

  4. Use the void drawLine(const GISPOI &city01, const GISPOI &city02) method of the w object to draw a line connecting the two closest cities.

Exercise 4 - Compute the Cycle Distance

  1. Read the documentation and implement the function double cycleDistance(GISPOI A[], int size, int P[]). Invoke the function from main() as indicated in the comments inside the main() function:

    • First, with
    • Then, with

Exercise 5 - More Fun!

  1. Change your code so that it now opens the pr.txt file. Validate your results and marvel at your great achievement!


Deliverables

Use "Deliverable" in Moodle to hand in the main.cpp file. Remember to use good programming techniques, include the name of the programmers involved, and document your program.



References

[1] https://en.wikipedia.org/wiki/Great-circle_distance

results matching ""

    No results matching ""