使用L.esri.DynamicMapLayer,是否可以在动态地图上绑定鼠标悬停事件而不是弹出窗口?
Using L.esri.DynamicMapLayer, is it possible to bind a mouseover event rather than a pop-up on a dynamic map?
我知道将弹出窗口绑定到 ESRI 的 L.esri.DynamicMapLayer
here。下面的代码是成功的。
$.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);
}
});
}
});
但我想知道您是否可以 mouseover
在动态地图上使用 bindTooltip
而不是 click
响应。我查看了我不完全理解的 documentation for L.esri.DynamicMapLayer
which says it is an extension of L.ImageOverlay
. But perhaps there is an issue outlined here。也许它甚至没有关系。
除此之外,我一直在测试即使是最简单的代码的多种变体以使下面的代码正常工作,但没有成功。也许因为这是异步行为,所以不可能。寻找任何指导 and/or 解释。非常新手的程序员,非常感谢专业知识。
$.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);
}
});
}
});
偶然地,我一直在研究 different problem,这个问题的副产品之一可能对你有用。
您的主要问题是点击事件的异步性质。如果您打开您的地图(您评论中的第一个 jsfiddle),打开您的开发工具网络选项卡,然后开始四处点击,您将看到每次点击都会发出一个新的网络请求。这就是许多 esri 查询函数的工作方式 - 它们需要查询服务器并检查数据库中给定 latlng
处所需的值。如果您尝试将相同的行为附加到 mousemove
事件,您将触发大量网络请求并使浏览器超载 - 坏消息。
您可以执行的一个解决方案是读取从 esri 图像服务返回的图像的光标下的像素数据,并且需要做更多的工作。如果你知道光标下像素的准确rgb值,并且你知道该rgb值对应于地图图例中的什么值,你就可以得到你的结果。
Here is a working example
而Here是codesandbox源码。不要害怕点击刷新,CSB 在转译模块的方式上有点不稳定。
这里发生了什么?让我们一步步来看:
- 在
load
、zoomend
、moveend
等地图事件中,一个专门的函数正在获取与 L.esri.dynamicMapLayer 相同的图像,使用的是 EsriImageRequest
,这是我写的 class 重用了很多 esri-leaflet 的内部逻辑:
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
});
}
EsriImageRequest
的实例具有 fetchImage
方法,它采用 L.LatLngBounds
数组和地图缩放级别,以及 returns 图像 - 相同的图像您的 dynamicMapLayer 显示在地图上。
EsriImageRequest
可能是您不需要的额外代码,但我正好有 运行 进入这个问题。我写这个是因为我的应用程序 运行s 在 nodejs 服务器上,而且我没有带有 L.esri.dynamicMapLayer
的地图实例。作为一种更简单的替代方法,您可以定位显示动态地图层的传单 DOM <img>
元素,将其用作我们在第 2 步中需要的图像源。您必须 该元素的 src
属性,以及该侦听器中的 运行 applyImage
。如果您不熟悉传单如何管理 DOM,请查看检查器中的元素选项卡,您可以在此处找到 <img>
元素:
我建议这样做,不要我的示例显示的方式。就像我说的,我碰巧刚刚在处理一个相关的问题。
- 在代码的前面,我设置了一个 canvas,并使用 css
position
、pointer-events
和 opacity
属性,它正好位于地图上方,但设置为不进行交互(我在示例中为其设置了少量不透明度,但您可能希望将不透明度设置为 0)。在applyImage
函数中,我们得到的图像写入那个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);
});
现在我们有一个不可见的 canvas 像素内容与 dynamicMapLayer 的完全相同。
- 现在我们可以监听地图的
mousemove
事件,并从我们创建的canvas中获取鼠标的rgba像素值。如果您阅读我的 other question,您会看到我如何获得图例值数组,以及我如何使用该数组将像素的 rgba 值映射回该颜色的图例值。我们可以使用该像素的图例值,并将弹出内容设置为该值。
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"}`);
});
如您所见,我还将弹出窗口的 latlng
设置为鼠标所在的位置。在弹出选项中使用 closeButton: false
,它的行为很像工具提示。我试着让它与适当的 L.tooltip
一起工作,但我自己也遇到了一些麻烦。这似乎产生了相同的效果。
很抱歉,如果回答很长。有很多方法可以调整/改进我的代码示例,但这应该可以帮助您入门。
我知道将弹出窗口绑定到 ESRI 的 L.esri.DynamicMapLayer
here。下面的代码是成功的。
$.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);
}
});
}
});
但我想知道您是否可以 mouseover
在动态地图上使用 bindTooltip
而不是 click
响应。我查看了我不完全理解的 documentation for L.esri.DynamicMapLayer
which says it is an extension of L.ImageOverlay
. But perhaps there is an issue outlined here。也许它甚至没有关系。
除此之外,我一直在测试即使是最简单的代码的多种变体以使下面的代码正常工作,但没有成功。也许因为这是异步行为,所以不可能。寻找任何指导 and/or 解释。非常新手的程序员,非常感谢专业知识。
$.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);
}
});
}
});
偶然地,我一直在研究 different problem,这个问题的副产品之一可能对你有用。
您的主要问题是点击事件的异步性质。如果您打开您的地图(您评论中的第一个 jsfiddle),打开您的开发工具网络选项卡,然后开始四处点击,您将看到每次点击都会发出一个新的网络请求。这就是许多 esri 查询函数的工作方式 - 它们需要查询服务器并检查数据库中给定 latlng
处所需的值。如果您尝试将相同的行为附加到 mousemove
事件,您将触发大量网络请求并使浏览器超载 - 坏消息。
您可以执行的一个解决方案是读取从 esri 图像服务返回的图像的光标下的像素数据,并且需要做更多的工作。如果你知道光标下像素的准确rgb值,并且你知道该rgb值对应于地图图例中的什么值,你就可以得到你的结果。
Here is a working example
而Here是codesandbox源码。不要害怕点击刷新,CSB 在转译模块的方式上有点不稳定。
这里发生了什么?让我们一步步来看:
- 在
load
、zoomend
、moveend
等地图事件中,一个专门的函数正在获取与 L.esri.dynamicMapLayer 相同的图像,使用的是EsriImageRequest
,这是我写的 class 重用了很多 esri-leaflet 的内部逻辑:
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
});
}
EsriImageRequest
的实例具有 fetchImage
方法,它采用 L.LatLngBounds
数组和地图缩放级别,以及 returns 图像 - 相同的图像您的 dynamicMapLayer 显示在地图上。
EsriImageRequest
可能是您不需要的额外代码,但我正好有 运行 进入这个问题。我写这个是因为我的应用程序 运行s 在 nodejs 服务器上,而且我没有带有 L.esri.dynamicMapLayer
的地图实例。作为一种更简单的替代方法,您可以定位显示动态地图层的传单 DOM <img>
元素,将其用作我们在第 2 步中需要的图像源。您必须 src
属性,以及该侦听器中的 运行 applyImage
。如果您不熟悉传单如何管理 DOM,请查看检查器中的元素选项卡,您可以在此处找到 <img>
元素:
我建议这样做,不要我的示例显示的方式。就像我说的,我碰巧刚刚在处理一个相关的问题。
- 在代码的前面,我设置了一个 canvas,并使用 css
position
、pointer-events
和opacity
属性,它正好位于地图上方,但设置为不进行交互(我在示例中为其设置了少量不透明度,但您可能希望将不透明度设置为 0)。在applyImage
函数中,我们得到的图像写入那个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);
});
现在我们有一个不可见的 canvas 像素内容与 dynamicMapLayer 的完全相同。
- 现在我们可以监听地图的
mousemove
事件,并从我们创建的canvas中获取鼠标的rgba像素值。如果您阅读我的 other question,您会看到我如何获得图例值数组,以及我如何使用该数组将像素的 rgba 值映射回该颜色的图例值。我们可以使用该像素的图例值,并将弹出内容设置为该值。
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"}`);
});
如您所见,我还将弹出窗口的 latlng
设置为鼠标所在的位置。在弹出选项中使用 closeButton: false
,它的行为很像工具提示。我试着让它与适当的 L.tooltip
一起工作,但我自己也遇到了一些麻烦。这似乎产生了相同的效果。
很抱歉,如果回答很长。有很多方法可以调整/改进我的代码示例,但这应该可以帮助您入门。