XP360 Integration with ArcGIS JS API

Combining AerialSpheres XP360 API into existing location based API’s is incredibly straight forward. In this explanation we will be breaking down how to take ESRIs JavaScript API and have it communicate with XP360.

For this demo we will utilizing several ESRI get-started links located on the right.




For the demo above, we are initializing two separate maps that allow us to render both a 2d (flat) map and a 3d (globe) scene. Let’s make sure to setup our index file with the proper script imports per ESRIs documentation.








Now lets cover starting up the ESRI maps.

Displayed are all of the required modules for this demo. These modules will allow us to:

  1. Assign our personal ESRI API key

  2. Start-up the map(s)

  3. Apply graphics to the map

    1. used for data points to show

  4. Apply data to the map














Now that we have ESRI configured, lets start up our maps:

  1. First we are will initialize a 2d map.

  2. Please note that as of right now, we are using variables to set certain fields inside of:

    1. The new map

    2. The feature layer

    3. These will filled in but variables when making the call to initialize this map, however you can fill in the values from the ESRI walkthrough while following this to see it updating as we go along.









The 3d Scene functions just the same, however while working on this demo itself, there was inconsistent behavior when setting the map center. So, we are telling the map to go to the center after first loading.

Again you can use the data from ESRIs demo to fill in until fully complete.











Now we have to figure out how to give the AerialSphere viewer our data when we click on a marker. Let’s do some house keeping before actually setting up the Viewer.

This will setup our popup. You can setup how it handles closing the pop-up to your own specific liking, we will be attaching a close function later on.









Now let’s setup a click function for handling either the public ESRI data, or our public AerialSphere data.

Phew, this one has a bit more going on:

  1. We continue to use variables that will be provided later when calling the ESRI initialization.

    1. You can continue to use the ESRI demo data here, for the layer title you can use whatever layer title you would like.

  2. Since we are using multiple potential data sources, we need to handle those accordingly

    1. Since the AerialSphere data is also setup for OIC data, we need to just parse the CamOri string for the lat/lon data.

    2. For a breakdown of what the Camera Orientation string does and how the values are listed you can click here.

  3. We also need to handle sending the information over to the viewer, we have a initAerialShere() function that is taking a lat / lon / icon / layerName

  4. Don’t forget you should have separate click handlers for both your 2d and 3d maps. Otherwise later on they will mix in negative ways.












Now that the handle click function is built out, make sure you attach the correct one to the correct map.









It is now time to setup our AerialSphere viewer using the available documentation here.

Make sure to add the script imports into your index file and also create the viewer.js file.



We already know that on click we will be sending data over to the initAerialSphere() function and it will have 4 variables.

Let’s also add in the brains of that operation. Per the documentation, you need to initialize the AerialSphere class and pass along basic information to start. What we will do is check if that class is already initialized in a variable, if it is we will only send the updated information. Which will be the new lat/lng pair and the accompanying icon data, if that exists.





























That is it for the viewer, now lets setup a trigger on the page load to call the ESRI initialization that will get everything rolling.


This file has a few things going on:

  1. We setup the closePopup function mentioned earlier that will be attached after the entire window loads.

  2. Inside the window.onload() callback we are:

    1. loading 2 seperate instances of the ESRI map, one for flat 2d and one for 3d.

    2. We are also calling the setOnClick() function to attach the close functionality to the popup. This can be done several ways, you might even just attach it straight to the button.


















Since we are now passing a bunch of variables to the loadEsriMap() function we need to add that into our esri_map.js file. Once completed you should have multiple maps loading with data!




  1. Getting Started

  2. Displaying a Map

  3. Adding a Point

  4. Add a feature layer



<html> <head> <link rel="stylesheet" href="https://js.arcgis.com/4.23/esri/themes/light/main.css"> <script src="https://app.aerialsphere.com/api/map/js?key=d84a1e7f-2cc2-4e2d-8d8d-f186fe4038f3"></script> <script src="./esri_map.js" defer></script> </head> <body> <div id="esri-map-combined"></div> <div id="esri-3d-map-combined"></div> </body> </html>




let view; let map; let featureLayer; let graphicsLayer; //Scene prefixed variables used for 3d map let sceneView; let sceneMap; let sceneGraphicsLayer; let sceneFeatureLayer; require([ "esri/config", "esri/Map", "esri/views/MapView", "esri/views/SceneView", "esri/Graphic", "esri/layers/GraphicsLayer", "esri/layers/FeatureLayer" // Not loading Graphic in the function causes the maps to not load ], function (esriConfig, Map, MapView, SceneView, Graphic, GraphicsLayer, FeatureLayer) { esriConfig.apiKey = "YOUR ESRI API KEY HERE" });




map = new Map({ basemap: "arcgis-topographic" // Basemap layer }); view = new MapView({ map: map, center: mapCenter, zoom: 13, // scale: 72223.819286 container: mapId, constraints: { snapToZoom: false } }); graphicsLayer = new GraphicsLayer(); map.add(graphicsLayer); featureLayer = new FeatureLayer({ url: featureLayerUrl, outFields: outFields, title: layerTitle }); map.add(featureLayer);




sceneMap = new Map({ basemap: "topo-vector", // Basemap layer ground: "world-elevation" }); sceneView = new SceneView({ container: mapId, map: sceneMap, }); // Once the scene is loaded, move to desired center // 3d scenes are intended to have the same center indication as 2d maps, // however during development this approach was much more consistent to work. sceneView.when(() => { sceneView.goTo({ center: mapCenter, zoom: 13, // scale: 72223.819286 heading: 30, tilt: 60, }) });





<div id="viewer-popup" class="viewer-popup"> <div class="popup-title"> <div class="title-text"> AerialSphere Viewer </div> <div> <Button id="popup-button" class="popup-close"> Close </Button> </div> </div> <div class="popup-body"> <div id="aerialSphereDivId" class="aerialsphere-map"></div> </div> </div>



const handleSceneClick = (event) => { view.hitTest(event).then((response) => { if (response.results.length > 0){ const result = response.results[0].graphic; if (result && result.layer.title == layerTitle){ let { CamOri } = result.attributes; let useLat; let useLng; let icon = undefined; const layerName = 'trail_heads' // If CamOri exists, the layer data is from AerialSphere if (CamOri){ // Our data is presented in an easy to read fashion for Oriented Imagery Catalogs // As such we do not have a Lat/Lon field. Instead the CamOri data string includes many items // separated by pipes '|'. As such we split ths string and grab the lat/lon from that data. oriArray = CamOri.split('|'); useLat = oriArray[4]; useLng = oriArray[3]; } else { let { LAT, LON } = result.attributes; useLat = LAT; useLng = LON icon = "hiking" } console.log(`This lat/lng is: lat: ${useLat} and lng: ${useLng}`) // We display the popup and then give the viewer data via our initAerialSphere(); const popup = document.getElementById('viewer-popup') popup.style.display = 'block'; initAerialSphere(parseFloat(useLat), parseFloat(useLng), icon, layerName); } } }) };





view = new MapView({ map: map, center: mapCenter, zoom: 13, // scale: 72223.819286 container: mapId, constraints: { snapToZoom: false } }); sceneView.on("click", handleSceneClick);



<head> <script src="https://app.aerialsphere.com/api/map/js?key=YOUR AS API KEY HERE"></script> <script src="./aerialsphere_viewer.js" defer></script> </head>



let aerialSphere; // Default UI options to pass when AS Viewer is not already initialized. const uiOptions = { fullScreen: true, snapshot: false, help: false, info: false, view_toggle: false, navigation: true, } const initAerialSphere = (lat, lon, icon, layerName) => { const sphereData = { sphereLat: lat, sphereLng: lng, lookAtLat: lat, lookAtLng: lng, layers: [] } if (icon){ console.log("adding icon"); // How detailed the marker data from the feature layer is determines how detailed // the marker itself can be. sphereData.layers = [ { name: layerName, visible: true, markers: [ { lat: lat, lng: lng, icon: icon, popup: false } ] } ] } if (!aerialSphere){ sphereData.apiUIOptions = uiOptions; sphereData.sphereMarkerDistance = 0; aerialSphere = new AerialSphere( 'aerialSphereDivId', 'aerialSphereIframeClassname', sphereData ); } else { aerialSphere.sendData(sphereData); } }




<head> <script src="./on_load.js" defer></script> </head>


const closePopup = () => { const popup = document.getElementById('viewer-popup') popup.style.display = 'none'; console.log('closing') } const setOnClick = () => { const popupButton = document.getElementById('popup-button') popupButton.onclick = closePopup; } window.onload = () => { loadEsriMap( "3d", "esri-3d-map-combined", [ -118.805, 34.027 ], "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trailheads/FeatureServer/0", ['LAT', 'LON'], 'trail-heads-layer' ); loadEsriMap( "2d", "esri-map-combined", [ -117.213749, 34.055873 ], "https://services8.arcgis.com/cCqrcN8hCy9iDNrr/arcgis/rest/services/Redlands_CA_ExposurePoints/FeatureServer/1", ['CamOri'], "aerialSphere-layer" ); setOnClick(); }





const loadEsriMap = (mapType, mapId, mapCenter, featureLayerUrl, outFields, layerTitle) => { require([ ... // your requires go here ], () => { if (mapType === "3d"){ //create scene view } else { //create 2d view }; }); }