I have a MVC project that uses OpenLayers for mapping and Geoserver for the layer storage. Most of the layers are loaded in by way of WMTS. I have one layer that is loaded as a vector so I can access the individual features of it, but is loaded on click because the size of the layer takes to long to load so only certain features are loaded on click/draw, depending on click location or polygon points.
I have events that performs certain things at the end of click/draw. A summary of my relevant code is below:
var global = new Array();
selectInteraction.on('drawend', function (e) {
vectorSource.clear();
map.removeLayer(vector);
var point = e.feature.getGeometry();
global = new Array();
loadFeatures(point);
var feat = global[0];
drawVectorSource.clear();
vectorSource.clear();
});
function loadFeatures(intersect) { // intersect is a polygon/point
vectorSource.clear();
featureRequest = new ol.format.WFS().writeGetFeature({
srsName: 'EPSG:3857',
featureNS: ** url **,
featurePrefix: ** prefix **,
featureTypes: ['feature1'],
outputFormat: 'application/json',
maxFeatures: 1000,
filter: ol.format.filter.and(
ol.format.filter.equalTo('feat_type', 'feature1'),
ol.format.filter.intersects('geom', intersect, 'EPSG:3857')
)
});
fetch(geoserverUrl, {
method: 'POST',
body: new XMLSerializer().serializeToString(featureRequest)
}).then(function (response) {
return response.json();
}).then(function (json) {
var features = new ol.format.GeoJSON().readFeatures(json);
vectorSource.addFeatures(features);
global = new Array();
features.map(function (feature) {
global.push(feature.get('text'));
// In the case of a single point it will only have 1 feature
});
map.addLayer(vector);
});
}
The issue I'm having is with the loadFeatures() function and the setting/accessing of the array named global. I've thrown various breakpoints into the code above and the results have me dumbfounded. I'm not sure if the issue lies with the return from Geoserver fetch or with the javascript code itself.
When the program gets into the loadFeatures() function, it gets to the first part of the fetch call but returns and undefined response from the featureRequest post to the URL. It then proceeds out and over the promises and out of the function, leaving global as still an empty array. This, for obvious reasons, gfails when trying to set var feat = global[0].
If that were all, I'd assume the issue was with the geoserver fetch. But that's not all. If I throw a breakpoint inside of the .then(function (json) { ... } or .then(function (response) { ... }) that follow the fetch, and let the program continue to run after the function call, the break points somehow get hit again, without another call to the loadFeatures(), and the correct features ARE successfull added to the vector source and displayed on the map but not until after all other code has been executed. Also, no other breakpoints inside of the function get hit during that apparent second pass-through except for those inside of the two .then() calls.
This led me to assume that the fetch was just taking a while to give the response. The problem is that I'm a little out of my wheel house with that assumption. I tried my best to make the loadFeatures() function async with promises and even throwing a while loop after the function call in order to give the function time to complete before continuing but all it does is break my chrome and continue endlessly.
Can anyone help shed light onto what it is actually doing? I can't put my finger on whether this is a problem with how I have done my javascript or an error with the communication to geoserver.
Hank, the sort of problems that you're describing sure smell a bit like some asynchronous code isn't being executed as expected. I've got a couple of suggestions that might help.
Unfortunately I’ve never used OpenLayers, only Esri’s ArcGIS API for JavaScript so I don’t totally understand what the code is doing.
First, I would add a .catch() to the end of your Promise chain. It doesn't sound like you are getting Exceptions, but this is good practice.
Second, I would wrap the loadFeatures code in a Promise. This way, your drawend event handler which calls loadFeatures can wait to access the Array called global
after loadFeatures finishes its work and returns. I'd add a getFeatures function that returns a Promise and the features just to stick with the Single Responsibility principle.
The other item that I'm a bit fuzzy on is the use of several global variables that I do not see their declarations. I also see you've called new Array() several times for global and I don't see why that's necessary. You might new it once, then just clear it before filling it again. I'm not sure I follow what you're doing with global anyway and trust that you can debug that variable value.
The key with Promises is to return Promises from all functions when chaining. So check that all Promises are either resolved or rejected. Otherwise, they'll just hang out forever.
Also, could drawend be getting called multiple times? Perhaps not finishing fetching features before it gets called again?
The code below is just a refactor that might help you find the issue. I haven't tested the code and with missing global variable declarations it's my attempt to keep your logic with a bit of cleanup. From looking at a few examples on OpenLayers, it seems you create a new Vector, add features to the Vector object, then add it to the map as a layer.
var globalArray = [];
var vectorSource = new ol.source.Vector();
var vector = new ol.layer.Vector({
source: vectorSource,
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 255, 1.0)',
width: 2
})
})
});
selectInteraction.on('drawend', function (e) {
var point = e.feature.getGeometry();
// Remove all features from the source.
vectorSource.clear();
// Remove the layer from the map
map.removeLayer(vector);
loadFeatures(point).then(function(results) {
// clear the globalArray property
globalArray.length = 0;
// fill globalArray with the results from loadFeatures
globalArray = results;
// Do other stuff...
var feat;
if (global) {
feat = global[0];
}
drawVectorSource.clear();
vectorSource.clear();
}).catch(error) {
// perhaps clear arrays or other cleanup when an error occurs
});
});
function loadFeatures(intersect) { // intersect is a polygon/point
return new Promise(function (resolve, reject) {
getFeatures(intersect).then(function (features) {
vectorSource.addFeatures(features);
var featureTextArray = [];
features.map(function (feature) {
featureTextArray.push(feature.get('text'));
// In the case of a single point it will only have 1 feature
});
map.addLayer(vector);
return resolve(featureTextArray);
}).catch(error) {
// reject the Promise returning the error
reject(error);
});
})
}
function getFeatures(intersect) {
return new Promise(function (resolve, reject) {
var featureRequest = new ol.format.WFS().writeGetFeature({
srsName: 'EPSG:3857',
featureNS: ** url **,
featurePrefix: ** prefix **,
featureTypes: ['feature1'],
outputFormat: 'application/json',
maxFeatures: 1000,
filter: ol.format.filter.and(
ol.format.filter.equalTo('feat_type', 'feature1'),
ol.format.filter.intersects('geom', intersect, 'EPSG:3857')
)
});
fetch(geoserverUrl, {
method: 'POST',
body: new XMLSerializer().serializeToString(featureRequest)
}).then(function (response) {
// return JSON in a Promise
return response.json();
}).then(function (json) {
var features = new ol.format.GeoJSON().readFeatures(json);
// resolve the Promise and return the features
return resolve(features);
}).catch(error) {
// log it and reject the Promise returning the error
reject(error);
});
});
}
Add a breakpoint inside of the two callback functions for the Promises. So, that would be on the call to getFeatures() and the line of code that sets the variable featureRequest. See if it's still getting called twice. Good luck!