Hey there. Learning the Javascript API still and I'm trying to calculate some out statistics that then displays that info in an information box. These statistics are the GDP, Employment numbers, etc of each year for the Oil and Gas industry in Utah.
I keep getting an error related to the outStatistics when I try.
Here's the error in my console:
Here's the link to the feature layer:
Oil and Gas Well Locations Feature Layer
Here's the link to the website:
Oil and Gas Time Lapse Application
Here's Javascript Code:
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/widgets/Expand",
"esri/widgets/Legend",
"esri/widgets/TimeSlider"
], function (Map, MapView, FeatureLayer, Expand, Legend, TimeSlider) {
let OGLayerView;
const layer = new FeatureLayer({
portalItem: {
id: "dd28d5595a2940929574e79522bb4245"
}
});
// Create Map
const map = new Map({
basemap: "topo",
layers: [layer]
});
// Set the map view
const view = new MapView({
container: "viewDiv",
map: map,
center: [-111.65124179920204, 39.707361735142236],
zoom:7
});
// Create a collapsible legend
const legendExpand = new Expand({
collapsedIconClass: "esri-icon-collapse",
expandIconClass: "esri-icon-expand",
expandTooltip: "Legend",
view: view,
content: new Legend({
view: view
}),
expanded: false
});
view.ui.add(legendExpand, "top-left");
// Create events for the time slider
const events = [
{name:`Great Recession`, date: 2008},
{name: `Covid-19 Pandemic`, date: 2020}
];
// Create time slider with interval set to 5 years
const timeSlider = new TimeSlider({
container: "timeSlider",
playRate: 750,
stops: {
interval: {
value: 5,
unit: "years"
}
},
// time slider time extent
//fullTimeExtent: {
// start: new Date(1900,0,1),
// end: new Date(2022,0,1)
//},
// configure ticks for dates
tickConfigs: [{
mode: "position",
values: [
new Date(1900, 0, 1), new Date(1910, 0, 1), new Date(1920, 0, 1), new Date(1930, 0, 1), new Date(1940, 0, 1), new Date(1950, 0, 1), new Date(1960, 0, 1), new Date(1970, 0, 1),
new Date(1980, 0 , 1), new Date(1990, 0, 1), new Date(2000, 0, 1), new Date(2010, 0, 1), new Date(2020, 0, 1)
].map((date) => date.getTime()),
labelsVisible: true,
labelFormatFunction: (value) => {
const date = new Date(value);
return `${date.getUTCFullYear()}`;
},
tickCreatedFunction: (value, tickElement, labelElement) => {
tickElement.classList.add("custom-ticks2");
labelElement.classList.add("custom-labels2");
}
}, {
mode: "position",
values: events
.map((event) => new Date(event.date, 0, 1))
.map((date) => date.getTime()),
labelsVisible: true,
labelFormatFunction: (value) => {
const event = events.find(
(s) => new Date(s.date, 0, 1).getTime() === value
);
return event.name;
},
tickCreatedFunction: (value, tickElement, labelElement) => {
tickElement.classList.add("custom-ticks");
labelElement.classList.add("custom-labels");
}
}
]
});
// add time slider to view
view.ui.add(timeSlider);
// Creating view layer???
view.whenLayerView(layer).then((layerView) => {
OGLayerView = layerView;
// Setting start date for time slider
const start = new Date(1900, 0, 1);
// Extent for time Slider
timeSlider.fullTimeExtent = {
start: start,
end: layer.timeInfo.fullTimeExtent.end
};
// Show 5 year intervals
let end = new Date(start);
end.setDate(end.getDate() + 1825); // the number here is in days (1825 = 5 years)
timeSlider.timeExtent = {start,end};
});
// watch timeslider timeExtent change
timeSlider.watch("timeExtent", () => {
//oil wells that popped up before the end of the current time extent
OGLayerView.filter = {
where: "OrigComplDate <=" + timeSlider.timeExtent.end.getTime(),
}
// add grayscale effect to old wells (may or may not keep this)
OGLayerView.effect = {
filter: {
timeExtent:timeSlider.timeExtent,
geometry: view.extent
},
excludedEffect: "grayscale(80%) opacity(20%)"
};
});
// Run statistics for GDP within current time extent
const statQuery = OGLayerView.effect.filter.createQuery();
statQuery.outStatistics = [
GDPAvg
];
layer.queryFeatures(statQuery).then((result) => {
let htmls = [];
statsDiv.innerHTML = "";
if (result.error) {
return result.error;
} else {
if (result.feature.length >= 1) {
const attributes = result.features[0].attributes;
for (stat in statsFields) {
if (attributes[name] && attributes[name] != null) {
const html =
"<br/>" +
statsFields[name] +
": <b><span>" + // setting bolding and styling
attributes[name].toFixed(0) + // How many decimal places
"</span></b>"; // setting bolding and styling to attribute information
htmls.push(html) // push html into code into information box with attribute information
}
}
const yearHtml =
"<span>" +
result.features[0].attributes["GDP_Average"] +
"</span> billion dollars was added to Utah's GDP by the Oil and Gas Industry between" +
timeSlider.timeExtent.start.toLocaleDateString() +
" and " +
timeSlider.timeExtent.end.toLocaleDateString() +
".<br/>";
if (htmls[0] == undefined) {
statsDiv.innerHTML = yearHtml;
} else {
statsDiv.innerHTML =
yearHtml + htmls[0];
}
}
}
})
.catch((error) => {
console.log(error);
});
const GDPAvg = {
onstatisticField: "GDP_billions_", //using field name instead of field alias??? Don't know if I should. I have tried both ways now
outStatisticFieldName: "GDP_Average",
statisticType: "avg"
};
const statsFields = {
GDP_Average: "GDP Average"
};
const statsDiv = document.getElementById("statsDiv");
const infoDiv = document.getElementById("infoDiv");
const infoDivExpand = new Expand({
collapsedIconClass: "esri-icon-collapse",
expandIconClass: "esri-icon-expand",
expandTooltip: "Expand Oil and Gas Industry info",
view: view,
content: infoDiv,
expanded: true
});
view.ui.add(infoDivExpand, "top-right");
});
If you want:
HTML Code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>OGM Time Lapse</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/light/main.css">
<link rel="stylesheet" href="main.css">
<script src="https://js.arcgis.com/4.20/"></script>
<script type = "text/javascript" src="main.js"></script>
</head>
<body>
<div id="viewDiv"></div>
<div id="timeSlider" class = "sample-slider"></div>
<div id="infoDiv" class="esri-widget">
<div> <b>Growth of the Oil and Gas Industry</b></div><br/>
<div id="statsDiv" class="esri-widget"></div>
</div>
</body>
</html>
CSS Code:
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
.sample-slider {
position: absolute;
left: 2%;
right: 2%;
bottom: 20px;
margin: 20px ;
}
#timeSlider .custom-labels{
font-family: Georgia, 'Times New Roman', Times, serif;
font-size: 10px;
color: red;
margin-top: -35px;
}
#timeSlider .custom-ticks{
background-color: black;
width: 1px;
height: 8px;
margin-top: -30px;
}
#timeSlider .custom-labels2{
font-family: Georgia, 'Times New Roman', Times, serif;
font-size: 12px;
font-weight: bold;
color: black;
margin-top: 7 px;
}
#timeSlider .custom-ticks2{
background-color: black;
width: 1px;
height: 8px;
margin-top: 2px;
}
.esri-time-slider__slider .esri-slider {
margin-top: -5px;
}
#infoDiv {
height: 200px;
padding: 10px;
width: 280px;
}
#infoDiv span {
color: #77496B;
font-size: 12pt;
font-weight: bolder;
}
#titleDiv {
padding: 10px
}
#titleText {
font-family: Georgia, 'Times New Roman', Times, serif;
font-size: 20pt;
font-weight: 60;
padding-bottom: 10px;
}
Link to sample code from Esri that I've been referencing:
Esri Filter with Time Slider Example
Whole code if you want:
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"/>
<title>Filter features with TimeSlider | Sample | ArcGIS API for JavaScript 4.20</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#timeSlider {
width: 400px;
}
#infoDiv {
height: 200px;
padding: 10px;
width: 280px;
}
#infoDiv span {
color: #F9C653;
font-size: 12pt;
font-weight: bolder;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/dark/main.css" />
<script src="https://js.arcgis.com/4.20/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/GeoJSONLayer",
"esri/widgets/TimeSlider",
"esri/widgets/Expand",
"esri/widgets/Legend"
], (Map, MapView, GeoJSONLayer, TimeSlider, Expand, Legend) => {
let layerView;
// set the timeInfo on GeoJSONLayer at the time initialization
const layer = new GeoJSONLayer({
url: "https://bsvensson.github.io/various-tests/geojson/usgs-earthquakes-06182019.geojson",
copyright: "USGS Earthquakes",
title: "USGS Earthquakes",
// set the CSVLayer's timeInfo based on the date field
timeInfo: {
startField: "time", // name of the date field
interval: {
// set time interval to one day
unit: "days",
value: 1
}
},
renderer: {
type: "simple",
field: "mag",
symbol: {
type: "simple-marker",
color: "orange",
outline: null
},
visualVariables: [
{
type: "size",
field: "mag",
stops: [
{
value: 1,
size: "5px"
},
{
value: 2,
size: "15"
},
{
value: 3,
size: "35"
}
]
},
{
type: "color",
field: "depth",
stops: [
{
value: 2.5,
color: "#F9C653",
label: "<2km"
},
{
value: 3.5,
color: "#F8864D",
label: "3km"
},
{
value: 4,
color: "#C53C06",
label: ">4km"
}
]
}
]
},
popupTemplate: {
title: "{title}",
content: [
{
type: "fields",
fieldInfos: [
{
fieldName: "place",
label: "Location",
visible: true
},
{
fieldName: "mag",
label: "Magnitude",
visible: true
},
{
fieldName: "depth",
label: "Depth",
visible: true
}
]
}
]
}
});
const map = new Map({
basemap: "dark-gray-vector",
layers: [layer]
});
const view = new MapView({
map: map,
container: "viewDiv",
zoom: 13,
center: [-117.512764, 34.04355]
});
// create a new time slider widget
// set other properties when the layer view is loaded
// by default timeSlider.mode is "time-window" - shows
// data falls within time range
const timeSlider = new TimeSlider({
container: "timeSlider",
playRate: 50,
stops: {
interval: {
value: 1,
unit: "hours"
}
}
});
view.ui.add(timeSlider, "bottom-left");
// wait till the layer view is loaded
view.whenLayerView(layer).then((lv) => {
layerView = lv;
// start time of the time slider - 5/25/2019
const start = new Date(2019, 4, 25);
// set time slider's full extent to
// 5/25/5019 - until end date of layer's fullTimeExtent
timeSlider.fullTimeExtent = {
start: start,
end: layer.timeInfo.fullTimeExtent.end
};
// We will be showing earthquakes with one day interval
// when the app is loaded we will show earthquakes that
// happened between 5/25 - 5/26.
let end = new Date(start);
// end of current time extent for time slider
// showing earthquakes with one day interval
end.setDate(end.getDate() + 1);
// timeExtent property is set so that timeslider
// widget show the first day. We are setting
// the thumbs positions.
timeSlider.timeExtent = {start, end};
});
// watch for time slider timeExtent change
timeSlider.watch("timeExtent", () => {
// only show earthquakes happened up until the end of
// timeSlider's current time extent.
layer.definitionExpression =
"time <= " + timeSlider.timeExtent.end.getTime();
// now gray out earthquakes that happened before the time slider's current
// timeExtent... leaving footprint of earthquakes that already happened
layerView.effect = {
filter: {
timeExtent: timeSlider.timeExtent,
geometry: view.extent
},
excludedEffect: "grayscale(20%) opacity(12%)"
};
// run statistics on earthquakes fall within the current time extent
const statQuery = layerView.effect.filter.createQuery();
statQuery.outStatistics = [
magMax,
magAvg,
magMin,
tremorCount,
avgDepth
];
layer.queryFeatures(statQuery).then((result) => {
let htmls = [];
statsDiv.innerHTML = "";
if (result.error) {
return result.error;
} else {
if (result.features.length >= 1) {
const attributes = result.features[0].attributes;
for (name in statsFields) {
if (attributes[name] && attributes[name] != null) {
const html =
"<br/>" +
statsFields[name] +
": <b><span> " +
attributes[name].toFixed(2) +
"</span></b>";
htmls.push(html);
}
}
const yearHtml =
"<span>" +
result.features[0].attributes["tremor_count"] +
"</span> earthquakes were recorded between " +
timeSlider.timeExtent.start.toLocaleDateString() +
" - " +
timeSlider.timeExtent.end.toLocaleDateString() +
".<br/>";
if (htmls[0] == undefined) {
statsDiv.innerHTML = yearHtml;
} else {
statsDiv.innerHTML =
yearHtml + htmls[0] + htmls[1] + htmls[2] + htmls[3];
}
}
}
})
.catch((error) => {
console.log(error);
});
});
const avgDepth = {
onStatisticField: "depth",
outStatisticFieldName: "Average_depth",
statisticType: "avg"
};
const magMax = {
onStatisticField: "mag",
outStatisticFieldName: "Max_magnitude",
statisticType: "max"
};
const magAvg = {
onStatisticField: "mag",
outStatisticFieldName: "Average_magnitude",
statisticType: "avg"
};
const magMin = {
onStatisticField: "mag",
outStatisticFieldName: "Min_magnitude",
statisticType: "min"
};
const tremorCount = {
onStatisticField: "mag",
outStatisticFieldName: "tremor_count",
statisticType: "count"
};
const statsFields = {
Max_magnitude: "Max magnitude",
Average_magnitude: "Average magnitude",
Min_magnitude: "Min magnitude",
Average_depth: "Average Depth"
};
// add a legend for the earthquakes layer
const legendExpand = new Expand({
collapsedIconClass: "esri-icon-collapse",
expandIconClass: "esri-icon-expand",
expandTooltip: "Legend",
view: view,
content: new Legend({
view: view
}),
expanded: false
});
view.ui.add(legendExpand, "top-left");
const statsDiv = document.getElementById("statsDiv");
const infoDiv = document.getElementById("infoDiv");
const infoDivExpand = new Expand({
collapsedIconClass: "esri-icon-collapse",
expandIconClass: "esri-icon-expand",
expandTooltip: "Expand earthquakes info",
view: view,
content: infoDiv,
expanded: true
});
view.ui.add(infoDivExpand, "top-right");
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="timeSlider"></div>
<div id="infoDiv" class="esri-widget">
<div> <b>USGS Earthquakes</b></div><br/>
<div id="statsDiv" class="esri-widget"></div>
</div>
</body>
</html>
Any and all suggestions are much appreciated. Javascript isn't a language I am well-versed with. Still in the beginner stages of learning how it all works. Pretty good with python, so I understand the basics of loops, functions, etc.