From 96c29ab9d9c0c73b9ea66d9259a95b2ad229460b Mon Sep 17 00:00:00 2001 From: IK Date: Tue, 22 Sep 2026 20:00:08 +0700 Subject: [PATCH] feat: separate activity maps by waterbody --- apps/web/src/components/ActivityMap.astro | 59 ++++++++++++++++------- docs/ROADMAP.md | 2 +- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/apps/web/src/components/ActivityMap.astro b/apps/web/src/components/ActivityMap.astro index 70dd22a..c522272 100644 --- a/apps/web/src/components/ActivityMap.astro +++ b/apps/web/src/components/ActivityMap.astro @@ -2,27 +2,47 @@ import { spotPath, type Activity } from "../lib/api"; const { items } = Astro.props as { items: Activity[] }; -const xValues = items.map((item) => item.x); -const yValues = items.map((item) => item.y); -const minX = Math.min(...xValues, 0); -const maxX = Math.max(...xValues, 1); -const minY = Math.min(...yValues, 0); -const maxY = Math.max(...yValues, 1); -const xRange = Math.max(1, maxX - minX); -const yRange = Math.max(1, maxY - minY); -const column = (value: number) => Math.min(20, Math.max(1, Math.round(((value - minX) / xRange) * 18) + 1)); -const row = (value: number) => Math.min(20, Math.max(1, Math.round((1 - (value - minY) / yRange) * 18) + 1)); +const groups = [...items.reduce((result, item) => { + const group = result.get(item.waterbody_slug) ?? []; + group.push(item); + result.set(item.waterbody_slug, group); + return result; +}, new Map()).values()]; +const plotPoints = (group: Activity[]) => { + const minX = Math.min(...group.map(item => item.x), 0); + const maxX = Math.max(...group.map(item => item.x), 1); + const minY = Math.min(...group.map(item => item.y), 0); + const maxY = Math.max(...group.map(item => item.y), 1); + const xRange = Math.max(1, maxX - minX); + const yRange = Math.max(1, maxY - minY); + const occupied = new Set(); + return group.map(item => { + const targetColumn = Math.min(19, Math.max(2, Math.round(((item.x - minX) / xRange) * 16) + 2)); + const targetRow = Math.min(19, Math.max(2, Math.round((1 - (item.y - minY) / yRange) * 16) + 2)); + let column = targetColumn; + let row = targetRow; + for (let distance = 0; occupied.has(`${column}:${row}`) && distance < 20; distance += 1) { + column = ((targetColumn - 2 + distance + 1) % 18) + 2; + row = ((targetRow - 2 + Math.floor((distance + 1) / 18)) % 18) + 2; + } + occupied.add(`${column}:${row}`); + return { item, column, row }; + }); +}; ---
-
Режим схемы

Горячие точки на координатной сетке

-

Показаны подтверждённые координаты текущей выборки. Это схема `x:y`, а не географическая карта водоёма.

+
Режим схемы

Горячие точки по водоёмам

+

Для каждого водоёма своя координатная сетка `x:y`. Совпадающие точки разнесены, чтобы каждая оставалась доступной.

-
- - - {items.map((item) => {item.x}:{item.y}{item.fish})} -
+ {groups.map(group =>
+

{group[0].waterbody} {group.length} {group.length === 1 ? "точка" : "точки"}

+
+ + + {plotPoints(group).map(({ item, column, row }) => {item.x}:{item.y}{item.fish})} +
+
)}