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:
- 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.
- Authorization: Once authenticated, the system confirms you have the rights to access the content you're requesting.
- 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.
- 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 inArcGisControlPanel.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
) usescreateAsyncThunk
to perform an asynchronous operation: callingarcGisRequestLogin()
fromsrc/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 callsupdateSessionInfo
which serializes the session and the user's email intolocalStorage
for persistence across sessions and retrieves the user's email.- The
arcGisLogin
thunk completes, and theextraReducers
inarcgis-auth-slice.ts
update theuser
state in Redux with the logged-in user's email. - Components like
ArcGisControlPanel
read the updateduser
state usingselectUser
, 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
) usescreateAsyncThunk
to callgetArcGisUserContent()
fromsrc/utils/arcgis.ts
. getArcGisUserContent
first retrieves the saved session fromlocalStorage
usinggetArcGisSession
.- 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 theextraReducers
inarcgis-content-slice.ts
update thearcGisContent
state with the fetched list and set thestatus
to 'idle'. - The
ArcGisImportPanel
reads thearcGisContent
state usingselectArcGisContent
and renders the list in the modal.
3. Loading Secure Content:
- When "Import Selected" is clicked, the
handleImport
function inArcGisImportPanel
retrieves the fullIArcGisContent
object for the selected item from the Redux state. This object includes the item'surl
and the fetchedtoken
. - It calls the
onImport
prop, passing an object containing thename
,url
, andtoken
. - 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
orArcgisWrapper
) renders the layers based on the updated state, it uses the layer definition which now includes thetoken
. - The underlying map library (e.g.,
loaders.gl
'sI3SLoader
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)