da753b4ca9
Polygons crossing the antimeridian were rendered with a large visual jump. Now each polygon is drawn three times (dx = 0, ±worldSize) after unwrapping X-coordinates, with viewport culling to skip invisible copies. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import type { MapProjection } from "../geometry/mapProjection";
|
|
import type { MapPolygon } from "../model/mapTypes";
|
|
|
|
const SURVEY_COLORS: Record<string, string> = {
|
|
OPTICS: "#FFA040",
|
|
RSA: "#40AFFF",
|
|
COMBINED: "#C080FF"
|
|
};
|
|
const DEFAULT_COLOR = "#FFA040";
|
|
|
|
type Pt = { x: number; y: number };
|
|
|
|
function unwrapX(points: Pt[], worldSize: number): Pt[] {
|
|
if (points.length === 0) return [];
|
|
const out: Pt[] = [points[0]];
|
|
let prevX = points[0].x;
|
|
for (let i = 1; i < points.length; i++) {
|
|
let x = points[i].x;
|
|
while (x - prevX > worldSize / 2) x -= worldSize;
|
|
while (prevX - x > worldSize / 2) x += worldSize;
|
|
out.push({ x, y: points[i].y });
|
|
prevX = x;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function drawRequests(
|
|
ctx: CanvasRenderingContext2D,
|
|
polygons: MapPolygon[],
|
|
projection: MapProjection,
|
|
viewport: { width: number; height: number }
|
|
) {
|
|
ctx.save();
|
|
for (const polygon of polygons) {
|
|
if (polygon.points.length < 3) continue;
|
|
const raw = polygon.points.map((p) => projection.project(p));
|
|
const pts = unwrapX(raw, projection.worldSize);
|
|
const color = SURVEY_COLORS[polygon.surveyType ?? ""] ?? DEFAULT_COLOR;
|
|
ctx.fillStyle = hexToRgba(color, 0.18);
|
|
ctx.strokeStyle = hexToRgba(color, 0.85);
|
|
ctx.lineWidth = 1.5;
|
|
ctx.setLineDash([4, 3]);
|
|
for (const dx of [0, projection.worldSize, -projection.worldSize]) {
|
|
const visible = pts.some((p) => {
|
|
const x = p.x + dx;
|
|
return x >= -64 && x <= viewport.width + 64 && p.y >= -64 && p.y <= viewport.height + 64;
|
|
});
|
|
if (!visible) continue;
|
|
ctx.beginPath();
|
|
for (let i = 0; i < pts.length; i++) {
|
|
if (i === 0) ctx.moveTo(pts[i].x + dx, pts[i].y);
|
|
else ctx.lineTo(pts[i].x + dx, pts[i].y);
|
|
}
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
}
|
|
ctx.setLineDash([]);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function hexToRgba(hex: string, alpha: number): string {
|
|
const value = hex.replace("#", "");
|
|
const red = Number.parseInt(value.slice(0, 2), 16);
|
|
const green = Number.parseInt(value.slice(2, 4), 16);
|
|
const blue = Number.parseInt(value.slice(4, 6), 16);
|
|
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
|
|
}
|