Clear the extent GUI element created by QgsMapToolExtent prior to QGIS 3.20 - qgis

Since QGIS 3.20 the QgsMapToolExtent class has a clearRubberBand method.
For versions of QGIS prior to 3.20, it appears that the element created by QgsMapToolExtent is not a QgsRubberBand, but rather a QGraphicsItem.
What is a safe/robust way of clearing the extent element off the map canvas, without unsetting the map tool?
The following snippet instantiates the map tool and sets it to the map canvas. Dragging a rectangle on the canvas leaves the extent object that I want to clear.
canvas = iface.mapCanvas()
t = QgsMapToolExtent(canvas)
canvas.setMapTool(t)
The object appears in the list of iface.mapCanvas().scene().items() but I don't know how to properly identify it. It is not necessarily the last element in the list, and it is not assigned an objectName() or a toolTip().
items = [x for x in canvas.scene().items()]

The method deactivate() clears the extent GUI element without unsetting the tool.
canvas = iface.mapCanvas()
t = QgsMapToolExtent(canvas)
canvas.setMapTool(t)
# after dragging an extent:
t.deactivate()

Related

only showing the overlays that have elements in the viewport

I'd like it if the Layer Control dialog only showed overlays that had elements in the viewport. I setup an example here that illustrates what I mean:
https://jsfiddle.net/7e84rh06/2/
So in that example the "Cities" overlay has no elements but if you click on "Parks" one element for that overlay does appear. So that overlay has elements in the viewport whereas the "Cities" overlay doesn't. Consequently I'd like it if the dialog only showed "Parks". eg. this:
...instead of this:
I'd like this to be dynamic too. eg. if more elements from other overlays enter into the viewport when zooming out then I'd like additional checkboxes to appear in that dialog and if no elements remain in the viewport from an overlay then I'd like that the checkbox corresponding to that overlay to disappear.
Is this even possible?
(SO requires jsfiddle.net links be accompanied by code so... token code)
Building off of Grzgorz T's comments... all the markers can be grouped together in a FeatureGroup and then you can call getBounds() on that. Let's save the resultant LatLngBounds to markerBounds.
Then, you create a function that removes the overlay from the layerControl (layerControl.removeLayer(cities)), calls map.getBounds() and then checks to see if the LatLngBounds returned by getBounds overlaps with bounds from the FeatureGroup. ie. map.getBounds().overlaps(markerBounds). If they do overlap then you readd the overlay. eg. layerControl.addOverlay(cities, 'Cities').
You then run this new function once and you create an event handler to run it every time the moveend event happens. eg. map.on('moveend', function() { callFunc(); });

Changing DOM content of konva container causes crash?

I try to save the content of my last konva stage object in a Map in window object. My original aim is to minimize the loading time when I want to open the same page that has been drawn. Things are going well within my own frame. However, when I load my frame which has Konva content in another window (which becomes the parent of my Konva frame), the konva frame crashes and the console does not show any errors.
My code snipplet looks like this:
var xjGraph = cachedGraphObject; //Konva graph that already drawn, loaded from a js Map in parent window
var containerDOM = document.getElementById(oldId);
var konvaJsContent = xjGraph.getStage().content; //get the div of "konvajs-content" class
var oldChild = containerDOM.firstChild;
oldChild.remove();
containerDOM.appendChild(konvaJsContent); //remove the old div of "konvajs-content" and replace it with the one that I want to show; I could use replaceChild() here, but I get the same result
PS: If there is another way to save a stage in browser and load it when I need it for later use, please kindly help. Very much appreciated!

Using dat.gui slider triggers orbit controls

I'm building an origami simulator, I want to be able to 'fold' the paper using a dat.gui slider. I want to also include orbital controls.
However, when I click on the slider and subsequently move my mouse out of the dat.gui window, the orbital controls have been triggered causing the model to jump to a different orientation.
Is there a way to prevent orbital controls from being triggered when I click in the dat.gui window?
Quoting the docs:
Constructor
OrbitControls( object : Camera, domElement : HTMLDOMElement )
object: (required) The camera to be controlled.
domElement: (optional) The HTML element used for event listeners. By default this is the whole document, however if you only want to the controls to work over a specific element (e.g. the canvas) you can specify that here.
In other words, to restrict the OrbitControls so that they don't enable when you click on the GUI, you must supply a reference to the canvas (renderer.domElement) or container (a div that contains the canvas) as second argument to the constructor. Happy coding! (And folding!)
My solution to your question is to: have dat.GUI "listen" to the values controlled by OrbitControls (the camera), so that the values are in sync between the two different "controllers" (OrderbitControls and dat.GUI), like so:
let gui = new dat.GUI();
let folder1 = gui.addFolder("Camera Position");
folder1.add(camera.position, "x", -100, 100).listen();
folder1.add(camera.position, "y", -100, 100).listen();
folder1.add(camera.position, "z", -100, 100).listen();
The listen() function on a tracked variable in dat.GUI will keep the values in sync, and relieve the "jumpiness" you are experiencing.
Reference to dat.GUI Controller functions documentation:
dat.gui
OrbitControls has an "enabled" property (in fact, I believe all of the controls have an "enabled" property - might want to check though). So if you have
orbitControls = new THREE.OrbitControls(...);
...
// in your dat.GUI block of code (or wherever you want to disable orbitControls)
orbitControls.enabled = false;
// this should disable orbitControls, and when you want to use orbitControls again, just do
orbitControls.enabled = true;

No setBounds function for Leaflet imageOverlay

I'm reading an imageOverlay URL from an ArcGIS webserver that uses the leaflet getBound() coordinates as part of the URL (we have large maps that are filtered for the current window 'extent'). Apologies for not including the actual path (I'm working with sensitive client data). Eg:
http://myarcgiswebserver.com/MapServer/export/dpi=96&format=png32&bbox=27.119750976562504%2C-31.194007509998823%2C32.39044189453126%2C-29.692824739380754&size=1719%2C434
[bbox] = current imageBounds
When dragging my map the imageOverlay url is updated correctly but my leaflet window is no longer aligned to the imageBound values that were set when first adding the imageOverlay which results in a skewed output (this is my assumption):
The only workaround is to remove the existing imageOverlay and add a new one (which ruins the user experience as the map disappears then reappears each time the window is dragged or zoomed).
Am i approaching this problem incorrectly or would the introduction of a function to update the current imageBounds resolve this? Perhaps not a new function but the expansion of setUrl with additional parameters...?
Many thanks for any feedback...
As #ghybs pointed out, your use case might be better served by using the WMS
interface of your ArcGIS server.
Anyway, you say
The only workaround is to remove the existing imageOverlay and add a new one (which ruins the user experience as the map disappears then reappears each time the window is dragged or zoomed).
Well, that glitch is due to you probably doing something like:
Remove old overlay
Add new overlay
Wait until the image is received from the network
Wait one frame so the new overlay is shown
and instead you should be doing something like:
Add new overlay
Wait until the image is received from the network
Remove old overlay
Wait one frame so the new overlay is shown
The problem is just the async wait and the possible race conditions there, but should be easy to hack together, e.g.:
var activeOverlay = null;
var overlayInRequest = null;
map.on('moveend zoomend', {
// If we are already requesting a new overlay, ignore it.
// This might need some additional debouncing logic to prevent
// lots of concurrent requests
if (overlayInRequest) {
overlayInRequest.off('load', showOverlay);
}
overlayInRequest = L.imageOverlay( computeUrl( map.getBounds() ), myOverlayOptions );
overlayInRequest.on('load', showOverlay);
});
function showOverlay(ev) {
activeOverlay.remove();
activeOverlay = overlayInRequest;
activeOverlay.addTo(map);
overlayInRequest = undefined;
}
If you use an ImageOverlay but change its url dynamically, with a new image that reflects a new bounding box, then indeed that is the reason for the behaviour you describe: you display an image that has been generated using a new bbox, but positioned in the initial bbox, since the image overlay remains at the same geographical position on the map.
Instead, it sounds to me that you should use a TileLayer.WMS.
It would automatically manage the bounding box update for you. You may need to find the correct options to fit your service provider required URL syntax, though.
Example: http://playground-leaflet.rhcloud.com/yel/1/edit?html,output

GWT drag and drop animation

I have a flow panel with many photo-widgets inside (gallery with random number of rows and columns, depends on screen size) for which I want to implement drag and drop behavior to change their order. I am using gwt-dnd library. Its FlowPanelDropController allows you to define your own positioner (delimiter) which shows the candidate location for dropping the dragged widget.
I want this positioner to be the empty space with defined width, and the challenging thing is to implement sliding animation effect for the when positioner is added and removed.
If you are a desktop Picasa app user you know what I mean: the target row slides both sides (little to the left, little to the right) extending the space between the items where you are going to drop a photo.
The whole thing is complex enough, but any help related to how to apply the animation for positioner attach/detach is appreciated. Maybe I need to use a different approach (e.g., use GWT native dnd instead of gwt-dnd lib and no "positioners" at all) if you have any ideas how this could be helpful.
Thanks.
Well, I ended up overriding AbstractPositioningDropController (parent of FlowPanelDropController) and adding some extra features.
1) newPositioner() method now builds the Label, which is vertical space with some small width, height and decoration. This widget's element has constant id (say, "POSITIONER"), which helps to distinguish between multiple positioners if you plan to have several of them while navigating with a drag object over multiple drop targets. Also some transition CSS effects were applied to the Label, which will be responsible for handling animated extension of Label's width.
2) in onEnter() I do the following
...
removePositioner(getPositionerElement());
Widget positioner = newPositioner();
dropTarget.insert(positioner, targetIndex);
animatePositionerExtension();
where getPositionerElement() returns DOM.getElementById(POSITIONER)
At the same time removePositioner(..) resets the id of this element to something abstract and ideally should provide some animation before calling .removeFromParent(). But I didn't have enough time to properly debug this so ended up just removing the old positioner with no animation.
Method animatePositionerExtension() contains the code that changes the width of the positioner widget, so that CSS transition will catch that and provides animation.
All access to positioner widget in the class should be provided through updated methods.
3) onLeave() contains line removePositioner(getPositionerElement());
4) In the end of onMove() I added a couple of lines:
galleryWidget.extendHoveredRow(targetIndex - 1);
animatePositionerExtension();
where extendHoveredRow(hoveredWidgetOrdinal) implemented the logic to "limit" the sliding effect in the single line:
int rowHovered = -1;
public void extendHoveredRow(int hoveredWidgetOrdinal) {
int newRowHovered = getRowByOrdinalHovered(hoveredWidgetOrdinal);
if (rowHovered != newRowHovered) {
// adjust position of items in the previously hovered row
int firstInPreviouslyHoveredRow = (rowHovered - 1) * itemsInARow;
shiftFirstItemLeft(firstInPreviouslyHoveredRow, false);
rowHovered = newRowHovered;
// extend this row
int firstInThisRow = getOrdinalFirstInThisRowByOrdinal(hoveredWidgetOrdinal);
shiftFirstItemLeft(firstInThisRow, true);
}
}
This is in short how I did the thing. And still there's some room for improvements, like adding animated removal.
Again, it's all about overriding DropController and manipulations with elements inside the "gallery" widget. The benefit of this approach is that I remain in the gwt-dnd operations framework, and also reused a bunch of existent code.
Some notes:
CSS transition is not supported in IE pre-9, but this is unrelated to
this topic.
Put a transparent "glass" div on top of the Image widget if you use it
as a face of dragProxy. This will save you tons of time trying to
understand why either setting element's draggable to false, or
calling event.preventDefault() somewhere else, or other workarounds don't work in one or several browsers and the image itself is being dragged instead of the whole dragProxy widget.