Implement Reporting Library

In this exercise, you'll learn to create a new script library in which you'll implement the reporting library code that executes the third part of the service.

Create A New Script Utility Library

  1. Return to the running Shader application.
  2. Select Desktop » Create Script... from the main menu.

    The software presents a wizard that helps you create a new script document.

    This is a picture of the create script wizard.
  3. Set Script Folder to Application Scripts Folder if it is not already set.
  4. Set Script Type to Function Library.
  5. Set Script Author to your name or company name.
  6. Set Script Description to This script contains functions for generating an HTML report for the terrain analysis.

    Copy Text To Clipboard

    This script contains functions for generating an HTML report for the terrain analysis.
  7. Set Library Name to LibTerrainAnalysisGenerateReport.

    Copy Text To Clipboard

    LibTerrainAnalysisGenerateReport
  8. Set File Name to app_service_analyze_terrain_generate_report_util.ssl.

    Copy Text To Clipboard

    app_service_analyze_terrain_generate_report_util.ssl
  9. Click OK or hit ENTER when you are finished.

    The software creates a new script and copies the path to the Windows® clipboard.

Open The Script Library

  1. Return to the text editor and select the option to open a file from disk.
  2. Select CTRL + V to paste the script command library file path (into the place in the dialog where you specify the file to open).
  3. Open the file.

    The script appears in the text editor.

    ///////////////////////////////////////////////////////////////////////////////
    //
    // $author           Scenomics LLC
    // $description      This script contains functions for GPU image processing.
    //
    // Copyright 2023 Scenomics LLC. All Rights Reserved.
    //
    ///////////////////////////////////////////////////////////////////////////////
    
    library LibTerrainAnalysisGenerateReport;
    
    import library "app_service_assert_util.ssl";
    import library "app_service_console_util.ssl";
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void MyFunction()
    {
    }
    

Implement Functions

  1. Find the script imports.

    Instead of copying each function in, one-by-one, in this exercise we'll copy in the entire script source code and then review the functions in the next section.

    import library "app_service_assert_util.ssl";
    import library "app_service_console_util.ssl";
    
  2. Replace everything in the script from the imports to the bottom of the script file, including the function MyFunction, with the following:

    (Do not replace the header at the top of the script and do not replace the library declaration!)

    Copy Text To Clipboard

    import library "app_service_assert_util.ssl";
    import library "app_service_console_util.ssl";
    import library "app_service_main_util.ssl";
    import library "type_file_path_algorithms.ssl";
    import library "type_float32_array_util.ssl";
    import library "type_image_channel_extract_util.ssl";
    import library "type_int32_array_util.ssl";
    import library "type_service_enumeration_util.ssl";
    import library "type_str_list_util.ssl";
    
    import library "app_service_workload_analyze_terrain_classes.ssl";
    import library "app_service_analyze_terrain_cpu_image_processing_util.ssl";
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function string CreateMapLinkPath( BuildInputs p_oInputs, FilePath p_oReportPath, string p_sMapPath )
    {
       auto FilePath a_oLinkMapPath = new FilePath( p_oInputs.m_oMapsPath.GetPath() );
       a_oLinkMapPath.AppendPathWithFile( p_sMapPath );
       a_oLinkMapPath.Canonicalize();
       a_oLinkMapPath.MakeRelativeToPath( p_oReportPath.GetPath() );
       a_oLinkMapPath.SetPath( a_oLinkMapPath.Str.Replace( "\\", "/" ) );
       return a_oLinkMapPath.GetPath();
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function string CreateMapLink( FilePath p_oPath )
    {
       string a_sFullLinkTitle = "title=\"Opens the full-size image in a new tab or browser window.\" target=\"_blank\"";
       return "(<a href=\"" + p_oPath.GetPath() + "\" " + a_sFullLinkTitle + ">Full Size Image</a>) ";
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void PrintUserGuideDisclaimer( StrList p_slReport )
    {
       string a_sDisclaimer = "This report produces statistical analysis meant for avalanche professionals only " +
          "and may contain serious errors.";
       string a_sParksLink = "https://www.pc.gc.ca/en/pn-np/mtn/securiteenmontagne-mountainsafety/avalanche/echelle-ratings";
       string a_sParksInfo = "Please see <a href=\"" + a_sParksLink + "\" target=\"_blank\" \">Avalanche Terrain Exposure Scale &#8212; Technical Model ( v.1-04 )</a> at the ParksCanada web site.";
       string a_sTitle = "Avalanche Terrain Exposure Scale Analysis";
    
       string a_sElevationScoreInfo = "<b>Elevation</b> scores provide information about the minimum " +
          "and maximum elevations in a particular area. In general, avalanche exposure rises with elevation " +
          "because tree-cover becomes more sparse, and colder temperatures can prolong instability. Terrain " +
          "with large ranges in elevation also supports larger avalanche paths, which are an important criteria " +
          "to consider.";
    
       string a_sAnyTerrainScoreInfo = "<b>Avalanche terrain</b> scores provide information about the amount " +
          "of terrain over 35%, which is commonly considered to be terrain where avalanches will start readily. " +
          "It goes without saying that avalanches can be triggered remotely from low angle terrain, but for the " +
          "purposes of this analysis, we are interested in areas where avalanches are more likely to start.";
    
       string a_sSurfaceAreaScoreInfo = "<b>Surface area</b> scores compare the surface area if the " +
          "region were flat with the surface area after displacement by elevation values. " +
          "As the surface area increases, the terrain generally becomes steeper and can hold " +
          "more snow. In many cases, the overall amount of convolution also increases along with " +
          "surface area. This can indicate the presence of rollovers and depressions that increase " +
          "the likelihood of a deep burial.";
    
       string a_sOpennessAreaScoreInfo = "<b>Topographic openness</b> scores reflect the amount of " +
          "openness to the sky found at each point in the terrain, as well openness in general " +
          "for sub-regions of the terrain. Sub-regions with low openness are often terrain traps where " +
          "avalanches form deep deposits, or areas where line of sight may be limited due to " +
          "obstruction by local terrain features. Broad reductions in openness generally " +
          "indicate that there are significant limitations to line of sight at various spatial scales.";
    
       p_slReport.Add( "            <h1>Instructions</h1>" );
       p_slReport.Add( "            <p>" + a_sDisclaimer + " " + a_sParksInfo + "</p>" );
       p_slReport.Add( "            <p>" + a_sElevationScoreInfo + "</p>" );
       p_slReport.Add( "            <p>" + a_sAnyTerrainScoreInfo + "</p>" );
       p_slReport.Add( "            <p>" + a_sSurfaceAreaScoreInfo + "</p>" );
       p_slReport.Add( "            <p>" + a_sOpennessAreaScoreInfo + "</p>" );
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void UnpackImagery(
    
       BuildInputs p_oInputs,
       BuildOutputs p_oOutputs,
       FilePath p_oBasePath
    
       )
    {
       auto FilePath a_oImagesPath = new FilePath( p_oInputs.m_oImagesPath.GetPath() );
    
       // Delete the existing images.
       // Otherwise we will have leftovers
       // if we change from building 8x8 to
       // 4x4, or any time we reduce the
       // area we're analysing if we
       // have already analyzed something.
       auto StrList a_slImageFiles;
       FileSystemTree.FindFiles( a_oImagesPath, a_slImageFiles, "*.*", false );
       //LibStrList.Out( a_slImageFiles );
       LibFilePathAlgorithms.DeleteFiles( a_slImageFiles );
    
       int a_nElevationIdx = 0;
       int a_nAspectIdx = 1;
       int a_nSlopeIdx = 2;
       int a_nOpenIdx = 3;
       int a_nAreaIdx = 4;
       int a_nSumIdx = 5;
    
       Image a_oMap = (Image)p_oOutputs.m_apMapImages.GetFirst();
    
       Image a_oElevationImage =
          (Image)p_oOutputs.m_apArrayImages.Get( a_nElevationIdx );
    
       LibAppOctopusWorkloadTerrainCpuImage.UnpackImages(
          a_oElevationImage,
          a_oImagesPath,
          p_oInputs.m_oLens,
          "elevation",
          p_oOutputs.m_apElevationImages,
          p_oOutputs.m_slElevationPaths,
          p_oInputs.m_slMessages );
    
       Image a_oSlopeImage =
          (Image)p_oOutputs.m_apArrayImages.Get( a_nSlopeIdx );
    
       LibAppOctopusWorkloadTerrainCpuImage.UnpackImages(
          a_oSlopeImage,
          a_oImagesPath,
          p_oInputs.m_oLens,
          "slope_angle",
          p_oOutputs.m_apSlopeImages,
          p_oOutputs.m_slSlopePaths,
          p_oInputs.m_slMessages );
    
       Image a_oOpenImage =
          (Image)p_oOutputs.m_apArrayImages.Get( a_nOpenIdx );
    
       LibAppOctopusWorkloadTerrainCpuImage.UnpackImages(
          a_oOpenImage,
          a_oImagesPath,
          p_oInputs.m_oLens,
          "topographic_openness",
          p_oOutputs.m_apOpenImages,
          p_oOutputs.m_slOpenPaths,
          p_oInputs.m_slMessages );
    
       Image a_oAspectImage =
          (Image)p_oOutputs.m_apArrayImages.Get( a_nAspectIdx );
    
       LibAppOctopusWorkloadTerrainCpuImage.UnpackImages(
          a_oAspectImage,
          a_oImagesPath,
          p_oInputs.m_oLens,
          "aspect",
          p_oOutputs.m_apAspectImages,
          p_oOutputs.m_slAspectPaths,
          p_oInputs.m_slMessages );
    
       Image a_oAreaImage =
          (Image)p_oOutputs.m_apArrayImages.Get( a_nAreaIdx );
    
       LibAppOctopusWorkloadTerrainCpuImage.UnpackImages(
          a_oAreaImage,
          a_oImagesPath,
          p_oInputs.m_oLens,
          "area",
          p_oOutputs.m_apAreaImages,
          p_oOutputs.m_slAreaPaths,
          p_oInputs.m_slMessages );
    
       Image a_oAreaImageFloat =
          (Image)p_oOutputs.m_oSurfaceTiles;
    
       LibAppOctopusWorkloadTerrainCpuImage.UnpackImages(
          a_oAreaImageFloat,
          a_oImagesPath,
          p_oInputs.m_oLens,
          "area_float",
          p_oOutputs.m_apAreaImagesFloat,
          p_oOutputs.m_slAreaPathsFloat,
          p_oInputs.m_slMessages );
    
       Image a_oSumImage =
          (Image)p_oOutputs.m_apArrayImages.Get( a_nSumIdx );
    
       LibAppOctopusWorkloadTerrainCpuImage.UnpackImages(
          a_oSumImage,
          a_oImagesPath,
          p_oInputs.m_oLens,
          "sum",
          p_oOutputs.m_apSumImages,
          p_oOutputs.m_slSumPaths,
          p_oInputs.m_slMessages );
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void GenerateTileReport(
    
       BuildInputs p_oInputs,
       BuildOutputs p_oOutputs,
       FilePath p_oReportPath,
       StrList p_slReport
    
       )
    {
       auto FilePath a_oRelative = new FilePath(
          p_oReportPath.GetPath() );
       a_oRelative.AppendPath( "Reports" );
    
       auto FilePath a_oCssPath = new FilePath(
          LibAppServiceMain.GetTemplatesPath() );
       a_oCssPath.AppendPath( "avalanche" );
       a_oCssPath.AppendPath( "css" );
       a_oCssPath.AppendPath( "analysis.css" );
       a_oCssPath.Canonicalize();
       a_oCssPath.MakeRelativeToPath( a_oRelative.GetPath() );
       string a_sStyleLink = a_oCssPath.GetPath();
    
       Int32Vector a_viLens = p_oInputs.m_oLens.Value;
    
       int a_nStartX = a_viLens.Value.X;
       int a_nStartY = a_viLens.Value.Y;
       int a_nBaseX = 0;
       int a_nBaseY = 0;
       int a_nCounter = 0;
       int a_nElevIndex = 0;
       int a_nChunkDimX = p_oInputs.m_oGdalImageInfo.GetChunkDimX();
       int a_nChunkDimY = p_oInputs.m_oGdalImageInfo.GetChunkDimY();
       int a_nChunkPixelCount = a_nChunkDimX * a_nChunkDimY;
    
       p_oInputs.m_slMessages.AddBlank();
    
       auto Uint8ArrayAlgorithms a_auAlgorithms;
       auto Float32ArrayAlgorithms a_afAlgorithms;
    
       for( int x = a_nStartX; x < a_nStartX + a_viLens.Value.Z; ++x )
       {
          for( int y = a_nStartY; y < a_nStartY + a_viLens.Value.W; ++y )
          {
             string a_sTileCoords = a_nBaseX + "x" + a_nBaseY;
             string a_sReportCoords = a_nBaseX + "x" + a_nBaseY;
             //Console.Out( x + " x " + y );
    
             //////////////////////////////////////////////////
             // Create a report for each tile.
             //////////////////////////////////////////////////
    
             p_slReport.AddBlank();
             p_slReport.Add( "         <div id=\"" + a_sTileCoords + "\">" );
             p_slReport.Add( "            <h1>Report &#8212; " + a_sReportCoords + "</h1>" );
    
             // Insert an <img> element for the relevant tiles.
             auto FilePath a_oElevationPath = new FilePath( p_oOutputs.m_slElevationPaths.Get( a_nCounter ) );
             p_slReport.Add( "            <img src=\"images/" + a_oElevationPath.GetFileName() + "\" />" );
    
             auto FilePath a_oOpenPath = new FilePath( p_oOutputs.m_slOpenPaths.Get( a_nCounter ) );
             p_slReport.Add( "            <img src=\"images/" + a_oOpenPath.GetFileName() + "\" />" );
    
             auto FilePath a_oSlopePath = new FilePath( p_oOutputs.m_slSlopePaths.Get( a_nCounter ) );
             p_slReport.Add( "            <img src=\"images/" + a_oSlopePath.GetFileName() + "\" />" );
    
             p_slReport.Add( "            <table>" );
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td><b>Category</b></td>" );
             p_slReport.Add( "                  <td><b>Score</b></td>" );
             p_slReport.Add( "               </tr>" );
    
             int a_nRangeX = p_oInputs.m_dChunkRangeX; // This assignment casts to int.
             int a_nRangeY = p_oInputs.m_dChunkRangeY; // This assignment casts to int.
    
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Dimensions</td>" );
             p_slReport.Add( "                  <td>" + a_nRangeX + " metres x " + a_nRangeY + " metres</td>" );
             p_slReport.Add( "               </tr>" );
    
             //////////////////////////////////////////////////
             // Present elevation statistics.
             //////////////////////////////////////////////////
    
             int a_nMinEl = p_oInputs.m_afMinMaxElevation.Get( a_nElevIndex );
             int a_nMaxEl = p_oInputs.m_afMinMaxElevation.Get( a_nElevIndex + 1 );
    
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Minimum Elevation</td>" );
             p_slReport.Add( "                  <td>" + a_nMinEl + " metres ASL" + "</td>" );
             p_slReport.Add( "               </tr>" );
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Maximum Elevation</td>" );
             p_slReport.Add( "                  <td>" + a_nMaxEl + " metres ASL" + "</td>" );
             p_slReport.Add( "               </tr>" );
    
             //////////////////////////////////////////////////
             // Present avalanche terrain % statistics.
             //////////////////////////////////////////////////
    
             // Avalanche terrain greater than 35% is
             // marked with 255 in the red channel of
             // the Image.
             auto Image a_oSlopeTile;
             a_oSlopeTile.OpenFile( a_oSlopePath.GetPath() );
    
             auto Image a_oSlopeChannel;
             LibImageChannelExtract.Extract( a_oSlopeTile, 2, a_oSlopeChannel );
    
             auto Uint8ArrayView slope_view = a_oSlopeChannel.GetUint8View();
             uint a_uSlopeMax = a_nChunkPixelCount * NumericLimits.color_max(); // 100% avalanche terrain.
             uint a_uSlopeVal = a_auAlgorithms.Accumulate( slope_view.First, slope_view.Last );
    
             double a_dAvyTerrainPercent = ((double)a_uSlopeVal) / ((double)a_uSlopeMax);
             int a_nAnyTerrainPercent = a_dAvyTerrainPercent * 100.0;
    
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Terrain Over 35 Degrees Steepness</td>" );
             p_slReport.Add( "                  <td>" + a_nAnyTerrainPercent + "% Avalanche Terrain</td>" );
             p_slReport.Add( "               </tr>" );
    
             //////////////////////////////////////////////////
             // Present surface area statistics.
             //////////////////////////////////////////////////
    
             auto FilePath a_oAreaPath = new FilePath(
                p_oOutputs.m_slAreaPathsFloat.GetAt( a_nCounter ) );
    
             //Console.Out( a_oAreaPath.GetPath() );
    
             auto Image a_oAreaTile;
             a_oAreaTile.OpenFile( a_oAreaPath.GetPath() );
             auto Float32ArrayView area_view = a_oAreaTile.GetFloat32View();
    
             // Cast to int to remove decimal. Sub-decimal precision doesn't matter.
             uint a_uFlatSum = p_oInputs.m_dChunkRangeX * p_oInputs.m_dChunkRangeY;
             uint a_uConvSum = a_afAlgorithms.Accumulate( area_view.First, area_view.Last );
    
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Surface Area (If Flat)</td>" );
             p_slReport.Add( "                  <td>" + a_uFlatSum + " square metres</td>" );
             p_slReport.Add( "               </tr>" );
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Surface Area (Actual)</td>" );
             p_slReport.Add( "                  <td>" + a_uConvSum + " square metres</td>" );
             p_slReport.Add( "               </tr>" );
    
             //////////////////////////////////////////////////
             // Present openness statistics.
             //////////////////////////////////////////////////
    
             auto Image a_oOpenTile;
             a_oOpenTile.OpenFile( a_oOpenPath.GetPath() );
    
             // Max openness would be "pure white" for every pixel.
             uint a_uFlatOpen = a_nChunkPixelCount * NumericLimits.color_max();
    
             // All the channels in the openness .PNG file
             // contain the same data (except the alpha channel).
             // To construct the score, we want to sum the value
             // of the pixels in the first channel. This is just
             // a proportional score so we only need to consider
             // these values in a general way.
             auto Image a_oDst;
             LibImageChannelExtract.Extract( a_oOpenTile, 0, a_oDst );
             auto Uint8ArrayView open_view = a_oDst.GetUint8View();
             uint a_uConvOpen = a_auAlgorithms.Accumulate( open_view.First, open_view.Last );
             double a_dOpenPercent = ((double)a_uConvOpen) / ((double)a_uFlatOpen);
             int a_nOpenPercent = a_dOpenPercent * 100.0;
             int a_nReducedPercent = 100 - a_nOpenPercent;
    
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Raw Openness If Flat</td>" );
             p_slReport.Add( "                  <td>" + a_uFlatOpen + "</td>" );
             p_slReport.Add( "               </tr>" );
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Raw Openness</td>" );
             p_slReport.Add( "                  <td>" + a_uConvOpen + "</td>" );
             p_slReport.Add( "               </tr>" );
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Openness</td>" );
             p_slReport.Add( "                  <td>" + a_nOpenPercent + "%</td>" );
             p_slReport.Add( "               </tr>" );
             p_slReport.Add( "               <tr>" );
             p_slReport.Add( "                  <td>Relative Reduction In Openness</td>" );
             p_slReport.Add( "                  <td>" + a_nReducedPercent + "%</td>" );
             p_slReport.Add( "               </tr>" );
    
             // Finish HTML.
             p_slReport.Add( "            </table>" );
             p_slReport.Add( "         </div>" );
    
             //////////////////////////////////////////////////
             // Update counters and loop control variables.
             //////////////////////////////////////////////////
    
             a_nElevIndex += 2;
    
             ++a_nCounter;
             ++a_nBaseY;
    
             if( a_nBaseY == a_viLens.Value.W )
             {
                a_nBaseY = 0;
             }
          }
    
          ++a_nBaseX;
       }
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void GenerateReport( BuildInputs p_oInputs, BuildOutputs p_oOutputs )
    {
       auto FilePath a_oBasePath = new FilePath( p_oInputs.m_oModel.Filename );
       string a_sReportTitle = a_oBasePath.GetFileNameNoExtension();
       a_oBasePath.RemoveFileName();
       a_oBasePath.AppendPath( p_oInputs.m_sBuildFolder );
       a_oBasePath.AppendPath( "Analysis" );
    
       UnpackImagery( p_oInputs, p_oOutputs, a_oBasePath );
    
       auto FilePath a_oReportPath = new FilePath( a_oBasePath.GetPath() );
       a_oReportPath.RemoveFileName();
       a_oReportPath.AppendPath( "Analysis" );
       a_oReportPath.AppendPathWithFile( "report.htm" );
    
       auto FilePath a_oCssPath = new FilePath(
          LibAppServiceMain.GetTemplatesPath() );
       a_oCssPath.AppendPath( "avalanche" );
       a_oCssPath.AppendPath( "css" );
       a_oCssPath.AppendPath( "analysis.css" );
       a_oCssPath.Canonicalize();
       a_oCssPath.MakeRelativeToPath( a_oReportPath.GetPath() );
       a_oCssPath.SetPath( a_oCssPath.Str.Replace( "\\", "/" ) );
       string a_sStyleLink = a_oCssPath.GetPath();
    
       auto FilePath a_oScriptPath = new FilePath(
          LibAppServiceMain.GetTemplatesPath() );
       a_oScriptPath.AppendPath( "avalanche" );
       a_oScriptPath.AppendPath( "js" );
       a_oScriptPath.AppendPath( "transform_click_to_report.js" );
       a_oScriptPath.Canonicalize();
       a_oScriptPath.MakeRelativeToPath( a_oReportPath.GetPath() );
       a_oScriptPath.SetPath( a_oScriptPath.Str.Replace( "\\", "/" ) );
       string a_sScriptLink = a_oScriptPath.GetPath();
    
       auto FilePath a_oBaseMapPath = new FilePath(
          CreateMapLinkPath( p_oInputs, a_oReportPath, p_oInputs.m_sElevationMapFilePath ) );
    
       string a_sBaseMap = "<img class=\"base_map\" id=\"overview\" src=\"" +
          a_oBaseMapPath.GetPath() + "\"" + " onclick=\"show_report(); return false;\" />";
       string a_sFullSizeBaseMapLink = CreateMapLink( a_oBaseMapPath );
    
       auto FilePath a_oAspectMapPath = new FilePath(
          CreateMapLinkPath( p_oInputs, a_oReportPath, p_oInputs.m_sAspectMapFilePath ) );
       string a_sFullSizeAspectMapLink = CreateMapLink( a_oAspectMapPath );
    
       auto FilePath a_oSlopeMapPath = new FilePath(
          CreateMapLinkPath( p_oInputs, a_oReportPath, p_oInputs.m_sSlopeMapFilePath ) );
       string a_sFullSizeSlopeMapLink = CreateMapLink( a_oSlopeMapPath );
    
       auto FilePath a_oOpennessMapPath = new FilePath(
          CreateMapLinkPath( p_oInputs, a_oReportPath, p_oInputs.m_sOpennessMapFilePath ) );
       string a_sFullSizeOpennessMapLink = CreateMapLink( a_oOpennessMapPath );
    
       auto StrList a_slReport;
       a_slReport.Add( "<!DOCTYPE html>" );
       a_slReport.Add( "<html lang=\"en\">" );
       a_slReport.Add( "   <head>" );
       a_slReport.Add( "      <meta charset=\"utf-8\">" );
       a_slReport.Add( "      <title>Report for: " + a_sReportTitle + "</title>" );
       a_slReport.Add( "      <link href=\"" + a_sStyleLink + "\" rel=\"stylesheet\" type=\"text/css\"/>" );
       a_slReport.Add( "      <script src=\"" + a_sScriptLink + "\"></script>" );
       a_slReport.Add( "   </head>" );
       a_slReport.Add( "<body onload=\"show_report();\">" );
       a_slReport.AddBlank();
       a_slReport.Add( "   <div class=\"report\">" );
    
       // DEBUG Javascript image click -> tile coordinates code.
       //a_slReport.Add( "      <p>X:<span id=\"x\"></span></p>" );
       //a_slReport.Add( "      <p>Y:<span id=\"y\"></span></p>" );
       a_slReport.Add( "      <div id=\"cx\">" + p_oInputs.m_oLens.Z + "</div>" );
       a_slReport.Add( "      <div id=\"cy\">" + p_oInputs.m_oLens.W + "</div>" );
    
       a_slReport.Add( "      <div id=\"nav\">" );
       a_slReport.Add( "         <a href=\"\" title=\"Click here to return to the first page.\">Instructions</a> |" );
       a_slReport.Add( "         <a href=\"#\" title=\"Click here to view elevation map.\" onclick=\"show_elevation(); return false;\">Elevation</a> " + a_sFullSizeBaseMapLink + "|" );
       a_slReport.Add( "         <a href=\"#\" title=\"Click here to view slope aspect map.\" onclick=\"show_aspect(); return false;\">Aspect</a> " + a_sFullSizeAspectMapLink + "|" );
       a_slReport.Add( "         <a href=\"#\" title=\"Click here to view slope angle map.\" onclick=\"show_slope(); return false;\">Slope</a> " + a_sFullSizeSlopeMapLink + "|" );
       a_slReport.Add( "         <a href=\"#\" title=\"Click here to view topographic openness map.\" onclick=\"show_openness(); return false;\">Openness</a> " + a_sFullSizeOpennessMapLink );
       a_slReport.Add( "      </div>" );
       a_slReport.AddBlank();
    
       a_slReport.Add( "      <div class=\"row\">" );
       a_slReport.Add( "         <div class=\"column\" id=\"left\">" );
       a_slReport.Add( "            <a href=\"#\" title=\"Click a tile to view analysis.\">" );
       a_slReport.Add( "               " + a_sBaseMap );
       a_slReport.Add( "            </a>" );
       a_slReport.Add( "         </div>" );
       a_slReport.Add( "         <div class=\"column\" id=\"right\">" );
       PrintUserGuideDisclaimer( a_slReport );
       a_slReport.Add( "         </div>" );
       int a_nInsIdx = a_slReport.GetCount();
       a_slReport.Add( "      </div>" );
    
       a_slReport.AddBlank();
    
       a_slReport.Add( "      <div class=\"analysis_container\">" );
    
       auto StrList a_slAnalysis;
       GenerateTileReport( p_oInputs, p_oOutputs, a_oReportPath, a_slAnalysis );
       a_slReport.InsertAll( a_slAnalysis, a_slReport.GetCount(), false );
    
       a_slReport.Add( "      </div>" );
    
       a_slReport.AddBlank();
    
       a_slReport.AddBlank();
    
       a_slReport.Add( "      <p>Copyright &copy; 2021 Scenomics LLC. All Rights Reserved.</p>" );
       a_slReport.Add( "   </div>" );
       a_slReport.Add( "</body>" );
       a_slReport.Add( "</html>" );
    
       auto TextFile a_oReport;
       LibStrList.WriteToDisk( a_oReport, a_oReportPath, a_slReport );
    
       p_oInputs.m_slMessages.Add( "Generated report at: " + a_oReportPath.GetPath() );
    
       p_oOutputs.m_oReportPath.SetPath( a_oReportPath.GetPath() );
    }
    
  3. Save changes to the script.

Review Functions

Let's review the functions in this library.

Table 1.1. Review Script Contents

Function Description
CreateMapLinkPath This creates a file path from the HTML document to one of the large preview map images. This function reduces copy/paste.
CreateMapLink Returns a string that contains a hyperlink.
PrintUserGuideDisclaimer Returns a string containing a disclaimer and instructions on using the report.
UnpackImagery This function unpacks all the individual <Image> objects from all the array textures produced during the analysis.
GenerateTileReport This function uses each individual <Image> object tile to generate geostatistical information for the analyst who reads the report.
GenerateReport This function generates an simple 'single page application' web site that contains the analysis. This takes the form of a large, clickable map on the left, with individual reports appearing on the right.

Import Reporting Library Into Service Script

  1. Return to the running text editor.
  2. Find the script named app_service_workload_analyze_terrain_util.ssl.
  3. Find the import statements.

    import library "app_service_assert_util.ssl";
    import library "app_service_build_util.ssl";
    import library "app_service_console_util.ssl";
    import library "app_service_main_util.ssl";
    import library "type_file_path_algorithms.ssl";
    import library "type_float32_array_util.ssl";
    import library "type_image_channel_extract_util.ssl";
    import library "type_image_format_util.ssl";
    import library "type_image_util.ssl";
    import library "type_int32_array_util.ssl";
    import library "type_render3d_util.ssl";
    import library "type_service_enumeration_util.ssl";
    import library "type_str_list_util.ssl";
    import library "app_service_workload_analyze_terrain_classes.ssl";
    import library "app_service_analyze_terrain_cpu_image_processing_util.ssl";
    import library "app_service_analyze_terrain_gpu_image_processing_util.ssl";
    
  4. Copy the following import statement and paste it at the end of the existing import statements.

    Copy Text To Clipboard

    import library "app_service_analyze_terrain_generate_report_util.ssl";

Test Code Changes

  1. Return to the running Shader app.
  2. Select File » Run Service... from the main menu.

    The software displays the service selection dialog. Since we're dynamically loading this script, using the Desktop » Refresh Scripts command won't work. We need to invoke the service in order to compile the script.

    This is a picture of the service selection dialog.

    If there are script errors, you'll see the following dialog:

    This is a picture of script error dialog.

    You'll also see script compiler errors in the output window:

    D:\release6\scripts\app_service_analyze_terrain_cpu_image_processing_util.ssl(31) : error: undeclared identifier 'a'
    A runtime error ocurred:
    
    Error:       RUNTIME_STATUS_NULL_OBJECT_POINTER
    Function:    ApplicationRunService
    File:        D:\release6\scripts\app_service_file_menu_scripts.ssl
    Line:        1346
    
    Call Stack:
       ApplicationRunService()  app_service_file_menu_scripts.ssl(1346)
    
    Script execution has been stopped.
    

    If there are errors, return to the start of this tutorial and retrace your steps. Otherwise, continue to the next step.

  3. Double click the entry named Spa.Service.AnalyzeTerrain.
  4. The service runs and then displays the build log in the output window.

    --- <Executing Service 'Spa.Service.AnalyzeTerrain'> ---
    
    Executed service: Spa.Service.AnalyzeTerrain
    

    The service runs correctly.