Tonight
This commit is contained in:
parent
28323f145e
commit
94f9c0d594
76 changed files with 3238 additions and 1230 deletions
128
frontend/src/lib/h3-selection.ts
Normal file
128
frontend/src/lib/h3-selection.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { cellToBoundary, cellToLatLng, getResolution } from 'h3-js';
|
||||
import type { HexagonData } from '../types';
|
||||
|
||||
type Point = [number, number];
|
||||
|
||||
const EPSILON = 1e-12;
|
||||
|
||||
function samePoint(a: Point, b: Point): boolean {
|
||||
return Math.abs(a[0] - b[0]) <= EPSILON && Math.abs(a[1] - b[1]) <= EPSILON;
|
||||
}
|
||||
|
||||
function cellBoundary(h3: string): Point[] {
|
||||
const boundary = cellToBoundary(h3, true) as Point[];
|
||||
if (boundary.length > 1 && samePoint(boundary[0], boundary[boundary.length - 1])) {
|
||||
return boundary.slice(0, -1);
|
||||
}
|
||||
return boundary;
|
||||
}
|
||||
|
||||
function orientation(a: Point, b: Point, c: Point): number {
|
||||
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
||||
}
|
||||
|
||||
function pointOnSegment(point: Point, start: Point, end: Point): boolean {
|
||||
if (Math.abs(orientation(start, end, point)) > EPSILON) return false;
|
||||
return (
|
||||
point[0] >= Math.min(start[0], end[0]) - EPSILON &&
|
||||
point[0] <= Math.max(start[0], end[0]) + EPSILON &&
|
||||
point[1] >= Math.min(start[1], end[1]) - EPSILON &&
|
||||
point[1] <= Math.max(start[1], end[1]) + EPSILON
|
||||
);
|
||||
}
|
||||
|
||||
function segmentsIntersect(a: Point, b: Point, c: Point, d: Point): boolean {
|
||||
const abC = orientation(a, b, c);
|
||||
const abD = orientation(a, b, d);
|
||||
const cdA = orientation(c, d, a);
|
||||
const cdB = orientation(c, d, b);
|
||||
|
||||
if (
|
||||
((abC > EPSILON && abD < -EPSILON) || (abC < -EPSILON && abD > EPSILON)) &&
|
||||
((cdA > EPSILON && cdB < -EPSILON) || (cdA < -EPSILON && cdB > EPSILON))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
pointOnSegment(c, a, b) ||
|
||||
pointOnSegment(d, a, b) ||
|
||||
pointOnSegment(a, c, d) ||
|
||||
pointOnSegment(b, c, d)
|
||||
);
|
||||
}
|
||||
|
||||
function pointInPolygon(point: Point, polygon: Point[]): boolean {
|
||||
let inside = false;
|
||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
const current = polygon[i];
|
||||
const previous = polygon[j];
|
||||
|
||||
if (pointOnSegment(point, previous, current)) return true;
|
||||
|
||||
if (current[1] > point[1] !== previous[1] > point[1]) {
|
||||
const x =
|
||||
((previous[0] - current[0]) * (point[1] - current[1])) /
|
||||
(previous[1] - current[1]) +
|
||||
current[0];
|
||||
if (point[0] < x) inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
export function polygonsOverlap(a: Point[], b: Point[]): boolean {
|
||||
if (a.length < 3 || b.length < 3) return false;
|
||||
|
||||
if (a.some((point) => pointInPolygon(point, b))) return true;
|
||||
if (b.some((point) => pointInPolygon(point, a))) return true;
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const aNext = (i + 1) % a.length;
|
||||
for (let j = 0; j < b.length; j++) {
|
||||
const bNext = (j + 1) % b.length;
|
||||
if (segmentsIntersect(a[i], a[aNext], b[j], b[bNext])) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasMatchingHexagonAtResolution(
|
||||
hexagons: HexagonData[],
|
||||
resolution: number
|
||||
): boolean {
|
||||
return hexagons.some(
|
||||
(hexagon) => hexagon.count > 0 && getResolution(hexagon.h3) === resolution
|
||||
);
|
||||
}
|
||||
|
||||
export function findOverlappingMatchingHexagon(
|
||||
previousH3: string,
|
||||
hexagons: HexagonData[],
|
||||
resolution: number
|
||||
): HexagonData | null {
|
||||
const previousBoundary = cellBoundary(previousH3);
|
||||
const [previousLat, previousLng] = cellToLatLng(previousH3);
|
||||
let best: HexagonData | null = null;
|
||||
let bestDistance = Infinity;
|
||||
|
||||
for (const hexagon of hexagons) {
|
||||
if (hexagon.count <= 0 || getResolution(hexagon.h3) !== resolution) continue;
|
||||
if (!polygonsOverlap(previousBoundary, cellBoundary(hexagon.h3))) continue;
|
||||
|
||||
const distance = (hexagon.lat - previousLat) ** 2 + (hexagon.lon - previousLng) ** 2;
|
||||
if (
|
||||
!best ||
|
||||
distance < bestDistance - EPSILON ||
|
||||
(Math.abs(distance - bestDistance) <= EPSILON &&
|
||||
(hexagon.count > best.count ||
|
||||
(hexagon.count === best.count && hexagon.h3.localeCompare(best.h3) < 0)))
|
||||
) {
|
||||
best = hexagon;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue