Skip to content

fix Geometry cursor Not working properly #2149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/core/util/pick.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
*
* layer pick utils
* can use by PointLayer,LineStringLayer,PolygonLayer,VectorTileLayer,Geo3DTilesLayer,GLTFLayer etc
*
* @returns
*/
import Canvas from '../Canvas';

export function isGLRenderLayer(layer) {
if (!layer) {
return false;
}
const renderer = layer.getRenderer && layer.getRenderer();
return !!(renderer && renderer.gl);
}

export function checkCanvasSize(targetCanvas, sourceCanvas) {
if (!targetCanvas || !sourceCanvas) {
return null;
}
const { width, height, style } = sourceCanvas;
if (targetCanvas.width !== width || targetCanvas.height !== height) {
targetCanvas.width = width;
targetCanvas.height = height;
}
if (targetCanvas.style.width !== style.width || targetCanvas.style.height !== style.height) {
targetCanvas.style.width = style.width;
targetCanvas.style.height = style.height;
}
return targetCanvas;
}

export function clearCanvas(canvas) {
if (!canvas) {
return null;
}
const ctx = Canvas.getCanvas2DContext(canvas);
Canvas.clearRect(ctx, 0, 0, canvas.width, canvas.height);
return ctx;
}

let tempCanvas;
export function layerIsBlankInPoint(layer, containerPoint, tolerance = 1) {
if (!layer || !containerPoint) {
return true;
}
const renderer = layer.getRenderer && layer.getRenderer();
if (!renderer || !renderer.canvas) {
return true;
}
const map = layer.getMap();
if (!map) {
return true;
}
if (!tempCanvas) {
tempCanvas = Canvas.createCanvas(1, 1);
// document.body.appendChild(tempCanvas);
}
tempCanvas = checkCanvasSize(tempCanvas, renderer.canvas);
if (!tempCanvas) {
return true;
}
const ctx = clearCanvas(tempCanvas);
if (!ctx) {
return true;
}
tolerance = Math.max(layer.options.geometryEventTolerance || 1, tolerance);
tolerance = Math.abs(Math.round(tolerance));
tolerance = Math.max(1, tolerance);
const r = map.getDevicePixelRatio();
const { x, y } = containerPoint;
let left = x - tolerance, top = y - tolerance;
left = Math.round(left * r);
top = Math.round(top * r);
left = Math.max(0, left);
top = Math.max(0, top);
const size = tolerance * 2;
let imageData;
try {
ctx.drawImage(renderer.canvas, 0, 0);
imageData = ctx.getImageData(left, top, size, size);
} catch (error) {
console.warn('hit detect failed with tainted canvas, some geometries have external resources in another domain:\n', error);
}
if (!imageData) {
return false;
}
const data = imageData.data;
for (let i = 0, len = data.length; i < len; i += 4) {
const A = data[i + 3];
if (A > 0) {
return false;
}
}
return true;

}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import * as Util from './core/util';
import * as DomUtil from './core/util/dom';
import * as StringUtil from './core/util/strings';
import * as MapboxUtil from './core/mapbox';

import * as MicroTask from './core/MicroTask';
export { Util, DomUtil, StringUtil, MapboxUtil, MicroTask };

export { default as LRUCache } from './core/util/LRUCache';
export { default as Ajax } from './core/Ajax';
export { default as Canvas } from './core/Canvas';
Expand Down
52 changes: 31 additions & 21 deletions src/layer/OverlayLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,29 @@ class OverlayLayer extends Layer {
}
}

isGeometryListening(types) {
if (!this._geoList) {
return false;
}
if (!Array.isArray(types)) {
types = [types];
}
for (let i = 0, l = this._geoList.length; i < l; i++) {
const geometry = this._geoList[i];
if (!geometry) {
continue;
}
for (let j = 0; j < types.length; j++) {
if (geometry.listens(types[j])) {
return true;
}
}
}
return false;
}
// isGeometryListening(types) {
// if (!this._geoList) {
// return false;
// }
// if (!Array.isArray(types)) {
// types = [types];
// }
// for (let i = 0, l = this._geoList.length; i < l; i++) {
// const geometry = this._geoList[i];
// if (!geometry) {
// continue;
// }
// if (geometry.options.cursor) {
// return true;
// }
// for (let j = 0; j < types.length; j++) {
// if (geometry.listens(types[j])) {
// return true;
// }
// }
// }
// return false;
// }

/**
* Get a geometry by its id
Expand Down Expand Up @@ -712,9 +715,16 @@ class OverlayLayer extends Layer {
}
const geos = this.getGeometries() || [];
for (let i = 0, len = geos.length; i < len; i++) {
const geometry = geos[i];
if (!geometry) {
continue;
}
if (geometry.options.cursor) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个判断有点宽泛了,感觉最合理的做法是判断当前点是否于geometry的containerExtent相交,只有相交才return true,尽量避免该查询

Copy link
Collaborator Author

@deyihu deyihu Dec 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有道理,不过:

  • 用户设置cursor这个是小概率事件,大部分用户没有这个需求,花大代价做小概率的事情
  • 如果用包围盒判断,那么需要计算所有的geometry的bbox,那么性能更差了,还不如没有这个判断函数了,包围盒的计算还是很复杂的
  • VectorLayer已经拥有了颜色判断,当且仅当当前画布处有颜色才会走具体的事件逻辑,应该考虑其他图层也实现类似逻辑,或者在地图上实现这个逻辑,即没有颜色的地方应该直接忽略这个地图的事件继续往下走,即当前鼠标处有颜色值,那么其一定在图形内
  • 可以在地图画布上实现这逻辑,当地图画布处为空时,直接不走地图的事件系统

Copy link
Member

@fuzhenn fuzhenn Dec 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果用包围盒判断,那么需要计算所有的geometry的bbox,那么性能更差了,还不如没有这个判断函数了,包围盒的计算还是很复杂的

这个倒是不会,geometry的bbox计算不咋耗性能,比webgl图层去read pixel要好很多

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

经过测试,改方法不可行,太耗时了,改用地图画布颜色检测,即地图画布的全局图形法

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

刚试了下,不行,因为地图画布可能一直不是空的,比如有tilelayer的情况下,永远有颜色值的

Copy link
Collaborator Author

@deyihu deyihu Dec 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

经过验证还是要通过判断图层的颜色值是否为空来完成,参考VectorLayer的实现
@fuzhenn

return true;
}
for (let j = 0, len1 = eventTypes.length; j < len1; j++) {
const eventType = eventTypes[j];
const listens = geos[i].listens(eventType);
const listens = geometry.listens(eventType);
if (listens > 0) {
return true;
}
Expand Down
14 changes: 11 additions & 3 deletions src/map/Map.Topo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { INTERNAL_LAYER_PREFIX } from '../core/Constants';
import { isNil, isString, isArrayHasData, pushIn, isFunction } from '../core/util';
import { isGLRenderLayer, layerIsBlankInPoint } from '../core/util/pick';
import Coordinate from '../geo/Coordinate';
import Point from '../geo/Point';
import Map from './Map';
Expand Down Expand Up @@ -102,22 +103,29 @@ Map.include(/** @lends Map.prototype */ {
const coordinate = this.containerPointToCoord(containerPoint);
return this._identify(opts, callback, layer => {
let result;
const containerPoint = opts.containerPoint;
if (isMapGeometryEvent && !isNil(layer.options.geometryEventTolerance)) {
opts.tolerance = opts.tolerance || 0;
opts.tolerance += layer.options.geometryEventTolerance;
}
if (layer.isGeometryListening && isMapGeometryEvent && opts.eventTypes.indexOf('mousemove') >= 0) {
if (!layer.isGeometryListening(opts.eventTypes)) {
//only gllayer,exclude canvas layer
const isGLLayer = isGLRenderLayer(layer);
const isBlank = isGLLayer && layerIsBlankInPoint(layer, containerPoint, opts.tolerance);
if (!isBlank && layer._hasGeoListeners && isMapGeometryEvent && opts.eventTypes.indexOf('mousemove') >= 0) {
if (!layer._hasGeoListeners(opts.eventTypes)) {
return [];
}
}
if (layer.identifyAtPoint) {
if (isBlank) {
result = [];
} else if (layer.identifyAtPoint) {
result = layer.identifyAtPoint(containerPoint, opts);
} else if (coordinate && layer.identify) {
result = layer.identify(coordinate, opts);
} else {
result = [];
}

if (isMapGeometryEvent) {
if (isNil(tolerance)) {
delete opts.tolerance;
Expand Down
2 changes: 1 addition & 1 deletion src/map/handler/Map.GeometryEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ class MapGeometryEventsHandler extends Handler {
}
return false;
});
map._setPriorityCursor(geometryCursorStyle);
// map._setPriorityCursor(geometryCursorStyle);
if (!layers.length) {
return;
}
Expand Down
23 changes: 23 additions & 0 deletions test/geometry/event/GeometryEventSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -484,4 +484,27 @@ describe('Geometry.Events', function () {
});
}, 50);
});

it('#2148 geometry cursor when listens is empty', function (done) {
var circle = new maptalks.Circle(map.getCenter(), 10, { cursor: 'zoom-in' });
circle.addTo(layer);
var domPosition = GET_PAGE_POSITION(container);
var point = map.coordinateToContainerPoint(center).add(domPosition);

happen.mousemove(eventContainer, {
'clientX': point.x,
'clientY': point.y
});
setTimeout(() => {
happen.mousemove(eventContainer, {
'clientX': point.x + 2,
'clientY': point.y + 2
});

setTimeout(() => {
expect(map._priorityCursor).to.be.eql('zoom-in');
done();
}, 100);
}, 16);
});
});