From db5dbac494867840ce2c9574eb0501bdbb74d7ed Mon Sep 17 00:00:00 2001 From: Mstislav Zhivodkov Date: Wed, 12 Feb 2020 12:43:11 +0700 Subject: [PATCH] Revert to v1.0.0 (#37) * Revert to v1.0.0 * Change version to 3.0.0 --- README.md | 9 -- demo/index.ts | 9 +- package-lock.json | 2 +- package.json | 2 +- src/General.ts | 45 ++----- src/labelArray.ts | 59 --------- src/markerArray.ts | 11 +- src/types.ts | 24 ---- src/worker/generalize.ts | 221 +++++------------------------- src/worker/index.ts | 14 +- src/worker/minZoom.ts | 106 --------------- test/generalize.spec.ts | 280 +-------------------------------------- test/markerArray.spec.ts | 16 +++ 13 files changed, 73 insertions(+), 725 deletions(-) delete mode 100644 src/labelArray.ts delete mode 100644 src/worker/minZoom.ts create mode 100644 test/markerArray.spec.ts diff --git a/README.md b/README.md index 1387b96..889e27d 100644 --- a/README.md +++ b/README.md @@ -106,15 +106,6 @@ general.generalize(bounds, groups, atlas, markers); Каждый `Marker` состоит из: - `pixelPosition` – позиция маркера в пикселях, должна вычисляться вне алгоритма генерализации и находиться в той же системе координат, что и `bounds` переданные в метод `generalize` - `groupIndex` – индекс в массиве групп, к которой маркер будет изначально принадлежать -- `priority?: boolean` — опциональный флаг, позволяющий поднять приоритет маркера над другими. Приоритетные маркеры генерализуются раньше остальных, даже если расположены после них в исходном массиве. -- `htmlLabel?: Label` — опциональная подпись маркера. Подписи генерализуются аналогично маркерам. У подписей при генерализации меньший приоритет, чем у маркеров. Другими словами, сначала генерализуются все маркеры, а затем все подписи. - -`Label` описывается следующим образом: -- `offset: Vec2` - отступ в пикселях вправо и вниз от маркера -- `width: number` - ширина в пикселях -- `height: number` - высота в пикселях -- `display: boolean` - флаг, означающий, нужно ли отображать подпись. Выставляется в процессе генерализации. -- `minZoom: number` — минимальный зум, на котором можно отображать подпись без пересечения с другими подписями. Выставляется в процессе генерализации. Метод `generalize` **не возвращает** новый массив маркеров, он мутирует старый. diff --git a/demo/index.ts b/demo/index.ts index 238e190..7c62fbe 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -64,13 +64,6 @@ Promise.all([loadAtlas(), loadMarkersData()]) iconIndex: -1, data: markerData, pixelPosition: [pixelPosition[0] * retinaFactor, pixelPosition[1] * retinaFactor], - htmlLabel: { - offset: [20, -10], - width: 180, - height: 80, - display: false, - minZoom: -Infinity, - }, }; markers.push(marker); } @@ -151,7 +144,7 @@ Promise.all([loadAtlas(), loadMarkersData()]) })); console.time('gen'); - general.generalize(bounds, priorityGroups, atlas.sprites, markers, zoom).then(() => { + general.generalize(bounds, priorityGroups, atlas.sprites, markers).then(() => { console.timeEnd('gen'); generalizationIsBusy = false; diff --git a/package-lock.json b/package-lock.json index c53766b..a7fb118 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@2gis/general", - "version": "2.1.0", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index abf52f0..ca83a61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@2gis/general", - "version": "2.1.0", + "version": "3.0.0", "description": "Fast marker generalization algorithm", "contributors": [ { diff --git a/src/General.ts b/src/General.ts index fe8b03f..52599f9 100644 --- a/src/General.ts +++ b/src/General.ts @@ -1,5 +1,4 @@ -import * as markerArray from './markerArray'; -import * as labelArray from './labelArray'; +import { stride, pack, unpack } from './markerArray'; import { BBox, PriorityGroup, @@ -16,14 +15,12 @@ export class General { private queue: Job[]; private currentJob: Job | undefined; private markerArray: Float32Array; - private labelArray: Float32Array; constructor() { this.worker = work(require.resolve('./worker')); this.queue = []; this.currentJob = undefined; - this.markerArray = new Float32Array(1000 * markerArray.stride); - this.labelArray = new Float32Array(1000 * labelArray.stride); + this.markerArray = new Float32Array(1000 * stride); this.worker.onmessage = (event) => { if (this.currentJob === undefined) { @@ -31,14 +28,8 @@ export class General { } const { markers, resolve } = this.currentJob; - const { data } = event; - - markerArray.unpack(markers, data.markerArray); - labelArray.unpack(markers, data.labelArray); - - this.markerArray = data.markerArray; - this.labelArray = data.labelArray; - + unpack(markers, event.data); + this.markerArray = event.data; this.currentJob = undefined; this.dequeue(); @@ -51,20 +42,15 @@ export class General { priorityGroups: PriorityGroup[], sprites: Sprite[], markers: Marker[], - currentZoom: number, ): Promise { const message: JobMessage = { bounds, priorityGroups, sprites, - currentZoom, }; - const markerCount = markers.length; - const labelCount = markers.filter((marker) => marker.htmlLabel !== undefined).length; - return new Promise((resolve) => { - this.queue.push({ message, markers, resolve, markerCount, labelCount }); + this.queue.push({ message, markers, resolve }); this.dequeue(); }); } @@ -73,17 +59,12 @@ export class General { this.queue = []; } - private pack(markers: Marker[], markerCount: number, labelCount: number): void { - if (markerCount * markerArray.stride > this.markerArray.length) { - this.markerArray = new Float32Array(markerCount * markerArray.stride); - } - - if (labelCount * labelArray.stride > this.labelArray.length) { - this.labelArray = new Float32Array(labelCount * labelArray.stride); + private pack(markers: Marker[]) { + if (markers.length * stride > this.markerArray.length) { + this.markerArray = new Float32Array(markers.length * stride); } - markerArray.pack(this.markerArray, markers); - labelArray.pack(this.labelArray, markers, window.devicePixelRatio); + pack(this.markerArray, markers); } private dequeue() { @@ -97,15 +78,13 @@ export class General { return; } - this.pack(job.markers, job.markerCount, job.labelCount); + this.pack(job.markers); const message = job.message as WorkerMessage; message.markers = this.markerArray; - message.markerCount = job.markerCount; - message.labels = this.labelArray; - message.labelCount = job.labelCount; + message.markerCount = job.markers.length; - this.worker.postMessage(message, [message.markers.buffer, message.labels.buffer]); + this.worker.postMessage(message, [message.markers.buffer]); this.currentJob = job; } diff --git a/src/labelArray.ts b/src/labelArray.ts deleted file mode 100644 index 7f67778..0000000 --- a/src/labelArray.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Marker } from './types'; - -let i = 0; -export const offsets = { - markerIndex: i++, - offsetX: i++, - offsetY: i++, - width: i++, - height: i++, - display: i++, - minZoom: i++, -}; - -export const stride = i; - -/** - * Запаковывает переданный массив маркеров в типизированный массив для быстрой передачи в воркер - */ -export function pack(labelArray: Float32Array, markers: Marker[], devicePixelRatio: number) { - let labelOffset = 0; - - for (let i = 0; i < markers.length; i++) { - const label = markers[i].htmlLabel; - - if (label === undefined) { - continue; - } - - labelArray[labelOffset + offsets.markerIndex] = i; - labelArray[labelOffset + offsets.offsetX] = label.offset[0] * devicePixelRatio; - labelArray[labelOffset + offsets.offsetY] = label.offset[1] * devicePixelRatio; - labelArray[labelOffset + offsets.width] = label.width * devicePixelRatio; - labelArray[labelOffset + offsets.height] = label.height * devicePixelRatio; - labelArray[labelOffset + offsets.display] = label.display ? 1 : 0; - labelArray[labelOffset + offsets.minZoom] = label.minZoom; - - labelOffset += stride; - } -} - -/** - * Вынимает значения из запакованного типизированного массива в массив маркеров - */ -export function unpack(markers: Marker[], labelArray: Float32Array) { - let labelOffset = 0; - - for (const marker of markers) { - const {htmlLabel: label} = marker; - - if (label === undefined) { - continue; - } - - label.display = labelArray[labelOffset + offsets.display] === 1; - label.minZoom = labelArray[labelOffset + offsets.minZoom]; - - labelOffset += stride; - } -} diff --git a/src/markerArray.ts b/src/markerArray.ts index 65ff560..77fd836 100644 --- a/src/markerArray.ts +++ b/src/markerArray.ts @@ -1,13 +1,11 @@ import { Marker } from './types'; // Оффсеты должны быть пронумерованы по порядку -let i = 0; export const offsets = { - pixelPositionX: i++, - pixelPositionY: i++, - groupIndex: i++, - iconIndex: i++, - priority: i++, + pixelPositionX: 0, + pixelPositionY: 1, + groupIndex: 2, + iconIndex: 3, }; export const stride = Object.keys(offsets).length; @@ -23,7 +21,6 @@ export function pack(markerArray: Float32Array, markers: Marker[]) { markerArray[markerOffset + offsets.pixelPositionY] = marker.pixelPosition[1]; markerArray[markerOffset + offsets.groupIndex] = marker.groupIndex; markerArray[markerOffset + offsets.iconIndex] = marker.iconIndex; - markerArray[markerOffset + offsets.priority] = Boolean(marker.priority) ? 1 : 0; } } diff --git a/src/types.ts b/src/types.ts index 0de9e6a..b077adb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,18 +14,6 @@ export interface Marker { groupIndex: number; /** Индекс спрайта в атласе, добавляется в ходе генерализации */ iconIndex: number; - /** Опциональный флаг, указывающий приоритетность маркера */ - priority?: boolean; - /** Подпись маркера */ - htmlLabel?: Label; -} - -export interface Label { - offset: Vec2; - width: number; - height: number; - display: boolean; - minZoom: number; } export interface PriorityGroup { @@ -46,17 +34,10 @@ export interface BBox { maxY: number; } -export type LabelBBox = BBox & { - anchorX: number; - anchorY: number; - minZoom: number; -}; - export interface JobMessage { bounds: BBox; priorityGroups: PriorityGroup[]; sprites: Sprite[]; - currentZoom: number; } export interface WorkerMessage { @@ -65,15 +46,10 @@ export interface WorkerMessage { sprites: Sprite[]; markerCount: number; markers: Float32Array; - labelCount: number; - labels: Float32Array; - currentZoom: number; } export interface Job { message: JobMessage; markers: Marker[]; - markerCount: number; - labelCount: number; resolve: () => void; } diff --git a/src/worker/generalize.ts b/src/worker/generalize.ts index 4ec2640..f6c3dec 100644 --- a/src/worker/generalize.ts +++ b/src/worker/generalize.ts @@ -1,17 +1,12 @@ -import { BBox, Vec2, WorkerMessage, PriorityGroup, Sprite, LabelBBox } from '../types'; -import { getIntersectionZoom } from './minZoom'; -import * as markerArray from '../markerArray'; -import * as labelArray from '../labelArray'; +import { BBox, Vec2, WorkerMessage, PriorityGroup, Sprite } from '../types'; +import { stride, offsets } from '../markerArray'; const collideBBox: BBox = { minX: 0, minY: 0, maxX: 0, maxY: 0 }; const marginBBox: BBox = { minX: 0, minY: 0, maxX: 0, maxY: 0 }; const degradationBBox: BBox = { minX: 0, minY: 0, maxX: 0, maxY: 0 }; -const noMarginBBox: BBox = { minX: 0, minY: 0, maxX: 0, maxY: 0 }; - -let survivedLabelBoxes: LabelBBox[] = []; export function generalize(data: WorkerMessage) { - const { bounds, priorityGroups, markers, markerCount, labels, labelCount } = data; + const { bounds, priorityGroups, sprites, markers, markerCount } = data; const planeWidth = (bounds.maxX - bounds.minX >> 3) + 1 << 3; // Ширина должна быть кратна 8 const planeHeight = bounds.maxY - bounds.minY; @@ -25,12 +20,9 @@ export function generalize(data: WorkerMessage) { * У нас есть несколько плоскостей. */ - // С помощью этой плоскости проверяется попадание маркеров с safeZone, и вставляются они с margin + // Это первая, с помощью нее мы просто проверяем попадание маркеров с safeZone и вставляем их с margin const plane = new Uint8Array(planeLength); - // Эта плоскость используется для подписей - const labelPlane = new Uint8Array(planeLength); - /** * Следующие плоскости используются для проверки на деградацию маркеров. * @@ -48,7 +40,7 @@ export function generalize(data: WorkerMessage) { const prevIconIndices = new Int8Array(markerCount); for (let i = 0; i < markerCount; i++) { - const index = i * markerArray.stride + markerArray.offsets.iconIndex; + const index = i * stride + offsets.iconIndex; // Сохраняем предыдущие индексы иконок для работы гистерезиса prevIconIndices[i] = markers[index]; @@ -57,82 +49,9 @@ export function generalize(data: WorkerMessage) { markers[index] = -1; } - const prevLabelState = new Uint8Array(labelCount); - const prevLabelMinZoom = new Float32Array(labelCount); - for (let i = 0; i < labelCount; i++) { - const displayIndex = i * labelArray.stride + labelArray.offsets.display; - const minZoomIndex = i * labelArray.stride + labelArray.offsets.minZoom; - - // Сохраняем предыдущее состояние лейблов - prevLabelState[i] = labels[displayIndex]; - prevLabelMinZoom[i] = labels[minZoomIndex]; - - // Сбрасываем состояние лейблов - labels[displayIndex] = 0; - labels[minZoomIndex] = -Infinity; - } - - survivedLabelBoxes = []; - - // Здесь начинает работу основной алгоритм генерализации - // Генерализуем в таком порядке: - // 1. Приоритетные маркеры, видимые на экране - // 2. Приоритетные маркеры, не видимые на экране - // 3. Приоритетные подписи, видимые на экране - // 4. Приоритетные подписи, не видимые на экране - // 5. Неприоритетные маркеры, видимые на экране - // 6. Неприоритетные маркеры, не видимые на экране - // 7. Неприоритетные подписи, видимые на экране - // 8. Неприоритетные подписи, не видимые на экране - generalizeMarkers( - data, plane, degradationPlanes, planeWidth, planeHeight, - prevIconIndices, true, true, - ); - generalizeMarkers( - data, plane, degradationPlanes, planeWidth, planeHeight, - prevIconIndices, false, true, - ); - - generalizeLabels( - data, labelPlane, planeWidth, planeHeight, prevLabelState, - prevLabelMinZoom, true, true, - ); - generalizeLabels( - data, labelPlane, planeWidth, planeHeight, prevLabelState, - prevLabelMinZoom, false, true, - ); - - generalizeMarkers( - data, plane, degradationPlanes, planeWidth, planeHeight, - prevIconIndices, true, false, - ); - generalizeMarkers( - data, plane, degradationPlanes, planeWidth, planeHeight, - prevIconIndices, false, false, - ); - - generalizeLabels( - data, labelPlane, planeWidth, planeHeight, prevLabelState, - prevLabelMinZoom, true, false, - ); - generalizeLabels( - data, labelPlane, planeWidth, planeHeight, prevLabelState, - prevLabelMinZoom, false, false, - ); -} - -function generalizeMarkers( - data: WorkerMessage, - plane: Uint8Array, - degradationPlanes: Uint8Array[], - planeWidth: number, - planeHeight: number, - prevIconIndices: Int8Array, - processVisible: boolean, - processPriority: boolean, -): void { - const { bounds, priorityGroups, sprites, markers, markerCount } = data; - + // Здесь начинает работу основной алгоритм генерализации. + // У нас два вложенных цикла: по группам -> по маркерам. + // Циклы запускаются два раза: первый прогон — для маркеров, которые были видны на экране for (let i = 0; i < priorityGroups.length; i++) { const sprite = sprites[priorityGroups[i].iconIndex]; if (!sprite) { @@ -144,13 +63,7 @@ function generalizeMarkers( const degradationPlane = i !== priorityGroups.length - 1 ? degradationPlanes[i] : undefined; for (let j = 0; j < markerCount; j++) { - const isVisible = prevIconIndices[j] !== -1; - const isPriority = markers[markerArray.stride * j + markerArray.offsets.priority] === 1; - - const visibilityOk = processVisible && isVisible || !processVisible && !isVisible; - const priorityOk = processPriority && isPriority || !processPriority && !isPriority; - - if (visibilityOk && priorityOk) { + if (prevIconIndices[j] !== -1) { generalizeMarker( markers, priorityGroups, @@ -166,31 +79,33 @@ function generalizeMarkers( } } } -} - -function generalizeLabels( - data: WorkerMessage, - labelPlane: Uint8Array, - planeWidth: number, - planeHeight: number, - prevLabelState: Uint8Array, - prevLabelMinZoom: Float32Array, - processVisible: boolean, - processPriority: boolean, -): void { - const { bounds, markers, labels, labelCount, currentZoom } = data; - for (let i = 0; i < labelCount; i++) { - const markerIndex = labels[i * labelArray.stride + labelArray.offsets.markerIndex]; - - const isVisible = prevLabelState[i] === 1 && currentZoom >= prevLabelMinZoom[i]; - const isPriority = markers[markerIndex * markerArray.stride + markerArray.offsets.priority] === 1; + // Второй прогон циклов — для маркеров, которые в данный момент не видны на экране. + for (let i = 0; i < priorityGroups.length; i++) { + const sprite = sprites[priorityGroups[i].iconIndex]; + if (!sprite) { + // Защищаемся от ситуации, когда в конфиге передан некорректный индекс спрайта + continue; + } - const visibilityOk = processVisible && isVisible || !processVisible && !isVisible; - const priorityOk = processPriority && isPriority || !processPriority && !isPriority; + const prevDegradationPlane = i !== 0 ? degradationPlanes[i - 1] : undefined; + const degradationPlane = i !== priorityGroups.length - 1 ? degradationPlanes[i] : undefined; - if (visibilityOk && priorityOk) { - generalizeLabel(markers, labels, bounds, labelPlane, planeWidth, planeHeight, currentZoom, i); + for (let j = 0; j < markerCount; j++) { + if (prevIconIndices[j] === -1) { + generalizeMarker( + markers, + priorityGroups, + sprites, + bounds, + prevDegradationPlane, + degradationPlane, + plane, + planeWidth, + planeHeight, + i, j, + ); + } } } } @@ -210,7 +125,6 @@ function generalizeMarker( ): void { const { safeZone, iconIndex, margin, degradation } = priorityGroups[groupIndex]; const { size, anchor } = sprites[iconIndex]; - const { stride, offsets } = markerArray; const markerOffset = markerIndex * stride; const markerGroupIndex = markers[markerOffset + offsets.groupIndex]; @@ -241,7 +155,7 @@ function generalizeMarker( createBBox(degradationBBox, planeWidth, planeHeight, size, anchor, pixelPositionX, pixelPositionY, degradation); - // Если все хорошо и маркер выжил, закрашиваем его в плоскости + // Если все хорошо и маркер выжил, закрашиваем его в двух плоскостях putToArray(plane, planeWidth, marginBBox); if (degradationPlane) { @@ -252,73 +166,6 @@ function generalizeMarker( } } -function generalizeLabel( - markers: Float32Array, - labels: Float32Array, - bounds: BBox, - labelPlane: Uint8Array, - planeWidth: number, - planeHeight: number, - currentZoom: number, - labelIndex: number, -): void { - const { stride, offsets } = labelArray; - const labelOffset = labelIndex * stride; - - const markerIndex = labels[labelOffset + offsets.markerIndex]; - const markerOffset = markerIndex * markerArray.stride; - const markerIsDisplayed = markers[markerOffset + markerArray.offsets.iconIndex] !== -1; - - if (!markerIsDisplayed) { - return; - } - - const pixelPositionX = markers[markerOffset + markerArray.offsets.pixelPositionX] - bounds.minX; - const pixelPositionY = markers[markerOffset + markerArray.offsets.pixelPositionY] - bounds.minY; - - const size = [ - labels[labelOffset + offsets.width], - labels[labelOffset + offsets.height], - ]; - - const anchor = [ - -labels[labelOffset + offsets.offsetX] / size[0], - -labels[labelOffset + offsets.offsetY] / size[1], - ]; - - createBBox(noMarginBBox, planeWidth, planeHeight, size, anchor, pixelPositionX, pixelPositionY, 0); - if (bboxIsEmpty(noMarginBBox)) { - return; - } - - if (!collide(labelPlane, planeWidth, noMarginBBox)) { - putToArray(labelPlane, planeWidth, noMarginBBox); - labels[labelOffset + offsets.display] = 1; - - const labelBox: LabelBBox = { - anchorX: pixelPositionX, - anchorY: pixelPositionY, - minX: -size[0] * anchor[0], - minY: -size[1] * anchor[1], - maxX: size[0] * (1 - anchor[0]), - maxY: size[1] * (1 - anchor[1]), - minZoom: -Infinity, - }; - - for (const existingBox of survivedLabelBoxes) { - const minZoom = getIntersectionZoom(existingBox, labelBox, currentZoom); - - if (minZoom > labelBox.minZoom && minZoom > existingBox.minZoom) { - labelBox.minZoom = minZoom; - } - } - - labels[labelOffset + offsets.minZoom] = labelBox.minZoom; - - survivedLabelBoxes.push(labelBox); - } -} - /** * Проверяет, пересекает ли область что-либо в плоскости * diff --git a/src/worker/index.ts b/src/worker/index.ts index 7e77b09..b249459 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -9,16 +9,8 @@ export interface WorkerGlobalScope { // tslint:disable-next-line:no-default-export export default (self: WorkerGlobalScope) => { self.onmessage = (event) => { - generalize(event.data); - - const { markers, labels } = event.data; - - self.postMessage({ - markerArray: markers, - labelArray: labels, - }, [ - markers.buffer, - labels.buffer, - ]); + const data = event.data; + generalize(data); + self.postMessage(data.markers); }; }; diff --git a/src/worker/minZoom.ts b/src/worker/minZoom.ts deleted file mode 100644 index e4b894d..0000000 --- a/src/worker/minZoom.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { LabelBBox } from '../types'; - -interface Interval { - min: number; - max: number; -} - -const intersectionRangeX: Interval = { min: -Infinity, max: Infinity }; -const intersectionRangeY: Interval = { min: -Infinity, max: Infinity }; -const intersectionRange: Interval = { min: -Infinity, max: Infinity }; - -const intersectionRangeLimit: Interval = { min: 0, max: Infinity }; - -/** - * Вычисляет зум, на котором лейблинг-боксы bbox1 и bbox2 начинают пересекаться. - * - * BBox определяется: - * 1. Якорем — точкой в координатах экрана - * 2. Оффсетами от якоря, обозначающими границы бокса в каждую из четырёх сторон от якоря. - * Оффсеты называются minX, maxX, minY и maxY. - * - * Фукнция работает следующим образом: - * 1. Вычисляет текущее расстояние между якорями боксов по двум осям (dx и dy). - * 2. Вычисляет диапазон расстояний dx и dy, внутри которых боксы будут пересекаться. - * Эти вычисления производятся отдельно для двух осей. Результаты вычислений делятся - * на текущие расстояния dx и dy. Таким образом, мы получаем результат в виде - * множителей текущего расстояния. - * 3. Выполняется пересечение полученных интервалов для x и y. - * 4. Берётся максимальное граница интервала, из него вычисляется финальное значение зума. - */ -export function getIntersectionZoom( - bbox1: LabelBBox, - bbox2: LabelBBox, - currentZoom: number, -): number { - // Вычисляем текущее расстояние между якорями боксов - const dx = bbox2.anchorX - bbox1.anchorX; - const dy = bbox2.anchorY - bbox1.anchorY; - - // Вычисляем диапазон множителей dx, внутри которого боксы пересекаются вдоль оси X - calcIntersectionRange( - intersectionRangeX, - bbox1.minX, - bbox1.maxX, - bbox2.minX, - bbox2.maxX, - dx, - ); - - // Вычисляем диапазон множителей dy, внутри которого боксы пересекаются вдоль оси Y - calcIntersectionRange( - intersectionRangeY, - bbox1.minY, - bbox1.maxY, - bbox2.minY, - bbox2.maxY, - dy, - ); - - // Находим пересечение этих диапазонов - intersect( - intersectionRange, - intersectionRangeX, - intersectionRangeY, - intersectionRangeLimit, - ); - - // Получили пустой интервал — боксы не пересекутся никогда - if (intersectionRange.min >= intersectionRange.max) { - return -Infinity; - } - - // Берём максимальное значение диапазона и вычисляем из него зум - return currentZoom + Math.log(intersectionRange.max) / Math.log(2); -} - -/** - * Вычисляет диапазон расстояний между якорями, в котором два бокса пересекаются вдоль одной оси. - */ -function calcIntersectionRange( - dest: Interval, - min1: number, - max1: number, - min2: number, - max2: number, - d0: number, -): void { - const min = d0 > 0 ? min1 - max2 : min2 - max1; - const max = d0 > 0 ? max1 - min2 : max2 - min1; - - dest.min = min / Math.abs(d0); - dest.max = max / Math.abs(d0); -} - -/** - * Выполняет операцию пересечения трёх интервалов. Результат записывается в интервал dest. - */ -function intersect( - dest: Interval, - interval1: Interval, - interval2: Interval, - interval3: Interval, -): void { - dest.min = Math.max(interval1.min, interval2.min, interval3.min); - dest.max = Math.min(interval1.max, interval2.max, interval3.max); -} diff --git a/test/generalize.spec.ts b/test/generalize.spec.ts index 2f70003..7b0fc01 100644 --- a/test/generalize.spec.ts +++ b/test/generalize.spec.ts @@ -1,9 +1,8 @@ import { ok, equal, deepEqual } from 'assert'; -import { BBox, Marker, WorkerMessage, Label } from '../src/types'; +import { BBox, Marker, WorkerMessage } from '../src/types'; import { testHandlers } from '../src/worker/generalize'; import { pack, stride, unpack } from '../src/markerArray'; -import * as labels from '../src/labelArray'; const { putToArray, @@ -214,9 +213,6 @@ describe('generalize.ts', () => { }], markerCount: markers.length, markers: markerArray, - labels: new Float32Array(), - labelCount: 0, - currentZoom: 0, }; }); @@ -309,9 +305,6 @@ describe('generalize.ts', () => { }], markerCount: markers.length, markers: markerArray, - labels: new Float32Array(), - labelCount: 0, - currentZoom: 0, }; }); @@ -343,271 +336,6 @@ describe('generalize.ts', () => { }); }); - describe('Hysteresis and priority markers', () => { - let msg: WorkerMessage; - - beforeEach(() => { - msg = { - bounds: { minX: 0, minY: 0, maxX: 100, maxY: 100 }, - priorityGroups: [{ - safeZone: 0, - margin: 0, - degradation: 0, - iconIndex: 0, - }], - sprites: [{ - size: [10, 10], - anchor: [0.5, 0.5], - }], - markerCount: 0, - markers: new Float32Array(), - labels: new Float32Array(), - labelCount: 0, - currentZoom: 0, - }; - }); - - it ('Приоритетный маркер победил неприоритетный, расположенный раньше него в выдаче', () => { - const markers = [{ - pixelPosition: [50, 50], - groupIndex: 0, - iconIndex: 0, - }, { - pixelPosition: [50, 50], - groupIndex: 0, - iconIndex: -1, - priority: true, - }]; - - const markerArray = new Float32Array(markers.length * stride); - pack(markerArray, markers); - - msg.markerCount = markers.length; - msg.markers = markerArray; - - generalize(msg); - unpack(markers, markerArray); - - equal(markers[0].iconIndex, -1); - equal(markers[1].iconIndex, 0); - }); - - it ('Видимый на экране маркер победил скрытый маркер, расположенный раньше него в выдаче', () => { - const markers = [{ - pixelPosition: [50, 50], - groupIndex: 0, - iconIndex: -1, - }, { - pixelPosition: [50, 50], - groupIndex: 0, - iconIndex: 0, - }]; - - const markerArray = new Float32Array(markers.length * stride); - pack(markerArray, markers); - - msg.markerCount = markers.length; - msg.markers = markerArray; - - generalize(msg); - unpack(markers, markerArray); - - equal(markers[0].iconIndex, -1); - equal(markers[1].iconIndex, 0); - }); - }); - - describe('Marker labels', () => { - let msg: WorkerMessage; - - beforeEach(() => { - msg = { - bounds: { minX: 0, minY: 0, maxX: 100, maxY: 100 }, - priorityGroups: [{ - safeZone: 0, - margin: 0, - degradation: 0, - iconIndex: 0, - }], - sprites: [{ - size: [10, 10], - anchor: [0.5, 0.5], - }], - markerCount: 0, - markers: new Float32Array(), - labels: new Float32Array(), - labelCount: 0, - currentZoom: 10, - }; - }); - - it ('Подпись первого маркера не убита вторым маркером', () => { - const markers: Marker[] = [{ - pixelPosition: [5, 5], - groupIndex: 0, - iconIndex: -1, - htmlLabel: { - width: 10, - height: 10, - offset: [5, 5], - display: false, - minZoom: -Infinity, - }, - }, { - pixelPosition: [15, 15], - groupIndex: 0, - iconIndex: -1, - }]; - - const markerArray = new Float32Array(markers.length * stride); - const labelArray = new Float32Array(labels.stride); - pack(markerArray, markers); - labels.pack(labelArray, markers, 1); - - msg.markerCount = markers.length; - msg.markers = markerArray; - msg.labelCount = 1; - msg.labels = labelArray; - - generalize(msg); - unpack(markers, markerArray); - labels.unpack(markers, labelArray); - - equal(markers[0].iconIndex, 0); - equal(markers[1].iconIndex, 0); - - equal((markers[0].htmlLabel as Label).display, true); - }); - - it ('Подпись первого маркера выжила', () => { - const markers: Marker[] = [{ - pixelPosition: [5, 5], - groupIndex: 0, - iconIndex: -1, - htmlLabel: { - width: 10, - height: 10, - offset: [10, 10], - display: false, - minZoom: -Infinity, - }, - }, { - pixelPosition: [35, 35], - groupIndex: 0, - iconIndex: -1, - }]; - - const markerArray = new Float32Array(markers.length * stride); - const labelArray = new Float32Array(labels.stride); - pack(markerArray, markers); - labels.pack(labelArray, markers, 1); - - msg.markerCount = markers.length; - msg.markers = markerArray; - msg.labelCount = 1; - msg.labels = labelArray; - - generalize(msg); - unpack(markers, markerArray); - labels.unpack(markers, labelArray); - - equal(markers[0].iconIndex, 0); - equal(markers[1].iconIndex, 0); - - equal((markers[0].htmlLabel as Label).display, true); - }); - - it ('Подпись второго маркера перекрыта подписью первого маркера', () => { - const markers: Marker[] = [{ - pixelPosition: [5, 5], - groupIndex: 0, - iconIndex: -1, - htmlLabel: { - width: 10, - height: 10, - offset: [5, 5], - display: false, - minZoom: -Infinity, - }, - }, { - pixelPosition: [15, 5], - groupIndex: 0, - iconIndex: -1, - htmlLabel: { - width: 10, - height: 10, - offset: [-5, 5], - display: false, - minZoom: -Infinity, - }, - }]; - - const markerArray = new Float32Array(markers.length * stride); - const labelArray = new Float32Array(markers.length * labels.stride); - pack(markerArray, markers); - labels.pack(labelArray, markers, 1); - - msg.markerCount = markers.length; - msg.markers = markerArray; - msg.labelCount = 2; - msg.labels = labelArray; - - generalize(msg); - unpack(markers, markerArray); - labels.unpack(markers, labelArray); - - equal((markers[0].htmlLabel as Label).display, true); - equal((markers[1].htmlLabel as Label).display, false); - }); - - it ( - 'Подпись второго маркера не перекрыта подписью первого маркера, ' + - 'ей выставляется minZoom меньше текущего', () => { - const markers: Marker[] = [{ - pixelPosition: [10, 50], - groupIndex: 0, - iconIndex: -1, - htmlLabel: { - width: 10, - height: 10, - offset: [10, -10], - display: false, - minZoom: -Infinity, - }, - }, { - pixelPosition: [90, 50], - groupIndex: 0, - iconIndex: -1, - htmlLabel: { - width: 10, - height: 10, - offset: [-30, -10], - display: false, - minZoom: -Infinity, - }, - }]; - - const markerArray = new Float32Array(markers.length * stride); - const labelArray = new Float32Array(markers.length * labels.stride); - pack(markerArray, markers); - labels.pack(labelArray, markers, 1); - - msg.markerCount = markers.length; - msg.markers = markerArray; - msg.labelCount = 2; - msg.labels = labelArray; - - generalize(msg); - unpack(markers, markerArray); - labels.unpack(markers, labelArray); - - equal((markers[0].htmlLabel as Label).display, true); - equal((markers[1].htmlLabel as Label).display, true); - equal((markers[0].htmlLabel as Label).minZoom, -Infinity); - equal((markers[1].htmlLabel as Label).minZoom, 9.321928024291992); - }); - }); - it('маркер повторно проходящий генерализацию, ' + 'но не попадающий в переданные границы, должен иметь iconIndex = -1', () => { @@ -633,9 +361,6 @@ describe('generalize.ts', () => { }], markerCount: markers.length, markers: markerArray, - labels: new Float32Array(), - labelCount: 0, - currentZoom: 0, }; generalize(msg); @@ -669,9 +394,6 @@ describe('generalize.ts', () => { }], markerCount: markers.length, markers: markerArray, - labels: new Float32Array(), - labelCount: 0, - currentZoom: 0, }; generalize(msg); diff --git a/test/markerArray.spec.ts b/test/markerArray.spec.ts new file mode 100644 index 0000000..8328dae --- /dev/null +++ b/test/markerArray.spec.ts @@ -0,0 +1,16 @@ +import { equal } from 'assert'; + +import { offsets } from '../src/markerArray'; + +describe('markerArray.ts', () => { + it('значения полей в offsets должны идти по порядку и с 0', () => { + let lastValue = -1; + + for (const key in offsets) { + if (offsets.hasOwnProperty(key)) { + equal(offsets[key], lastValue + 1); + lastValue = offsets[key]; + } + } + }); +});