You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在2D网格仿真环境中映射真实坐标并消除重叠

问题:移除仿真环境中的重叠坐标

我正在基于真实世界坐标构建仿真环境模型,项目采用Java与JASON开发。目前遇到坐标重叠问题(结果图中红线标记的即为重叠坐标),这些重叠坐标对后续智能体仿真无意义,需将其移除。

结果图:
Result Env

当前建模代码

package src.model;

import jason.environment.grid.GridWorldModel;
import jason.environment.grid.Location;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

public class AgentModel extends GridWorldModel {

    Logger log = Logger.getLogger(AgentModel.class.getName());

    public static final int WALL = 16;
    public static final int AGENTS = 2;

    private final Set<Location> wallLocation;
    private final Set<Location> processedLocations = new HashSet<>();
    private final int screenWidth;
    private final int screenHeight;
    private final double[][] coordinates;
    private final double[] minMax;
    private double lonScale;
    private double latScale;
    private double x0;
    private double y0;

    protected AgentModel(int gridWidth, int gridHeight, double[][] coordinates, double[] minMax) {
        super(gridWidth, gridHeight, WALL);
        this.coordinates = coordinates;
        this.screenWidth = gridWidth;
        this.screenHeight = gridHeight;
        this.minMax = minMax;
        this.wallLocation = new HashSet<>();

        initializeScaling();
        addWall();
        findEmptyCellsUntilWall();
    }

    private void initializeScaling() {
        double minLon = minMax[0];
        double maxLon = minMax[1];
        double minLat = minMax[2];
        double maxLat = minMax[3];

        double deltaLon = maxLon - minLon;
        double deltaLat = maxLat - minLat;

        if (deltaLon != 0.0 && deltaLat != 0.0) {
            lonScale = (screenWidth - 1) / deltaLon;
            latScale = (screenHeight - 1) / deltaLat;
            x0 = -minLon * lonScale;
            y0 = maxLat * latScale;

            if (lonScale > latScale) {
                lonScale = latScale;
                x0 = ((screenWidth - 1) - (minLon + maxLon) * lonScale) / 2.0;
            } else {
                latScale = lonScale;
                y0 = ((screenHeight - 1) + (minLat + maxLat) * latScale) / 2.0;
            }
        } else if (deltaLon != 0.0) {
            lonScale = (screenWidth - 1) / deltaLon;
            latScale = 0;
            x0 = -minLon * lonScale;
            y0 = (screenHeight - 1) / 2.0;
        } else if (deltaLat != 0.0) {
            lonScale = 0;
            latScale = (screenHeight - 1) / deltaLat;
            x0 = (screenWidth - 1) / 2.0;
            y0 = maxLat * latScale;
        } else {
            lonScale = 0;
            latScale = 0;
            x0 = (screenWidth - 1) / 2.0;
            y0 = (screenHeight - 1) / 2.0;
        }
    }

    private void addWall() {
        List<Location[]> coordinatePairs = new ArrayList<>();
        for (int i = 0; i < coordinates.length - 1; i++) {
            Location start = transformCoordinates(coordinates[i][0], coordinates[i][1]);
            Location end = transformCoordinates(coordinates[i + 1][0], coordinates[i + 1][1]);
            coordinatePairs.add(new Location[]{start, end});
        }
        addWallsBetweenCoordinates(coordinatePairs);
    }

    private void addWallsBetweenCoordinates(List<Location[]> coordinates) {
        for (Location[] pair : coordinates) {
            if (pair.length < 2) continue;

            Location start = pair[0];
            Location end = pair[1];

            if (processedLocations.contains(start) && processedLocations.contains(end)) {
                continue;
            }

            addWallInBetween(start, end);
            processedLocations.add(start);
            processedLocations.add(end);
        }
    }

    private Location transformCoordinates(double x, double y) {
        int gridX = (int) (x0 + x * lonScale);
        int gridY = (int) (y0 - y * latScale);
        return new Location(gridX, gridY);
    }

    private void addWallInBetween(Location start, Location end) {
        int dx = Math.abs(end.x - start.x);
        int dy = Math.abs(end.y - start.y);
        int sx = start.x < end.x ? 1 : -1;
        int sy = start.y < end.y ? 1 : -1;
        int err = dx - dy;

        int x = start.x;
        int y = start.y;

        while (true) {
            Location loc = new Location(x, y);
            if (!wallLocation.contains(loc)) {
                wallLocation.add(loc);
                if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
                    add(WALL, loc);
                }
            }
            if (x == end.x && y == end.y) {
                break;
            }
            int e2 = 2 * err;
            if (e2 > -dy) {
                err -= dy;
                x += sx;
            }
            if (e2 < dx) {
                err += dx;
                y += sy;
            }
        }
    }

    public void findEmptyCellsUntilWall() {
        for (int x = 0; x < getWidth(); x++) {
            for (int y = 0; y < getHeight(); y++) {
                if (hasObject(WALL, x, y)) {
                    break;
                } else {
                    paintEmptyCells(x, y);
                }
            }
        }
    }

    private void paintEmptyCells(int startX, int startY) {
        for (int x = startX; x < getWidth(); x++) {
            if (hasObject(WALL, x, startY)) {
                break;
            } else {
                add(OBSTACLE, x, startY);
            }
        }

        for (int x = startX; x >= 0; x--) {
            if (hasObject(WALL, x, startY)) {
                break;
            } else {
                add(OBSTACLE, x, startY);
            }
        }

        for (int y = startY; y >= 0; y--) {
            if (hasObject(WALL, startX, y)) {
                break;
            } else {
                add(OBSTACLE, startX, y);
            }
        }

        for (int y = startY; y < getHeight(); y++) {
            if (hasObject(WALL, startX, y)) {
                break;
            } else {
                add(OBSTACLE, startX, y);
            }
        }
    }
}

问题分析与解决方法

坐标重叠的核心原因是真实世界坐标转换为网格坐标时的精度丢失,以及画线算法处理连续线段时重复标记同一网格位置。以下是针对性修复方案:

1. 前置去重:过滤转换后的重复端点

在生成墙的坐标对前,跳过与前一个位置重复的网格点,避免无效的重复线段:

private void addWall() {
    List<Location[]> coordinatePairs = new ArrayList<>();
    Location prevLoc = null;
    for (int i = 0; i < coordinates.length; i++) {
        Location currLoc = transformCoordinates(coordinates[i][0], coordinates[i][1]);
        // 跳过重复的连续点
        if (prevLoc != null && currLoc.equals(prevLoc)) {
            continue;
        }
        if (prevLoc != null) {
            coordinatePairs.add(new Location[]{prevLoc, currLoc});
        }
        prevLoc = currLoc;
    }
    addWallsBetweenCoordinates(coordinatePairs);
}

2. 优化障碍物填充的重复标记问题

添加集合记录已填充的障碍物位置,避免同一网格被多次标记:

// 在类中新增集合
private final Set<Location> obstacleLocations = new HashSet<>();

private void paintEmptyCells(int startX, int startY) {
    // 向右填充
    for (int x = startX; x < getWidth(); x++) {
        Location loc = new Location(x, startY);
        if (hasObject(WALL, loc)) break;
        if (!obstacleLocations.contains(loc)) {
            obstacleLocations.add(loc);
            add(OBSTACLE, loc);
        }
    }

    // 向左填充
    for (int x = startX - 1; x >= 0; x--) {
        Location loc = new Location(x, startY);
        if (hasObject(WALL, loc)) break;
        if (!obstacleLocations.contains(loc)) {
            obstacleLocations.add(loc);
            add(OBSTACLE, loc);
        }
    }

    // 向上填充
    for (int y = startY - 1; y >= 0; y--) {
        Location loc = new Location(startX, y);
        if (hasObject(WALL, loc)) break;
        if (!obstacleLocations.contains(loc)) {
            obstacleLocations.add(loc);
            add(OBSTACLE, loc);
        }
    }

    // 向下填充
    for (int y = startY + 1; y < getHeight(); y++) {
        Location loc = new Location(startX, y);
        if (hasObject(WALL, loc)) break;
        if (!obstacleLocations.contains(loc)) {
            obstacleLocations.add(loc);
            add(OBSTACLE, loc);
        }
    }
}

// 同时修改空单元格遍历方法,避免重复处理同一位置
public void findEmptyCellsUntilWall() {
    Set<Location> processedCells = new HashSet<>();
    for (int x = 0; x < getWidth(); x++) {
        for (int y = 0; y < getHeight(); y++) {
            Location loc = new Location(x, y);
            if (hasObject(WALL, loc)) break;
            if (!processedCells.contains(loc)) {
                processedCells.add(loc);
                paintEmptyCells(x, y);
            }
        }
    }
}

3. 可选:提升坐标转换精度

用Math.round替代强制类型转换,减少截断导致的不必要重叠,同时确保坐标在网格范围内:

private Location transformCoordinates(double x, double y) {
    int gridX = (int) Math.round(x0 + x * lonScale);
    int gridY = (int) Math.round(y0 - y * latScale);
    // 限制坐标在网格边界内
    gridX = Math.max(0, Math.min(gridX, getWidth() - 1));
    gridY = Math.max(0, Math.min(gridY, getHeight() - 1));
    return new Location(gridX, gridY);
}

内容的提问来源于stack exchange,提问作者Duda Validator

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 19:53:10