Skip to content

Chapter 7: ArcGIS Integration#

Welcome back to the loaders.gl-showcases tutorial! In our previous chapters, we've covered the foundational elements of the application: the User Interface Components, the Map Visualization stage, how we control what appears on that stage through Layer Management, the central "control room" of Global State Management, and features built upon this like Bookmarks and Comparison Mode.

All the examples we've seen so far likely involved loading publicly available datasets. But what if you have your own private 3D content hosted on ArcGIS Online or ArcGIS Enterprise? You need a way to securely access your specific data that isn't publicly shared. This is where the ArcGIS Integration comes in.

What is ArcGIS Integration?#

Imagine you have a collection of special 3D maps and models that only you (or your organization) can see, stored securely on ArcGIS. You need a special "key" to unlock access to this private collection and load it into the loaders.gl-showcases application.

As the concept description says:

This part allows users to securely log in to their ArcGIS Online or ArcGIS Enterprise accounts. Once authenticated, it can fetch a list of their private 3D Scene Service content, which can then be loaded and viewed in the application. It's like having a special key that unlocks access to a private collection of 3D maps and models.

This feature provides the mechanism to perform the necessary login, retrieve your private content list, and then import those items as layers into the application's Layer Management system so they can be visualized on the Map Visualization.

The Central Use Case: Accessing Your Private 3D Scene Services#

Let's say you have a private 3D Scene Layer Package (SLPK) or an I3S service containing a detailed model of your company's campus, hosted on your organization's ArcGIS Enterprise portal. You want to load this specific model into the loaders.gl-showcases application to visualize it, maybe compare it with other data in Comparison Mode, or save a Bookmark of a specific viewpoint.

The traditional way to load data usually involves providing a public URL. But for private content, just the URL isn't enough. You need to prove you have permission to access it. The central use case is logging into ArcGIS through the application, browsing your private content, and selecting an item to load securely.

Key Concepts#

The ArcGIS Integration relies on a few key steps:

  1. Authentication: The process of logging into your ArcGIS account to prove your identity and get permission to access your content. This typically involves a secure flow called OAuth2.
  2. Authorization: Once authenticated, the system confirms you have the rights to access the content you're requesting.
  3. Fetching Content: After successful authentication, the application communicates with the ArcGIS portal API to get a list of the 3D Scene Services (like SLPKs or I3S services) you own or have access to.
  4. Loading Secure Layers: When you choose an item from your content list to load, the application needs to use the credentials obtained during authentication (often in the form of a temporary "token") to authorize the request when loading the actual 3D data from the service URL.

How to Use (The ArcGIS Section in the Layers Panel)#

The primary interface for interacting with the ArcGIS Integration is located within the Layers Panel. You'll find a dedicated section or tab for ArcGIS content. The main component managing this UI is ArcGisControlPanel (src/components/layers-panel/arcgis-control-panel.tsx).

Let's walk through the typical user flow:

1. Logging In#

  • Open the Layers Panel.
  • Find the ArcGIS section.
  • If you are not logged in, you will see a button labeled "Login to ArcGIS".
  • Click this button. The application will typically open a separate popup window controlled by ArcGIS, prompting you to enter your ArcGIS username and password.
  • Once you log in successfully in the popup, the window will close, and the main application will update.
// Simplified view of the Login button part in ArcGisControlPanel.tsx
export const ArcGisControlPanel = ({ /* ... */ }) => {
  const dispatch = useAppDispatch();
  const username = useAppSelector(selectUser); // Get login status from state
  const isLoggedIn = !!username; // Are we logged in?

  const onArcGisActionClick = (): void => {
    if (isLoggedIn) {
      // Action for logged-in state (Import)
    } else {
      // Action for logged-out state (Login)
      void dispatch(arcGisLogin()); // Dispatch the login action
    }
  };

  return (
    <>
      <ActionIconButtonContainer>
        <ActionIconButton
          Icon={ImportIcon}
          style={isLoggedIn ? "active" : "disabled"}
          onClick={onArcGisActionClick} // This button handles both login and import
        >
          {isLoggedIn ? "Import from ArcGIS" : "Login to ArcGIS"} {/* Text changes based on login status */}
        </ActionIconButton>
        <EsriStyledImage /> {/* Esri logo */}
      </ActionIconButtonContainer>
      {/* ... rest of the component ... */}
    </>
  );
};

This snippet shows how the ActionIconButton component is used. Its text and click behavior (onArcGisActionClick) depend on the isLoggedIn state, which is read from the Global State using useAppSelector(selectUser). When not logged in, clicking it dispatches the arcGisLogin action.

2. Browsing Your Content#

  • Once successfully logged in, the button text in the ArcGIS section changes to "Import from ArcGIS", and your username might be displayed (handled by the AcrGisUser component in ArcGisControlPanel.tsx).
  • Click the "Import from ArcGIS" button.
  • A modal dialog will appear, listing your private 3D Scene Services. This modal is handled by the ArcGisImportPanel component (src/components/layers-panel/arcgis-import-panel/arcgis-import-panel.tsx).
  • The list shows details like the item's title and date created. You can usually sort the list by clicking the column headers.
  • Select one of the items from the list by clicking on its row. A radio button will indicate which item is selected.
// Simplified view of handling content fetching and showing the import panel
export const ArcGisControlPanel = ({ onArcGisImportClick }: ArcGisControlPanelProps) => {
  const dispatch = useAppDispatch();
  const username = useAppSelector(selectUser);
  const isLoggedIn = !!username;

  const [showArcGisImportPanel, setShowArcGisImportPanel] = useState(false); // State to control modal visibility

  const onArcGisActionClick = (): void => {
    if (isLoggedIn) {
      void dispatch(getArcGisContent()); // Dispatch action to fetch content
      setShowArcGisImportPanel(true); // Show the modal panel
    } else {
      void dispatch(arcGisLogin());
    }
  };

  return (
    <>
      {/* ... Login/Import button ... */}

      {/* The modal dialog */}
      {showArcGisImportPanel && (
        <ArcGisImportPanel
          onImport={(item) => {
            // This function is called when "Import Selected" is clicked in the modal
            onArcGisImportClick(item); // Call the handler provided by the parent (LayersPanel)
            setShowArcGisImportPanel(false); // Hide the modal
          }}
          onCancel={() => {
            setShowArcGisImportPanel(false); // Hide the modal when canceled
          }}
        />
      )}

      {/* ... Logout warning modal ... */}
    </>
  );
};

This shows that when logged in, clicking the button dispatches getArcGisContent (an action to fetch the list) and sets showArcGisImportPanel state to true, causing the modal to appear. The ArcGisImportPanel receives onImport and onCancel handlers.

Inside ArcGisImportPanel.tsx, the content list and selection are managed using Global State Management selectors and actions from the arcgis-content-slice.ts:

// Simplified view inside ArcGisImportPanel.tsx
export const ArcGisImportPanel = ({ onImport, onCancel }: InsertLayerProps) => {
  const dispatch = useAppDispatch();
  const arcGisContentArray = useAppSelector(selectArcGisContent); // Get the content list from state
  const arcGisContentSelected = useAppSelector(selectArcGisContentSelected); // Get the selected item ID from state
  const loadingStatus = useAppSelector(selectStatus); // Get loading status

  const isLoading = loadingStatus === "loading";

  const handleImport = () => {
    const arcGisItem = arcGisContentArray.find(
      (item) => item.id === arcGisContentSelected
    ); // Find the selected item data
    if (arcGisItem) {
      onImport(arcGisItem); // Call the onImport handler passed from ArcGisControlPanel
    }
  };

  return (
    <ModalDialog
      title={"Select map to import"}
      okButtonText={"Import Selected"}
      onConfirm={handleImport} // When "Import Selected" clicked, call handleImport
      onCancel={onCancel}
    >
      <SpinnerContainer visible={isLoading}> {/* Show spinner if loading */}
        <LoadingSpinner />
      </SpinnerContainer>
      <Table>
        <TableContent>
          {arcGisContentArray.map((contentItem) => { // Loop through the content list
            const isRowSelected = arcGisContentSelected === contentItem.id; // Check if this row is selected

            return (
              <TableRow
                key={contentItem.id}
                checked={isRowSelected}
                onClick={() => {
                  dispatch(setArcGisContentSelected(contentItem.id));
                }} // Dispatch action to select this item when row is clicked
              >
                {/* ... render table cells with contentItem data and RadioButton ... */}
              </TableRow>
            );
          })}
        </TableContent>
      </Table>
    </ModalDialog>
  );
};

This component reads the content list (arcGisContentArray) and the currently selected item ID (arcGisContentSelected) from the Redux state. It renders a TableRow for each item. Clicking a row dispatches setArcGisContentSelected to update the state, which in turn updates which radio button appears checked.

3. Importing the Selected Content#

  • In the modal dialog listing your content, click the "Import Selected" button after selecting an item.
  • The modal will close.
  • The selected ArcGIS item will be added to the list of available layers in the main Layers Panel. It will then be loaded and displayed on the Map Visualization.

The handleImport function in the ArcGisImportPanel finds the data for the selected item from the arcGisContentArray (which includes the necessary url and token) and calls the onImport prop. This onImport prop, provided by ArcGisControlPanel, is responsible for integrating the ArcGIS item data into the application's main layer list (via state updates managed in the LayersPanel's parent logic).

4. Logging Out#

  • If your username is displayed in the ArcGIS section of the Layers Panel, you are logged in.
  • Click on your username.
  • A modal dialog will appear asking for confirmation to log out.
  • Click "Log out" to confirm.
// Simplified view of the Logout part in ArcGisControlPanel.tsx
export const ArcGisControlPanel = ({ /* ... */ }) => {
  const dispatch = useAppDispatch();
  const username = useAppSelector(selectUser);
  const isLoggedIn = !!username;

  const [showLogoutWarning, setShowLogoutWarning] = useState(false);

  const onArcGisLogoutClick = (): void => {
    setShowLogoutWarning(true); // Show the logout warning modal
  };

  return (
    <>
      {/* ... Login/Import button ... */}

      {isLoggedIn && ( // Only show if logged in
        <AcrGisUser onClick={onArcGisLogoutClick}>{username}</AcrGisUser> // Button with username
      )}

      {/* ... Import panel modal ... */}

      {/* The logout warning modal */}
      {showLogoutWarning && (
        <ModalDialog
          title={"Logout from ArcGIS"}
          okButtonText={"Log out"}
          cancelButtonText={"Cancel"}
          onConfirm={() => {
            void dispatch(arcGisLogout()); // Dispatch the logout action on confirm
            setShowLogoutWarning(false); // Hide the modal
          }}
          onCancel={() => {
            setShowLogoutWarning(false); // Hide the modal on cancel
          }}
        >
          {/* ... warning text ... */}
          <TextUser>{username}</TextUser>
        </ModalDialog>
      )}
    </>
  );
};

Clicking the AcrGisUser component (showing the username) sets showLogoutWarning state, displaying the modal. Confirming the logout in the modal dispatches the arcGisLogout action.

Under the Hood: Connecting to ArcGIS and State Management#

The ArcGIS Integration relies heavily on utility functions that wrap the @esri/arcgis-rest-* libraries and integrate with the application's Global State Management using Redux Toolkit (Chapter 4).

1. Authentication Flow:

  • The arcGisLogin action creator (src/redux/slices/arcgis-auth-slice.ts) uses createAsyncThunk to perform an asynchronous operation: calling arcGisRequestLogin() from src/utils/arcgis.ts.
  • arcGisRequestLogin internally uses @esri/arcgis-rest-request (ArcGISIdentityManager.beginOAuth2) to initiate the OAuth2 flow. This library handles opening the popup, listening for the redirect, and completing the authentication handshake with ArcGIS.
  • Upon successful authentication, ArcGISIdentityManager.beginOAuth2 returns a session object.
  • arcGisRequestLogin then calls updateSessionInfo which serializes the session and the user's email into localStorage for persistence across sessions and retrieves the user's email.
  • The arcGisLogin thunk completes, and the extraReducers in arcgis-auth-slice.ts update the user state in Redux with the logged-in user's email.
  • Components like ArcGisControlPanel read the updated user state using selectUser, causing the UI to change from "Login" to "Import" and display the username.

2. Fetching Content Flow:

  • The getArcGisContent action creator (src/redux/slices/arcgis-content-slice.ts) uses createAsyncThunk to call getArcGisUserContent() from src/utils/arcgis.ts.
  • getArcGisUserContent first retrieves the saved session from localStorage using getArcGisSession.
  • It then uses @esri/arcgis-rest-portal (getUserContent) to fetch the list of content items associated with the authenticated user's portal, passing the retrieved session for authorization.
  • It filters the returned items to find "Scene Service" items that are "Hosted Service".
  • For each relevant item, it requests a token using the session (authentication.getToken(item.url)).
  • It formats the necessary item data (id, url, name, title, token, created date formatted) into an IArcGisContent object.
  • The getArcGisContent thunk completes, and the extraReducers in arcgis-content-slice.ts update the arcGisContent state with the fetched list and set the status to 'idle'.
  • The ArcGisImportPanel reads the arcGisContent state using selectArcGisContent and renders the list in the modal.

3. Loading Secure Content:

  • When "Import Selected" is clicked, the handleImport function in ArcGisImportPanel retrieves the full IArcGisContent object for the selected item from the Redux state. This object includes the item's url and the fetched token.
  • It calls the onImport prop, passing an object containing the name, url, and token.
  • The parent component (Layers Panel's logic) receives this object and adds it to the main list of layers to be loaded (likely by dispatching an action to a layer-specific Redux slice, similar to how other layers are managed in Chapter 3 and Chapter 4).
  • When the application's Map Visualization component (DeckGlWrapper or ArcgisWrapper) renders the layers based on the updated state, it uses the layer definition which now includes the token.
  • The underlying map library (e.g., loaders.gl's I3SLoader used by Deck.gl, or the ArcGIS Maps SDK) detects the presence of the token and includes it in the HTTP requests made to fetch data from the secured ArcGIS Scene Service URL, thereby authorizing access.

Here's a simplified sequence diagram focusing on the authentication and content fetching:

sequenceDiagram
    Participant User
    Participant ArcGisControlPanel
    Participant ReduxDispatch
    Participant ArcGisAuthSlice
    Participant ArcGisContentSlice
    Participant ArcGisUtils
    Participant ArcGISAPIs

    User->>ArcGisControlPanel: Clicks "Login to ArcGIS"
    ArcGisControlPanel->>ReduxDispatch: Dispatches arcGisLogin action
    ReduxDispatch->>ArcGisAuthSlice: Calls arcGisLogin thunk
    ArcGisAuthSlice->>ArcGisUtils: Calls arcGisRequestLogin()
    ArcGisUtils->>ArcGISAPIs: Initiates OAuth2 flow (e.g., opens popup)
    ArcGISAPIs-->>User: Prompts for login
    User->>ArcGISAPIs: Enters credentials
    ArcGISAPIs->>ArcGisUtils: Completes OAuth2, returns session/token
    ArcGisUtils->>ArcGisUtils: Stores session/user in localStorage
    ArcGisUtils-->>ArcGisAuthSlice: Returns user email
    ArcGisAuthSlice->>ArcGisAuthSlice: Updates 'user' state
    ArcGisAuthSlice-->>ArcGisControlPanel: State change notifies UI
    ArcGisControlPanel->>User: UI updates to "Import from ArcGIS", shows username

    User->>ArcGisControlPanel: Clicks "Import from ArcGIS"
    ArcGisControlPanel->>ReduxDispatch: Dispatches getArcGisContent action
    ReduxDispatch->>ArcGisContentSlice: Calls getArcGisContent thunk, sets status 'loading'
    ArcGisContentSlice->>ArcGisUtils: Calls getArcGisUserContent()
    ArcGisUtils->>ArcGisUtils: Retrieves session from localStorage
    ArcGisUtils->>ArcGISAPIs: Calls getUserContent(session)
    ArcGISAPIs-->>ArcGisUtils: Returns list of content items
    ArcGisUtils->>ArcGisUtils: Filters, gets token for each item
    ArcGisUtils-->>ArcGisContentSlice: Returns formatted content list
    ArcGisContentSlice->>ArcGisContentSlice: Updates 'arcGisContent' state, sets status 'idle'
    ArcGisContentSlice-->>ArcGisControlPanel: State change notifies UI
    ArcGisControlPanel->>User: Shows ArcGisImportPanel modal with list

This diagram shows the flow from user action in the UI to the underlying utility functions interacting with ArcGIS APIs and how the results update the Global State to reflect the logged-in status and the fetched content list. Loading the selected layer then follows the established pattern of adding layers via Layer Management and state updates.

The actual interaction with the ArcGIS REST APIs and the storage of the authentication session happens in src/utils/arcgis.ts. This file contains functions like arcGisRequestLogin, arcGisRequestLogout, and getArcGisUserContent which encapsulate the specific calls to the @esri/arcgis-rest-* libraries.

// Simplified snippets from src/utils/arcgis.ts
import { ArcGISIdentityManager } from "@esri/arcgis-rest-request";
import { getUserContent, type IItem } from "@esri/arcgis-rest-portal";

const ARCGIS_REST_USER_SESSION = "__ARCGIS_REST_USER_SESSION__"; // Key for localStorage

// Utility function to get the saved session from localStorage
function getArcGisSession(): ArcGISIdentityManager | undefined {
  const itemString = localStorage.getItem(ARCGIS_REST_USER_SESSION);
  if (itemString) {
    // Deserialize the saved session object
    return ArcGISIdentityManager.deserialize(itemString);
  }
  return undefined;
}

// Function to initiate the login flow
export const arcGisRequestLogin = async () => {
  // ... get OAuth options (redirect URI, client ID) ...
  let session: ArcGISIdentityManager | undefined;
  try {
    // Begin the OAuth2 flow - this opens the popup
    session = await ArcGISIdentityManager.beginOAuth2(options);
  } finally {
    // Update localStorage with the new session info
    return await updateSessionInfo(session);
  }
};

// Function to fetch user content
export const getArcGisUserContent = async (): Promise<IArcGisContent[]> => {
  const contentItems: IArcGisContent[] = [];
  const authentication = getArcGisSession(); // Get the current session

  if (authentication) {
    // Call the ArcGIS REST API to get user content using the session
    const content = await getUserContent({ authentication });

    for (const item of content.items) {
      // Filter for Scene Services
      if (item.url && item.type === "Scene Service" && item.typeKeywords?.includes("Hosted Service")) {
        // Get a token for this specific item's URL
        const token = await authentication.getToken(item.url);
        // Create the formatted object including the token
        const contentItem: ArcGisContent = new ArcGisContent(item, token);
        contentItems.push(contentItem);
      }
    }
  }
  return contentItems; // Return the list for the Redux thunk
};

These utility functions demonstrate how the application uses the ArcGIS REST JS libraries to perform the authentication handshake (beginOAuth2), manage the session (deserialize, getToken), and fetch content (getUserContent). The logic for filtering 3D Scene Services and including the token in the resulting object (ArcGisContent class) is crucial for loaders.gl or ArcGIS Maps SDK to be able to load the secured content later.

Conclusion#

In this chapter, we explored ArcGIS Integration, the feature that allows users to securely access their private 3D content from ArcGIS Online or ArcGIS Enterprise. We saw how it fits into the application's workflow, starting with logging in via the Layers Panel, browsing content in a modal dialog, and importing selected items. Under the hood, we learned that this process involves standard OAuth2 authentication, fetching content using ArcGIS REST APIs (wrapped in src/utils/arcgis.ts), and leveraging Global State Management (via Redux slices like arcgis-auth-slice.ts and arcgis-content-slice.ts) to track the login status, store the content list, and manage the selected item before it's added as a layer to the Map Visualization using the obtained security token.

With a solid understanding of how the application handles data loading and visualization, including external sources like ArcGIS, we're ready to look at the tools available within the application to help developers and users understand and validate the data being displayed.

Chapter 8: Debug and Validation Tools


Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/__mocks__/@esri/arcgis-rest-portal.ts), 2(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/__mocks__/@esri/arcgis-rest-request.ts), 3(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/layers-panel/arcgis-control-panel.tsx), 4(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/layers-panel/arcgis-import-panel/arcgis-import-panel.tsx), 5(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/redux/slices/arcgis-auth-slice.ts), 6(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/redux/slices/arcgis-content-slice.ts), 7(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/utils/arcgis.ts)