• Mobile Data Collection Using CSPro
    • Session 01: Overview of CSPro, Dictionary and Forms
    • Session 02: Skips and Prefills
    • Session 03: Consistency Checks
    • Session 04: More Logic in Data Entry, Rosters, Subscripts and Loops
    • Session 05: CAPI Features
    • Session 06: Functions, Lookup Files, Navigation & System Control
    • Session 07: Multimedia & GPS
    • Session 08: Menu Programs
    • Session 09: Synchronization
    • Session 10: Batch Edit and Export

Session 07: Multimedia & GPS

At the end of this session participants will be able to:
  • Use images in value sets
  • View external documents and media files from a data entry application
  • Take photos in a data entry application
  • Capture GPS coordinates in a data entry application
  • Display captured GPS points on a map
  • Generate and view reports from a data entry application
Value Set Images
In addition to showing text in a value set we can also display images. To add an image to a value set, select the variable in the dictionary editor and click on the "…" button next to the value. Browse to pick an image file. CSPro supports standard image file formats: png, jpeg, bmp… We recommend using jpeg as it tends to have smaller file sizes. The images are not added to the pen file by default so you would need to copy them to the device along with the pen file.
Let's add images for the roof types in F08. Copy the RoofImages folder into the application directory. Now open the value set for F08 and add each image to its respective value. We do not have a picture for other (specify) so we will leave that one without an image. Run the application on Windows and on mobile and see the images. Note that on mobile we have to copy the images to the device since they are not part of the pen file by default.
The Resource Folder
Instead of copying the image files to the mobile device each time they are updated we can put them in the resource directory of the application. All files in the resource folder are built into the pen file and extracted on the device when the application is run. This makes it more convenient to distribute an application with additional files. Let's create a resource folder, add it to our application and put our image files in it. To create the resource file just create a new folder in Windows explorer. We will create the folder "resources" in the household folder. Then in CSPro go to Add Files… from the File menu, choose "resource folder" and browse to the new folder. Now put the whole RoofImages folder in the resource folder and rebuild the pen file.
Viewing External Files
The function view() can be used to launch files with their default viewer. The view() function takes the path to the file to display. The device must have an application installed capable of viewing the type of file specified. The function determines which application to launch to view the file based on the file extension. Most devices have built-in viewers for most photo, music, and video file formats, however not all have a built-in PDF viewer. If you do not have one on your device, you can always download one. The view() function can also display websites if you pass it a URL.
view("/mnt/sdcard/csentry/picture.jpg");
view("/mnt/sdcard/csentry/audio.mp3");
view("/mnt/sdcard/csentry/movie.mp4");
view("/mnt/sdcard/csentry/document.pdf");
view("https://www.census.gov/data/software/cspro.html");
Let's add a userbar button to show the interview manual as an aid to the interviewer :
// Display the interviewer manual
function showInterviewerManual()
   
view("/mnt/sdcard/csentry/Popstan2020/Housing/resources/InterviewerManual.pdf");
end;
We can make our code a little more flexible by using the function pathname() to get the directory where the application is stored. This way if someone copies it into a different folder on the phone/tablet it will still work:
// Display the interviewer manual
function showInterviewerManual()
   
view(pathname(Application) + "resources/InterviewerManual.pdf");
end;
Taking Photos on Android
To take a picture on Android you can use the function execsystem which is another way to launch external applications. To use execsystem to take a photo pass it a string with "camera:" followed by the full path in which to save the photo:
execsystem("camera:/mnt/sdcard/csentry/photo.jpg");
Taking photos is currently only supported on Android. execsystem on Windows does not support the camera.
Note that photos do not get saved into the application data file. You need to save them to a folder/file on the device. The filename you use should make it easy to link the photo back to the interview that it was taken for. The easiest way to do this is to make the name of the photo based on the ID items of the questionnaire:
string photoFilename = pathname(application) +
               
maketext("../Data/HouseholdPhotos/photo%v%v%v%v.jpg",
                                PROVINCE, DISTRICT, ENUMERATION_AREA,
                                HOUSEHOLD_NUMBER);
execsystem(maketext("camera:%s", photoFilename));
Note that we are putting the photo in a subdirectory of the data directory named HouseholdPhotos. We will need to create this folder on the tablet for this to work.
Let's have the interviewer take a photo of the household being interviewed in section A. Rather than adding a userbar button which would be easy for the interviewer to forget, we will make it part of the questionnaire by adding a dummy variable to the dictionary. In the postproc of the variable we will call execsystem() to take the photo. We can add a refused code in case the household doesn't want a picture taken.
PROC PHOTO

if PHOTO = 1 then
   
// Take photo
    string photoFilename = pathname(application) +
               
maketext("../Data/HouseholdPhotos/photo%v%v%v%v.jpg",
                                PROVINCE, DISTRICT, ENUMERATION_AREA,
                                HOUSEHOLD_NUMBER);
   
execsystem(maketext("camera:%s", photoFilename));
elseif PHOTO = 9 then
   
// refused, move to next field with no photo
endif;
This works the first time through the questionnaire but if we go back through the photo question a second time then we are forced to take another photo. We can avoid this by adding another option to the value set "Keep photo" that the interviewer will select once they are happy with the photo that they took. When "keep photo" is selected we won't launch the camera. We can add another option "View photo" to let them look at the photo that was taken.
PROC PHOTO

string photoFilename = pathname(application) +
               
maketext("../Data/HouseholdPhotos /photo%v%v%v%v.jpg",
                                PROVINCE, DISTRICT, ENUMERATION_AREA,
                                HOUSEHOLD_NUMBER);

if PHOTO = 1 then
   
// Take/retake photo
    execsystem(maketext("camera:%s", photoFilename));

   
// reenter so that interview can retake if it is not good.
    reenter;
elseif PHOTO = 2 then
   
// view existing photo
    view(photoFilename);

   
// reenter so that interview can retake if it is not good.
    reenter;
elseif PHOTO = 3 then
   
// Keep photo - move to next field
elseif PHOTO = 9 then
   
// No photo (refused)
    // Delete photo if it exists
    filedelete(photoFilename);
endif;
This works except that it is possible for the interviewer to select "Keep photo" even when there is no existing photo and to attempt to view a photo that doesn't exist. We can solve this by only showing the "Keep photo" and "View photo" options in the value set if the photo exists already.
PROC PHOTO
onfocus
string photoFilename = pathname(application) +
               
maketext("../Data/HouseholdPhotos/photo%v%v%v%v.jpg",
                                PROVINCE, DISTRICT, ENUMERATION_AREA,
                                HOUSEHOLD_NUMBER);

// Only show the view and keep options in value set
// if the photo exists.
if fileexist(photoFilename) then
   
setvalueset(PHOTO, PHOTO_VS_PHOTO_TAKEN);
else
   
setvalueset(PHOTO, PHOTO_VS_NO_PHOTO);
endif;
Capturing GPS Points
To capture a GPS point first you need to start up the GPS hardware:
gps(open);
Then you request a GPS reading giving it a timeout in seconds and optionally a desired accuracy in meters:
if gps(read, 60, 10)  then // Read up to 60 seconds, try for 10m accuracy
    errmsg("Latitude is %f, longitude is %f",gps(latitude),gps(longitude));
else
   
errmsg("GPS signal could not be acquired");
endif;
Finally, you close the GPS:
gps(close);
In addition to querying the GPS latitude and longitude you can also query the accuracy, number of satellites, and altitude (although on Android altitude is not very accurate). See the help on GPS for details.
The longitude and latitude reported are in degrees in the WGS84 datum and are returned as decimal numbers.
To save the GPS coordinates in our data file we need to add the appropriate variables to our dictionary. To store the coordinates so that they can cover the entire earth with sufficient precision they should have space for 3 digits before the decimal point, 1 digit for the decimal point, and at least 5 digits after the decimal and a minus sign for a minimum total length of 10. Add the variables LATITUDE and LONGITUDE to the dictionary and to section on the forms.
Let's add a user bar button to update the GPS coordinates for the household:
// Capture current household location using GPS
function getGPS()
   
gps(open);
   
if gps(read, 60, 10)  then // Read up to 60 seconds, try for 10m accuracy
        LATITUDE = gps(latitude);
        LONGITUDE = 
gps(longitude);
   
else
       
errmsg("GPS signal could not be acquired");
   
endif;
   
gps(close);
end;
Displaying GPS Points on a Map
If we want to be fancy, we can display the point on the map first so that the interviewer can view it and then decide whether or not to use it. We can do this using a variable of type Map. Like ValueSet variables, Map variables have functions that can be called on them using a dot ("."). For example, to add a marker to a map you would call:
    Map mymap;
    mymap.
addMarker(longitude, latitude);
To display the map, call the show() function. This displays a Google Map containing the marker that was added.
We can use a map variable to display a map that has a marker at the GPS location that we captured:
// Capture current household location using GPS
function getGPS()
   
gps(open);
   
if gps(read, 60, 10)  then // Read up to 60 seconds, try for 10m accuracy
        // Show map so that interviewer can see result
        Map m;
        m.
addMarker(gps(latitude), gps(longitude));
        m.
show();
       
if accept("Save this result", "Yes", "No") = 1 then
            LATITUDE = 
gps(latitude);
            LONGITUDE = 
gps(longitude);
       
endif;
   
else
       
errmsg("GPS signal could not be acquired");
   
endif;
   
gps(close);
end;
Variables of type Map have many other functions that let you customize the map, the markers, add buttons and run logic functions when markers are tapped. You can also use offline base maps for situations where you do not have an Internet connection. See the help for maps for details.
Showing the Case Listing on a Map
If you have latitude and longitude variables in your data dictionary for each household you can configure the case listing screen to display the households on a map on Android. To do this, open the Mapping Options dialog from the Options menu in CSPro designer. Check the box "Show the case listing on a map" and choose the latitude and longitude variables from your dictionary. When you run the application on Android, the case listing screen will show a map with a marker for each household. Tapping on a marker launches data entry for that household.
Group Exercise
Build a CSPro application for the household listing questionnaire. Place your application in the folder Popstan2020/Listing. Treat each household as a separate case. In other words, do not use a repeating record and roster to try to match the paper questionnaire. When creating the listing dictionary use "LI_" as a prefix for the all the variable names so that they will not conflict with the names in the household dictionary. There is no need to validate the ID items using a lookup file like we did for the household questionnaire. We will prefill the ID items from the menu program tomorrow. For each household, automatically record the GPS coordinates in the latitude field. Give the interviewer the option of taking a photo of the household but allow for refusal. Name the photo based on the case ID items and put the photos in the directory Popstan2020/Data/ListingPhotos. Use the Mapping Options dialog to show the case listing on a map for the listing program. Go outside and test your listing application on a tablet.
Writing and Viewing Reports
With the view() function you can display text files on your device. We can use this to show text documents that we write out from CSPro. To write out files from a CSEntry application we have to first declare a variable of type file :
File tempFile;
Then we can use the commands setfile(), filewrite() and close() to open, write to and close the file. Let's create a userbar button to write a simple text report that lists the household members:
// Write out and display household summary report
function showHouseholdReport()
   
string reportFilename = maketext("%sreport.txt", pathname(Application));
   
File tempFile;
   
setfile(tempFile, reportFilename, create);
   
filewrite(tempFile, "Household Summary Report");
   
filewrite(tempFile, "------------------------");
   
filewrite(tempFile, "");
   
filewrite(tempFile, "Province %d District %d EA %d Area type %d Household Number %d",
                       
visualvalue(PROVINCE),
                       
visualvalue(DISTRICT),
                       
visualvalue(ENUMERATION_AREA),
                       
visualvalue(AREA_TYPE),
                       
visualvalue(HOUSEHOLD_NUMBER));
   
filewrite(tempFile, "");
   
filewrite(tempFile, "Household Members:");
   
filewrite(tempFile, "");
   
filewrite(tempFile, "Name                           Sex    Age  Relationship");
   
filewrite(tempFile, "----                           ---    ---  ------------");
   
do numeric i = 1 while i <= totocc(HOUSEHOLD_MEMBERS_ROSTER)
       
filewrite(tempFile, "%s %-6s %3d  %s",
                            NAME(i),
                           
getlabel(SEX, visualvalue(SEX(i))),
                           
visualvalue(AGE(i)),
                           
getlabel(RELATIONSHIP_VS1,
                           
visualvalue(RELATIONSHIP(i))));
   
enddo;
   
close(tempFile);

   
view(reportFilename);
end;
If you are familiar with HTML you can write out HTML and have nicer formatting and pictures. CSPro also has a built in templating engine to generate HTML reports using templates. See Templated Reports in the CSPro helps for details.
Exercises
  1. Add a button to the userbar that shows your website using the view() function.
  2. Add the following additional information to the summary report. a. Total number of household members, total males, total females. b. List of household assets from section G with quantity. Do not list assets where the quantity is zero.