【Java实现】南京地铁导航系统的简单实现(一)—— 存储站点信息

目录

实现内容

地铁站点信息存储

分析需要的数据结构Station类:

数据导入方案:

数据处理过程:

 站点信息验证:

 站点信息的xml读写:

读取xml

写入xml  



实现内容

        以南京地铁运营示意图为模板,实现任意两个站点之间最优路径导航的规划与动态展示效果。具体模板图片以及要求如下:

图1 南京地铁运营示意图

        1. 存储南京地铁线路站点信息。

        2. 给定起点站和终点站,假设相邻站点路径长度相等,求路径最短的地铁乘坐方案;

        3. 给定起点站和终点站,假设相邻站点路径长度相等,求换乘次数最少的地铁乘坐方案,若存在多条换乘次数相同的乘坐方案,则给出换乘次数最少且路径长度最短的乘坐方案。

        4. 在实际应用中,相邻站点的距离并不相等,假设中转站地铁停留时间为t1,非中转站地铁停留时间为T2,地铁换乘一次的时间消耗为T3(不考虑等待地铁的时间),地铁平均速度为v,相邻站点的路径长度已知,试求:在给定起点站和终点站的情况下,求乘坐时间最短的地铁乘坐方案。

        5. 设计可视化的查询界面,对以上内容进行动态化展示。


地铁站点信息存储

分析需要的数据结构Station类:

      Station类成员变量需要包含的有如下几个方面信息: 站名、逻辑地址、所属线路编号(表)、[临接站点]。对于这个类而言静态变量需要记录几张表项:所有站点目录、所有线路目录、逻辑地图、[临接站点的实际距离],具体临接站点实际距离是针对计算用时短单独设定的,暂时不做考虑。

        对于该类对象的初始化需要生成相应的站点,并更新类的所有表项,较为繁琐,对于外界用户而言很容易考虑不周而造成表项更新重、漏现象,不利于数据维护。因此最好的方法是“仿照”单例模式,私有化构造器然后使用NewInstance(...)来生成。在NewInstance(...)函数中具体分析相应的参数是否需要生成新对象,是否需要对其参数表修改,是否需要去更新信息,提高了程序的健壮性。

        对于类对象的判断是否相等采用了只判别站名的方式,因此站名成为了站的唯一标识符,这对于一个城市(几乎没有相同站名)而言是可行的(否则要加id)。因此重写equals方法和hashcode算法(对这个实现没啥用,可以删去),得到Station类。

        其他一些set get方法设计等暂且隐去。



/**
 * @author Kksp993
 */
public class Station {
    /**
     * station Map's width
     */
    public static final int MAP_WIDTH = 40;
    /**
     * station name
     */
    private String name = "";
    /**
     * station position on the screen
     */
    private LogicalPoint loc;
    /*
     * the line number of the station
     */
    private ArrayList<Integer> lineNums = new ArrayList<>();
    /**
     * register all the stations connected to this
     */
    private ArrayList<Station> connStations = new ArrayList<>();
    /**
     * all station registered list
     */
    private static ArrayList<Station> stations = new ArrayList<Station>();
    /**
     * all station registered logical address map
     */
    private static Station[][] stationsMap = new Station[MAP_WIDTH][MAP_WIDTH];
    /**
     * Entry<key,value>==<lineNum,lineList>
     */
    private static HashMap<Integer, ArrayList<Station>> line_Map = new HashMap<>();
        
    //....

    private Station(String name, LogicalPoint loc, int line_Num, ArrayList<Station> connStations) {
        super();
        this.name = name;
        this.loc = loc;
        this.connStations = connStations;
        lineNums.add(line_Num);
        stations.add(this);
        stationsMap[loc.getX()][loc.getY()] = this;
    }

    /**
     * do not add to list
     */
    private Station(String name) {
        this.name = name;
    }

    /**
     * To getInstance forName.
     */
    public static Station getInstance(String name) {
        int idx = -1;
        if (-1 != (idx = stations.indexOf(new Station(name)))) {
            return stations.get(idx);
        }
        return new Station(name);
    }

    /**
     * Singleton Pattern;To initial station without duplicated items.
     * duplicated,update information and return the instance
     * else,generate and return a new instance based on the information
     * @return a station instance
     */
    public static Station newInstance(String name, LogicalPoint loc, int line_Num, ArrayList<Station> connStations) {
        int idx = -1;
        if (-1 != (idx = stations.indexOf(new Station(name)))) {
            Station station = stations.get(idx);
            if (!station.getLineNums().contains(line_Num)) {
                station.getLineNums().add(line_Num);
            }
            station.setLoc(loc);
            for (Station connStation : connStations) {
                if (!station.getConnStations().contains(connStation)) {
                    station.getConnStations().add(connStation);
                }
            }
            if (loc.getX() != station.getLoc().getX() || loc.getY() !=
                    station.getLoc().getY()) {
                System.err.println(station + "两次线路不一致!");
                System.out.println("line_Num:" + line_Num);
                System.out.println("pre:" + station.loc);
                System.out.println("cur:" + loc);
            }
            return station;
        }
        return new Station(name, loc, line_Num, connStations);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Station other = (Station) obj;
        if (name == null) {
            if (other.name != null) {
                return false;
            }
        } else if (!name.equals(other.name)) {
            return false;
        }
        return true;
    }
    //....省略其他set get方法以及其他功能性方法
}

数据导入方案:

      由于题目所给的站点信息均存放在图片中,难以像Excel、mySql等导入数据,需要手动编辑,工作量非常大。这里采用了分步导入的思想,通过名称走向两个数据量,逐步、快捷地导入了所有站点的信息、坐标等,具体如下:

                ①按线路顺序依次导入所有站点的名字

                ②按照数组顺序确定线路走向

                ③根据起始站坐标走向确定各个站点具体位置

                ④依照加入顺序双绑定确定相连站点线路编号

       这样比起通常思维的直接导入数据,大大减少了输入的数据数量,降低了不必要的错误输入概率。由图知,一共有157个站点,上述数据最少也需要如下数据向量表:

       而上述的优化输入方式仅需要输入157站点和方向信息,在站点信息n的数量级上,仅有两种数据——方向和名称(少于三种)。因此在处理更大范围数据的时候,该算法具有更大的优势。

        方向信息的记录为:从北顺时针转一圈,分别为0,1,2,...,7(如下图所示)。其中\large dir{_{i}}​表示原名称数组中第i-1​个节点去第i​个节点的路由方向。

* <li>7 0 1</li>

* <li>6 M 2</li>

* <li>5 4 3</li>

数据处理过程:

        首先数据从好友获取敲好的数据(如左下图),是一个所有站名的数组。由于中转站只录入一次,不好配合方向信息,更改数组信息过于麻烦,索性先读入所有数据,转化为ArrayList的形式(如下图)。确定导入站点正确后,根据地图加入方向信息(如右下图)。

 ​ ​ 

       这个时候,所需最少数据已经导入系统中了,再创建几个标志型的参数表:


    private static ArrayList<String> stationNames = new ArrayList<>();
    private static int[] tags = new int[]{1, 2, 3, 4, 10, -1, -3, -7, -8, -9};
    /**
     * <li>7 0 1</li>
     * <li>6 M 2</li>
     * <li>5 4 3</li>
     *  记录所有站点连接方向
     */
    private static ArrayList<Integer> direction = new ArrayList<>();
    /**
     * 记录所有线路变更的跳变点
     */
    private static ArrayList<Integer> breaks = new ArrayList<>();
    /**
     * 记录线路的起点逻辑坐标,依次是1, 2, 3, 4, 10, S1, S3, S7, S8, S9
     */
    private static LogicalPoint[] staPoints = new LogicalPoint[]{new
            LogicalPoint(14, 3), new LogicalPoint(26, 6),
            new LogicalPoint(3, 4), new LogicalPoint(8, 8), new LogicalPoint(1, 18), new
            LogicalPoint(13, 25),
            new LogicalPoint(2, 25), new LogicalPoint(18, 28), new LogicalPoint(6, 5),
            new LogicalPoint(13, 24)};

        经过简单的计算、操作就可以得到完整的Station类表了。

  private static void generateStationXMl() throws IOException {
        Init();
        //依照线路存储数据
        for (int i = 0; i < breaks.size(); i++)
            initialStation(breaks.get(i), i == breaks.size() - 1 ? direction.size() - 1 : breaks.get(i + 1) - 1, staPoints[i].getX(), staPoints[i].getY(), tags[i]);
        //...
    }


    /**
     * 按线路初始化站点信息
     */
    private static void initialStation(int sta, int end, int px, int py, int
            line_Num) {
        LogicalPoint point = new LogicalPoint(px, py);
        Station lastStation = Station.newInstance(stationNames.get(sta), point, line_Num, new ArrayList<Station>());
        Station.setLine_Map(line_Num, 0, lastStation);
        for (int i = sta + 1; i <= end; i++) {
            // new a Station Instance, bind the lastStation and Overlay it.
            lastStation = Station.newInstance(stationNames.get(i), LogicalPoint.forDirection(lastStation.getLoc(), direction.get(i)),
                    line_Num, new ArrayList<Station>()).bindStation(lastStation, line_Num);
            Station.setLine_Map(line_Num, i - sta, lastStation);
        }
    }

打印以下Station类对象个数,可以看到有数字,说明导入成功了。

 站点信息验证:

        为了更加确切验证这157是不是真正想要的站点,是否算法存在不合理的地方,造成一些站点错位,这里最好做一下验证:

        使用图像法检验,发现无法绘制,发现报了站点地址不一致的错误(getInstance(...)方法检测到了站点信息异常)。为什么会有错呢?仔细观察图片,发现是在下图箭头所示处:原图像为了美观,在个别站点一次绘制了两格的长度来连接线路。因为个别站点的不对,导致该线路后续所有站点均发生错位。具体加完节点后图像如下:(绘制方式详见后续GUI设计部分)

       

        使用#1,#2...占位符事先添加所有虚节点(跨格子的点),在加入所有节点信息后,删除这些占位虚节点,并对受影响的所有编号、绑定信息等进行更新代码如下:

private static void generateStationXMl() throws IOException {
        Init();
        System.out.println("\t//" + 1);
        for (int i = 0; i < breaks.size(); i++)
            initialStation(breaks.get(i), i == breaks.size() - 1 ? direction.size() - 1 : breaks.get(i + 1) - 1, staPoints[i].getX(), staPoints[i].getY(), tags[i]);
        ArrayList<Station> nullStation = new ArrayList<>();
        for (Station station : Station.getStations())
            nullStation.add(station);
        for (Station station : nullStation) {
            if (station.isTS())
                Utils.printAllField(station);
            if (station.getName().startsWith("#"))
                for (int line_Num : station.getLineNums()) {
                    station.getConnStations().get(0).bindStation(station.getConnStations().get(1),
                            line_Num);
                    station.deleteStation();
                }
        }
    }

        再次检验所有节点是否异常,确认无误。

 站点信息的xml读写:

        这里主要刚学了xml操作,拿这个项目练一练手,所以选用了比较简单规则的xml记录方式。


dom4j学习相关链接:【Dom4j】Dom4j完整教程详解_Cyber-Drunker-CSDN博客_dom4j

dom4j包下载链接:dom4j


读取xml

        读取xml需要按照从根元素依次去找标签的方式读取,建议对照xml里的DOC文件或者是xml的属性特征读取数据。

        关键操作只有以下两个:

                使用.element(name):访问当前变量下的标签名为name的变量

                使用.elementText(name):访问当前变量下的标签名为name变量的标签值

        一个返回的Element对象(可以循环调用);另一个是它的标签,是个String类型的变量。其他字符串操作最好使用StringBuilder,以免生成大量无用的字符串常量。

    /**
     * read XML file and get all station infomation
     *
     * @throws Exception
     */
    public static void parseXml() throws Exception {
        Document document = new SAXReader().read(new File("src/db/MetroInfo.xml"));
        Element root = document.getRootElement();
        Iterator<Element> iterator = root.elementIterator();
        int px = 0, py = 0;
        while (iterator.hasNext()) {
            Element staElem = iterator.next();
            px = Integer.parseInt(staElem.element("point").elementText("px"));
            py = Integer.parseInt(staElem.element("point").elementText("py"));
            String[] line_Nums = staElem.elementText("line_Num").split("#");
            String[] connString = staElem.elementText("connStation").split("#");
            String[] idxString = staElem.elementText("line_idx").split("#");
            ArrayList<Station> connStation = new ArrayList<>();
            for (String string : connString) {
                connStation.add(Station.getInstance(string));
            }
            for (int i = 0; i < line_Nums.length; i++) {
                Station station = Station.newInstance(staElem.elementText("name"),
                        new LogicalPoint(px, py), Integer.parseInt(line_Nums[i]), connStation);
                Station.setLine_Map(Integer.parseInt(line_Nums[i]),
                        Integer.parseInt(idxString[i]), station);
            }
        }
        HashMap<Integer, ArrayList<Station>> lm = Station.getLine_Map();
        for (int lineNum : lm.keySet()) {
            ArrayList<Station> line = lm.get(lineNum);
            line.removeIf(Objects::isNull);
        }
    }

写入xml  

      写入xml和读基本是一致的,只是反过来,生成一个Element Tree。依旧有两个关键操作,具体如下:

        使用.addElement(...):在该xml表中增加一个元素,返回添加的元素。

        使用.addTest(...):为当前元素添加标签值。

        生成完Element Tree后写入新的xml文件里,以备检验是否正确。写入xml选用prettyFormat就可以写出整齐的xml文件了,如果不用直接写入会造成所有xml信息在一行的问题(虽然IDE格式化一下就好了)。

/**
     * update the changes to XML if possible
     *
     * @throws IOException
     */
    public static void writeXml() throws IOException {
        Document document = DocumentHelper.createDocument();
        Element root = document.addElement("root");
        for (Station station : Station.getStations()) {
            // generate line number string
            StringBuilder line_Num_string = new StringBuilder();
            StringBuilder conn_Station_string = new StringBuilder();
            StringBuilder line_idx_string = new StringBuilder();
            for (int line_Num : station.getLineNums()) {
                line_Num_string.append("#").append(line_Num);
                line_idx_string.append("#").append(station.lineIdxOf(line_Num));
            }
            // remove the first "#"
            line_Num_string = new StringBuilder(line_Num_string.substring(1));
            line_idx_string = new StringBuilder(line_idx_string.substring(1));
            for (Station connStation : station.getConnStations()) {
                conn_Station_string.append("#").append(connStation);
            }
            // remove the first "#"
            conn_Station_string = new StringBuilder(conn_Station_string.substring(1));
            // generate the station element
            Element staElem = root.addElement("Station");
            staElem.addElement("name").addText(station.getName());
            Element point = staElem.addElement("point");
            point.addElement("px").addText(station.getLoc().getX() + "");
            point.addElement("py").addText(station.getLoc().getY() + "");
            staElem.addElement("line_Num").addText(line_Num_string.toString());
            staElem.addElement("line_idx").addText(line_idx_string.toString());
            staElem.addElement("connStation").addText(conn_Station_string.toString());
        }

        XMLWriter writer = new XMLWriter(new OutputStreamWriter(
                new FileOutputStream(new File("src/db/MetroInfo2.xml")), "UTF-8"), OutputFormat.createPrettyPrint());
        writer.write(document);
        writer.close();
    }

        最后写入结果如下图:


         好了,以上就是这一小节讲解的如何将地铁信息写入xml文件保存,方便后续读取的部分了。感谢大家的阅读,喜欢的朋友一键三连哦,下一节我们接着说路线推荐的算法实现

【Java实现】南京地铁导航系统的简单实现(二)—— 最短路径算法的实现_kksp993的博客-CSDN博客

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值