Set up your environment

First, you'll set up your environment to develop a 3D globe web app. You'll set up your browser, developer tools, text editor, and project folder.

Set up a browser

You'll need an updated modern web browser to open the documents you'll create throughout the lesson and to render the globe. You can choose from a variety of browsers, including Firefox, Chrome, Safari, or Edge. The steps and example images in this lesson will use Chrome. Other browsers may look slightly different.

  1. If you don't have a modern web browser, go to the Chrome download page and click Download Chrome.

    The ChromeSetup.exe file is downloaded to your computer.

  2. Locate and double-click the ChromeSetup.exe file. Follow the instructions in the installation wizard to install the Chrome browser.

Set up developer tools

The developer tools provided in browsers are essential for building a web app. You'll learn the basics of how to check your developer tools to understand what is happening in your code. Chrome's developer tools are called Chrome DevTools. Other browsers have similar developer tools that you can use.

  1. Open the Chrome browser.
  2. Click the Cutomize and control Google Chrome button, hover over More tools, and click Developer tools.

    Open the Chrome Developer tools.

    Note:

    You can also use the keyboard shortcut Ctrl+Shift+I or press the F12 key on Windows and Linux to open Chrome's developer tools. On a Mac, you can press Cmd+Opt+I.

    The developer tools pane appears.

    Elements tab of the developer tools pane

    The default tab of the developer tools pane is the Elements tab. This tab shows the Document Object Model (the HTML structure of the web page) and the Cascading Style Sheets (CSS) styles associated with each of the elements on the current web page.

  3. Click the Console tab.

    Console tab

    This tab is where error messages and any console prompts appear. Keep this tab open while you develop a web app.

    You can log information from parts of the program to the console from inside your code. Logging information is useful for debugging and allows you to inspect the attributes of the objects you're working with (such as layers, classes, and responses from requests).

    To log information to the console, you'll add a line to your code with the console.log() method, passing as a parameter the elements you want to see displayed in the console. After you finish debugging, you can remove the console logging lines.

Set up a text editor

The main tool you'll need to build the app is a text editor. The default editor from your operating system (Notepad on Windows, TextEdit on Mac, or your editor of choice in Linux) is acceptable.

Note:

You'll need a text editor, not word processing software. Word processing software usually introduces hidden formatting into your text, which will make it fail to run correctly as code.

You can also use a free downloadable code editor, which gives you hints while writing the code, highlights syntax, automatically completes text, and allows for more customization through its plugin ecosystem. Two such editors are Visual Studio Code and Atom.

This lesson's steps and example images use the Visual Studio Code editor. If you use a simple text editor or a different editor, the code may not be displayed the same way.

  1. Go to the Visual Studio Code download page and click the button to download the installer for your operating system.
  2. Run the installer and follow its instructions to install Visual Studio Code.

Set up the project folder and files

You'll write code in three files to build the globe and display it in the browser. Before you begin writing the code, you'll set up these files and the project folder.

  1. Create a folder on your desktop. Name the folder half_earth_globe.
  2. Open Visual Studio Code (or your preferred text editor). Click File and choose Create File.

    A new file is created.

  3. Click File and choose Save As. Save the file in the half_earth_globe folder with the name index.html.

    Later, when you want to view your app, you'll open this file. Once you've added some code and saved the file, you can double-click it and it will open in your default browser.

  4. Create a file. Save it in the same folder with the name main.css.

    This file will contain CSS code that determines the styling and appearance of the app.

  5. Create a file. Save it in the same folder with the name main.js.

    This file will contain JavaScript code for the app. Your folder now contains three files.

    Three files in folder

    Whenever you update any of these files and save the changes, you can reload or refresh the index.html file in your web browser to view the changes.

You've downloaded and opened the tools you need to build the app. You've also created the folder structure and files that you'll edit. Next, you'll begin building the globe app.


Build a 3D globe app

Now that you've set up your development environment and project structure, you'll begin building the globe and adding data to it. You'll use ArcGIS API for JavaScript to render the data in your browser.

Application framework

First, you'll write some basic framework code in your index.html file. You won't be rendering anything on the screen yet, but after this step, you'll be ready to interact with ArcGIS API for JavaScript.

  1. In your text editor, open the index.html file, if necessary.

    This file is an HTML file. HTML stands for HyperText Markup Language, a language that specifies web page structure. Browsers read HTML files and display them as web pages. HTML files usually contain text enclosed in tags. The tags are enclosed in angle brackets (greater than and less than signs).

    You'll copy and paste the code from the lesson into your text editor to create the structure of the web page and add a couple of useful elements to the head section of the page.

  2. Copy and paste the following code into the index.html file:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Half Earth Globe</title>
        <meta charset="utf-8">
      </head>
      <body>
      </body>
    </html>

    The first line, <!DOCTYPE html>, tells the browser that this is an HTML version 5 document. The next line, <html>, is called the root tag in the document. It starts a set of tags that the browser will read, finishing with the </html> tag. Many, though not all, HTML tag structures start with a start tag and end with an end tag. End tags include the slash (/) character before the tag name.

    The next line, <head>, is the head tag. The page is divided into head and body sections. The head is the first section of the page. The contents of the head are not meant to be displayed on the web page, but they contain information that is used by the browser when displaying the page. The types of tags that can be added to the head include links to resources and metadata. Inside the head section, the title tag tells the browser the title to display on the browser tab.

    The meta tag contains an attribute called charset. This declares the character encoding of the document to be utf-8, a character set that contains almost all human language characters. The next line is the end tag for the head section. This line indicates to the browser that the head section is done.

    The next two lines define the body of the document. Currently, there is nothing within this section, but you'll add code to it later. The last line is the end tag for the HTML document.

    Now that you've created this basic structure for the page, you'll add the references to access ArcGIS API for JavaScript code to the head of the document. The two assets that you need are the styles (CSS) and JavaScript code (JS) provided by the API. You'll fetch them from the web by adding a pointer to those resources in the head section of the HTML document.

  3. Click after <meta charset="utf-8"> and press Enter to add a line.
  4. Copy and paste the following code on the new line:

    <link rel="stylesheet" href="https://js.arcgis.com/4.17/esri/themes/dark-blue/main.css">
    <script src="https://js.arcgis.com/4.17/"></script>

    It is best practice to keep indentation consistent to improve the legibility of the code (it is also required by some languages, such as Python). These lines all belong together in a block and should be kept indented to the same level. If you copy and paste these two lines together, the second line may not be indented to the same level as the first. You can either copy and paste line by line, pressing Enter after each line, or you can add spaces (avoid using tabs) to indent the second line to match the first.

    This code tells the browser that you'll use style information from the main.css file from ArcGIS API for JavaScript. It also tells the browser to use the dark-blue theme and the JavaScript code from the API. These resources are hosted online and the lines of code point to the web addresses for them.

    Next, you'll add lines to tell the browser that your app also has some of its own local styling and JavaScript code.

  5. Add a blank line after the code you just pasted. Copy and paste the following code and, if necessary, indent the second line to match the first:

    <link rel="stylesheet" href="main.css">
    <script src="main.js"></script>

    This code tells the browser that, in addition to those CSS and JS web resources that you added in the previous step, the app will also use style information from the main.css file and JavaScript code from the main.js file, located in the same folder as the index.html file. You'll add code to them as well.

  6. Click after the <body> tag and add a new line.
  7. Copy and paste the following code on the new line:

    <div id="sceneContainer"></div>

    This line creates a content division in the web page, called a div element, that will hold the globe. The element has an ID attribute that is set to sceneContainer. This attribute allows the element to be referred to by this name in both the CSS and the JS files. You'll use this element to display the scene with the globe.

  8. Save the file.

    The index.html file in Visual Studio Code

    The HTML tags and attributes are color coded in Visual Studio Code. Paths and file names are underlined. Another code or text editor will show the code differently.

    The complete code is listed in the following code block:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Half Earth</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="https://js.arcgis.com/4.17/esri/themes/dark-blue/main.css">
        <link rel="stylesheet" href="main.css">
        <script src="https://js.arcgis.com/4.17/"></script>
        <script src="main.js"></script>
      </head>
      <body>
        <div id="sceneContainer"></div>
      </body>
    </html>

    To see anything on the screen, you'll add basic style information to the CSS document.

  9. In your text editor, open the main.css file.
  10. Copy and paste the following code into the file:

    body {
      padding: 0;
      margin: 0;
    }
    
    #sceneContainer {
      height: 100vh;
      width: 100vw;
      background: rgb(0,0,0);
    }

    The body { } section resets the padding and margin to 0 to override browser default styles.

    The #sceneContainer { } section sets style information for the sceneContainer div element you defined in the HTML file. It sets the height and width of the sceneContainer element to 100 percent of the viewport height and width. This setting means that the globe will display occupying the full window, regardless of the screen size (from a phone screen to a giant wall-mounted screen). It also sets the color of the background of the element to black.

    The name of the element is prefixed with a # character because the div element is a uniquely named element and this styling applies only to it.

  11. Save the main.css file.

    The main.css file in Visual Studio Code

    Note:

    Another code editor or a text editor will show the code differently than Visual Studio Code. For example, on line 9, Visual Studio Code draws a black color patch beside the RGB value. This color patch will not appear in other text editors.

  12. In your file system, open the half_earth_globe folder and double-click the index.html file.

    Double-click the HTML file

    The index.html file opens in your browser. It shows a black screen. This black screen is the sceneContainer div element being displayed.

  13. If the developer tools pane is not open, open it. Refresh the page.

    Web app showing black background of the sceneContainer div element and developer console

    If the page does not display similar to the example image or you get errors in the console, compare the code in your files with the code in the lesson.

Draw the globe

Now that the index.html file displays the sceneContainer element, it's time to work on the JavaScript part of the app. It's helpful to keep the API documentation open in another tab, so you can read about the modules you'll use to build the globe. The two main building blocks you'll use to develop this 3D globe scene with ArcGIS API for JavaScript are the Map and SceneView classes.

With the documentation, you can understand the properties exposed by these classes and which methods are available for instances of these classes.

  1. In your text editor, open the main.js file.
  2. Copy and paste the following code into the file:

    require([
    "esri/Map",
    "esri/views/SceneView",
    "dojo/domReady!" // will not be called until DOM is ready
    ], function (
    Map,
    SceneView
    ) {
      // These are comments. The code for the app will live inside these curly brackets
      // You will have access here to all the modules required and exposed on the
      // callback function. Right now, these are Map and SceneView
    });

    On the first line, the require method allows you to load modules from the JavaScript library. It is followed by an open parenthesis and an open square bracket. Inside the square brackets, the Map and SceneView modules are listed as the esri modules to load. The esri/Map line loads code specific to creating a map, while the esri/views/SceneView line loads code that allows for viewing the map in 3D. Including these lines in the require section makes them available in other parts of the application. The domReady! line instructs the browser to wait on execution of the code until the entire page has been loaded.

    The next three lines are a callback function that call the Map and SceneView classes once all of the code on the page has loaded. The last section of the code currently contains some comments inside a set of curly brackets. This is where the rest of the app will be built.

    The next step is to create a Map element. This element will store all the layers that will be displayed in the globe.

  3. Highlight the three lines of comments and delete them. Copy and paste the following code in place of the comments:

    const map = new Map({
      basemap: 'satellite'
    });

    These lines create an instance of the map class and set the basemap for the map to the default Esri satellite basemap. Next, you'll create a SceneView element. This element provides a 3D scene that uses the browser's Web Graphics Library (webGL) capabilities to render a globe on the HTML page.

  4. Add a line after line 11 and copy and paste the following code:

    const view = new SceneView({
      map: map,
      container: "sceneContainer",
    });

    These lines create an instance of the SceneView class and set the map that the scene will display to the map you just defined. It also specifies that this SceneView class is placed inside the sceneContainer division element of the HTML page.

  5. Adjust indentations, if necessary. Save the file.

    main.js file in Visual Studio Code

    The following codeblock contains the completed code:

    require([
    "esri/Map",
    "esri/views/SceneView",
    "dojo/domReady!" // will not be called until DOM is ready
    ], function (
    Map,
    SceneView
    ) {
      
      const map = new Map({
        basemap: 'satellite'
      });
    
      const view = new SceneView({
        map: map,
        container: "sceneContainer",
      });
    
    });

  6. In your web browser, reload or refresh the index.html file.

    The globe appears on the page.

    The globe displayed in the browser

Style the globe

Now that you have the globe displayed on your screen, you'll update some settings to modify the globe to mimic the Half-Earth Project map.

  1. In the main.js file, after the container:"sceneContainer", line, add a line. Copy and paste the following code:

    environment: {
        atmosphereEnabled: false,
        background: {
          type: "color",
          color: [0,10,16]
        }
      },

    These lines of code turn off the atmosphere effect that gave a halo to the globe. In its place, you provide a solid background color using RGB notation. Next, you'll simplify the user interface of the globe by removing some of the default controls.

  2. After the second closing curly bracket and comma that follows the color: [0,10,16] line, add a new line and copy and paste the following code:

    ui: {
        components: ["zoom"]
       }

    These lines remove the default controls except for the zoom control buttons.

  3. Save the file.

    The main.js file with atmosphere and zoom control changes

    The following codeblock contains the complete code:

    require([
      "esri/Map",
      "esri/views/SceneView",
      "dojo/domReady!" // will not be called until DOM is ready
      ], function (
      Map,
      SceneView
      ) {
        
        const map = new Map({
          basemap: 'satellite'
        });
      
        const view = new SceneView({
          map: map,
          container: "sceneContainer",
          environment: {
            atmosphereEnabled: false,
            background: {
              type: "color",
              color: [0,10,16]
            }
          },
          ui: {
            components: ["zoom"]
           }
        });  
      });

  4. Reload the index.html file in your web browser.

    The globe's controls are simplified, containing only buttons to zoom in or out.

    Globe with simplified controls

Add a custom basemap

Next, you'll enable the globe app to display a custom basemap based on two tile layer services. Tile layers are useful for static data that is displayed on the map; this type of layer is cached as sets of image tiles at multiple scales, and they load relatively quickly. They are often used for basemaps.

  1. In the main.js file, add a line before the const map = new Map({ line.
  2. Copy and paste the following code onto the new line:

    const satelliteLayer = new TileLayer({
      url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
      title: "satellite"
    })

    This code creates an layer named satelliteLayer that is an instance of the TileLayer class, sets it to fetch tiles from a specific URL, and gives it the satellite title.

  3. Add a line after the code you added in the previous step. Copy and paste the following code:

    const fireflyLayer = new TileLayer({
      url: "https://tiles.arcgis.com/tiles/nGt4QxSblgDfeJn9/arcgis/rest/services/HalfEarthFirefly/MapServer",
      title: "half-earth-firefly"
    })

    This code creates another layer named fireflyLayer that is an instance of the TileLayer class, sets it to fetch tiles from a specific URL, and gives it the half-earth-firefly title.

  4. Add a line after the code you added in the previous step. Copy and paste the following code:

    const basemap = new Basemap({
      baseLayers: [satelliteLayer, fireflyLayer],
      title: "half-earth-basemap",
      id: "half-earth-basemap"
    });

    This code creates a basemap that includes the two tile layers that you created and gives it the half-earth-basemap title.

  5. Locate the basemap: 'satellite' line and change it to basemap: basemap

    Now, the map's basemap will use the half-earth-basemap basemap you created.

    Code that creates the half-earth-basemap basemap

    The following codeblock contains the code for this section of the main.js file:

    ) {
    
        const satelliteLayer = new TileLayer({
          url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
          title: "satellite"
        })
        
        const fireflyLayer = new TileLayer({
          url: "https://tiles.arcgis.com/tiles/nGt4QxSblgDfeJn9/arcgis/rest/services/HalfEarthFirefly/MapServer",
          title: "half-earth-firefly"
        })
        
        const basemap = new Basemap({
          baseLayers: [satelliteLayer, fireflyLayer],
          title: "half-earth-basemap",
          id: "half-earth-basemap"
        });
        
        const map = new Map({
          basemap: basemap
        });

  6. Save the file. Reload the index.html file in your browser.

    The page only displays a black screen. There may be an error.

  7. Open the developer tools pane, if necessary, and click the Console tab.

    Error message in the developer tools pane

    Note:

    If the console section shows white space instead of error messages, it may be because a filter is causing the messages to be hidden. To remove the filter, delete any text in the Filter box.

    If you read the first several lines, you can gather some important information that will help you correct the error. The error message starts with the type of error, in this case, a ReferenceError, and the message says that TileLayer is not defined. ReferenceErrors occur when some variable has not been defined or is not available.

    The console also shows a Stack Trace, listing the files and line numbers leading to the error. The trace shows that the ReferenceError is occurring in the domReady callback and that the reference to the undefined variable is on line 10 (your line numbers may be slightly different) of the main.js file.

    Line 10 is: const satelliteLayer = new TileLayer({.

    The problem is that TileLayer is not defined or available. The solution is to add TileLayer to the require section and the function callback, at the beginning of the main.js file.

  8. In the main.js file, scroll to the top and find the require section. After the "esri/views/SceneView", line, add a line.
  9. Copy and paste the following code on the new line:

    "esri/layers/TileLayer",

  10. After the line that says ], function ( , find the SceneView line. Add a comma after SceneView and add a line after it.
  11. Copy and paste the following code on the new line:

    TileLayer

  12. Save the file.

    TileLayer added to require section of main.js

    The following codeblock contains the complete code:

    require([
    "esri/Map",
    "esri/views/SceneView",
    "esri/layers/TileLayer",
    "dojo/domReady!"
    ], function (
    Map,
    SceneView,
    TileLayer
    ) {

    These lines make the app load code from the JavaScript API that will support adding TileLayers.

  13. Reload the index.html file in your web browser.

    The page still only displays a black screen, but the error message and stack trace in the console are different.

    Second ReferenceError - Basemap is not defined.

    The problem is that Basemap is not defined or available. The solution is to add Basemap to the require section and the function callback at the beginning of the main.js file.

    You'll fix this error the same way you fixed the previous error.

  14. In the main.js file, after the "esri/layers/TileLayer", line, add a line.
  15. Copy and paste the following code on the new line:

    "esri/Basemap",

  16. After the line that says ], function ( , find the TileLayer line, add a comma after TileLayer, and add a line after it.
  17. Copy and paste the following code on the new line:

    Basemap

  18. Save the file.

    TileLayer and Basemap added to the code

    The following codeblock contains all of the code in the main.js file:

    require([
      "esri/Map",
      "esri/views/SceneView",
      "esri/layers/TileLayer",
      "esri/Basemap",
      "dojo/domReady!" // will not be called until DOM is ready
      ], function (
      Map,
      SceneView,
      TileLayer,
      Basemap
    
      ) {
    
        const satelliteLayer = new TileLayer({
          url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
          title: "satellite"
        })
        
        const fireflyLayer = new TileLayer({
          url: "https://tiles.arcgis.com/tiles/nGt4QxSblgDfeJn9/arcgis/rest/services/HalfEarthFirefly/MapServer",
          title: "half-earth-firefly"
        })
        
        const basemap = new Basemap({
          baseLayers: [satelliteLayer, fireflyLayer],
          title: "half-earth-basemap",
          id: "half-earth-basemap"
        });
        
        const map = new Map({
          basemap: basemap
        });
      
        const view = new SceneView({
          map: map,
          container: "sceneContainer",
          environment: {
            atmosphereEnabled: false,
            background: {
              type: "color",
              color: [0,10,16]
            }
          },
          ui: {
            components: ["zoom"]
           }
        });  
      });

  19. Reload the index.html page in your web browser.

    The globe appears with a new, darker basemap.

    Globe with darker basemap

    When zoomed out, the globe shows the dark firefly basemap, but as you zoom in, the satellite imagery appears.

Add third-party layers

Next, you'll enable the globe app to display some third-party layers. One of these is a TileLayer, like those used in the custom basemap, and one is a new type of layer called a FeatureLayer. In contrast with tile layers, feature layers are composed of a geometry component and a table component. Feature layers can be points, lines, or polygons and each feature inside the service has information linked to it via the attribute table. Feature layers can take a long time to load, depending on how many features and feature vertices they contain.

The layers you'll add are the current protected areas provided by UNEP-WCMC and the location of rangelands (Ellis et al. 2010) and their human modification value (Kennedy et al. 2019). For efficient conservation, it is key to understand which areas are already protected and the degree of human activities nearby, so you can identify and prioritize lower human modification areas that will require less habitat restoration. Rangelands could constitute restorable lands with the potential to reach near-natural state.

  1. In the main.js file, after the "esri/Basemap", line, add a line.
  2. Copy and paste the following code on the new line:

    "esri/layers/FeatureLayer",

  3. After the line that says ], function ( , add a comma after Basemap and add a new line.
  4. Copy and paste the following code on the new line:

    FeatureLayer

  5. Save the file.

    FeatureLayer added to require section

    The following codeblock contains the completed code:

    require([
      "esri/Map",
      "esri/views/SceneView",
      "esri/layers/TileLayer",
      "esri/Basemap",
      "esri/layers/FeatureLayer",
      "dojo/domReady!" // will not be called until DOM is ready
      ], function (
      Map,
      SceneView,
      TileLayer,
      Basemap,
      FeatureLayer
      ) {

    These new lines make the app load code from the JavaScript API that will support adding FeatureLayers and prevents the errors you saw before. Because the app already includes TileLayers, you don't have to add repeated lines for them here.

  6. In the main.js file, add two new lines before the const map = new Map({ line.
  7. Copy and paste the following code on the new line:

    const rangelands = new TileLayer({
      url: 'https://tiles.arcgis.com/tiles/IkktFdUAcY3WrH25/arcgis/rest/services/gHM_Rangeland_inverted/MapServer'
    })

    This code creates a layer named rangelands that is an instance of the TileLayer class and sets it to fetch tiles from a specific URL.

  8. Add two new lines after the lines you added in the previous step and copy and paste the following code on the new line:

    const protected = new FeatureLayer({
      url: 'https://services5.arcgis.com/Mj0hjvkNtV7NRhA7/arcgis/rest/services/WDPA_v0/FeatureServer/1'
    })

    This code creates another new layer named protected that is an instance of the FeatureLayer class and sets it to fetch features from a specific element of a feature service URL. You can find the URL for feature services by looking at their layer description page in ArcGIS Online. The URL for the server is found in the lower right part of the item description page.

    In the URL for this service, the service is coming from a FeatureServer. The URL ends in /1, which indicates that it is the second element of the service (items are numbered starting from 0, so 0 is the first item and 1 is the second). The first element corresponds to protected areas designated only as points, while the second one (the one you want for this app) provides polygons.

  9. In the main.js file, find the basemap: basemap line. Add a comma after basemap and add a line.
  10. Copy and paste the following code on the new line:

    layers: [protected, rangelands]

    This code adds the two new layers to the map.

    Two new layers added to the code

    The following codeblock contains the complete code for the current state of the main.js file:

    require([
      "esri/Map",
      "esri/views/SceneView",
      "esri/layers/TileLayer",
      "esri/Basemap",
      "esri/layers/FeatureLayer",
      "dojo/domReady!" // will not be called until DOM is ready
      ], function (
      Map,
      SceneView,
      TileLayer,
      Basemap,
      FeatureLayer
      ) {
    
        const satelliteLayer = new TileLayer({
          url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
          title: "satellite"
        })
        
        const fireflyLayer = new TileLayer({
          url: "https://tiles.arcgis.com/tiles/nGt4QxSblgDfeJn9/arcgis/rest/services/HalfEarthFirefly/MapServer",
          title: "half-earth-firefly"
        })
        
        const basemap = new Basemap({
          baseLayers: [satelliteLayer, fireflyLayer],
          title: "half-earth-basemap",
          id: "half-earth-basemap"
        });
    
        const rangelands = new TileLayer({
          url: 'https://tiles.arcgis.com/tiles/IkktFdUAcY3WrH25/arcgis/rest/services/gHM_Rangeland_inverted/MapServer'
        })
    
        const protected = new FeatureLayer({
          url: 'https://services5.arcgis.com/Mj0hjvkNtV7NRhA7/arcgis/rest/services/WDPA_v0/FeatureServer/1'
        })
        
        const map = new Map({
          basemap: basemap,
          layers: [protected, rangelands]
        });
      
        const view = new SceneView({
          map: map,
          container: "sceneContainer",
          environment: {
            atmosphereEnabled: false,
            background: {
              type: "color",
              color: [0,10,16]
            }
          },
          ui: {
            components: ["zoom"]
           }
        });  
      });

  11. Save the file and reload the index.html file.

    The globe contains layers for protected areas and rangelands.

    Globe with rangelands and protected areas

    When zoomed out, the globe shows the purple-shaded rangelands layer, but as you zoom in, the green protected areas polygons appear.

    Green protected areas on globe

    The protected areas layer was published with constraints on the zoom levels at which it displays. This is best practice for global feature layers with large numbers of features or complex geometry.

    When you develop an app with layers, it's useful to be able to interact with them to check that all the layers have loaded properly. In the next section, you'll add a widget to list the layers and show their status.

Add a layer list widget

The ArcGIS API for JavaScript offers ready-to-use widgets. Widgets are reusable user-interface components that encapsulate some functionality. The LayerList widget allows people using the app to interact with layers (turn them on and off), but it also helps understand the current state of the layers: loading, invisible at current zoom level. You'll add the LayerList widget to your web app.

Adding widgets to the app works in the same way as adding the other elements from the API. First, you'll add the new module that you want to use.

  1. In the main.js file, after the "esri/layers/FeatureLayer", line, add a line.
  2. Copy and paste the following code on the new line:

    "esri/widgets/LayerList",

  3. After the ], function ( line, add a comma after FeatureLayer and add a line after it.
  4. Copy and paste the following code on the new line:

    LayerList

    LayerList added to the require section

    These new lines make the app load code from the JavaScript API that will support adding the layer list widget. Next, you'll add the widget to the app.

  5. In the main.js file, find the following section:

    ui: {
            components: ["zoom"]
           }
        });

  6. After the line that says });, add a line.
  7. Copy and paste the following code on the new line:

    const layerList = new LayerList({
      view: view
    });
    
    view.ui.add(layerList, {
      position: "top-right"
    });

    The main.js code with layer list widget added

    The following codeblock contains the complete code for the current state of the main.js file:

    require([
      "esri/Map",
      "esri/views/SceneView",
      "esri/layers/TileLayer",
      "esri/Basemap",
      "esri/layers/FeatureLayer",
      "esri/widgets/LayerList",
      "dojo/domReady!" // will not be called until DOM is ready
      ], function (
      Map,
      SceneView,
      TileLayer,
      Basemap,
      FeatureLayer,
      LayerList
      ) {
    
        const satelliteLayer = new TileLayer({
          url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
          title: "satellite"
        })
        
        const fireflyLayer = new TileLayer({
          url: "https://tiles.arcgis.com/tiles/nGt4QxSblgDfeJn9/arcgis/rest/services/HalfEarthFirefly/MapServer",
          title: "half-earth-firefly"
        })
        
        const basemap = new Basemap({
          baseLayers: [satelliteLayer, fireflyLayer],
          title: "half-earth-basemap",
          id: "half-earth-basemap"
        });
    
        const rangelands = new TileLayer({
          url: 'https://tiles.arcgis.com/tiles/IkktFdUAcY3WrH25/arcgis/rest/services/gHM_Rangeland_inverted/MapServer'
        })
    
        const protected = new FeatureLayer({
          url: 'https://services5.arcgis.com/Mj0hjvkNtV7NRhA7/arcgis/rest/services/WDPA_v0/FeatureServer/1'
        })
        
        const map = new Map({
          basemap: basemap,
          layers: [protected, rangelands]
        });
      
        const view = new SceneView({
          map: map,
          container: "sceneContainer",
          environment: {
            atmosphereEnabled: false,
            background: {
              type: "color",
              color: [0,10,16]
            }
          },
          ui: {
            components: ["zoom"]
           }
        });
        const layerList = new LayerList({
          view: view
        });
        
        view.ui.add(layerList, {
          position: "top-right"
        });  
      });

  8. Save the file and reload the index.html file in your browser.

    The layer list widget appears in the app.

    App with layer widget

    Clicking the visibility button beside a layer turns it on or off. A layer that is not currently drawing because of scale dependencies will have its name drawn in dark text, while layers that are currently visible have their layer name drawn in light text.

You've created a web app with a custom basemap, added layers to it from web services, and added a widget to control the display of the layers. You've also learned how to read error messages in the console to find clues to help you debug JavaScript code. Next, you'll add code to allow for shapefile data to be uploaded to the app and displayed on the globe.


Add a data uploader

Previously, you built a globe app using ArcGIS API for JavaScript. On it you can visualize two key layers for understanding biodiversity conservation: current protected areas and the location of rangelands.

Next, you'll add a shapefile uploader to the app. A shapefile uploader allows you to visually overlay species range maps on the globe. The range of a species is a polygon that delineates the geographical limits and extent of a species' habitat. Range maps are typically derived from expert knowledge and observational data of species occurrence. This is the base of the data behind the Half-Earth Project Map.

Create a data upload form

First, you'll create an input form that will allow a user of the app to interact with their file system and select a file to upload to the map. Doing so will require changes to the three files.

The first thing you need to do on your main.html file is to create a form element. This element will allow you to interact with the web page to submit information, in this case a shapefile, to a server.

  1. In your text editor, open the index.html file.
  2. In the body section of the HTML file, after the line <div id="sceneContainer"></div>, add a line. Copy and paste the following code on the new line:

    <form enctype="multipart/form-data" method="post" id="uploadForm">
      
    </form>

    The first part of the first line identifies this as an HTML form tag. The tag has three attributes. The enctype attribute specifies the media type of data to be submitted. For file transmission, the enctype is set to multipart/form-data. The method attribute is next. It specifies the HTTP method that this form will trigger. The method is set to POST, a method that sends data to a server. The id attribute is set to uploadForm, which is how the form will be referenced in the main.css file.

    There is a line space and then the closing form tag, </form>, which indicates the end of the form. You will add additional code to the form between the two <form> tags.

    The body section of the index.html file at this stage of the lesson will look like this:

    First form uploader tags added to body section

  3. On the blank line between the <form> tags, copy and paste the following code:

    <div class="field">
        <label class="file-upload">
          <span><strong>Upload shapefile .zip</strong></span>
          <input type="file" name="file" id="inFile" />
        </label>
      </div>

    The first line in this code specifies a new div element that defines a field for the form. The code that follows defines how the field works and how it is displayed. The second line specifies a label for the field. The third line contains the text for the label, Upload shapefile .zip and specifies that it is written in the strong text style. The fourth line specifies that the form field will take file data input, that it is named file, and that it can be accessed elsewhere in the app code using the id inFile. The fifth and sixth lines are closing tags for the label and this div section.

    Form with input field defined

    The following codeblock contains the complete code:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Half Earth Globe</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="https://js.arcgis.com/4.17/esri/themes/dark-blue/main.css">
        <script src="https://js.arcgis.com/4.17/"></script>
        <link rel="stylesheet" href="main.css">
        <script src="main.js"></script>
      </head>
      <body>
        <div id="sceneContainer"></div>
        <form enctype="multipart/form-data" method="post" id="uploadForm">
          <div class="field">
            <label class="file-upload">
              <span><strong>Upload shapefile .zip</strong></span>
              <input type="file" name="file" id="inFile" />
            </label>
          </div>
        </form>
      </body>
    </html>

  4. Save the index.html file.

    Next, you'll add some code to the CSS file to style the upload form before you test it.

  5. In your text editor, open the main.css file. After the final line in the file, add two blank lines.
  6. On the new final line, copy and paste the following code:
    #uploadForm {
      position: absolute;
      font-size: 0.8em;
      font-family: monospace, sans-serif;
      top: 15px;
      left: 60px;
      background: #242424;
      padding: 6px;
      color: #69dcff;
    }

    The main.css file after upload form styling has been added

    This code refers to the uploadForm in the HTML file using its id attribute. It specifies where and how to draw the form and its text. It specifies colors using hexadecimal code values, rather than the RGB values used before.

    The following codeblock contains the complete code:

    body {
      padding: 0;
      margin: 0;
    }
    
    #sceneContainer {
      height: 100vh;
      width: 100vw;
      background: rgb(0,0,0);
    }
    
    #uploadForm {
      position: absolute;
      font-size: 0.8em;
      font-family: monospace, sans-serif;
      top: 15px;
      left: 60px;
      background: #242424;
      padding: 6px;
      color: #69dcff;
    }

  7. Save the main.css file and reload the index.html file in your web browser.

    The upload form is added to the top left section of the app.

    The upload form in the app

    Next to the Choose File button is the phrase No file chosen. You can click the button and select any file. If you do, the text changes to the name of the file you selected, but nothing else happens.

    Next, you'll add code to allow the app to use the file that you upload.

Connect the form to the app code

The HTML code now has an input form with a working button to submit a file. The app cannot do anything with a submitted file until you add code to handle the input.

  1. In the main.js file, scroll to the bottom of the file and add a line before the final line, });.
  2. On the new line, copy and paste the following code:

    const uploadForm = document.getElementById("uploadForm");
    
    uploadForm.addEventListener("change", function (event) {
      const filePath = event.target.value.toLowerCase();
      //only accept .zip files
      if (filePath.indexOf(".zip") !== -1) {
        // we would be able to use the file here
      } 
    });

    First part of upload code in JS file

    This code creates an element in the JavaScipt code called uploadForm and links it by the id value to the uploadForm in the HTML file, using document.getElementById.

    Next, the code adds an EventListener that monitors activity involving the form. Specifically, it triggers when a change event happens on the form.

    The next line converts the file path of the input to lowercase. The if statement is a test for whether or not the submitted file is a .zip file. The app only processes .zip files.

    Generally, when you create a web form to take user input, you must design it with some precautions in mind to prevent accidents and abuse. In this lesson, you won't perform any validation for the format of the file beyond making sure the file name has a .zip extension. The assumption is that this app would be used by a trusted set of people on an organization's internal web, not exposed to the whole web. More security is required for an outward-facing web app.

  3. Find the // we would be able to use the file here line and copy and paste the following code to replace it:

    generateFeatureCollection(uploadForm)

    This line runs a function to turn the uploaded data into an ArcGIS feature collection. The function is not yet defined. Next, you'll define it.

  4. In the main.js file, scroll to the bottom of the file and add a line before the final line, });. Copy and paste the following code on the new line:

    function generateFeatureCollection(uploadFormNode) {
    console.log(uploadFormNode)
    }

    This code begins to define the function to make the feature collection. This function will need code from ArcGIS API for JavaScript to handle requests. You'll make that code available to your app by adding it to the require section.

  5. Scroll to the top of the main.js file. After the "esri/widgets/LayerList" line, add a line.
  6. Copy and paste the following code on the new line:

    "esri/request",

  7. After the line that says ], function ( , add a comma after LayerList and add a line after it.
  8. Copy and paste the following code on the new line:

    request

    The request code added to the require section

    Now the Esri request code is available for your function.

  9. Scroll to the generateFeatureCollection function at the bottom of the file. Inside the function, find the console.log(uploadFormNode) line and add a line before it.
  10. Copy and paste the following code on the new line:

    request("https://www.arcgis.com/sharing/rest/content/features/generate", {
      query: generateRequestParams,
      body: uploadFormNode,
      responseType: "json"
    })

    This request function will allow the app to communicate with a server to retrieve a response using the GET request. The REST service it uses is the generate service, which will convert the uploaded shapefile into a feature collection and return it as the response for the request.

    The request function accepts the URL of the service to be accessed and an object with the request options. This function includes a constant, generateRequestParams, that hasn't been declared yet. For the request to work, you must indicate the parameters of the request.

  11. After the function generateFeatureCollection(uploadFormNode) { line, add a line.
  12. Copy and paste the following code on the new line.

    const generateRequestParams = {
      filetype: "shapefile",
        publishParameters: JSON.stringify({
        targetSR: view.spatialReference
      }),
      f: "json"
    };

    The request informs the server that the file being sent is a shapefile. It requests the feature collection in the spatial reference of the map view, specified as a JSON string.

  13. After the responseType: "json" line, delete the following three lines:

    })
        console.log(uploadFormNode)
        }

  14. After the responseType: "json" line, add a new line and copy and paste the following code:

    }).then(function (response) {
        // we will have here available the feature collection 
        // inside the data object of the response object
        // response.data.featureCollection
          console.log(response)
        })
    }

    The promise section with comment text

    This code section is called a promise. A promise is the action that will take place once the request has been completed. In this case, the request is to generate a feature collection, and the response that the server returns contains that data as a JSON object.

  15. Save the main.js file.

    The uploader is not yet ready to run. Although it now accepts the .zip file and requests features back as JSON, there is not yet code to display them on the map.

Create a layer from the response and add it

Once the app gets the response from the server, you want it to add the features to the map and move the scene camera to focus on those features. However, you cannot add the JSON directly to the map. First you have to create a graphic to be displayed and then gather all those graphics into a dynamically created FeatureLayer. This logic will be built into different functions that you will add next.

The first of these functions creates a graphic from each of the featureCollection features included in the uploaded shapefile. This function will use the Esri Graphic class. You'll need to add this class to the Require section.

  1. Scroll to the top of the main.js file and add a line after "esri/request",. On the new line, add "esri/Graphic",.
  2. After request, add a comma and a line. On the new line, add Graphic.

    Now the Esri Graphic code is available for your function.

  3. Scroll to the bottom of the file and add a line before the final line (the last curly bracket).
  4. Copy and paste the following code on the new line:

    function createFeaturesGraphics(layer) {
      console.log(layer)
      return layer.featureSet.features.map(function (feature) {
        return Graphic.fromJSON(feature);
      });
    }

    Create feature graphics function

    This code section makes a new function named createFeaturesGraphics that iterates through the JSON from the server and creates a graphic for each feature in it. Once the app has created graphics, you'll put them together to create a FeatureLayer. The next function takes the graphics as input and returns a new FeatureLayer.

  5. At the bottom of the file, add a line before the final line (the last curly bracket). Copy and paste the following code on the new line:

    function createFeatureLayerFromGraphic(graphics) {
      return new FeatureLayer({
        objectIdField: "FID",
        source: graphics,
        title: 'User uploaded shapefile'
      });
    }

    Create feature layer

    This function makes a new feature layer, setting the objectIDField to FID (the shapefile's Object ID field), using the graphics collection as the source data, and titling the new layer User uploaded shapefile. This title will show in the list of layers of the viewer when uploading a shapefile.

    Now that these two functions have been created, you can use them inside a new function that will add the layer to the map.

  6. At the bottom of the file, add a line before the final line (the last curly bracket). Copy and paste the following code on the new line:

    function addShapefileToMap(featureCollection) {
      let sourceGraphics = [];
      const collectionLayers = featureCollection.layers;
      const mapLayers = collectionLayers.map(function (layer) {
        const graphics = createFeaturesGraphics(layer);
        sourceGraphics = sourceGraphics.concat(graphics);
        const featureLayer = createFeatureLayerFromGraphic(graphics)
        return featureLayer;
      });
      map.addMany(mapLayers);
      view.goTo({target: sourceGraphics, tilt: 40});
    }

    Add shapefile to map function

    This function is called addShapefileToMap. It uses the other two functions to create graphics and create a layer from those graphics. It then adds the layer to the map, and the view of the map is changed so it is centered on the new layer.

    The next step is to use this function in the unfinished generateFeatureCollection function. In the promise section you added earlier, you'll add the addShapeFileToMap function, passing it as a parameter the JSON response to the request.

  7. Find the following comment block in the promise section:

    // we will have here available the feature collection 
        // inside the data object of the response object
        // response.data.featureCollection

  8. Replace the comment block with the following line:

    addShapefileToMap(response.data.featureCollection);

    The code looks like this.

    Replaced comment with addShapefileToMap code

    This code calls the addShapefileToMap function on the response data. The following codeblock contains the complete code (the complete code may have additional line breaks):

    require([
      "esri/Map",
      "esri/views/SceneView",
      "esri/Basemap",
      "esri/layers/TileLayer",
      "esri/layers/FeatureLayer",
      "esri/widgets/LayerList",
      "esri/request",
      "esri/Graphic",
      "dojo/domReady!" // will not be called until DOM is ready
      ], function (
      Map,
      SceneView,
      Basemap,
      TileLayer,
      FeatureLayer,
      LayerList,
      request,
      Graphic
      ) {
    
        const satelliteLayer = new TileLayer({
          url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
          title: "satellite"
        })
        
        const fireflyLayer = new TileLayer({
          url: "https://tiles.arcgis.com/tiles/nGt4QxSblgDfeJn9/arcgis/rest/services/HalfEarthFirefly/MapServer",
          title: "half-earth-firefly"
        })
        
        const basemap = new Basemap({
          baseLayers: [satelliteLayer, fireflyLayer],
          title: "half-earth-basemap",
          id: "half-earth-basemap"
        });
    
        const rangelands = new TileLayer({
          url: 'https://tiles.arcgis.com/tiles/IkktFdUAcY3WrH25/arcgis/rest/services/gHM_Rangeland_inverted/MapServer'
        })
    
        const protected = new FeatureLayer({
          url: 'https://services5.arcgis.com/Mj0hjvkNtV7NRhA7/arcgis/rest/services/WDPA_v0/FeatureServer/1'
        })
        
        const map = new Map({
          basemap: basemap,
          layers: [protected, rangelands]
        });
      
        const view = new SceneView({
          map: map,
          container: "sceneContainer",
          environment: {
            atmosphereEnabled: false,
            background: {
              type: "color",
              color: [0,10,16]
            }
          },
          ui: {
            components: ["zoom"]
           }
        });
        const layerList = new LayerList({
          view: view
        });
        
        view.ui.add(layerList, {
          position: "top-right"
        });
    
        const uploadForm = document.getElementById("uploadForm");
    
        uploadForm.addEventListener("change", function (event) {
          const filePath = event.target.value.toLowerCase();
          //only accept .zip files
          if (filePath.indexOf(".zip") !== -1) {
            generateFeatureCollection(uploadForm)
          } 
        });
    
        function generateFeatureCollection(uploadFormNode) {
          const generateRequestParams = {
            filetype: "shapefile",
              publishParameters: JSON.stringify({
              targetSR: view.spatialReference
            }),
            f: "json"
          };
    
          request("https://www.arcgis.com/sharing/rest/content/features/generate", {
            query: generateRequestParams,
            body: uploadFormNode,
            responseType: "json"
          }).then(function (response) {
            addShapefileToMap(response.data.featureCollection);
            console.log(response)
            })
          }
    
        function createFeaturesGraphics(layer) {
          console.log(layer)
          return layer.featureSet.features.map(function (feature) {
            return Graphic.fromJSON(feature);
          });
        }
        
        function createFeatureLayerFromGraphic(graphics) {
          return new FeatureLayer({
            objectIdField: "FID",
            source: graphics,
            title: 'User uploaded shapefile'
          });
        }
    
        function addShapefileToMap(featureCollection) {
          let sourceGraphics = [];
          const collectionLayers = featureCollection.layers;
          const mapLayers = collectionLayers.map(function (layer) {
            const graphics = createFeaturesGraphics(layer);
            sourceGraphics = sourceGraphics.concat(graphics);
            const featureLayer = createFeatureLayerFromGraphic(graphics)
            return featureLayer;
          });
          map.addMany(mapLayers);
          view.goTo({target: sourceGraphics, tilt: 40});
        } 
    
    });

  9. Save the main.js file and reload the index.html file in the web browser.

    The app now accepts an uploaded zipped shapefile and adds it to the globe.

Test the app

You can test the app by running it and browsing to a zipped shapefile on your computer.

  1. Download this sample zipped shapefile.

    The shapefile contains the potential range area as identified by the Iberian lynx Conservation Programme of the Consejería de Medio Ambiente (Junta de Andalucía, Spain). This feline is only found in Europe's Iberian Peninsula and has been considered endangered by the International Union for Conservation of Nature (IUCN) since 2014. Prior to 2014, it was considered critically endangered, but the conservation actions carried out in Portugal and Spain—including establishing protected areas inside the lynx's range—have begun to assist in the recovery of the population. The area of implementation is larger than the range of the lynx in the region with the hope that the conservation actions will help increase its range.

  2. In the app, click the Choose File button.
  3. Browse to the location where you downloaded the zipped shapefile.
  4. Click the ambito_lince.zip zipped shapefile and click Open.
  5. Wait briefly while the zipped shapefile is processed and displayed.

    The globe zooms to the extent of the shapefile with a 40 degree tilt to provide an oblique view.

    Iberian Lynx data displayed in the app

You've built a web app that displays a globe; has a customized interface, basemap, and context layers; and allows for the upload and display of a zipped shapefile. You've also learned some of the basics of HTML, CSS, and JavaScript coding and debugging and were introduced to using ArcGIS API for JavaScript.

You can find more lessons in the Learn ArcGIS Lesson Gallery.

References:

  • Ellis, E. C., Klein Goldewijk, K., Siebert, S., Lightman, D., & Ramankutty, N. (2010). Anthropogenic transformation of the biomes, 1700 to 2000. Global ecology and biogeography, 19(5), 589-606.

  • Kennedy, C. M., Oakleaf, J. R., Theobald, D. M., Baruch‐Mordo, S., & Kiesecker, J. (2019). Managing the middle: A shift in conservation priorities based on the global human modification gradient. Global Change Biology, 25(3), 811-826.