WMS GetFeatureInfo; multiple layers, different sources - extjs

I'm developing a web application using GeoExt, OpenLayers and having my own GeoServer to serve various maps. Still, I want to let the user add other WMS's if needed, to be able to play around with all desired layers.
Thus, my problem with the GetFeatureInfo request. Right now I have a toolbar button attached to geoext's map panel,
new GeoExt.Action({
iconCls: "feature",
map: map,
toggleGroup: "tools",
tooltip: "Feature",
control: featureControl
})
its control attribute being
var featureControl = new OpenLayers.Control.WMSGetFeatureInfo({
queryVisible: true,
drillDown: true,
infoFormat:"application/vnd.ogc.gml"
});
I've also defined an event listener to do what I really want once I receive the responses, but that is not relevant here. My problem is the following:
Considering the user clicks on a point where there are 2+ visible layers and at least one of them is from a different source, OpenLayers will have to do one AJAX request per different source and, from OpenLayers own documentation,
Triggered when a GetFeatureInfo response is received. The event
object has a text property with the body of the response (String), a
features property with an array of the parsed features, an xy property
with the position of the mouse click or hover event that triggered the
request, and a request property with the request itself. If drillDown
is set to true and multiple requests were issued to collect feature
info from all layers, text and request will only contain the response
body and request object of the last request.
so, yeah, it will obviously wont work like that right away. Having a look at the debugger I can clearly see that, giving two layers from different sources, it actually DOES the request, it's just that it doesn't wait for the first's response and jumps for the next one (obviously, being asynchronous). I've thought about doing the requests one-by-one, meaning doing the first one as stated above and once it's finished and the response saved, go for the next one. But I'm still getting used to the data structure GeoExt uses.
Is there any API (be it GeoExt or OpenLayers) option/method I'm missing? Any nice workarounds?
Thanks for reading :-)
PS: I'm sorry if I've not been clear enough, english is not my mother tongue. Let me know if something stated above was not clear enough :)

i Hope this help to someone else, I realized that: you're rigth this control make the request in asynchronous mode, but this is ok, no problem with that, the real problem is when the control handle the request and trigger the event "getfeatureinfo" so, i modified 2 methods for this control and it works!, so to do this i declare the control first, and then in the savage mode i modified the methods here is de code:
getInfo = new OpenLayers.Control.WMSGetFeatureInfo({ drillDown:true , queryVisible: true , maxFeatures:100 });
//then i declare a variable that help me to handle more than 1 request.....
getInfo.responses = [];
getInfo.handleResponse=function(xy, request) { var doc = request.responseXML;
if(!doc || !doc.documentElement) { doc = request.responseText; }
var features = this.format.read(doc);
if (this.drillDown === false) {
this.triggerGetFeatureInfo(request, xy, features);
} else {
this._requestCount++;
this._features = (this._features || []).concat(features);
if( this._numRequests > 1){
//if the num of RQ, (I mean more than 1 resource ), i put the Request in array, this is for maybe in a future i could be need other properties or methods from RQ, i dont know.
this.responses.push(request);}
else{
this.responses = request;}
if (this._requestCount === this._numRequests) {
//here i change the code....
//this.triggerGetFeatureInfo(request, xy, this._features.concat());
this.triggerGetFeatureInfo(this.responses, xy, this._features.concat());
delete this._features;
delete this._requestCount;
delete this._numRequests;
// I Adding this when the all info is done 4 reboot
this.responses=[];
}
}
}
getInfo.triggerGetFeatureInfo= function( request , xy , features) {
//finally i added this code for get all request.responseText's
if( isArray( request ) ){
text_rq = '';
for(i in request ){
text_rq += request[i].responseText;
}
}
else{
text_rq = request.responseText;
}
this.events.triggerEvent("getfeatureinfo", {
//text: request.responseText,
text : text_rq,
features: features,
request: request,
xy: xy
});
// Reset the cursor.
OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");}
Thanks, you bring me a way for discover my problem and here is the way i solved, i hope this can help to somebody else.

saheka's answer was almost perfect! Congratulations and thank you, I had the same problem, and with it I finally managed to solve it.
What I would change in your code:
isArray() does not work, change it like this: if(request instanceof Array) {...} at the first line of getInfo.triggerGetFeatureInfo()
to show the results in a popup this is the way:
My code:
getInfo.addPopup = function(map, text, xy) {
if(map.popups.length > 0) {
map.removePopup(map.popups[0]);
}
var popup = new OpenLayers.Popup.FramedCloud(
"anything",
map.getLonLatFromPixel(xy),
null,
text,
null,
true
);
map.addPopup(popup);
}
and in the getInfo.triggerGetFeatureInfo() function, after the last line, append:
this.addPopup(map, text_rq, xy);

A GetFeatureInfo request is send as a JavaScript Ajax call to the external server. So, the requests are likely blocked for security reasons. You'll have to send the requests to the external servers by a proxy on your own domain.
Then, configure this proxy in openlayers by setting OpenLayers.ProxyHost to the proper path. For example:
OpenLayers.ProxyHost = "/proxy_script";
See http://trac.osgeo.org/openlayers/wiki/FrequentlyAskedQuestions#ProxyHost for more background information.

Related

Loading PIXI textures, handling failures

I'm working on a map project under React, using react-leaflet, and leaflet-pixi-overlay. Markers are implemented using the PIXI overlay (React 16.13.1, pixi.js 5.3.0, leaflet 1.6.0, leaflet-pixi-overlay 1.8.1).
I am struggling a bit with the PIXI documentation. I would like to use this PIXI.Texture.fromURL method (http://pixijs.download/release/docs/PIXI.Texture.html#.fromURL)
However neither my VS Code environment, nor my compiled source can access this method.
I am using instead PIXI.Texture.from(imageUrl), as well as PIXI.Texture.fromLoader(imageUrl). Both seem to work, but I don't get the difference between the two? The docs don't show these as being promises, yet they seem to work well with an async await?
Then, when a load fails, I don't see how to tell that things went wrong. Actually what I don't see is how to tell that things went right!
If I do:
let failed = false;
let newTexture;
try {
newTexture = await PIXI.Texture.from(url);
} catch (err) {
console.log(`FAILED loading texture from ${url}, err=${err}`);
failed = true;
}
console.log(`valid=${texture.valid}`);
Then:
texture.valid is always false, even when the texture loaded and displays just fine
no error is ever thrown when the url points to nowhere
Any pointers, is there a site with good (recent as of 2020) PIXI references? Am I missing something basic? Thanks.
Edit 07/06/2020:
Issues were largely due to both my IDE and webpack not 'seeing' that I had update pixi.js to 5.3.0, restart of both gave me access to Texture.fromURL.
The Texture.from call is a synchronous one. My understanding is that by default, it will load a 'valid' texture of 1x1 px in case of failure. Texture.fromURL was added to provide an async solution, see https://github.com/pixijs/pixi.js/issues/6514
Looks to me like Texture.fromURL still needs a bit of work, as it doesn't seem to ever return on a failed fetch (at least for relative paths, which is what I use). I see the same thing when using the following approach:
const texture = PIXI.Texture.from(url, {
resourceOptions: { autoLoad: false }}
);
await texture.baseTexture.resource.load();
With a bad relative path, the load function never returns in my test environment.
Then, when a load fails, I don't see how to tell that things went wrong. Actually what I don't see is how to tell that things went right! If I do:
...
Then:
texture.valid is always false, even when the texture loaded and displays just fine
no error is ever thrown when the url points to nowhere
Ok, first thing: please use the newest version of PIXI which is now 5.3.0: https://github.com/pixijs/pixi.js/releases/tag/v5.3.0
Texture.fromUrl was added in this PR: https://github.com/pixijs/pixi.js/pull/6687/files . Please read description of this PR: https://github.com/pixijs/pixi.js/pull/6687 - user bigtimebuddy describes 3 ways to load Texture synchronously. From this you should understand why it didnt worked in your code.
Also see: https://gamedev.stackexchange.com/questions/175313/determine-when-a-pixi-texture-is-loaded-to-clone-it
About error handling, catching errors and checking if Texture is "valid": please try running following example (modified version of yours) :
let failed = false;
let newTexture;
try {
newTexture = await PIXI.Texture.fromURL('https://loremflickr.com/100/100');
// to see how failure works comment above line and uncomment line below:
// newTexture = await PIXI.Texture.fromURL('http://not-existing-site-0986756.com/not_existing.jpg');
} catch (err) {
console.log(`FAILED loading texture`);
console.log(err);
failed = true;
}
console.log('failed: ' + (failed ? 'yes' : 'no'));
console.log(`valid=${typeof newTexture !== 'undefined' ? newTexture.valid : 'variable newTexture is undefined'}`);
console.log(newTexture ? newTexture : 'n/a');
And lastly about method not found in IDE:
I would like to use this PIXI.Texture.fromURL method (http://pixijs.download/release/docs/PIXI.Texture.html#.fromURL)
However neither my VS Code environment, nor my compiled source can access this method.
I use PhpStorm (but other IntelliJ editor should be similar - for example: WebStorm) and it finds this method:
/**
* Useful for loading textures via URLs. Use instead of `Texture.from` because
* it does a better job of handling failed URLs more effectively. This also ignores
* `PIXI.settings.STRICT_TEXTURE_CACHE`. Works for Videos, SVGs, Images.
* #param {string} url The remote URL to load.
* #param {object} [options] Optional options to include
* #return {Promise<PIXI.Texture>} A Promise that resolves to a Texture.
*/
Texture.fromURL = function (url, options) {
var resourceOptions = Object.assign({ autoLoad: false }, options === null || options === void 0 ? void 0 : options.resourceOptions);
var texture = Texture.from(url, Object.assign({ resourceOptions: resourceOptions }, options), false);
var resource = texture.baseTexture.resource;
// The texture was already loaded
if (texture.baseTexture.valid) {
return Promise.resolve(texture);
}
// Manually load the texture, this should allow users to handle load errors
return resource.load().then(function () { return Promise.resolve(texture); });
};
Do you use the development build of Pixi, or production one? ( https://github.com/pixijs/pixi.js/releases ).
Update 2020-07-06:
Your comment:
One thing still not clear to me though: when using an approach based on PIXI.Loader, do the sprites using a given texture get automatically refreshed once the texture has been loaded, or is there a manual refresh process required?
If you use "PIXI.Loader" approach then you can set the "load" callback - in which you should have all resources / textures already loaded. See: https://pixijs.download/dev/docs/PIXI.Loader.html
First you define which resources need to be loaded:
// Chainable `add` to enqueue a resource
loader.add('bunny', 'data/bunny.png')
.add('spaceship', 'assets/spritesheet.json');
loader.add('scoreFont', 'assets/score.fnt');
and then you define the callback:
// The `load` method loads the queue of resources, and calls the passed in callback called once all
// resources have loaded.
loader.load((loader, resources) => {
// resources is an object where the key is the name of the resource loaded and the value is the resource object.
// They have a couple default properties:
// - `url`: The URL that the resource was loaded from
// - `error`: The error that happened when trying to load (if any)
// - `data`: The raw data that was loaded
// also may contain other properties based on the middleware that runs.
sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture);
sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture);
sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture);
});
You can try this way and inside this callback you can observe that texture of each resource is valid - for example: resources.bunny.texture.valid - it should be true.
Also, as you see in that doc, you can use other more advanced features like middleware or other callbacks for error handling etc.

Protractor - Unable to access element due to fixed Top navigation bar

I'm facing the following issue in protractor with jasmine
Click/mouse hover not working because of fixed top navigation bar in my application. I need to click/perform mouse hover on a web page.
Unfortunately that element is displaying behind that fixed navigation bar. So scroll till element present & click by x & y coordinates are not working.
My dependencies are :
protractor version 5.2.2
node 8.9.3
selenium standalone 3.13
chrome driver-2.40
chromebrowser v67
OS- Windows 10
Thanks in advance
Try using prototype executeScript
Just try clicking that element from the browser console using id,name or xpath.
For example :
var el = element(by.module('header'));
var tag = browser.executeScript('return arguments[0].click()', el).then(function() {
expect(something).toMatch(something);
});
Another way, along the same lines as what Bharath Kumar S and knowing JeffC's caveat that this approach is cheating, I had a similar issue where the App-Header kept getting in my way of clicking, and I knew I was willing to never need it (so, for instance, to find other ways to navigate or log out and not check for stuff that was on it). I, therefore, did the following, which solved the problem. Note if you refresh the screen, you have to call it again. Also note I am using a number of functions from https://github.com/hetznercloud/protractor-test-helper, which do what you would expect from their names.
var removeAppHeaderIfAny = async function() {
//this function hides the app header
//it is useful to avoid having covers there when Protractor worries that something else will get the click
let found = false;
try {
found = await waitToBeDisplayed(by.className("app-header"), 2000);
} catch (e) {
let s: string = "" + e;
if (s.search("TimeoutError") != 0) flowLog("presumably fine, cover already removed: " + e);
found = false;
}
if (!found) return;
if (found) {
let coverElement = await element(by.className("app-header"));
browser.executeScript(
"arguments[0].style.visibility='hidden';",
coverElement
);
await waitToBeNotDisplayed(by.className("app-header"), 10000);
}
return;
//note after this is called you will not see the item, so you cannot click it
};
As I look at the code, it strikes me one can probably remove the if (found) and associated brackets at the end. But I pasted in something I know has been working, so I am not messing with that.
As indicated up front, I knew I was willing to forego use of the app-header, and it is a bit crude.

Request Deferrer with Service Worker in PWA

I am making a PWA where users can answer the forms. I want it to make also offline, so when a user fills out a form and does not have the internet connection, the reply will be uploaded when he is back online. For this, I want to catch the requests and send them when online. I wanted to base it on the following tutorial:
https://serviceworke.rs/request-deferrer_service-worker_doc.html
I have managed to implement the localStorage and ServiceWorker, but it seems the post messages are not caught correctly.
Here is the core function:
function tryOrFallback(fakeResponse) {
// Return a handler that...
return function(req, res) {
// If offline, enqueue and answer with the fake response.
if (!navigator.onLine) {
console.log('No network availability, enqueuing');
return;
// return enqueue(req).then(function() {
// // As the fake response will be reused but Response objects
// // are one use only, we need to clone it each time we use it.
// return fakeResponse.clone();
// });
}
console.log("LET'S FLUSH");
// If online, flush the queue and answer from network.
console.log('Network available! Flushing queue.');
return flushQueue().then(function() {
return fetch(req);
});
};
}
I use it with:
worker.post("mypath/add", tryOrFallback(new Response(null, {
status: 212,
body: JSON.stringify({
message: "HELLO"
}),
})));
The path is correct. It detects when the actual post event happens. However, I can't access the actual request (the one displayed in try or fallback "req" is basically empty) and the response, when displayed, has the custom status, but does not contain the message (the body is empty). So somehow I can detect when the POST is happening, but I can't get the actual message.
How to fix it?
Thank you in advance,
Grzegorz
Regarding your sample code, the way you're constructing your new Response is incorrect; you're supplying null for the response body. If you change it to the following, you're more likely to see what you're expecting:
new Response(JSON.stringify({message: "HELLO"}), {
status: 212,
});
But, for the use case you describe, I think the best solution would be to use the Background Sync API inside of your service worker. It will automatically take care of retrying your failed POST periodically.
Background Sync is currently only available in Chrome, so if you're concerned about that, or if you would prefer not to write all the code for it by hand, you could use the background sync library provided as part of the Workbox project. It will automatically fall back to explicit retries whenever the real Background Sync API isn't available.

Publish/Subscribe not working automatically when data added to the mongodb

I have the following publisher and subscriber code.
It works for the first time when the app starts, but when I try to insert data directly into the Mongo database, it will not automatically update the user screen or I don't see the alert popping.
Am I missing something?
Publish
Meteor.publish('userConnections', function(){
if(!this.userId){
return;
}
return Connections.find({userId: this.userId});
})
Subscribe
$scope.$meteorSubscribe('userConnections').then(function () {
var userContacts = $scope.$meteorCollection(Connections);
alert("subscriber userConnections is called");
if (userContacts && userContacts[0]) {
....
}
}, false);
First off, if you are not using angular-meteor 1.3 you should be. The API has changed a lot. $meteorSubscribe has been deprecated!
To directly answer your question, $meteorSubscribe is a promise that gets resolved (only once) when the subscription is ready. So, it will only ever be called once. If you look at the documentation for subscribe you'll see how to make the binding "reactive", by assigning it to a scope variable. In your case it would be something like:
$scope.userContacts = $scope.$meteorCollection(Connections);
Doing it this way, when the collection gets updated, the $scope.userContacts should get updated as well.

Extjs - Generating a unique url for the tabs

I understand that ExtJS uses AJAX for all server side communication, and that ideally there would be only one page per application. But I am exploring the possibility of generating a unique url for a ExtJS tab which the user can then copy from the address bar for later use(traditional web application approach - making a page bookmarkable). Please let me know if anyone has done anything similar.
You can make use of the "hash". This is the portion of the URL which follows the "#" character.
If you only need to react to the hash at time of page load to support the bookmarking feature then you can get away with something like:
Ext.onReady(function() {
var tabPanel = new Ext.TabPanel({
// Configure for use in viewport as needed.
listeners: {
tabchange: function( tabPanel, tab ) {
window.location.hash = '#'+ tab.itemId;
}
}
});
var token = window.location.hash.substr(1);
if ( token ) {
var tab = tabPanel.get(token);
if ( ! tab ) {
// Create tab or error as necessary.
tab = new Ext.Panel({
itemId: token,
title: 'Tab: '+ token
});
tabPanel.add(tab);
}
tabPanel.setActiveTab(tab);
}
});
You may also choose to go further and employ the hashchange event supported in recent versions of most browsers. This will allow you to react to the hash being changed by either user or programmatic means after the page has finished loading:
if ( 'onhashchange' in window ) {
window.onhashchange = function() {
var token = window.location.hash.substr(1);
// Handle tab creation and activation as above.
}
}
It is worth noting that the Ext.History singleton promises functionality similar to this. However, as of ExtJS 3.3.1 it has not been given support for the hashchange event and is instead wholly dependent on a polling interval and a hidden iframe hack. I was not satisfied with its performance in modern browsers - IE in particular - until I rewrote it to use hashchange where available.

Resources