This sample demonstrates how to use a ShadowCastAnalysis to evaluate sunlight duration around a proposed building. The user can interactively adjust the building’s number of floors, width, and position using sliders, while the analysis updates in real time to show how much sunlight reaches a defined project area and neighboring measurement points.

In this example, the goal is to keep the total sunlight duration across the surrounding measurement points above 70 hours, while each individual measurement point should receive at least 5.5 hours of sunlight. Note that the API is flexible enough to support any custom rules or thresholds.

To evaluate a worst-case scenario, the analysis is performed on the day with the least daylight (winter solstice).

To set up the analysis, create a ShadowCastAnalysis instance and configure it with a desired mode, time range, and options for the selected mode. This sample uses "total-duration" mode with visualizeSunlight set to true, so the analysis visualizes accumulated sunlight duration instead of accumulated shadow duration.

The sample also uses the geometry property to limit the visualization to the polygon of the project area. This keeps the color ramp focused on the area being evaluated instead of drawing sunlight duration across the entire scene.

Because the sample uses "total-duration" mode, it configures totalDurationOptions for further customization. The colorStops property maps sunlight duration values, in milliseconds, to a custom color ramp. In this sample, the ramp progresses from darker colors for lower sunlight duration to bright yellow for areas that receive the most sunlight.

Then add it to the scene:

const totalSunlightDuration = new ShadowCastAnalysis({
mode: "total-duration",
date: new Date("2025-12-21"),
startTimeOfDay: 8.25 * 60 * 60 * 1000,
endTimeOfDay: 16.5 * 60 * 60 * 1000,
utcOffset: 1,
// Limit the visualization to the project area polygon.
geometry: projectAreaPolygon,
totalDurationOptions: {
mode: "hourly",
colorStops: [
{ value: 1 * 3600 * 1000, color: [67, 97, 141, 1] }, // dark blue for the 0 ≤ 1h range
{ value: 2 * 3600 * 1000, color: [127, 99, 151, 1] },
{ value: 3 * 3600 * 1000, color: [181, 120, 162, 1] },
{ value: 4 * 3600 * 1000, color: [201, 133, 147, 1] }, // dusty rose for the 3 ≤ 4h range
{ value: 5 * 3600 * 1000, color: [218, 152, 138, 1] },
{ value: 6 * 3600 * 1000, color: [222, 170, 126, 1] },
{ value: 9 * 3600 * 1000, color: [254, 248, 124, 1] }, // bright yellow for the > 8h range
],
},
visualizeSunlight: true,
});
viewElement.analyses.add(totalSunlightDuration);

To query sunlight duration at specific locations, retrieve the ShadowCastAnalysisView3D using the Scene component’s whenAnalysisView, then call getDurationAtScreen to get the duration at a screen coordinate. Because visualizeSunlight is true, the method returns sunlight duration instead of shadow duration.

const analysisView = await viewElement.whenAnalysisView(totalSunlightDuration);
// Query sunlight duration at a screen point
const screenPoint = viewElement.toScreen(point);
const durationMs = await analysisView.getDurationAtScreen({
x: screenPoint.x,
y: screenPoint.y,
});

The sample builds the proposed building programmatically using Mesh.createBox for each floor and meshUtils.merge to combine them into a single mesh. The building geometry updates dynamically when the user adjusts the number of floors, width, or position shift sliders.

const floors = [];
for (let i = 0; i < numFloors; i++) {
const floorMesh = Mesh.createBox(floorOrigin, {
size: { width: 50, depth: 20, height: floorHeight },
});
floors.push(floorMesh);
}
const buildingMesh = meshUtils.merge(floors);