栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【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方法设计等暂且隐去。



public class Station {
    
    public static final int MAP_WIDTH = 40;
    
    private String name = "";
    
    private LogicalPoint loc;
    
    private ArrayList lineNums = new ArrayList<>();
    
    private ArrayList connStations = new ArrayList<>();
    
    private static ArrayList stations = new ArrayList();
    
    private static Station[][] stationsMap = new Station[MAP_WIDTH][MAP_WIDTH];
    
    private static HashMap> line_Map = new HashMap<>();
        
    //....

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

    
    private Station(String name) {
        this.name = name;
    }

    
    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);
    }

    
    public static Station newInstance(String name, LogicalPoint loc, int line_Num, ArrayList 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(如下图所示)。其中​表示原名称数组中第​个节点去第​个节点的路由方向。

*

  • 7 0 1
  • *

  • 6 M 2
  • *

  • 5 4 3
  • 数据处理过程:

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

     ​​ ​ 

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

        private static ArrayList stationNames = new ArrayList<>();
        private static int[] tags = new int[]{1, 2, 3, 4, 10, -1, -3, -7, -8, -9};
        
        private static ArrayList direction = new ArrayList<>();
        
        private static ArrayList breaks = new ArrayList<>();
        
        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.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()).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 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,以免生成大量无用的字符串常量。

        
        public static void parseXml() throws Exception {
            document document = new SAXReader().read(new File("src/db/MetroInfo.xml"));
            Element root = document.getRootElement();
            Iterator 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 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> lm = Station.getLine_Map();
            for (int lineNum : lm.keySet()) {
                ArrayList line = lm.get(lineNum);
                line.removeIf(Objects::isNull);
            }
        }
    

    写入xml  

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

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

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

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

        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文件保存,方便后续读取的部分了。感谢大家的阅读,喜欢的朋友一键三连哦,下一节我们接着说路线推荐的算法实现。

    转载请注明:文章转载自 www.mshxw.com
    本文地址:https://www.mshxw.com/it/488965.html
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 (c)2021-2022 MSHXW.COM

    ICP备案号:晋ICP备2021003244-6号