fix(tgu-ops-ui): fix track antimeridian wrap and polygon culling on 2D map
drawTracks: detect antimeridian crossing by comparing consecutive screen X positions against worldSize/2; use moveTo instead of lineTo on large jumps so tracks don't draw a horizontal line across the globe. Tgu2DMapView: replace the heuristic lon-range check (> 300°) with an exact condition — skip bbox culling when the canvas is wider than the world tile (entire globe visible) or when topLeft.lon >= bottomRight.lon (viewport straddles the antimeridian). Both cases previously produced a nonsensical bbox that hid polygons near ±180°. drawRequests: remove per-polygon screen-space culling; viewport filtering is already handled upstream by visiblePolygons, so the redundant check was incorrectly hiding requests at high zoom levels near canvas edges. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -83,18 +83,19 @@ export function Tgu2DMapView({
|
|||||||
const polygonIndex = useMemo(() => createPolygonSpatialIndex(activePolygons), [activePolygons]);
|
const polygonIndex = useMemo(() => createPolygonSpatialIndex(activePolygons), [activePolygons]);
|
||||||
const projection = useMemo(() => createMapProjection(view, size), [view, size]);
|
const projection = useMemo(() => createMapProjection(view, size), [view, size]);
|
||||||
const visiblePolygons = useMemo(() => {
|
const visiblePolygons = useMemo(() => {
|
||||||
|
// When the canvas is wider than the world tile, the entire globe is visible —
|
||||||
|
// bbox filtering would produce a nonsensical range, so skip it.
|
||||||
|
// When the viewport crosses the antimeridian, topLeft.lon ends up east of
|
||||||
|
// bottomRight.lon — same situation, show everything.
|
||||||
const topLeft = projection.unproject({ x: 0, y: 0 });
|
const topLeft = projection.unproject({ x: 0, y: 0 });
|
||||||
const bottomRight = projection.unproject({ x: size.width, y: size.height });
|
const bottomRight = projection.unproject({ x: size.width, y: size.height });
|
||||||
const minLon = Math.min(topLeft.lon, bottomRight.lon);
|
if (size.width >= projection.worldSize || topLeft.lon >= bottomRight.lon) {
|
||||||
const maxLon = Math.max(topLeft.lon, bottomRight.lon);
|
|
||||||
|
|
||||||
if (maxLon - minLon > 300) {
|
|
||||||
return activePolygons;
|
return activePolygons;
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryPolygonsByBBox(polygonIndex, {
|
return queryPolygonsByBBox(polygonIndex, {
|
||||||
minLon,
|
minLon: topLeft.lon,
|
||||||
maxLon,
|
maxLon: bottomRight.lon,
|
||||||
minLat: Math.min(topLeft.lat, bottomRight.lat),
|
minLat: Math.min(topLeft.lat, bottomRight.lat),
|
||||||
maxLat: Math.max(topLeft.lat, bottomRight.lat)
|
maxLat: Math.max(topLeft.lat, bottomRight.lat)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,15 +12,12 @@ export function drawRequests(
|
|||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
polygons: MapPolygon[],
|
polygons: MapPolygon[],
|
||||||
projection: MapProjection,
|
projection: MapProjection,
|
||||||
viewport: { width: number; height: number }
|
_viewport: { width: number; height: number }
|
||||||
) {
|
) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
for (const polygon of polygons) {
|
for (const polygon of polygons) {
|
||||||
if (polygon.points.length < 3) continue;
|
if (polygon.points.length < 3) continue;
|
||||||
const screenPoints = polygon.points.map((point) => projection.project(point));
|
const screenPoints = polygon.points.map((point) => projection.project(point));
|
||||||
if (!screenPoints.some((point) => point.x >= -32 && point.x <= viewport.width + 32 && point.y >= -32 && point.y <= viewport.height + 32)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const color = SURVEY_COLORS[polygon.surveyType ?? ""] ?? DEFAULT_COLOR;
|
const color = SURVEY_COLORS[polygon.surveyType ?? ""] ?? DEFAULT_COLOR;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ export function drawTracks(ctx: CanvasRenderingContext2D, lines: MapLine[], proj
|
|||||||
ctx.strokeStyle = "rgba(104, 195, 189, 0.76)";
|
ctx.strokeStyle = "rgba(104, 195, 189, 0.76)";
|
||||||
ctx.lineWidth = 1.2;
|
ctx.lineWidth = 1.2;
|
||||||
|
|
||||||
|
const wrapThreshold = projection.worldSize / 2;
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.points.length < 2) continue;
|
if (line.points.length < 2) continue;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
line.points.forEach((point, index) => {
|
let prevX: number | undefined;
|
||||||
|
for (const point of line.points) {
|
||||||
const screen = projection.project(point);
|
const screen = projection.project(point);
|
||||||
if (index === 0) {
|
if (prevX === undefined || Math.abs(screen.x - prevX) > wrapThreshold) {
|
||||||
ctx.moveTo(screen.x, screen.y);
|
ctx.moveTo(screen.x, screen.y);
|
||||||
} else {
|
} else {
|
||||||
ctx.lineTo(screen.x, screen.y);
|
ctx.lineTo(screen.x, screen.y);
|
||||||
}
|
}
|
||||||
});
|
prevX = screen.x;
|
||||||
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user