Adding results from Feature Layer's query to map

1620
6
Jump to solution
05-07-2019 02:11 PM
Kevin_C
New Contributor III

I'm working with the API for JavaScript to create a map of tornadoes in the US. I'm using sliders and other inputs to allow the user to query the data client-side. I'm having some difficulty getting the results from the query to be added to the map. The client-side slider is supposed to query the layer for number of fatalities from tornado events.

I used this sample code from the API for JS page as reference, but can't figure out where I made a mistake. 

Link to entire code on jsfiddle

Here's the gist of what I did: 

Added an event listener to listen for slider input change. 

fatal.addEventListener("input", function() {
queryFatalities().then(displayResults);
});

Performed query of feature layer using createQuery() method to search for number of fatalities:

function queryFatalities() {
var query = tornadoLayer.createQuery();
query.where = "fat <=" + fatal.value;

return tornadoLayer.queryFeatures(query);
}

Used addMany() method to add queried features to webmap. This is the part that I suspect I screwed up on. 

function displayResults(results) {
resultsLayer.removeAll();
var features = results.features.map(function(graphic) {
graphic.symbol = {
type: "simple-line",
width: 2,
color: "darkorange"
};
return graphic;
});
resultsLayer.addMany(features);
}

Any advice on how to proceed to get this query function working would be much appreciated. 

Thanks!

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Esteemed Contributor

Kevin,

  Here is my update to your code that fixes everything that Noah points out but fixes the tornado layer rending issue introduced by his code and fixes the apps height issue and finishes the other query cases. The only thing I would suggest is that the text says "Showing" but your sliders are just highlighting.

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Tornado paths in the United States, 2017</title>
<style>
html,
body {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}

#header {
height: 52px;
text-align: center
}

#viewDiv {
height: calc(100% - 52px);
}

#sidebar {
z-index: 99;
position: absolute;
top: 52px;
right: 0;
bottom: 0;
background: rgba(176, 204, 231, 0.4);
width: 320px;
padding: 2.0rem;
color: white;
}

#sidebar h5 {
float: left;
clear: left;
;
}

#state,
#efNum {
float: right;
}

.vals {
font-weight: bolder;
}
</style>

<link rel="stylesheet" href="https://js.arcgis.com/4.11/esri/themes/dark-blue/main.css">
<link rel="stylesheet"
href="https://s3-us-west-1.amazonaws.com/patterns.esri.com/files/calcite-web/1.2.5/css/calcite-web.min.css">
<script src="https://s3-us-west-1.amazonaws.com/patterns.esri.com/files/calcite-web/1.2.5/js/calcite-web.min.js">
</script>
<script src="https://js.arcgis.com/4.11/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/widgets/BasemapToggle",
"esri/layers/FeatureLayer",
"esri/widgets/Legend",
"esri/layers/GraphicsLayer",
"esri/Graphic"
], function (Map, MapView, BasemapToggle, FeatureLayer, Legend, GraphicsLayer, Graphic) {

// GraphicsLayer for displaying results
var resultsLayer = new GraphicsLayer();

var map = new Map({
basemap: "dark-gray-vector"
});

var view = new MapView({
container: "viewDiv",
map: map,
center: [-90.049, 35.1495],
zoom: 4.75
});

var basemapToggle = new BasemapToggle({
view: view,
secondMap: "satellite"
});

// add querySelectors for input elements
var state = document.querySelector("#state");
var efNum = document.querySelector("#efNum");
var fatal = document.querySelector("#fatal");
var injury = document.querySelector("#injury");
var loss = document.querySelector("#loss");

fatal.addEventListener("input", function () {
document.querySelector("#fatal-val").innerText = fatal.value;
queryTornados().then(displayResults);
});

injury.addEventListener("input", function () {
document.querySelector("#injury-val").innerText = injury.value;
queryTornados().then(displayResults);
});

loss.addEventListener("input", function () {
document.querySelector("#loss-val").innerText = loss.value;
queryTornados().then(displayResults);
});

state.addEventListener("change", function () {
var type = event.target.value;
if (type === "United States") {
tornadoLayer.visible = true;
}
setTornadoInStateExpression(type);
document.querySelector("#state-val").innerHTML = type;
});

const ef0 = {
type: "simple-line",
color: [239, 170, 110],
width: 1,
style: "solid"
};

const ef1 = {
type: "simple-line",
color: [234, 60, 225],
width: 1,
style: "solid"
};

const ef2 = {
type: "simple-line",
color: [237, 230, 104],
width: 1,
style: "solid"
};

const ef3 = {
type: "simple-line",
color: [165, 18, 18],
width: 1,
style: "solid"
};

const ef4 = {
type: "simple-line",
color: [219, 89, 8],
width: 1,
style: "solid"
};

const other = {
type: "simple-line",
color: [150, 150, 150],
width: 1,
style: "solid"
};

const tornadoRenderer = {
type: "unique-value",
field: "MAG",
defaultSymbol: other,
defaultLabel: "Unclassified EF score (-9)",
uniqueValueInfos: [{
value: 0,
symbol: ef0,
label: "EF 0"
},
{
value: 1,
symbol: ef1,
label: "EF 1"
},
{
value: 2,
symbol: ef2,
label: "EF 2"
},
{
value: 3,
symbol: ef3,
label: "EF 3"
},
{
value: 4,
symbol: ef4,
label: "EF 4"
}
]
}

var tornadoPopups = {
"title": "2017 Tornado information",
"content": "<b>Date: </b> {Date} <br> <b>EF (Fujita) Scale Number: </b> {mag} <br> <b> Injuries: </b> {inj} <br> <b> Fatalities: </b> {fat} <br> <b>Estimated Property Loss ($): </b> {loss}"
};

var tornadoLayer = new FeatureLayer({
url: "https://services5.arcgis.com/njRHYVhl2CMXMsap/arcgis/rest/services/2017_Tornado_paths_in_the_US/FeatureServer",
renderer: tornadoRenderer,
popupTemplate: tornadoPopups
});

map.addMany([resultsLayer, tornadoLayer]);

view.ui.add(basemapToggle, "top-left");

const legend = new Legend({
view: view,
layerInfos: [{
layer: tornadoLayer
}]
});

view.ui.add(legend, "bottom-left");

// query all features from the tornado layer
view.when(function () {
return tornadoLayer.when(function () {
var query = tornadoLayer.createQuery();
return tornadoLayer.queryFeatures(query);
});
})
.then(getValues)
.then(getUniqueValues)
.then(addToSelect);

// return an array of all the values in the State field of the tornado layer
function getValues(response) {
var features = response.features;
var values = features.map(function (feature) {
return feature.attributes.st;
});
return values;
}

// return an array of all unique values in the State field of the tornado layer
function getUniqueValues(values) {
var uniqueValues = [];
values.forEach(function (item, i) {
if ((uniqueValues.length < 1 || uniqueValues.indexOf(item) === -1) &&
item !== ""
) {
uniqueValues.push(item);
}
});
return uniqueValues;
}

// add the unique values to the states select element. Allows user to filter by state.
function addToSelect(values) {
values.sort();
values.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
state.add(option);
});
return setTornadoInStateExpression(state.value);
}

// set the definition expression on the tornado layer to reflect user choice for state

function setTornadoInStateExpression(newValue) {
if (newValue !== "United States") {
tornadoLayer.definitionExpression = "st = '" + newValue + "'";
} else {
tornadoLayer.definitionExpression = "st LIKE '%'";
}
tornadoLayer.visible = true;
return queryforTornadoGeometries();
}

// get all the geometries of the tornado layer. The createQuery() method creates a query object that respects the definitionExpression of the layer
function queryforTornadoGeometries() {
var tornadoQuery = tornadoLayer.createQuery();

return tornadoLayer.queryFeatures(tornadoQuery).then(function (response) {
tornadoGeometries = response.features.map(function (feature) {
return feature.geometry;
});
return tornadoGeometries;
});
}

function queryTornados() {
var query = tornadoLayer.createQuery();
query.where = "fat <= " + fatal.value;
query.where += " AND inj <= " + injury.value;
query.where += " AND loss <= " + loss.value;
if(tornadoLayer.definitionExpression){
query.where += " AND " + tornadoLayer.definitionExpression;
}

return tornadoLayer.queryFeatures(query);
}

function displayResults(results) {
resultsLayer.removeAll();
var features = results.features;
features.map(function(gra) {
gra.symbol = {
type: "simple-line",
width: 3,
color: "cyan"
};
return gra;
});
resultsLayer.addMany(features);
}

});
</script>
</head>

<body>
<div id="header">
<h1>Tornadoes in the United States, 2017</h1>
</div>
<div id="viewDiv">
<div id="sidebar">
<h4>Query the Tornado layer</h4>
<h5>State: </h5> <select name="state" id="state">
<option value="United States">United States</option>
</select>
<h5>Fatalities: </h5> <input type="range" id="fatal" name="Deaths" min="0" max="11" value="11">
<h5>Injuries: </h5> <input type="range" id="injury" name="Injured" min="0" max="88" value="88">
<h5>Loss ($): </h5> <input type="range" id="loss" name="loss" min="0" max="310300000" step="100000"
value="310300000">
<br>
<div id="results">
<h5>Showing tornadoes that occured in <br>
<span class="vals" id="state-val">United States</span> which caused <br>
<span class="vals" id="fatal-val">11</span> or fewer deaths, <br>
<span class="vals" id="injury-val">88</span> or fewer injuries, and <br> $
<span class="vals" id="loss-val">310,000,000</span> or less in financial losses.
</h5>
</div>
</div>
</div>
</body>

</html>

View solution in original post

6 Replies
Noah-Sager
Esri Regular Contributor

Hi Kevin,

I noticed that in your sample you have an issue with the modules. There is a local argument passed in for "Query", but this module is not actually loaded. This means that Query is the alias for "esri/layers/GraphicsLayer", and GraphicsLayer is the alias for "esri/Graphic". Removing the Query local argument will help. It feels like there is more work to be done here, but this moves it one step further (and I can see some changes when using the Fatalities slider).

updated sample

https://codepen.io/noash/pen/xNGaXQ

Kevin_C
New Contributor III

Thanks for the help, Noah! 

I missed the module issue because I was using a bunch of different sample query examples as reference and forgot to do a final check. 

I think the edited code you provided is very helpful, especially because it helped me realize that the crucial component I'm missing is the missing "resultsLayer" in my layers property in the Map object. 

See below:

var map = new Map({
basemap: "dark-gray-vector",
layers: [tornadoLayer, resultsLayer]
});
RobertScheitlin__GISP
MVP Esteemed Contributor

Kevin,

  Here is my update to your code that fixes everything that Noah points out but fixes the tornado layer rending issue introduced by his code and fixes the apps height issue and finishes the other query cases. The only thing I would suggest is that the text says "Showing" but your sliders are just highlighting.

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Tornado paths in the United States, 2017</title>
<style>
html,
body {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}

#header {
height: 52px;
text-align: center
}

#viewDiv {
height: calc(100% - 52px);
}

#sidebar {
z-index: 99;
position: absolute;
top: 52px;
right: 0;
bottom: 0;
background: rgba(176, 204, 231, 0.4);
width: 320px;
padding: 2.0rem;
color: white;
}

#sidebar h5 {
float: left;
clear: left;
;
}

#state,
#efNum {
float: right;
}

.vals {
font-weight: bolder;
}
</style>

<link rel="stylesheet" href="https://js.arcgis.com/4.11/esri/themes/dark-blue/main.css">
<link rel="stylesheet"
href="https://s3-us-west-1.amazonaws.com/patterns.esri.com/files/calcite-web/1.2.5/css/calcite-web.min.css">
<script src="https://s3-us-west-1.amazonaws.com/patterns.esri.com/files/calcite-web/1.2.5/js/calcite-web.min.js">
</script>
<script src="https://js.arcgis.com/4.11/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/widgets/BasemapToggle",
"esri/layers/FeatureLayer",
"esri/widgets/Legend",
"esri/layers/GraphicsLayer",
"esri/Graphic"
], function (Map, MapView, BasemapToggle, FeatureLayer, Legend, GraphicsLayer, Graphic) {

// GraphicsLayer for displaying results
var resultsLayer = new GraphicsLayer();

var map = new Map({
basemap: "dark-gray-vector"
});

var view = new MapView({
container: "viewDiv",
map: map,
center: [-90.049, 35.1495],
zoom: 4.75
});

var basemapToggle = new BasemapToggle({
view: view,
secondMap: "satellite"
});

// add querySelectors for input elements
var state = document.querySelector("#state");
var efNum = document.querySelector("#efNum");
var fatal = document.querySelector("#fatal");
var injury = document.querySelector("#injury");
var loss = document.querySelector("#loss");

fatal.addEventListener("input", function () {
document.querySelector("#fatal-val").innerText = fatal.value;
queryTornados().then(displayResults);
});

injury.addEventListener("input", function () {
document.querySelector("#injury-val").innerText = injury.value;
queryTornados().then(displayResults);
});

loss.addEventListener("input", function () {
document.querySelector("#loss-val").innerText = loss.value;
queryTornados().then(displayResults);
});

state.addEventListener("change", function () {
var type = event.target.value;
if (type === "United States") {
tornadoLayer.visible = true;
}
setTornadoInStateExpression(type);
document.querySelector("#state-val").innerHTML = type;
});

const ef0 = {
type: "simple-line",
color: [239, 170, 110],
width: 1,
style: "solid"
};

const ef1 = {
type: "simple-line",
color: [234, 60, 225],
width: 1,
style: "solid"
};

const ef2 = {
type: "simple-line",
color: [237, 230, 104],
width: 1,
style: "solid"
};

const ef3 = {
type: "simple-line",
color: [165, 18, 18],
width: 1,
style: "solid"
};

const ef4 = {
type: "simple-line",
color: [219, 89, 8],
width: 1,
style: "solid"
};

const other = {
type: "simple-line",
color: [150, 150, 150],
width: 1,
style: "solid"
};

const tornadoRenderer = {
type: "unique-value",
field: "MAG",
defaultSymbol: other,
defaultLabel: "Unclassified EF score (-9)",
uniqueValueInfos: [{
value: 0,
symbol: ef0,
label: "EF 0"
},
{
value: 1,
symbol: ef1,
label: "EF 1"
},
{
value: 2,
symbol: ef2,
label: "EF 2"
},
{
value: 3,
symbol: ef3,
label: "EF 3"
},
{
value: 4,
symbol: ef4,
label: "EF 4"
}
]
}

var tornadoPopups = {
"title": "2017 Tornado information",
"content": "<b>Date: </b> {Date} <br> <b>EF (Fujita) Scale Number: </b> {mag} <br> <b> Injuries: </b> {inj} <br> <b> Fatalities: </b> {fat} <br> <b>Estimated Property Loss ($): </b> {loss}"
};

var tornadoLayer = new FeatureLayer({
url: "https://services5.arcgis.com/njRHYVhl2CMXMsap/arcgis/rest/services/2017_Tornado_paths_in_the_US/FeatureServer",
renderer: tornadoRenderer,
popupTemplate: tornadoPopups
});

map.addMany([resultsLayer, tornadoLayer]);

view.ui.add(basemapToggle, "top-left");

const legend = new Legend({
view: view,
layerInfos: [{
layer: tornadoLayer
}]
});

view.ui.add(legend, "bottom-left");

// query all features from the tornado layer
view.when(function () {
return tornadoLayer.when(function () {
var query = tornadoLayer.createQuery();
return tornadoLayer.queryFeatures(query);
});
})
.then(getValues)
.then(getUniqueValues)
.then(addToSelect);

// return an array of all the values in the State field of the tornado layer
function getValues(response) {
var features = response.features;
var values = features.map(function (feature) {
return feature.attributes.st;
});
return values;
}

// return an array of all unique values in the State field of the tornado layer
function getUniqueValues(values) {
var uniqueValues = [];
values.forEach(function (item, i) {
if ((uniqueValues.length < 1 || uniqueValues.indexOf(item) === -1) &&
item !== ""
) {
uniqueValues.push(item);
}
});
return uniqueValues;
}

// add the unique values to the states select element. Allows user to filter by state.
function addToSelect(values) {
values.sort();
values.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
state.add(option);
});
return setTornadoInStateExpression(state.value);
}

// set the definition expression on the tornado layer to reflect user choice for state

function setTornadoInStateExpression(newValue) {
if (newValue !== "United States") {
tornadoLayer.definitionExpression = "st = '" + newValue + "'";
} else {
tornadoLayer.definitionExpression = "st LIKE '%'";
}
tornadoLayer.visible = true;
return queryforTornadoGeometries();
}

// get all the geometries of the tornado layer. The createQuery() method creates a query object that respects the definitionExpression of the layer
function queryforTornadoGeometries() {
var tornadoQuery = tornadoLayer.createQuery();

return tornadoLayer.queryFeatures(tornadoQuery).then(function (response) {
tornadoGeometries = response.features.map(function (feature) {
return feature.geometry;
});
return tornadoGeometries;
});
}

function queryTornados() {
var query = tornadoLayer.createQuery();
query.where = "fat <= " + fatal.value;
query.where += " AND inj <= " + injury.value;
query.where += " AND loss <= " + loss.value;
if(tornadoLayer.definitionExpression){
query.where += " AND " + tornadoLayer.definitionExpression;
}

return tornadoLayer.queryFeatures(query);
}

function displayResults(results) {
resultsLayer.removeAll();
var features = results.features;
features.map(function(gra) {
gra.symbol = {
type: "simple-line",
width: 3,
color: "cyan"
};
return gra;
});
resultsLayer.addMany(features);
}

});
</script>
</head>

<body>
<div id="header">
<h1>Tornadoes in the United States, 2017</h1>
</div>
<div id="viewDiv">
<div id="sidebar">
<h4>Query the Tornado layer</h4>
<h5>State: </h5> <select name="state" id="state">
<option value="United States">United States</option>
</select>
<h5>Fatalities: </h5> <input type="range" id="fatal" name="Deaths" min="0" max="11" value="11">
<h5>Injuries: </h5> <input type="range" id="injury" name="Injured" min="0" max="88" value="88">
<h5>Loss ($): </h5> <input type="range" id="loss" name="loss" min="0" max="310300000" step="100000"
value="310300000">
<br>
<div id="results">
<h5>Showing tornadoes that occured in <br>
<span class="vals" id="state-val">United States</span> which caused <br>
<span class="vals" id="fatal-val">11</span> or fewer deaths, <br>
<span class="vals" id="injury-val">88</span> or fewer injuries, and <br> $
<span class="vals" id="loss-val">310,000,000</span> or less in financial losses.
</h5>
</div>
</div>
</div>
</body>

</html>

View solution in original post

Kevin_C
New Contributor III

Hi Robert, 

Thanks for taking the time to work on this code. I definitely learned some new things from this. Starting with the way to calculate dimensions in CSS using calc. And huge props for taking the time to combine the 3 different query functions into one queryTornadoes() function that also combines all the queries. 

Thanks!

Kevin

0 Kudos
RobertScheitlin__GISP
MVP Esteemed Contributor

Don't forget to mark this question as answered by clicking on the "Mark Correct" link on the reply that answered your question.

0 Kudos
mgeorge
Esri Contributor

Hi Kevin C‌!

You might want to try out the new FeatureFilter we added in the last release. This allows you to specify a where clause/geometry expression/etc. and filter out everything but those features. Seems like this might be an easier way to accomplish what you are trying to do. We also added (in beta) the ability to specify a FeatureEffect that will similarly change the styling of features that pass or fail the effect's filter (for instance, you could gray out features that don't pass the effect's filter).