OL5.3 ZoomSlider and the steps between the zoom-levels - how? - view

I used a useful bug in older OL5, now it was fixed and I seaching again for a solution since weeks...
My wish with the ZoomSlider is to stay at the step between the integer zooming levels. When I click on the Slider it moves e.g. from 16 to 17. My Polygon in the View may a little bit bigger than I need. Then I move the Sliderbutton a little bit and the View ist perfect - but as I leave the mousebutton the Sliderbutton jumps to the nearest level...
The bug in the past, there I could leave the Sliderbutton to left or right and the perfect View stood at the step between the levels (but the view did't refresh in this state, but most time this view was ok for me; I printed this view).
Now sadly this bug is fixed... - so I search a possibility to reach the steps between the levels again.
I tried with combinations (surely wrong) of constainResolution false/true, pinchZoom false/true, zoomDelta or delta values between 0.5 and 2 ... nothing helps and after searching in API, google, .. and trying a lot, I'm very confused now.
I don't understand the meaning of "pinch" (normally I speak German; in the dictionary there are so much meanings for this word...) - because I don't see any change, neither I set pinchZoom to true nor set to false...
Is there any solution for "hold view between the Zoom Level"?

I cannot find any earlier version of OpenLayers where ZoomSlider works as you described.
One workaround would be to set the view zoom factor very small to create many more zoom levels and set the delta options on the zoom button control and interactions correspondingly large so they continue to work at normal zoom levels.
e.g. to divide each normal zoom level into 100 zoom levels:
var zoomFactorDelta = 100;
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
controls: ol.control.defaults({
zoomOptions: { delta: zoomFactorDelta }
}).extend([
new ol.control.ZoomSlider()
]),
interactions: ol.interaction.defaults({
zoomDelta: zoomFactorDelta
}),
keyboardEventTarget: document,
view: new ol.View({
center: ol.proj.fromLonLat( [8, 50] ),
zoomFactor: Math.pow(2,1/zoomFactorDelta),
zoom: 10 * zoomFactorDelta,
maxZoom: 20 * zoomFactorDelta
})
});
However, while the zoomDelta setting works as expected for keyboard and double click zooms, it doesn't have any effect on mousewheel zoom (despite the documentation description "Zoom level delta when using keyboard or mousewheel zoom"), making mousewheel zoom 100 times slower than before. An alternative which keeps mousewheel zoom working at normal speed is to redefine the view's constrainResolution function to multiply the default delta parameter:
var zoomFactorDelta = 100;
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
controls: ol.control.defaults().extend([
new ol.control.ZoomSlider()
]),
keyboardEventTarget: document,
view: new ol.View({
center: ol.proj.fromLonLat( [8, 50] ),
zoomFactor: Math.pow(2,1/zoomFactorDelta),
zoom: 10 * zoomFactorDelta,
maxZoom: 20 * zoomFactorDelta
})
});
var view = map.getView();
var defaultConstrainResolution = view.constrainResolution.bind(view);
view.constrainResolution = function(resolution, delta, direction) {
return defaultConstrainResolution(resolution, delta*zoomFactorDelta, direction)
};
This workaround using only an override of the constrainResolution function also works, but it might have side effects elsewhere:
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
controls: ol.control.defaults().extend([
new ol.control.ZoomSlider()
]),
keyboardEventTarget: document,
view: new ol.View({
center: ol.proj.fromLonLat( [8, 50] ),
zoom: 10
})
});
var view = map.getView();
var defaultConstrainResolution = view.constrainResolution.bind(view);
view.constrainResolution = function(resolution, delta, direction) {
return Math.abs(delta) > 0 ? defaultConstrainResolution(resolution, delta, direction)
: resolution;
};

Related

Why does Scrolltrigger not update my dynamically x value on window resize?

First things first. I absolutely love GSAP and their Scrolltrigger. Everything works fine, but unfortunately, the object I move on the x-axis based on scroll does not update when the user resizes his browser window.'
The x value is based on a formula I wrote.
Here is a part of the function later added in a master timeline with matchMedia Scrolltrigger:
var seoShowBenefitsTl = gsap.timeline();
seoShowBenefitsTl.addLabel("first Benefit")
.to(".anim-screen-item-0", {opacity: 0, duration: 0.5}, "first Benefit")
.to(seoATFHardware, {x: () => (document.documentElement.clientWidth - seoATFHardware.clientWidth) / 2 + calculateOffsetToElement(document.querySelector('.anim-point-item.--revenue'), 120, "right"), duration: 1, ease: Power3.in, onUpdate: function() {
if (this.progress() >= 0.5) {
document.querySelector('.anim-screen-item.--revenue').classList.add("js-screen-item-engaged");
document.querySelector('.anim-point-item.--revenue').classList.add("js-point-item-engaged");
} else {
document.querySelector('.anim-screen-item.--revenue').classList.remove("js-screen-item-engaged");
document.querySelector('.anim-point-item.--revenue').classList.remove("js-point-item-engaged");
}
}}, "first Benefit")
//some more code
return seoShowBenefitsTl
Thank you so much in advance!
Make sure you add the actual tween to the ScrollTrigger timeline, not a timeline. This helps making sure the values get update easier because of the following point.
Use invalidateOnRefresh: true on the ScrollTrigger timeline.
Doing those things results in this demo.

Using L.esri.DynamicMapLayer, is it possible to bind a mouseover event rather than a pop-up on a dynamic map?

I'm aware of binding a pop-up to ESRI's L.esri.DynamicMapLayer here. The following code below is successful.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindPopup(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
var obj = featureCollection.features[0].properties;
var val = obj['Pixel Value'];
var lat = featureCollection.features[0].geometry.coordinates[1];
var lon = featureCollection.features[0].geometry.coordinates[0];
new L.responsivePopup({
autoPanPadding: [10, 10],
closeButton: true,
autoPan: false
}).setContent(parseFloat(val).toFixed(2)).setLatLng([lat, lon]).openOn(map);
}
});
}
});
But rather than a click response I am wondering as to whether you can mouseover using bindTooltip instead on a dynamic map. I've looked at the documentation for L.esri.DynamicMapLayer which says it is an extension of L.ImageOverlay. But perhaps there is an issue outlined here that I'm not fully understanding. Maybe it is not even related.
Aside, I've been testing multiple variations of even the simplest code to get things to work below but have been unsuccessful. Perhaps because this is asynchronous behavior it isn't possible. Looking for any guidance and/or explanation(s). Very novice programmer and much obliged for expertise.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindTooltip(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
new L.tooltip({
sticky: true
}).setContent('blah').setLatLng([lat,lng]).openOn(map);
}
});
}
});
Serendipitously, I have been working on a different problem, and one of the byproducts of that problem may come in handy for you.
Your primary issue is the asynchronous nature of the click event. If you open up your map (the first jsfiddle in your comment), open your dev tools network tab, and start clicking around, you will see a new network request made for every click. That's how a lot of esri query functions work - they need to query the server and check the database for the value you want at the given latlng. If you tried to attach that same behavior to a mousemove event, you'll trigger a huge number of network requests and you'll overload the browser - bad news.
One solution of what you can do, and its a lot more work, is to read the pixel data under the cursor of the image returned from the esri image service. If you know the exact rgb value of the pixel under the cursor, and you know what value that rgb value corresponds to in the map legend, you can achieve your result.
Here is a working example
And Here is the codesandbox source code. Don't be afraid to hit refresh, CSB is little wonky in the way it transpiles the modules.
What is happening here? Let's look step by step:
On map events like load, zoomend, moveend, a specialized function is fetching the same image that L.esri.dynamicMapLayer does, using something called EsriImageRequest, which is a class I wrote that reuses a lot of esri-leaflet's internal logic:
map.on("load moveend zoomend resize", applyImage);
const flashFloodImageRequest = new EsriImageRequest({
url: layer_url,
f: "image",
sublayer: "3",
});
function applyImage() {
flashFloodImageRequest
.fetchImage([map.getBounds()], map.getZoom())
.then((image) => {
//do something with the image
});
}
An instance of EsriImageRequest has the fetchImage method, which takes an array of L.LatLngBounds and a map zoom level, and returns an image - the same image that your dynamicMapLayer displays on the map.
EsriImageRequest is probably extra code that you don't need, but I happen to have just run into this issue. I wrote this because my app runs on a nodejs server, and I don't have a map instance with an L.esri.dynamicMapLayer. As a simpler alternative, you can target the leaflet DOM <img> element that shows your dynamicMapLayer, use that as your image source that we'll need in step 2. You will have to set up a listener on the src attribute of that element, and run the applyImage in that listener. If you're not familiar with how leaflet manages the DOM, look into your elements tab in the inspector, and you can find the <img> element here:
I'd recommend doing it that way, and not the way my example shows. Like I said, I happened to have just been working on a sort-of related issue.
Earlier in the code, I had set up a canvas, and using the css position, pointer-events, and opacity properties, it lays exactly over the map, but is set to take no interaction (I gave it a small amount of opacity in the example, but you'd probably want to set opacity to 0). In the applyImage function, the image we got is written to that canvas:
// earlier...
const mapContainer = document.getElementById("leafletMapid");
const canvas = document.getElementById("mycanvas");
const height = mapContainer.getBoundingClientRect().height;
const width = mapContainer.getBoundingClientRect().width;
canvas.height = height;
canvas.width = width;
const ctx = canvas.getContext("2d");
// inside applyImage .then:
.then((image) => {
image.crossOrigin = "*";
ctx.drawImage(image, 0, 0, width, height);
});
Now we have an invisible canvas who's pixel content is exactly the same as the dynamicMapLayer's.
Now we can listen to the map's mousemove event, and get the mouse's rgba pixel value from the canvas we created. If you read into my other question, you can see how I got the array of legend values, and how I'm using that array to map the pixel's rgba value back to the legend's value for that color. We can use the legend's value for that pixel, and set the popup content to that value.
map.on("mousemove", (e) => {
// get xy position on cavnas of the latlng
const { x, y } = map.latLngToContainerPoint(e.latlng);
// get the pixeldata for that xy position
const pixelData = ctx.getImageData(x, y, 1, 1);
const [R, G, B, A] = pixelData.data;
const rgbvalue = { R, G, B, A };
// get the value of that pixel according to the layer's legend
const value = legend.find((symbol) =>
compareObjectWithTolerance(symbol.rgbvalue, rgbvalue, 5)
);
// open the popup if its not already open
if (!popup.isOpen()) {
popup.setLatLng(e.latlng);
popup.openOn(map);
}
// set the position of the popup to the mouse cursor
popup.setLatLng(e.latlng);
// set the value of the popup content to the value you got from the legend
popup.setContent(`Value: ${value?.label || "unknown"}`);
});
As you can see, I'm also setting the latlng of the popup to wherever the mouse is. With closeButton: false in the popup options, it behaves much like a tooltip. I tried getting it to work with a proper L.tooltip, but I was having some trouble myself. This seems to create the same effect.
Sorry if this was a long answer. There are many ways to adapt / improve my code sample, but this should get you started.

Openlayers 3 turn off smooth scroll

I currently have an Openlayers 3 integration with many updating features, these make the scrolling stutter, especially when the 'kinetic' movement (flick scroll) is used. Is there a way to turn that smooth scrolling with inertia off so the user has to drag to move the map?
Removing the animation on zoom would help too.
I've been looking in the ol.animation area for these - is that the right place?
The kinetic can be turned off in the ol.interaction.DragPan interaction. Removing the animation while zooming can be done by passing duration: 0 to the ol.interaction.MouseWheelZoom.
See a live example here: http://jsfiddle.net/9v6fd6as/1/
Here's the example source code:
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
interactions: ol.interaction.defaults({
dragPan: false,
mouseWheelZoom: false
}).extend([
new ol.interaction.DragPan({kinetic: false}),
new ol.interaction.MouseWheelZoom({duration: 0})
]),
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
Above answer from Alexandre Dube is correct for older versions of openlayers.
If you're using OpenLayers 6+ with TypeScript, this is how you can disable animated panning:
import Interaction from "ol/interaction/Interaction";
import DragPan from "ol/interaction/DragPan";
import {defaults as defaultInteractions} from 'ol/interaction.js';
import {Kinetic} from "ol";
// ...
ngOnInit() {
this.map = new Map({
//...
interactions: defaultInteractions({
dragPan: false
}).extend([new DragPan({kinetic: new Kinetic(0, 0, 0)})]),
//...
});
}

Changing spotLight.shadowCameraFov 'on the fly'

I'm trying to change shadowCameraFov property of spot light object using onChange method, here is the code:
gui.add(controls, 'spotCameraFov', 30, 270).onChange( function (e) {
spotLight.shadowCameraFov = e;
console.log(e);
});
I can see that the value is changing ( from the console.log ), but it actually cause no effect on the spot light shadowCameraFov. Any suggestion on how can I make it works ?
Here is the pattern you need to follow:
gui.add( controls, 'shadowCameraFov', 30, 120 ).onChange( function() {
spotLight.shadowCameraFov = controls.shadowCameraFov;
spotLight.shadowCamera.fov = spotLight.shadowCameraFov;
spotLight.shadowCamera.updateProjectionMatrix();
});
You can see a live example here: http://threejs.org/examples/webgl_shading_physical.html. Controls are on the top right.
three.js r.62

GoogleMaps apiv3 Events - addListener and addDomListener not working the same way?

I'm having trouble trying to add a polygon to the map by clicking something outside of the map area, e.g. a text link on the same page as the map.
I can add the polygon by clicking on the map via the addListener method but the same code with addDomListener doesn't work. Was wondering if anyone knows how to do this correctly?
My code that is not working (i get the alert test so the listener is working but the polygon is not added to the map):
google.maps.event.addDomListener(document.getElementById('thediv'), 'click',function (event){
drawPolygon();
alert("test");
});
The code that is working but not what I want (polygon is drawn and alert test is received):
google.maps.event.addListener(map, 'click',function (event){
drawPolygon();
alert("test");
});
and the drawPolygon code which works:
function drawPolygon(){
var bermudaTriangle;
var triangleCoords = [
new google.maps.LatLng(25.774252, -80.190262),
new google.maps.LatLng(18.466465, -66.118292),
new google.maps.LatLng(32.321384, -64.75737),
new google.maps.LatLng(25.774252, -80.190262)
];
bermudaTriangle = new google.maps.Polygon({
paths: triangleCoords,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35
});
bermudaTriangle.setMap(map);
}

Resources