Hi All,
I could use some help (be kind I'm extremely new to JavaScript).
I'm trying to get an array of unique values (MICROFILMS) from all layers/selected features in my map.
My problem is the end result in the program below ends up being an empty array, but logging the array inside the forEach I see my results being built. How do I get the final array after it loops through each layer/feature?
What I think is happening is that my function has not finished adding the values to my array before it moves on. I'm assuming I need to add some sort wait, but I really can't seem to get the logic/syntax down.
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Intro to MapView - Create a 2D map | Sample | ArcGIS API for JavaScript 4.19</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#layerToggle {
top: 20px;
right: 20px;
position: absolute;
z-index: 99;
background-color: white;
border-radius: 8px;
padding: 10px;
opacity: 0.75;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.19/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.19/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/widgets/LayerList",
"esri/widgets/Sketch/SketchViewModel",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngineAsync"
], (
Map,
MapView,
FeatureLayer,
LayerList,
SketchViewModel,
GraphicsLayer,
geometryEngineAsync
) => {
const map = new Map({
basemap: "topo-vector"
});
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 12,
center: [-122.228558,47.303845]
});
//Add Layer
let stormPipes = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Pipes/FeatureServer",
id: "Storm Pipes",
title: "Storm Pipes"
});
map.add(stormPipes);
//Add Layer
let stormCatchBasins = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Catch_Basins/FeatureServer",
id: "Storm Catch Basins",
title: "Storm Catch Basins"
});
map.add(stormCatchBasins);
let microFilms = [];
//layerList with Legend //
const layerList = new LayerList({
view: view,
listItemCreatedFunction: function(event) {
const item = event.item;
if (item.layer.type != "group") {
item.panel = {
content: "legend",
open: false
};
}
}
});
view.ui.add(layerList, "top-right");
//
//polgon sketch tool//////////////////////////
polygonGraphicsLayer = new GraphicsLayer({listMode: "hide"});
map.add(polygonGraphicsLayer);
view.ui.add("select-by-polygon", "top-left");
const selectButton = document.getElementById("select-by-polygon");
selectButton.addEventListener("click", function() {
polygonGraphicsLayer.removeAll();
microFilms = []
view.popup.close();
sketchViewModel.create("rectangle");
});
sketchViewModel = new SketchViewModel({
view: view,
layer: polygonGraphicsLayer,
pointSymbol: {
type: "simple-fill",
color: "yellow",
style: "solid",
outline: {
color: "red",
width: 1
}
}
});
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
sketchViewModel.on("create", async (event) => {
if (event.state === "complete") {
view.map.layers.map(function(lyr){
if (lyr.visible === true && lyr.type != "graphics"){
console.log(lyr.title);
}
});
const geometries = polygonGraphicsLayer.graphics.map(function(graphic){
return graphic.geometry
});
const queryGeometry = await geometryEngineAsync.union(geometries.toArray());
function getMicroFilms (){
view.map.layers.forEach((layer) => {if (layer.visible === true && layer.type != "graphics"){
const query = {
geometry: queryGeometry,
outFields: ["*"]
};
layer.queryFeatures(query).then((results) => {
results.features.forEach((feature) => {if (feature.attributes.MICROFILM != "UNKNOWN"){
microFilms.push(feature.attributes.MICROFILM)}});
console.log(microFilms.filter(onlyUnique))
})
return microFilms
}});
}
console.log(getMicroFilms()); //NEED HELP HERE (this returns an empty array)
}
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div
id="select-by-polygon"
class="esri-widget esri-widget--button esri-widget esri-interactive"
title="Select features by polygon"
> <span class="esri-icon-checkbox-unchecked"></span>
</body>
</html>
Solved! Go to Solution.
Yeah, sorry, you can't just leave 'All' hanging like that, it's actually another promise so it needs a function for its asynchronous result. I've updated your code below to not error out. I'm not sure how you are trying to access the sketch mode, but at least it won't error out when you run it.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Intro to MapView - Create a 2D map | Sample | ArcGIS API for JavaScript 4.19</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#layerToggle {
top: 20px;
right: 20px;
position: absolute;
z-index: 99;
background-color: white;
border-radius: 8px;
padding: 10px;
opacity: 0.75;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.19/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.19/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/widgets/LayerList",
"esri/widgets/Sketch/SketchViewModel",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngineAsync",
"esri/layers/GroupLayer",
"dojo/Deferred",
"dojo/promise/all"
], (
Map,
MapView,
FeatureLayer,
LayerList,
SketchViewModel,
GraphicsLayer,
geometryEngineAsync,
GroupLayer,
Deferred,
all
) => {
const map = new Map({
basemap: "topo-vector"
});
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 12,
center: [-122.228558, 47.303845]
});
//Add Layer
let stormPipes = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Pipes/FeatureServer",
id: "Storm Pipes",
title: "Storm Pipes"
});
map.add(stormPipes);
//Add Layer
let stormCatchBasins = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Catch_Basins/FeatureServer",
id: "Storm Catch Basins",
title: "Storm Catch Basins"
});
map.add(stormCatchBasins);
let microFilms = [];
//layerList with Legend //
const layerList = new LayerList({
view: view,
listItemCreatedFunction: function (event) {
const item = event.item;
if (item.layer.type != "group") {
item.panel = {
content: "legend",
open: false
};
}
}
});
view.ui.add(layerList, "top-right");
//
/* var stormGroup = new GroupLayer({
title: "Storm",
layers: [stormPipes, stormCatchBasins]
});
map.add(stormGroup);
console.log(stormGroup)
*/
//polgon sketch tool//////////////////////////
polygonGraphicsLayer = new GraphicsLayer({ listMode: "hide" });
map.add(polygonGraphicsLayer);
view.ui.add("select-by-polygon", "top-left");
const selectButton = document.getElementById("select-by-polygon");
selectButton.addEventListener("click", function () {
polygonGraphicsLayer.removeAll();
microFilms = []
view.popup.close();
sketchViewModel.create("rectangle");
});
sketchViewModel = new SketchViewModel({
view: view,
layer: polygonGraphicsLayer,
pointSymbol: {
type: "simple-fill",
color: "yellow",
style: "solid",
outline: {
color: "red",
width: 1
}
}
});
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
sketchViewModel.on("create", async (event) => {
if (event.state === "complete") {
view.map.layers.map(function (lyr) {
if (lyr.visible === true && lyr.type != "graphics" && lyr.type != "group") {
console.log(lyr.title);
}
});
const geometries = polygonGraphicsLayer.graphics.map(function (graphic) {
return graphic.geometry
});
const queryGeometry = await geometryEngineAsync.union(geometries.toArray());
let promises = [];
//let microFilms = [];
function getMicroFilms() {
view.map.layers.forEach((layer) => {
if (layer.visible === true && layer.type != "graphics") {
const query = {
geometry: queryGeometry,
outFields: ["*"]
};
let defLayer = new Deferred;
promises.push(defLayer);
layer.queryFeatures(query)
.then((results) => {
results.features.forEach((feature) => {
if (feature.attributes.MICROFILM != "UNKNOWN") {
microFilms.push(feature.attributes.MICROFILM);
}
});
return defLayer.resolve(results);
})
.catch(function (error) {
console.error(outError);
return defAmenities.reject(error);
});
}
});
all(promises).then(function (results) {
console.log("After all promises are returned");
console.log(results);
console.log(microFilms); //.filter(onlyUnique));
return microFilms;
});
}
console.log(microFilms); //getMicroFilms());
}
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="select-by-polygon"
class="esri-widget esri-widget--button esri-widget esri-interactive"
title="Select features by polygon"> <span class="esri-icon-checkbox-unchecked"></span>
</body>
</html>
You could probably benefit from cleaning up your indentation to keep track of your scopes. It looks like the return statement for getMicroFilms() is inside the forEach() loop; meaning it will kick out after checking the first feature in the FeatureSet. This is probably what you want the function to look like.
function getMicroFilms () {
view.map.layers.forEach((layer) => {
if (layer.visible === true && layer.type != "graphics") {
const query = {
geometry: queryGeometry,
outFields: ["*"]
};
layer.queryFeatures(query).then((results) => {
results.features.forEach((feature) => {
if (feature.attributes.MICROFILM != "UNKNOWN") {
microFilms.push(feature.attributes.MICROFILM);
}
});
console.log(microFilms.filter(onlyUnique));
});
}
});
return microFilms;
}
Thanks for the response! That does make things a bit more clear, but I'm still getting an empty array.
Try logging the query results before you start looping so you can verify if there are actually any features where MICROFILM != "UNKNOWN"
The layers forEach will cycle through each layer synchronously, but the call to 'queryFeatures' results in an asynchronous promise. Esri has a PromiseAll option that I haven't been able to figure out yet, but I have good results using the Dojo versions.
If you add "dojo/Deferred", "dojo/promise/all" to the 'require' statement (and the associated variables), you might try something like this:
let promises = [];
let microFilms = [];
function getMicroFilms () {
view.map.layers.forEach((layer) => {
if (layer.visible === true && layer.type != "graphics") {
const query = {
geometry: queryGeometry,
outFields: ["*"]
};
let defLayer = new Deferred;
promises.push(defLayer);
layer.queryFeatures(query)
.then((results) => {
results.features.forEach((feature) => {
if (feature.attributes.MICROFILM != "UNKNOWN") {
microFilms.push(feature.attributes.MICROFILM);
}
});
return defLayer.resolve(results);
})
.catch(function (error) {
console.error(outError);
return defAmenities.reject(error);
});
}
});
all(promises) {
console.log(microFilms.filter(onlyUnique));
return microFilms;
}
}
That may work. If not, separate the queryFeatures calls into another function and create the deferred there. Either way the All will only run after all of the separate deferreds have run successfully or failed.
Thank you, I think this might work but I could use some help implementing. I've updated the requirements and function but am getting an error at line: all(promises) {
Error: ';' expected
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Intro to MapView - Create a 2D map | Sample | ArcGIS API for JavaScript 4.19</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#layerToggle {
top: 20px;
right: 20px;
position: absolute;
z-index: 99;
background-color: white;
border-radius: 8px;
padding: 10px;
opacity: 0.75;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.19/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.19/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/widgets/LayerList",
"esri/widgets/Sketch/SketchViewModel",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngineAsync",
"esri/layers/GroupLayer",
"dojo/Deferred",
"dojo/promise/all"
], (
Map,
MapView,
FeatureLayer,
LayerList,
SketchViewModel,
GraphicsLayer,
geometryEngineAsync,
GroupLayer,
Deferred,
all
) => {
const map = new Map({
basemap: "topo-vector"
});
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 12,
center: [-122.228558,47.303845]
});
//Add Layer
let stormPipes = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Pipes/FeatureServer",
id: "Storm Pipes",
title: "Storm Pipes"
});
map.add(stormPipes);
//Add Layer
let stormCatchBasins = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Catch_Basins/FeatureServer",
id: "Storm Catch Basins",
title: "Storm Catch Basins"
});
map.add(stormCatchBasins);
let microFilms = [];
//layerList with Legend //
const layerList = new LayerList({
view: view,
listItemCreatedFunction: function(event) {
const item = event.item;
if (item.layer.type != "group") {
item.panel = {
content: "legend",
open: false
};
}
}
});
view.ui.add(layerList, "top-right");
//
/* var stormGroup = new GroupLayer({
title: "Storm",
layers: [stormPipes, stormCatchBasins]
});
map.add(stormGroup);
console.log(stormGroup)
*/
//polgon sketch tool//////////////////////////
polygonGraphicsLayer = new GraphicsLayer({listMode: "hide"});
map.add(polygonGraphicsLayer);
view.ui.add("select-by-polygon", "top-left");
const selectButton = document.getElementById("select-by-polygon");
selectButton.addEventListener("click", function() {
polygonGraphicsLayer.removeAll();
microFilms = []
view.popup.close();
sketchViewModel.create("rectangle");
});
sketchViewModel = new SketchViewModel({
view: view,
layer: polygonGraphicsLayer,
pointSymbol: {
type: "simple-fill",
color: "yellow",
style: "solid",
outline: {
color: "red",
width: 1
}
}
});
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
sketchViewModel.on("create", async (event) => {
if (event.state === "complete") {
view.map.layers.map(function(lyr){
if (lyr.visible === true && lyr.type != "graphics" && lyr.type != "group"){
console.log(lyr.title);
}
});
const geometries = polygonGraphicsLayer.graphics.map(function(graphic){
return graphic.geometry
});
const queryGeometry = await geometryEngineAsync.union(geometries.toArray());
let promises = [];
let microFilms = [];
function getMicroFilms () {
view.map.layers.forEach((layer) => {
if (layer.visible === true && layer.type != "graphics") {
const query = {
geometry: queryGeometry,
outFields: ["*"]
};
let defLayer = new Deferred;
promises.push(defLayer);
layer.queryFeatures(query)
.then((results) => {
results.features.forEach((feature) => {
if (feature.attributes.MICROFILM != "UNKNOWN") {
microFilms.push(feature.attributes.MICROFILM);
}
});
return defLayer.resolve(results);
})
.catch(function (error) {
console.error(outError);
return defAmenities.reject(error);
});
}
});
all(promises) {
console.log(microFilms.filter(onlyUnique));
return microFilms;
}
}
console.log(getMicroFilms());
}});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div
id="select-by-polygon"
class="esri-widget esri-widget--button esri-widget esri-interactive"
title="Select features by polygon"
> <span class="esri-icon-checkbox-unchecked"></span>
</body>
</html>
Yeah, sorry, you can't just leave 'All' hanging like that, it's actually another promise so it needs a function for its asynchronous result. I've updated your code below to not error out. I'm not sure how you are trying to access the sketch mode, but at least it won't error out when you run it.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Intro to MapView - Create a 2D map | Sample | ArcGIS API for JavaScript 4.19</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#layerToggle {
top: 20px;
right: 20px;
position: absolute;
z-index: 99;
background-color: white;
border-radius: 8px;
padding: 10px;
opacity: 0.75;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.19/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.19/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/widgets/LayerList",
"esri/widgets/Sketch/SketchViewModel",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngineAsync",
"esri/layers/GroupLayer",
"dojo/Deferred",
"dojo/promise/all"
], (
Map,
MapView,
FeatureLayer,
LayerList,
SketchViewModel,
GraphicsLayer,
geometryEngineAsync,
GroupLayer,
Deferred,
all
) => {
const map = new Map({
basemap: "topo-vector"
});
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 12,
center: [-122.228558, 47.303845]
});
//Add Layer
let stormPipes = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Pipes/FeatureServer",
id: "Storm Pipes",
title: "Storm Pipes"
});
map.add(stormPipes);
//Add Layer
let stormCatchBasins = new FeatureLayer({
url: "https://services9.arcgis.com/jyf59MjuiWfY46oy/arcgis/rest/services/Storm_Catch_Basins/FeatureServer",
id: "Storm Catch Basins",
title: "Storm Catch Basins"
});
map.add(stormCatchBasins);
let microFilms = [];
//layerList with Legend //
const layerList = new LayerList({
view: view,
listItemCreatedFunction: function (event) {
const item = event.item;
if (item.layer.type != "group") {
item.panel = {
content: "legend",
open: false
};
}
}
});
view.ui.add(layerList, "top-right");
//
/* var stormGroup = new GroupLayer({
title: "Storm",
layers: [stormPipes, stormCatchBasins]
});
map.add(stormGroup);
console.log(stormGroup)
*/
//polgon sketch tool//////////////////////////
polygonGraphicsLayer = new GraphicsLayer({ listMode: "hide" });
map.add(polygonGraphicsLayer);
view.ui.add("select-by-polygon", "top-left");
const selectButton = document.getElementById("select-by-polygon");
selectButton.addEventListener("click", function () {
polygonGraphicsLayer.removeAll();
microFilms = []
view.popup.close();
sketchViewModel.create("rectangle");
});
sketchViewModel = new SketchViewModel({
view: view,
layer: polygonGraphicsLayer,
pointSymbol: {
type: "simple-fill",
color: "yellow",
style: "solid",
outline: {
color: "red",
width: 1
}
}
});
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
sketchViewModel.on("create", async (event) => {
if (event.state === "complete") {
view.map.layers.map(function (lyr) {
if (lyr.visible === true && lyr.type != "graphics" && lyr.type != "group") {
console.log(lyr.title);
}
});
const geometries = polygonGraphicsLayer.graphics.map(function (graphic) {
return graphic.geometry
});
const queryGeometry = await geometryEngineAsync.union(geometries.toArray());
let promises = [];
//let microFilms = [];
function getMicroFilms() {
view.map.layers.forEach((layer) => {
if (layer.visible === true && layer.type != "graphics") {
const query = {
geometry: queryGeometry,
outFields: ["*"]
};
let defLayer = new Deferred;
promises.push(defLayer);
layer.queryFeatures(query)
.then((results) => {
results.features.forEach((feature) => {
if (feature.attributes.MICROFILM != "UNKNOWN") {
microFilms.push(feature.attributes.MICROFILM);
}
});
return defLayer.resolve(results);
})
.catch(function (error) {
console.error(outError);
return defAmenities.reject(error);
});
}
});
all(promises).then(function (results) {
console.log("After all promises are returned");
console.log(results);
console.log(microFilms); //.filter(onlyUnique));
return microFilms;
});
}
console.log(microFilms); //getMicroFilms());
}
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="select-by-polygon"
class="esri-widget esri-widget--button esri-widget esri-interactive"
title="Select features by polygon"> <span class="esri-icon-checkbox-unchecked"></span>
</body>
</html>
You're awesome, this is exactly what I needed! Thank you!
Glad you figured it out. Just one last thing, the 'catch' for the layer.queryFeatures should be:
.catch(function (error) {
console.error(outError);
return defLayer.reject(error);
});