Самый простой способ найти пересекающиеся элементы - это перебирать их и проверять пересечение по одному. Но это не оптимально, поскольку каждая итерация должна будет снова и снова считывать и анализировать атрибуты DOM.
Поскольку вы знаете, что map
является статическим и не изменится, вы можете заранее собрать информацию и подготовить данные длябыстрый поиск. Если мы предположим, что все риты на map
имеют одинаковый размер, мы можем сделать быстрый расчет, чтобы получить позиции ректов, пересекающихся с областью круга.
Поскольку ваш SVG слишком велик для включения в кодфрагменты, примеры кода ниже только для JavaScript, с дополнительными ссылками на скрипты.
Простая, неоптимальная реализация
/**
* @typedef Area
* @property {number} x1 X position of top-left
* @property {number} y1 Y position of top-left
* @property {number} x2 X position of bottom-right
* @property {number} y2 Y position of bottom-right
*/
/**
* Based on https://stackoverflow.com/a/2752387/6352710
* @param {SVGElement} $rect
* @param {Area} area
* @return {boolean}
*/
function areIntersecting ($rect, area) {
const x1 = parseFloat($rect.getAttribute('x'));
const y1 = parseFloat($rect.getAttribute('y'));
const x2 = x1 + parseFloat($rect.getAttribute('width')) + parseFloat($rect.getAttribute('stroke-width'));
const y2 = y1 + parseFloat($rect.getAttribute('height'));
return !(x1 > area.x2 ||
x2 < area.x1 ||
y1 > area.y2 ||
y2 < area.y1);
}
/**
* @param {SVGElement[]} rects
* @param {SVGElement} $circle
* @return {SVGElement[]}
*/
function findIntersectingRects (rects, $circle) {
let x = parseFloat($circle.getAttribute('cx'));
let y = parseFloat($circle.getAttribute('cy'));
let r = parseFloat($circle.getAttribute('r'));
let box = {
x1: x - r,
y1: y - r,
x2: x + r,
y2: y + r
};
return rects.filter($rect => areIntersecting($rect, box));
}
/*
* Following code is just for the example.
*/
// Get array of `RECT` elements
const $map = document.getElementById('map');
const rects = Array.from($map.querySelectorAll('rect'));
// Get array of `CIRCLE` elements
const $edit = document.getElementById('edit');
const circles = Array.from($edit.querySelectorAll('circle'));
// Change opacity of `RECT` elements that are
// intersecting with `CIRCLE` elements.
circles.forEach($circle => {
findIntersectingRects(rects, $circle).forEach($rect => $rect.setAttribute('style', 'fill-opacity: 0.3'))
});
Протестируйте его на https://jsfiddle.net/subw6reL/.
Aчуть более быстрая реализация
/**
* @typedef Area
* @property {number} x1 X position of top-left
* @property {number} y1 Y position of top-left
* @property {number} x2 X position of bottom-right
* @property {number} y2 Y position of bottom-right
* @property {SVGElement} [$e] optional reference to SVG element
*/
/**
* Besides properties defined below, grid may contain multiple
* objects named after X value of area, and those object may contain
* multiple Areas, named after Y value of those areas.
*
* @typedef Grid
* @property {number} x X position of top-left
* @property {number} y Y position of top-left
* @property {number} w Width of each rect in grid
* @property {number} h Height of each rect in grid
*/
/**
* @param {Grid} grid
* @param {SVGElement} $circle
* @return {SVGElement[]}
*/
function findIntersectingRects (grid, $circle) {
let r = parseFloat($circle.getAttribute('r'));
let x1 = parseFloat($circle.getAttribute('cx')) - r;
let y1 = parseFloat($circle.getAttribute('cy')) - r;
let x2 = x1 + r + r;
let y2 = y1 + r + r;
let gX = x1 - ((x1 - grid.x) % grid.w);
let gY = y1 - ((y1 - grid.y) % grid.h);
var result = [];
while (gX <= x2) {
let y = gY;
let row = grid[gX];
while (row && y <= y2) {
if (row[y]) {
result.push(row[y].$e);
}
y += grid.h;
}
gX += grid.w;
}
return result;
}
/**
* @param {SVGElement[]} rects
* @return {Grid}
*/
function loadGrid (rects) {
const grid = {
x: Infinity,
y: Infinity,
w: Infinity,
h: Infinity
};
rects.forEach($rect => {
let x = parseFloat($rect.getAttribute('x'));
let y = parseFloat($rect.getAttribute('y'));
let w = parseFloat($rect.getAttribute('width')) + parseFloat($rect.getAttribute('stroke-width'));
let h = parseFloat($rect.getAttribute('height'));
grid[x] = grid[x] || {};
grid[x][y] = grid[x][y] || {
x1: x,
y1: y,
x2: x + w,
y2: y + h,
$e: $rect
};
if (grid.w === Infinity) {
grid.w = w;
}
else if (grid.w !== w) {
console.error($rect, 'has different width');
}
if (grid.h === Infinity) {
grid.h = h;
}
else if (grid.h !== h) {
console.error($rect, 'has different height');
}
if (x < grid.x) {
grid.x = x;
}
if (y < grid.y) {
grid.y = y;
}
});
return grid;
}
/*
* Following code is just for the example.
*/
// Get array of `RECT` elements
const $map = document.getElementById('map');
const grid = loadGrid(Array.from($map.querySelectorAll('rect')));
// Get array of `CIRCLE` elements
const $edit = document.getElementById('edit');
const circles = Array.from($edit.querySelectorAll('circle'));
// Change opacity of `RECT` elements that are
// intersecting with `CIRCLE` elements.
circles.forEach($circle => {
findIntersectingRects(grid, $circle).forEach($rect => $rect.setAttribute('style', 'fill-opacity: 0.3'))
});
Протестируйте его на https://jsfiddle.net/f2xLq3ka/.
Возможны дополнительные оптимизации
Вместо использования обычного Object
для grid
, можно использовать Array
, вычисляя x и y примерно так: arrayGrid[rect.x / grid.w][rect.y / grid.h]
.
Приведенный выше пример кода не гарантирует округление значений, поэтому Math.floor
и Math.ceil
следует использовать для вычисляемых значений.
Если вы не знаете, будут ли элементы map
всегда иметь одинаковый размер, вы можете проверить это при инициализации, а затем подготовить функцию findIntersectingRects
, оптимизированную для данной ситуации.
Трюки
Есть и хитростьw сетка на холсте, каждый прямоугольник разного цвета (на основе прямоугольников x
и y
), а затем получить цвет пикселя в позиции / области круга;). Я сомневаюсь, что это будет быстрее, но может быть полезно в немного более сложных ситуациях (например, многослойная карта с неправильными формами).