Welcome to the Harris Geospatial product documentation center. Here you will find reference guides, help documents, and product libraries.


Harris Geospatial / Docs Center / Geospatial Services Framework / GSF Tutorial: WebApp Map Client

Geospatial Services Framework

GSF Tutorial: WebApp Map Client

GSF - Tutorial - WebApp Map Client

This tutorial demonstrates a multi-step workflow for creating a web application to analyze and visualize raster data overlaid on an interactive map. First, a new request handler must be created to support communication between the client and the server. The analytics require having access to an ENVI+IDL development environment and creating a custom ENVITask. A WebApp Map Client is then created to submit jobs requiring this custom task with the help of the custom request handler.

Note: As in other tutorials, the full example code blocks are provided here at the bottom of the page.

Create a WebApp Request Handler


A request handler is designed to support client access to the server. This tutorial sets up a webpage for a browser client to use. The browser then uses AJAX to communicate with the server.

Create a new folder under the server directory named webapp-request-handler. This folder should contain all files created during this tutorial:

Note: This tutorial provides the code for the webapp-request-handler needed for completing the WebApp Map Client tutorial. For an in-depth discussion of creating custom request handlers, see the Custom Request Handler Tutorial. As in other tutorials, the full example code blocks are provided here at the bottom of the page.

package.json

This file provides information about the request handler.

{
  "name": "Webapp-request-handler",
  "version": "1.0.0",
  "description": "Provides a web client for submitting a job to the server.",
  "main": "handler.js",
  "dependencies": {},
  "devDependencies": {},
  "scripts": {}
}

handler.js

This file contains the necessary code for the request handler.

var express = require('express');
/**
 * webappRequestHandler
 * Provides an endpoint for serving up a static HTML page for submitting jobs.
 */
function WebappRequestHandler() {
  this.init = function(app) {
    // Set up endpoint.
    app.use('/webapp/', require('express').static(__dirname + '/html/'));
  };
}
module.exports = WebappRequestHandler;

Configure The Server

The last step is to add the new webapp-request-handler to the list of request handlers. To do this from a command line, start a command prompt in the GSFxx directory and execute the following command:

node updateConfig.js config.json --arrayadd requestHandlers={\"type\":\"./webapp-request-handler\"}

The updateConfig.js script will automatically back up the original config.json file for you.

You may also manually enable the webapp-request-handler by editing the config.json as shown below. It is recommended that you back up your config file before making any changes manually.

  "requestHandlers": [
    ...,
    {
      "type": "./webapp-request-handler"
    }
  ],

Restart the server any time this file is changed so it reflects the new configuration.

Create a Bounding Box Task


Next, in order for the web application to place the raster over the correct map location, it must compute a bounding box. This ENVITask creates that bounding box, specifying the location and the extent of the raster in map coordinates. It takes as input a raster, and it returns the latitude and longitude coordinates of the farthest points within the raster in the four cardinal directions. The creation of this bounding box task requires three primary files: an IDL .pro file, an IDL .sav file, and an ENVI .task file. Save all of these files in the custom_code directory under the ENVI install (ex. linux: /usr/local/harris/envixx/custom_code/, windows: C:\Program Files\Harris\ENVIxx\custom_code).

Create the following files:

Note: As in other tutorials, the full example code blocks are provided here at the bottom of the page.

exampleboundingbox.pro

To create the ENVITask pro code, follow these steps.

  • Launch IDL
  • Copy the provided code into a new file.
  • Save the pro file as exampleboundingbox.pro in the ENVI install custom_code folder.

First, define the procedure. The compile option is recommended to ensure proper handling of arrays and array-indexing.

pro ExampleBoundingBox, INPUT_RASTER=inputRaster, $
                        NORTH=north, $
                        SOUTH=south, $
                        WEST=west, $
                        EAST=east
  compile_opt idl2                      
  ...
end

Check that the spatial reference information passed into the procedure is valid. Then proceed to extract column (sample) and row (line) information from the input raster.

  spatialRef = inputRaster.SpatialRef
  if (~Isa(spatialRef,'ENVIStandardRasterSpatialRef')) then begin
    message,'Input Raster must contain a standard spatial reference.'
  endif
  ns = inputRaster.NColumns
  nl = inputRaster.NRows

Build the file-coordinate bounding box from the input raster's number of lines and samples. Convert first into map coordinates and then into latitude and longitude. The WebMap API requires latitude and longitude coordinates.

  fileBoundingBox = [[0,0],[ns-1,0],[0,nl-1],[ns-1,nl-1]]
  ; Convert to map coordinates 
  spatialRef.ConvertFileToMap, fileBoundingBox[0,*],fileBoundingBox[1,*], $
    mapBoundingBoxX, mapBoundingBoxY
  ; Convert to longitude/latitude coordinates.
  spatialRef.ConvertMapToLonLat, mapBoundingBoxX, mapBoundingBoxY, $
    boundingLon, boundingLat

Finally, separate the information into north, south, east, and west. Be aware that each WebMap API may require different formats for the bounding box. This WebMap API calls for cardinal directions.

  south = Min(boundingLat, MAX=north)
  west = Min(boundingLon, MAX=east)

ExampleBoundingBox.task

To create the ENVITask task file, follow these steps.

  • Copy the below contents into a new file in the IDL editor.
  • Save the task file as ExampleBoundingBox.task in the ENVI install custom_code folder.

First, describe the properties of this task. For more information on creating ENVITasks, see the documentation provided at http://harrisgeospatial.com/docs/.

{
    "name": "ExampleBoundingBox",
    "baseClass": "ENVITaskFromProcedure",
    "routine": "exampleboundingbox",
    "displayName": "Lat/Lon Bounding Box",
    "description": "This task returns the farthest north, south, east, and west points of a raster in latitude/longitude.",
    "version": "5.3",
    "parameters": [
    ...
    ]
}

Next, populate the parameters array with the properties of the input raster and the four cardinal directions.

        {
            "name": "INPUT_RASTER",
            "displayName": "Input Raster",
            "dataType": "ENVIRASTER",
            "direction": "input",
            "parameterType": "required",
            "description": "Specify a raster with a standard projection to retrieve bounding box."
        },
        {
            "name": "NORTH",
            "displayName": "North",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest north point."
        },
        {
            "name": "SOUTH",
            "displayName": "South",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest south point."
        },
        {
            "name": "WEST",
            "displayName": "West",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest west point."
        },
        {
            "name": "EAST",
            "displayName": "East",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest east point."
        }

After completing the creation of exampleboundingbox.pro and exampleboundingbox.task you should verify the task executes successfully in your ENVI+IDL development environment.

First, verify the new ENVITask can load.

IDL>e=ENVI(/headless)
IDL>task=ENVITask('ExampleBoundingBox')

Typically, if the above step fails it is due to the JSON syntax in the task file. A good resource for validating and correcting JSON is http://jsonlint.com.

Next, verify the task can execute the code contained within exampleboundingbox.pro

IDL>file=FilePath('qb_boulder_msi',SUBDIR=['data'],ROOT_DIR=e.Root_Dir)
IDL>raster=e.OpenRaster(file)
IDL>task.Input_Raster = raster
IDL>task.execute
IDL>print, [[task.North,task.West],[task.South,task.East]]

The result printed should be:

       40.010834      -105.23121
       39.984964      -105.19757

exampleboundingbox.sav

By default, in GSF, the IDL/ENVI Service Engines require that IDL/ENVITasks are deployed with save files (.sav).

To generate an IDL save file you want a clean IDL session. Issue the following command at the IDL prompt.

.reset

Compile the exampleboundingbox.pro file in the IDL session. Then issue the save command with the location of your ENVI custom_code directory.

Linux

save, filename='/usr/local/harris/envixx/custom_code/exampleboundingbox.sav', /ROUTINES

Windows

save, filename='C:\Program Files\harris\envixx\custom_code\exampleboundingbox.sav', /ROUTINES

If your server environment has access to an equal number of IDL development licenses as GSF workers, you can update the engine configuration to pass the --compile flag through the "engineArgs" option to use the procedure file (.pro) instead.

To do this from a command line, start a command prompt in the GSFxx directory and execute the following command:

node updateConfig.js config.json --set engines[type:envi-service-engine].engineArgs=--compile

The updateConfig.js script will automatically back up the original config.json file for you.

You may also manually update the envi-service-engine by editing the config.json as shown below. It is recommended that you back up your config file before making any changes manually.

  "engines": [
    ...,
    {
      "type": "envi-service-engine",
      ...
      "engineArgs": "--compile"
    }
  ],

Create a WebApp Map Client


The WebApp Map Client created in this tutorial displays the result of the bounding box task and an ENVI raster analytic task overlayed on an interactive map, providing geographical context for satellite imagery.

Create the WebApp

Create a subfolder under the webapp-request-handler folder and name it html. Place the following files in the html folder.

Note: As in other tutorials, the full example code blocks are provided here at the bottom of the page.

index.html

Place the following code in an index.html file. The server, by default, serves up any file within this directory with the name index.html. The HTML page created from this file allows users to specify a task and its parameters and submit that task to the server.

First, set up the HTML file with the proper formatting.

<html>
<head>
    ...
</head>
<body>
  <!-- user input -->
  <form id="postJobForm" name="postJobForm">
    ...
  </form>
  ...
</body>
</html>

Filling in the head portion of the HTML file describes the support files to be loaded. Two of these files, GSFutils.js and style.css, are created as part of this tutorial.

    <title>GSF - GSF Web Map Example</title>
    <!-- Load GSFutils.js -->
    <script src="GSFutils.js"></script>
    <!-- Load stylesheet -->
    <link rel="stylesheet" href="style.css" />
    <!-- Load JQuery -->
    <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>

Then, the form section can be populated. The fieldset code block generates the fields the WebApp should display for taking user input, which here are Task, Input Raster in JSON format, and Input Parameters in JSON format. Since this is a tutorial, this code also auto-populates the fields with suggested input such as the SpectralIndex task. It describes the input raster type and the desired index. This block also defines the submit button to be added to the WebApp.

    <fieldset>
        <legend>Submit a Job</legend>
        Task:<br>
        <input id="Task" size="60" type="text" value="SpectralIndex"><br>
        <br>
        Input Raster (JSON): <br>
        <input id="inputRaster" size="80" type="text" value="http://localhost:9191/ese/data/qb_boulder_msi"><br>
        Input Parameters (JSON):<br>
        <textarea cols="80" id="InputParameters" name="inputs" rows="9">
{
    "INPUT_RASTER": {
      "FACTORY": "URLRaster",
      "URL":"%INPUT_RASTER%"
    },
    "INDEX": "Normalized Difference Vegetation Index"
}
        </textarea>
        <br>
        <button type="submit">Submit</button>
    </fieldset>

Outside of the form section but still within the body section, define labels for progress messages and map loading. Finally, load the JavaScript Leaflet library and the app.js WebApp logic file created later in this tutorial.

  <!-- This label shows the progress messages from the server -->
  <label id="progress"></label>
  <!-- this div will be where the webmap is loaded. -->
  <div id="map"> </div>
  <!-- load leaflet library -->
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
  <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
  <!-- load our app logic -->
  <script src="app.js"></script>

style.css

This file contains the information for how the map should be displayed and styled.

#map {
  width:100%;
  height: 500px;
}

GSFutils.js

This JavaScript utility makes submitting jobs easier. The first function in this file simply returns the value of an output parameter.

/**
 * Utility to find the value of an output parameter.
 */
function getResultValue(results, param) {
  return results.find(function(r){return r.name===param}).value
}

The next two methods are attached to server events and provide job progress and job completion events.

/**
 * Attach to server events so we can get job progress and completion events.
 */
var events = new EventSource('/events');
events.addEventListener('JobCompleted', function(evt){
  var data = JSON.parse(evt.data);
  $.get('/ese/jobs/'+data.jobId+'/status', function(response){
    waiting[data.jobId].done(response.jobErrorMessage, response.results);
    delete waiting[data.jobId];
  })
});
events.addEventListener('JobProgress', function(evt) {
  var data = JSON.parse(evt.data);
  waiting[data.jobId].progress(data.progress, data.message);
});

This function handles a specific job's asynchronous callbacks and waits until the job is done.

/**
 * A function to get async callbacks for a specific job.
 */
var waiting = {};
function waitForDone(id, progress, cb) {
  waiting[id] = {
    done:cb,
    progress: progress
  };
}

The final function in this utility runs a job and waits for completion.

/**
 * Run a job and wait for it to complete.
 */
function runJob(task, params, progress, done) {
  $.ajax({
      type       : "POST",
      url        : "/ese/services/ENVI/" + task + "/submitJob",
      data       : JSON.stringify(params),
      contentType: "application/json; charset=utf-8",
      dataType   : "json",
      cache      : false, // important to work on IE11
      error      : function(error) {
          alert(JSON.stringify(error));
      },
      success    : function(response) {
        waitForDone(response.jobId, progress, done);
      }
  });
}

app.js

This JavaScript file contains the code for the WebApp. This WebApp uses Leaflet, a JavaScript interactive map library. The latitude and longitude coordinates given for the center of the map display are for a location in Boulder, CO. The input raster used in the bounding box task example is satellite imagery of Boulder, CO over this same area.

/**
 * Setup Leaflet
 */
var map = L.map('map', {
  center: [40.0150, -105.2705],
  zoom: 3
});
/**
 * Add basemap
 */
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

Here, a function invokes the bounding box task and overlays the raster on the map.

/**
 * Add an imageOverlay and zoom to it.
 */
function addImageToMap(imageUrl, boundingBox) {
  L.imageOverlay(imageUrl, boundingBox).addTo(map);
  map.fitBounds(boundingBox);
}

Users often desire progress messages during execution, so this function creates a handler.

/**
 * Generate a progress message handler
 */
 function progressHandler(task) {
  return function(pct, message) {
    $('#progress').text(task + ' ' + pct + '% ' + message);
  }
 }

To ensure the images display and align properly, set the coordinate system.

/**
 * This coordSys should match the projection used in the webmap.
 * Leaflet uses 3857 by default.
 */
var coordSys = {
    FACTORY: "CoordSys",
    COORD_SYS_CODE: 3857
};

The code must also set up the submit button for the WebApp. To do this, get and parse input and task names.

// Attach an on click event to the submit button
$("#postJobForm").submit(function(evt) {
  evt.preventDefault();
  // Set progress so we know it it processing
  $("#progress").val("Running workflow");
  // Get input fields
  var rasterURL = $("#inputRaster").val();
  var inputParams = $("#InputParameters").val();
  // insert raster uri into input
  // This searches for the %INPUT_RASTER% string and replaces it with the specified rasterURL.
  // Then parse it into JSON
  var taskInput = JSON.parse(inputParams.replace('%INPUT_RASTER%', rasterURL));
  // Get the task name
  var taskName = $("#Task").val();
  var imageUrl, boundingBox;

Here, ensure there is a way to track output, and run the specified task.

  // Create a function to keep track of the output for both tasks.
  // This is a simple async parallel handler.
  function done() {
    if (boundingBox && imageUrl) {
      // If we have the image and the bounding box we can add it to the map.
      return addImageToMap(imageUrl, boundingBox);
    }
  }
  // Run the specified task. 
  // While that is running we will also calculate the bounding box of the image.
  runJob(taskName, taskInput, progressHandler(taskName), function(err, results) {
    if(err) {
      return alert('Error: ' + err);
    }
    //Get output Raster
  var taskOutputRaster = getResultValue(results, 'OUTPUT_RASTER');

The output raster generated by the task may not be web-friendly. By reprojecting it onto the map projection, applying a linear stretch, and exporting to PNG, the code produces a much nicer display.

  // Create Web Friendly output from ouput raster.
  // Reproject the image into the webmaps projection.
  // Use a 2% linear stretch to get it to display nicely.
  // Export as a PNG so the browser can display it.
    var reprojectedOutputRaster = {
    FACTORY: 'ReprojectRaster',
    INPUT_RASTER: {
      FACTORY: 'LinearPercentStretchRaster',
      INPUT_RASTER: taskOutputRaster,
      PERCENT: 2
    },
    COORD_SYS: coordSys
  }
    // Create the PNG
    runJob('ExportRasterToPNG', {INPUT_RASTER:reprojectedOutputRaster}, progressHandler('ExportRasterToPNG'), function(err, results) {
      if(err) {
        return alert('Error: ' + err);
      }
      // update the output variable so we know this task is done.
      imageUrl = getResultValue(results, 'OUTPUT_RASTER').url;
      // Call done so that we can update the map if this and the bounding box task are both done.
      done();
    });
  });
  // Prepare the input for the bounding box task.
  var reprojectedRasterInput = {
    FACTORY: 'ReprojectRaster',
    INPUT_RASTER: {
      FACTORY: "URLRaster",
      URL: rasterURL
    },
    COORD_SYS: coordSys
  }

The code here runs the ExampleBoundingBox job and gets the bounding box output coordinates.

  // While the task is running, we will query the server for the images bounding box.
  // This is our custom task that we deployed earlier.
  runJob('ExampleBoundingBox', {INPUT_RASTER:reprojectedRasterInput}, progressHandler('ExampleBoundingBox'), function(err, results) {
    if(err) {
      return alert('Error: ' + err);
    }
    // Get the bounding box values.
    var north = getResultValue(results, 'NORTH');
    var south = getResultValue(results, 'SOUTH');
    var west = getResultValue(results, 'WEST');
    var east = getResultValue(results, 'EAST');
    // Set the output variable so we know this task is complete
    boundingBox = [[north,west],[south,east]];
    // call the done function to allow the image to be displayed if the other task is complete as well.
    done();
  });
});

Run the WebApp

Before running the webapp, remember to configure the server and restart the server if it is running. Open the WebApp in a browser: http://localhost:9191/webapp

Full Example Code Blocks


Note: Do not forget to Configure the Server before Running the WebApp.

Code for WebApp Request Handler

Place the following files in a webapp-request-handler folder in the server directory, and add the provided contents.

package.json

{
  "name": "Webapp-request-handler",
  "version": "1.0.0",
  "description": "Provides a web client for submitting a job to the server.",
  "main": "handler.js",
  "dependencies": {},
  "devDependencies": {},
  "scripts": {}
}

handler.js

var express = require('express');
/**
 * webappRequestHandler
 * Provides an endpoint for serving up a static HTML page for submitting jobs.
 */
function WebappRequestHandler() {
  this.init = function(app) {
    // Set up endpoint.
    app.use('/webapp/', require('express').static(__dirname + '/html/'));
  };
}
module.exports = WebappRequestHandler;

Code for Bounding Box Task

Place all of the following files in the custom_code directory under the ENVI install (ex. C:\Program Files\Harris\ENVIxx\custom_code), and add the provided contents.

exampleboundingbox.pro

pro ExampleBoundingBox, INPUT_RASTER=inputRaster, $
                        NORTH=north, $
                        SOUTH=south, $
                        WEST=west, $
                        EAST=east
  compile_opt idl2                      
  spatialRef = inputRaster.SpatialRef
  if (~Isa(spatialRef,'ENVIStandardRasterSpatialRef')) then begin
    message,'Input Raster must contain a standard spatial reference.'
  endif
  ns = inputRaster.NColumns
  nl = inputRaster.NRows   
  ; Build the file coordinate bounding box from the input raster's number of
  ; lines and samples                     
  fileBoundingBox = [[0,0],[ns-1,0],[0,nl-1],[ns-1,nl-1]]
  ; Convert to map coordinates 
  spatialRef.ConvertFileToMap, fileBoundingBox[0,*],fileBoundingBox[1,*], $
    mapBoundingBoxX, mapBoundingBoxY
  ; Convert to longitude/latitude coordinates.  This is what the
  ; web map APIs require  
  spatialRef.ConvertMapToLonLat, mapBoundingBoxX, mapBoundingBoxY, $
    boundingLon, boundingLat
  ; Seperate the information into north, south, east, and west.  Each
  ; web map API may require different format for the bounding box 
  south = Min(boundingLat, MAX=north)
  west = Min(boundingLon, MAX=east)  
end

ExampleBoundingBox.task

{
    "name": "ExampleBoundingBox",
    "baseClass": "ENVITaskFromProcedure",
    "routine": "exampleboundingbox",
    "displayName": "Lat/Lon Bounding Box",
    "description": "This task returns the farthest north, south, east, and west points of a raster in latitude/longitude.",
    "version": "5.3",
    "parameters": [
        {
            "name": "INPUT_RASTER",
            "displayName": "Input Raster",
            "dataType": "ENVIRASTER",
            "direction": "input",
            "parameterType": "required",
            "description": "Specify a raster with a standard projection to retrieve bounding box."
        },
        {
            "name": "NORTH",
            "displayName": "North",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest north point."
        },
        {
            "name": "SOUTH",
            "displayName": "South",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest south point."
        },
        {
            "name": "WEST",
            "displayName": "West",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest west point."
        },
        {
            "name": "EAST",
            "displayName": "East",
            "dataType": "DOUBLE",
            "direction": "output",
            "parameterType": "required",
            "description": "The farthest east point."
        }
    ]
}

Code for WebApp Map Client

Create an html folder within the webapp-request-handler folder, and place these files here. Add the provided contents.

index.html

<html>
<head>
    <title>GSF - GSF Web Map Example</title>
    <!-- Load GSFutils.js -->
    <script src="GSFutils.js"></script>
    <!-- Load stylesheet -->
    <link rel="stylesheet" href="style.css" />
    <!-- Load JQuery -->
    <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
</head>
<body> 
  <!-- user input -->
  <form id="postJobForm" name="postJobForm">
    <fieldset>
        <legend>Submit a Job</legend>
        Task:<br>
        <input id="Task" size="60" type="text" value="SpectralIndex"><br>
        <br>
        Input Raster (JSON): <br>
        <input id="inputRaster" size="80" type="text" value="http://localhost:9191/ese/data/qb_boulder_msi"><br>
        Input Parameters (JSON):<br>
        <textarea cols="80" id="InputParameters" name="inputs" rows="9">
{
    "INPUT_RASTER": {
      "FACTORY": "URLRaster",
      "URL":"%INPUT_RASTER%"
    },
    "INDEX": "Normalized Difference Vegetation Index"
}
        </textarea>
        <br>
        <button type="submit">Submit</button>
    </fieldset>
  </form>
  <!-- This label shows the progress messages from the server -->
  <label id="progress"></label>
  <!-- this div will be where the webmap is loaded. -->
  <div id="map"> </div>
  <!-- load leaflet library -->
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
  <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
  <!-- load our app logic -->
  <script src="app.js"></script>
</body>
</html>

style.css

#map {
  width:100%;
  height: 500px;
}

GSFutils.js

/**
 * Utility to find the value of an output parameter.
 */
function getResultValue(results, param) {
  return results.find(function(r){return r.name===param}).value
}
/**
 * Attach to server events so we can get job progress and completion events.
 */
var events = new EventSource('/events');
events.addEventListener('JobCompleted', function(evt){
  var data = JSON.parse(evt.data);
  $.get('/ese/jobs/'+data.jobId+'/status', function(response){
    waiting[data.jobId].done(response.jobErrorMessage, response.results);
    delete waiting[data.jobId];
  })
});
events.addEventListener('JobProgress', function(evt) {
  var data = JSON.parse(evt.data);
  waiting[data.jobId].progress(data.progress, data.message);
});
/**
 * A function to get async callbacks for a specific job.
 */
var waiting = {};
function waitForDone(id, progress, cb) {
  waiting[id] = {
    done:cb,
    progress: progress
  };
}
/**
 * Run a job and wait for it to complete.
 */
function runJob(task, params, progress, done) {
  $.ajax({
      type       : "POST",
      url        : "/ese/services/ENVI/" + task + "/submitJob",
      data       : JSON.stringify(params),
      contentType: "application/json; charset=utf-8",
      dataType   : "json",
      cache      : false, // important to work on IE11
      error      : function(error) {
          alert(JSON.stringify(error));
      },
      success    : function(response) {
        waitForDone(response.jobId, progress, done);
      }
  });
}

app.js

/**
 * Setup Leaflet
 */
var map = L.map('map', {
  center: [40.0150, -105.2705],
  zoom: 3
});
/**
 * Add basemap
 */
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
/**
 * Add an imageOverlay and zoom to it.
 */
function addImageToMap(imageUrl, boundingBox) {
  L.imageOverlay(imageUrl, boundingBox).addTo(map);
  map.fitBounds(boundingBox);
}
/**
 * Generate a progress message handler
 */
 function progressHandler(task) {
  return function(pct, message) {
    $('#progress').text(task + ' ' + pct + '% ' + message);
  }
 }
/**
 * This coordSys should match the projection used in the webmap.
 * Leaflet uses 3857 by default.
 */
var coordSys = {
    FACTORY: "CoordSys",
    COORD_SYS_CODE: 3857
};
// Attach an on click event to the submit button
$("#postJobForm").submit(function(evt) {
  evt.preventDefault();
  // Set progress so we know it it processing
  $("#progress").val("Running workflow");
  // Get input fields
  var rasterURL = $("#inputRaster").val();
  var inputParams = $("#InputParameters").val();
  // insert raster uri into input
  // This searches for the %INPUT_RASTER% string and replaces it with the specified rasterURL.
  // Then parse it into JSON
  var taskInput = JSON.parse(inputParams.replace('%INPUT_RASTER%', rasterURL));
  // Get the task name
  var taskName = $("#Task").val();
  var imageUrl, boundingBox;
  // Create a function to keep track of the output for both tasks.
  // This is a simple async parallel handler.
  function done() {
    if (boundingBox && imageUrl) {
      // If we have the image and the bounding box we can add it to the map.
      return addImageToMap(imageUrl, boundingBox);
    }
  }
  // Run the specified task. 
  // While that is running we will also calculate the bounding box of the image.
  runJob(taskName, taskInput, progressHandler(taskName), function(err, results) {
    if(err) {
      return alert('Error: ' + err);
    }
    //Get output Raster
  var taskOutputRaster = getResultValue(results, 'OUTPUT_RASTER');
  // Create Web Friendly output from ouput raster.
  // Reproject the image into the webmaps projection.
  // Use a 2% linear stretch to get it to display nicely.
  // Export as a PNG so the browser can display it.
    var reprojectedOutputRaster = {
    FACTORY: 'ReprojectRaster',
    INPUT_RASTER: {
      FACTORY: 'LinearPercentStretchRaster',
      INPUT_RASTER: taskOutputRaster,
      PERCENT: 2
    },
    COORD_SYS: coordSys
  }
    // Create the PNG
    runJob('ExportRasterToPNG', {INPUT_RASTER:reprojectedOutputRaster}, progressHandler('ExportRasterToPNG'), function(err, results) {
      if(err) {
        return alert('Error: ' + err);
      }
      // update the output variable so we know this task is done.
      imageUrl = getResultValue(results, 'OUTPUT_RASTER').url;
      // Call done so that we can update the map if this and the bounding box task are both done.
      done();
    });
  });
  // Prepare the input for the bounding box task.
  var reprojectedRasterInput = {
    FACTORY: 'ReprojectRaster',
    INPUT_RASTER: {
      FACTORY: "URLRaster",
      URL: rasterURL
    },
    COORD_SYS: coordSys
  }
  // While the task is running, we will query the server for the images bounding box.
  // This is our custom task that we deployed earlier.
  runJob('ExampleBoundingBox', {INPUT_RASTER:reprojectedRasterInput}, progressHandler('ExampleBoundingBox'), function(err, results) {
    if(err) {
      return alert('Error: ' + err);
    }
    // Get the bounding box values.
    var north = getResultValue(results, 'NORTH');
    var south = getResultValue(results, 'SOUTH');
    var west = getResultValue(results, 'WEST');
    var east = getResultValue(results, 'EAST');
    // Set the output variable so we know this task is complete
    boundingBox = [[north,west],[south,east]];
    // call the done function to allow the image to be displayed if the other task is complete as well.
    done();
  });
});



© 2017 Exelis Visual Information Solutions, Inc. |  Legal
My Account    |    Buy    |    Contact Us