精华内容
下载资源
问答
  • 中国区域合并后的GIS地图
  • Visum中导入GIS地图

    千次阅读 2013-08-22 22:54:05
    翻译了一天,感觉人都快变成翻译机器了。...结果在一个新项目中我就尝试了一把,用的是在淘宝上花50元买的gis地图。我并不要求有多大精度,所以总的来说这个shapefile还是不错的。 但当我真的去尝试后

    翻译了一天,感觉人都快变成翻译机器了。终于可以看些其他的了。

    在刚用visum的时候就觉得虽然它network的编辑挺方便的,但是偷懒的我还是不希望一条link一条link的往上加。况且现在地图文件那么多,总能找到一个别人已经画好的吧。结果在一个新项目中我就尝试了一把,用的是在淘宝上花50元买的gis地图。我并不要求有多大精度,所以总的来说这个shapefile还是不错的。

    但当我真的去尝试后,才知道什么叫后悔莫及。

    首先,当我拿到地图打开时是一堆的乱码,根本不能识别中文,甚至打来图层属性时也是一堆火星文字。所以第一个问题需解决的是"如何解决GIS中文乱码的问题”

    答:在regedit中的ESRI中Common-> CodePage -> dbfDefault : UTF-8 or GB2312

    然后恭喜你,目前暂时能够看中文了,但是一旦对其图层进行合并或者投影操作时,得到的依旧是乱码。甚至导入到visum,还是可能出现乱码。这个问题实在没办法解决。最后只能通过手动修改成英文解决的。

    第二问,visum是否能导入多个图层?

    答,不能。Visum的network中根据不同类,有不同的图层。但是没法多次import shapefile,所以如果拿到的地图有很多不同的图层,特别是它是按照不同link type来画图层的(比如,有铁路层、国道层、县道层等,这些路网都需要等到visum中时)我们需要用gis中的tool来对不同的图层进行merge。 具体有很多种merge的方式,经过比对,用dissolvd选项最好,因为这样可以避免很多层线网的叠加。

    第三问,这样就能在visum导入shapefile了吗?

    答,不能。因为还有坐标系的问题。通常我们拿到的地图都是基于地理坐标系的,具体哪个坐标系可以咨询卖方。不过通常多用WGS 1984。需要运用gis的tool将GCS转化成PCS,简单解释就是,你拿到的地理坐标系是球型的,曲面的,而我们需要导入的地图是平面的,所以一定要投影一下。具体如何投影这里就不说了。

    第四问,那么多线路merge在一起我怎么分辨他们的类别呢?

    答,给link一个属性就行了。在merge之前给每个图层都添加一个type的属性,定义不同的数字,在merge的时候对自动合并在一起。到时用import 导入时,visum会提示type字段定义的是link的哪一个attribute,选择link type就行了。然后用GPA文件根据type class用不同颜色标示,一个大致的路网就显现出来了。

    这时候,是谁都会兴奋的想叫,但是痛苦才刚刚开始……

    1. 你会发现有很多 isolated node 和 end node。 暂且你可以通过filter没有link连接的点,然后multi delete 他们。

    2. 因为缺乏link的车道、等级等信息,你必须一条一条重新定义。

    3. 需要将地图中显示分割成双向的线路,删除其中的一段。因为visum一条link就能表现双向车道。

    4. 匝道、单行线的link,turn都得重新定义

    5. 交叉口的node许多点是重复覆盖的,肉眼无法看到,必须通过一个一个点手动移开,才能看到node和link的连接是否正确,turn没有问题。这一步关系到最后车辆能够找到short path ,公交线路是否连通等。

    6. 网络的图太过详细,支路太多,需要删除

    做完这些,你发现你所花的时间不仅够你手动用visum画两张图,还能将公交线网也加进去。

    好吧,写了那么多,我的建议是如果你不想自虐,还是不要在visum中import shapefile。最多你可以将shapefile作为background。

    04.24

    展开全文
  • 当我们需要在GIS地图上显示的点数据量比较大时,会考虑将多个点汇聚成一个点展示;汇聚后图标显示一个数字,以表示当前汇聚点包含了多少个原始数据对象。用户可以选择这些汇聚点查看单个原始数据的详细信息。 数据...

    背景

    当需要在GIS地图上显示的点数据量比较大时,会考虑将多个点汇聚成一个点展示;汇聚后图标上显示一个数字,以表示当前汇聚点包含了多少个原始数据对象。用户可以鼠标点击这些汇聚点查看单个原始数据的详细信息。

    GIS数据汇聚展示可以让地图呈现更简洁,美观(如果所有点展开,地图缩小时显示得密密麻麻)。另外更重要的一点是可以提升地图渲染的效率。

    方案分析

    汇聚算法与地图的放大级别(zoom size),以及当前屏幕窗口边界范围有关。当地图缩小时,数据需要汇聚,地图放大时数据需要展开。经纬度落在屏幕边界范围外的数据不需要展示,这样可以减少加载的数据量,提升性能(坏处是拖动地图时需要重新加载数据, 实测几十万点的情况下延迟两到三秒,还可以接受)。

    有两种实现方式:一种是采用空间搜索算法,在地图视窗变化时根据地图放大层级,搜索半径范围的点汇聚在一起;另一种是将屏幕范围内的地图区域划分多个网格,落到同一个网格类的点汇聚在一起(网格大小根据不同的放大层级指定)。

    前一种方案实现复杂,地图放大和缩小时点数据汇聚和展开的效果很平滑。后一种画网格的方式实现比较简单,但效果比较差,数据量,位置分布,离群值都会影响汇聚效果。

    汇聚算法只能解决点数据的汇聚,如果地图上同时有线数据,与点相连的;处理不好就会出现放大或者缩小时点与线脱节的情况,很影响呈现效果。这种情况通过画网格的方式是解决不了的。

    基于空间搜索的方案,有成熟的开源组件SuperCluster(https://github.com/mapbox/supercluster)可用,实测效果也非常好。在使用的过程中发现有性能瓶颈(不是组件本身的算法瓶颈),由于SuperCluster组件用JS开发的,在前端应用。这就要求后端一次把所有的点数据传到前端,数据量大时传输时间很长。

    为解决数据传输性能的问题,需要把汇聚算法放在后台实现,参考SuperCluster的实现逻辑,用Java代码重写了一遍(JSuperCluster)。数据在后台汇聚后再传到前台,10几万个点汇聚后通常只有只百个点。传输数据量减小后,性能得到很大提升。

    样例代码

    代码目录结构

    在这里插入图片描述

    定义算法基础模型

    1. 汇聚模型基础类定义-应用层模型从该类派生
    package com.elon.jsc.model;
    
    import lombok.Data;
    import java.io.Serializable;
    
    /**
     * 汇聚模型基础类定义。所有需要调用汇聚算法的模型从该类派生。
     *
     * @author elon
     * @version 1.0 2019-03-15
     */
    @Data
    public class AggregationModelBase implements Serializable {
    
        private static final long serialVersionUID = 4951392595928922035L;
    
        /**
         * 对象唯一ID(应用层定义,可以是数据库中对象的自增ID或者其它唯一标识)。
         */
        private int id = -1;
    
        /**
         * 经度
         */
        private double longitude = 0;
    
        /**
         * 维度
         */
        private double latitude = 0;
    }
    
    1. 汇聚算法参数模型定义-最大放大层级根据地图支持的规格而定
    package com.elon.jsc.model;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * 汇聚算法参数模型定义
     */
    @Data
    public class JClusterOption implements Serializable {
    
        private static final long serialVersionUID = -7916591464862026234L;
    
        /**
         * 生成聚合数据的最小地图放大层级
         */
        private int minZoom = 0;
    
        /**
         * 生成数据的最大地图放大层级
         */
        private int maxZoom = 16;
    
        /**
         * 聚合半径(单位:像素)
         */
        private int radius = 40;
    
        /**
         * 瓦片大小
         */
        private int extent = 512;
    
        /**
         * KD-Tree的叶子节点数量
         */
        private int nodeSize = 64;
    
        private Object reduce = null;
    
        private Object initial = null;
    
        private Object map = null;
    }
    
    
    1. KD树单点对象模型定义
    package com.elon.jsc.model;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * KD树单点对象模型定义。应用层的单个GIS点在计算前转换为该模型。
     *
     * @author elon
     */
    @Data
    public class JKDNode implements Serializable {
    
        private static final long serialVersionUID = -7134737233652288121L;
    
        /**
         * 对象索引ID(算法对数量重新编号后的id)
         */
        private int id = -1;
    
        /**
         * 父节点ID
         */
        private int parentId = 1;
    
        /**
         * 地图放大级别
         */
        private int zoom = Integer.MAX_VALUE;
    
        /**
         * 聚合点的数量
         */
        private int numPoints = 0;
    
        /**
         * 对象属性
         */
        private Object properties = null;
    
        /**
         * 对象原始ID,记录应用层汇聚模型的ID值
         */
        private int orignalId = -1;
    
        /**
         * X坐标
         */
        private double x = 0;
    
        /**
         * Y坐标
         */
        private double y = 0;
    
        private int index = -1;
    }
    
    
    1. 聚合节点模型定义
    package com.elon.jsc.model;
    
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * 聚合节点模型定义。
     *
     * @author elon
     * @version 1.0 2019-03-15
     */
    @Data
    public class JClusterNode <T extends AggregationModelBase> implements Serializable {
    
        private static final long serialVersionUID = -5358622773451333438L;
        /**
         * 是否聚合对象
         */
        private boolean isCluster = false;
    
        /**
         * 聚合点的ID
         */
        private int clusterId = -1;
    
        /**
         * 聚合点数量
         */
        private int pointCount = 0;
    
        /**
         * 聚合点的X坐标
         */
        private double x = 0;
    
        /**
         * Y坐标
         */
        private double y = 0;
    
        /**
         * 聚合点为单点时存储应用层的对象模型。
         */
        private T data = null;
    }
    
    
    

    定义算法底层使用的K-D树

    1. K-D树模型定义

    将GIS对象放到K-D Tree, 以支持后续的快速搜索。

    package com.elon.jsc.kdbush;
    
    import com.elon.jsc.model.JKDNode;
    
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * K-D树模型定义,用于GIS点汇聚时空间搜索。根据开源软件KDBush的JS代码翻译而来。
     *
     * @author elon
     * @version 1.0 2019-03-15
     */
    public class JKDBush {
    
        /**
         * KD树节点数量
         */
        private int nodeSize = 64;
    
        /**
         * 节点列表
         */
        private List<JKDNode> points = null;
    
        /**
         * 节点ID列表(从0开始新分配的ID)
         */
        private List<Integer> ids = null;
    
        /**
         * 节点坐标列表(同一个点的X和Y存储在相邻的位置)
         */
        private List<Double> coords = null;
    
        /**
         * 构造函数。根据传入的KDNode模型初始化数据。
         *
         * @param points KDNode对象模型
         */
        public JKDBush(List<JKDNode> points){
            this.points = points;
    
            // 分配ID和坐标的存储空间(坐标存储X和Y, 需要两倍的空间)
            ids = Arrays.asList(new Integer[points.size()]);
            coords = Arrays.asList(new Double[points.size() * 2]);
    
            // 初始化数据
            for (int i = 0; i < points.size(); ++i){
                ids.set(i, i);
    
                // 偶数位存储X坐标, 奇数位存储Y坐标
                coords.set(2 * i, points.get(i).getX());
                coords.set(2 * i + 1, points.get(i).getY());
            }
    
            // 排序以支持快速搜索
            new JKDSort(nodeSize, ids, coords).sort(0, ids.size() - 1, 0);
        }
    
        public List<Integer> range(double minX, double minY, double maxX, double maxY) {
            return new JKDRange(nodeSize, ids, coords).range(minX, minY, maxX, maxY);
        }
    
        public List<Integer> within(double x, double y, double r) {
            return new JKDWithin(nodeSize, ids, coords).within(x, y, r);
        }
    
        public int getNodeSize() {
            return nodeSize;
        }
    
        public void setNodeSize(int nodeSize) {
            this.nodeSize = nodeSize;
        }
    
        public List<JKDNode> getPoints() {
            return points;
        }
    
        public void setPoints(List<JKDNode> points) {
            this.points = points;
        }
    
        public List<Integer> getIds() {
            return ids;
        }
    
        public void setIds(List<Integer> ids) {
            this.ids = ids;
        }
    
        public List<Double> getCoords() {
            return coords;
        }
    
        public void setCoords(List<Double> coords) {
            this.coords = coords;
        }
    }
    
    
    1. KD树排序
    package com.elon.jsc.kdbush;
    
    import java.util.List;
    
    /**
     * KD树排序
     */
    public class JKDSort {
    
        /**
         * 节点数量
         */
        private int nodeSize;
    
        /**
         * 节点ID列表
         */
        private List<Integer> ids = null;
    
        /**
         * 节点坐标列表
         */
        private List<Double> coords = null;
    
        /**
         * 构造方法
         */
        public JKDSort(int nodeSize, List<Integer> ids, List<Double> coords) {
            this.nodeSize = nodeSize;
            this.ids = ids;
            this.coords = coords;
        }
    
        /**
         * 数据排序
         *
         * @param left
         * @param right
         * @param depth
         */
        public void sort(int left, int right, int depth) {
            if (right - left <= nodeSize) {
                return;
            }
    
            // 计算中间节点的位置
            int m = (left + right) / 2;
    
            // 以中间节点排序
            select(m, left, right, depth % 2);
    
            // 递归处理左右子树
            sort(left, m - 1, depth + 1);
            sort(m + 1, right, depth + 1);
        }
    
        /**
         * 排序使左子树的点小于右子树的点。
         *
         * @param k
         * @param left
         * @param right
         * @param depth
         */
        private void select(int k, int left, int right, int depth) {
    
            while (right > left) {
                if (right - left > 600) {
                    int n = right - left + 1;
                    int m = k - left + 1;
                    double z = Math.log(n);
                    double s = 0.5 * Math.exp(2 * z / 3);
                    double sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
                    int newLeft = (int) Math.max(left, Math.floor(k - m * s / n + sd));
                    int newRight = (int) Math.min(right, Math.floor(k + (n - m) * s / n + sd));
                    select(k, newLeft, newRight, depth);
                }
    
                double t = coords.get(2 * k + depth);
                int i = left;
                int j = right;
    
                swapItem(left, k);
                if (coords.get(2 * right + depth) > t){
                    swapItem(left, right);
                }
    
                while (i < j) {
                    swapItem(i, j);
                    i++;
                    j--;
    
                    while (coords.get(2 * i + depth) < t) {
                        i++;
                    }
                    while (coords.get(2 * j + depth) > t) {
                        j--;
                    }
                }
    
                if (Double.compare(coords.get(2 * left + depth), t) == 0) {
                    swapItem(left, j);
                } else {
                    j++;
                    swapItem(j, right);
                }
    
                if (j <= k) {
                    left = j + 1;
                }
                if (k <= j) {
                    right = j - 1;
                }
    
            }
        }
    
        private void swapItem(int i, int j){
            swapInt(ids, i, j);
            swapDouble(coords, 2 * i, 2 * j);
            swapDouble(coords, 2 * i + 1, 2 * j +1);
        }
    
        private void swapInt(List<Integer> arr, int i, int j) {
            int tmp = arr.get(i);
            arr.set(i, arr.get(j));
            arr.set(j, tmp);
        }
    
        private void swapDouble(List<Double> arr, int i, int j) {
            double tmp = arr.get(i);
            arr.set(i, arr.get(j));
            arr.set(j, tmp);
        }
    
    }
    
    1. KD-Tree空间范围查询
    package com.elon.jsc.kdbush;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Stack;
    
    /**
     * KD-Tree空间范围查询
     */
    public class JKDRange {
    
        /**
         * KD树节点数量
         */
        private int nodeSize;
    
        /**
         * 节点ID列表
         */
        private List<Integer> ids = null;
    
        /**
         * 节点坐标列表
         */
        private List<Double> coords = null;
    
        public JKDRange(int nodeSize, List<Integer> ids, List<Double> coords) {
            this.nodeSize = nodeSize;
            this.ids = ids;
            this.coords = coords;
        }
    
        /**
         * 查询矩形范围内的点,过滤不在屏幕范围内的对象。
         *
         * @param minX
         * @param minY
         * @param maxX
         * @param maxY
         * @return
         */
        public List<Integer> range(double minX, double minY, double maxX, double maxY) {
            Stack<Integer> stack = new Stack<>();
            stack.push(0);
            stack.push(ids.size() - 1);
            stack.push(0);
    
            List<Integer> result = new ArrayList<>();
    
            // 递归搜索KD-Tree上指定范围内的元素。
            while (!stack.isEmpty()) {
                int axis = stack.pop();
                int right = stack.pop();
                int left = stack.pop();
    
                if (right - left <= nodeSize) {
                    for (int i = left; i <= right; i++) {
                        double x = coords.get(2 * i);
                        double y = coords.get(2 * i + 1);
                        if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
                            result.add(ids.get(i));
                        }
                    }
    
                    continue;
                }
    
                int m = (left + right) >> 1;
    
                // 如果中间节点在范围内,加入结果集。
                double x = coords.get(2 * m);
                double y = coords.get(2 * m + 1);
                if (x >= minX && x <= maxX && y >= minY && y <= maxY){
                    result.add(ids.get(m));
                }
    
                int nextAxis = (axis + 1) % 2;
    
                if (axis == 0? minX <= x : minY <= y) {
                    stack.push(left);
                    stack.push(m - 1);
                    stack.push(nextAxis);
                }
    
                if (axis == 0 ? maxX >= x : maxY >= y) {
                    stack.push(m + 1);
                    stack.push(right);
                    stack.push(nextAxis);
                }
            }
    
            return result;
        }
    }
    
    
    1. 按搜索半径查询KD-Tree的数据
    package com.elon.jsc.kdbush;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Stack;
    
    /**
     * 按搜索半径查询KD-Tree的数据
     */
    public class JKDWithin {
        /**
         * KD树节点数量
         */
        private int nodeSize;
    
        /**
         * 节点ID列表
         */
        private List<Integer> ids = null;
    
        /**
         * 节点坐标列表
         */
        private List<Double> coords = null;
    
        public JKDWithin(int nodeSize, List<Integer> ids, List<Double> coords) {
            this.nodeSize = nodeSize;
            this.ids = ids;
            this.coords = coords;
        }
    
        /**
         * 搜索半径范围内的点。
         *
         * @param qx
         * @param qy
         * @param r
         * @return
         */
        public List<Integer> within(double qx, double qy, double r) {
            Stack<Integer> stack = new Stack<>();
            stack.push(0);
            stack.push(ids.size() - 1);
            stack.push(0);
    
            List<Integer> result = new ArrayList<>();
            double r2 = r * r;
    
            // 在KD-Tree上搜索半径范围内的数据
            while (!stack.isEmpty()) {
                int axis = stack.pop();
                int right = stack.pop();
                int left = stack.pop();
    
                if (right - left <= nodeSize) {
                    for (int i = left; i <= right; i++) {
                        if (sqDist(coords.get(2 * i), coords.get(2 * i + 1), qx, qy) <= r2) {
                            result.add(ids.get(i));
                        }
                    }
    
                    continue;
                }
    
                int m = (left + right) >> 1;
    
                double x = coords.get(2 * m);
                double y = coords.get(2 * m + 1);
                if (sqDist(x, y, qx, qy) <= r2) {
                    result.add(ids.get(m));
                }
    
                int nextAxis = (axis + 1) % 2;
    
                if (axis == 0 ? qx - r <= x : qy - r <= y) {
                    stack.push(left);
                    stack.push(m - 1);
                    stack.push(nextAxis);
                }
    
                if (axis == 0 ? qx + r >= x : qy + r >= y) {
                    stack.push(m + 1);
                    stack.push(right);
                    stack.push(nextAxis);
                }
            }
    
            return result;
        }
    
        private double sqDist(double ax, double ay, double bx, double by) {
            double dx = ax -bx;
            double dy = ay -by;
    
            return dx * dx + dy * dy;
        }
    }
    
    

    SuperCluster汇聚主类-提供给应用层调用的接口

    package com.elon.jsc.supercluster;
    
    import com.elon.jsc.kdbush.JKDBush;
    import com.elon.jsc.model.AggregationModelBase;
    import com.elon.jsc.model.JClusterNode;
    import com.elon.jsc.model.JClusterOption;
    import com.elon.jsc.model.JKDNode;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * SuperCluster汇聚类。
     */
    public class JSuperCluster<T extends AggregationModelBase> {
        /**
         * 汇聚算法参数
         */
        private JClusterOption option = null;
    
        /**
         * K-D树
         */
        private List<JKDBush> trees = null;
    
        /**
         * 参数汇聚的点数据列表
         */
        private List<T> points = null;
    
        public JSuperCluster(JClusterOption option) {
            this.option = option;
            this.trees = Arrays.asList(new JKDBush[option.getMaxZoom() + 2]);
        }
    
        /**
         * 加载数据,生成KD树。
         * @param points
         */
        public void load(List<T> points) {
            this.points = points;
    
            // 构建聚合点数据
            List<JKDNode> clusters = new ArrayList<>();
            for (int i = 0; i < this.points.size(); i++) {
                if (points.get(i) == null) {
                    continue;
                }
    
                clusters.add(createPointCluster(points.get(i), i));
            }
            trees.set(option.getMaxZoom() + 1, new JKDBush(clusters));
    
            // 逐层构建每一级的汇聚节点
            for (int z = option.getMaxZoom(); z >= option.getMinZoom(); z--) {
                List<JKDNode> clustersList = buildCluster(clusters, z);
    
                trees.set(z, new JKDBush(clustersList));
                clusters = clustersList;
            }
        }
    
        private JKDNode createPointCluster(T p, int id) {
            JKDNode node = new JKDNode();
            node.setId(id);
    
            node.setX(lngX(p.getLongitude()));
            node.setY(latY(p.getLatitude()));
            node.setParentId(-1);
            node.setZoom(Integer.MAX_VALUE);
            node.setOrignalId(p.getId());
            node.setIndex(id);
    
            return node;
        }
    
        /**
         * 获取聚簇对象。
         * @param bbox
         * @param zoom
         * @return
         */
        public List<JClusterNode<T>> getClusters(List<Double> bbox, int zoom) {
            double minLng = ((bbox.get(0) + 180) % 360 + 360) % 360 - 180;
            double minLat = Math.max(-90, Math.min(90, bbox.get(1)));
            double maxLng = bbox.get(2) == 180 ? 180 : ((bbox.get(2) + 180) % 360 + 360) % 360 -180;
            double maxLat = Math.max(-90, Math.min(90, bbox.get(3)));
    
            if (bbox.get(2) - bbox.get(0) >= 360) {
                minLng = -180;
                maxLng = 180;
            } else if (minLng > maxLng) {
                List<Double> easternBBox = new ArrayList<>();
                easternBBox.add(minLng);
                easternBBox.add(minLat);
                easternBBox.add(180.0);
                easternBBox.add(maxLat);
                List<JClusterNode<T>> easternHem = getClusters(easternBBox, zoom);
    
                List<Double> westernBBox = new ArrayList<>();
                westernBBox.add(-180.0);
                westernBBox.add(minLat);
                westernBBox.add(maxLng);
                westernBBox.add(maxLat);
                List<JClusterNode<T>> westernHem = getClusters(westernBBox, zoom);
    
                easternHem.addAll(westernHem);
    
                return easternHem;
            }
    
            JKDBush tree = trees.get(limitZoom(zoom));
            List<Integer> ids = tree.range(lngX(minLng), latY(maxLat), lngX(maxLng), latY(minLat));
            List<JClusterNode<T>> clusters = new ArrayList<>();
    
            for (int id : ids) {
                JKDNode c = tree.getPoints().get(id);
                if (c.getNumPoints() > 0) {
                    JClusterNode<T> cn = new JClusterNode<>();
                    cn.setCluster(true);
                    cn.setClusterId(c.getId());
                    cn.setPointCount(c.getNumPoints());
                    cn.setX(xLng(c.getX()));
                    cn.setY(yLat(c.getY()));
                    clusters.add(cn);
                } else {
                    T vo = points.get(c.getIndex());
                    JClusterNode<T> cn = new JClusterNode<>();
                    cn.setClusterId(vo.getId());
                    cn.setX(xLng(c.getX()));
                    cn.setY(yLat(c.getY()));
                    cn.setData(vo);
                    clusters.add(cn);
                }
            }
    
            return clusters;
        }
    
        /**
         * 获取聚簇节点下所有叶子节点。
         *
         * @param clusterId
         * @return
         */
        public List<T> getLeaves(int clusterId) {
            int limit = Integer.MAX_VALUE;
            int offset = 0;
    
            List<T> leaves = new ArrayList<>();
            appendLeaves(leaves, clusterId, limit, offset, 0);
    
            return leaves;
        }
    
        /**
         * 构建聚簇对象。
         * @param points
         * @param zoom
         * @return
         */
        private List<JKDNode> buildCluster(List<JKDNode> points, int zoom) {
            List<JKDNode> clusters = new ArrayList<>();
            double r = option.getRadius() / (option.getExtent() * Math.pow(2, zoom));
    
            for (int i = 0; i < points.size(); i++) {
                JKDNode p = points.get(i);
                if (p.getZoom() <= zoom) {
                    continue;
                }
    
                p.setZoom(zoom);
    
                // 找到所有临近的节点做汇聚
                JKDBush tree = trees.get(zoom + 1);
                List<Integer> neighborIds = tree.within(p.getX(), p.getY(), r);
    
                int numPoints = (p.getNumPoints() != 0) ? p.getNumPoints() : 1;
                double wx = p.getX() * numPoints;
                double wy = p.getY() * numPoints;
    
                // cluster id中包含的zoom和原始对象ID的信息
                int id = (i << 5) + (zoom + 1);
    
                for (int neighborId : neighborIds) {
                    JKDNode b = tree.getPoints().get(neighborId);
    
                    // 过滤掉已处理过的邻居节点
                    if (b.getZoom() <= zoom) {
                        continue;
                    }
    
                    b.setZoom(zoom);
                    int numPoints2 = (b.getNumPoints() != 0) ? b.getNumPoints() : 1;
                    wx += b.getX() * numPoints2;
                    wy += b.getY() * numPoints2;
    
                    numPoints += numPoints2;
                    b.setParentId(id);
                }
    
                if (numPoints == 1) {
                    clusters.add(p);
                } else {
                    p.setParentId(id);
                    clusters.add(createCluster(wx / numPoints, wy / numPoints, id, numPoints, null));
                }
            }
    
            return clusters;
        }
    
        /**
         * 获取聚簇节点下的子节点。
         *
         * @param clusterId
         * @return
         */
        private List<JClusterNode<T>> getChildren(int clusterId) {
            int originId = clusterId >> 5;
            int originZoom = clusterId % 32;
    
            List<JClusterNode<T>> children = new ArrayList<>();
    
            JKDBush index = this.trees.get(originZoom);
            if (index == null) {
                return children;
            }
    
            JKDNode origin = index.getPoints().get(originId);
            if (origin == null) {
                return children;
            }
    
            double r = option.getRadius() / (option.getExtent() * Math.pow(2, originZoom - 1));
            List<Integer> ids = index.within(origin.getX(), origin.getY(), r);
    
            for (int id : ids) {
                JKDNode c = index.getPoints().get(id);
                if (c.getParentId() == clusterId) {
                    if (c.getNumPoints() > 0) {
                        JClusterNode<T> cn = new JClusterNode<>();
                        cn.setCluster(true);
                        cn.setClusterId(c.getId());
                        cn.setPointCount(c.getNumPoints());
                        cn.setX(c.getX());
                        cn.setY(c.getY());
                        children.add(cn);
                    } else {
                        T vo = points.get(c.getIndex());
                        JClusterNode<T> cn = new JClusterNode<>();
                        cn.setClusterId(vo.getId());
                        cn.setX(vo.getLongitude());
                        cn.setY(vo.getLatitude());
                        cn.setData(vo);
                        children.add(cn);
                    }
                }
            }
    
            return children;
        }
    
        /**
         * 添加叶子节点。
         *
         * @param result
         * @param clusterId
         * @param limit
         * @param offset
         * @param skipped
         * @return
         */
        private int appendLeaves(List<T> result, int clusterId, int limit, int offset, int skipped) {
           List<JClusterNode<T>> children = getChildren(clusterId);
    
           for (JClusterNode<T> child : children) {
               if (child.isCluster()) {
                   if (skipped + child.getPointCount() <= offset) {
                       // 跳过整个聚簇节点
                       skipped += child.getPointCount();
                   } else {
                       skipped = appendLeaves(result, child.getClusterId(), limit, offset, skipped);
                   }
               } else if (skipped < offset) {
                   skipped++;
               } else {
                   result.add(child.getData());
               }
    
               if (result.size() == limit) {
                   break;
               }
           }
    
           return skipped;
        }
    
        private  int limitZoom(int z) {
            return  Math.max(option.getMinZoom(), Math.min(z, option.getMaxZoom() + 1));
        }
    
        /**
         * 将经度转成墨卡托坐标
         * @param lng
         * @return
         */
        private double lngX(double lng) {
            return lng / 360 + 0.5;
        }
    
        private double latY(double lat) {
            double sin = Math.sin(lat * Math.PI / 180);
            double y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI);
            return y < 0 ? 0 : y > 1 ? 1 : y;
        }
    
        /**
         * 墨卡托坐标转经度。
         * @return
         */
        private double xLng(double x) {
            return (x - 0.5) * 360;
        }
    
        private double yLat(double y) {
            double y2 = (180 - y * 360) * Math.PI / 180;
            return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
        }
    
        /**
         * 构建聚合节点。
         *
         * @return
         */
        private JKDNode createCluster(double x, double y, int id, int numPoints, Object properties) {
           JKDNode cp = new JKDNode();
           cp.setId(id);
           cp.setX(x);
           cp.setY(y);
           cp.setParentId(-1);
           cp.setNumPoints(numPoints);
           cp.setProperties(properties);
    
           return cp;
        }
    }
    
    

    测试代码

    从汇聚基类派生应用层模型

    package com.elon.jsc.model;
    
    public class AppModelTest extends AggregationModelBase {
    
        private String remark = "";
    
        public String getRemark() {
            return remark;
        }
    
        public void setRemark(String remark) {
            this.remark = remark;
        }
    }
    
    

    构造数据测试

    package com.elon.jsc;
    import com.alibaba.fastjson.JSONObject;
    import com.elon.jsc.model.AppModelTest;
    import com.elon.jsc.model.JClusterNode;
    import com.elon.jsc.model.JClusterOption;
    import com.elon.jsc.supercluster.JSuperCluster;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 应用启动类
     */
    @SpringBootApplication
    public class StartupSuperCluster {
    
        private static Logger logger = LogManager.getLogger(StartupSuperCluster.class);
    
        public static void main(String[] args){
            SpringApplication.run(StartupSuperCluster.class, args);
            logger.info("Start up Java Super Cluster Success!");
    
            // 测试点的汇聚
            List<AppModelTest> testDataList = new ArrayList<>();
            AppModelTest test1 = new AppModelTest();
            test1.setId(1);
            test1.setLongitude(112.97362);
            test1.setLatitude(27.83088);
            testDataList.add(test1);
    
            AppModelTest test2 = new AppModelTest();
            test2.setId(2);
            test2.setLongitude(112.98363);
            test2.setLatitude(27.84087);
            testDataList.add(test2);
    
            JClusterOption option = new JClusterOption();
    
            JSuperCluster jsc = new JSuperCluster<AppModelTest>(option);
            jsc.load(testDataList);
    
            List<Double> bbox = new ArrayList<>();
            bbox.add(112.6675);
            bbox.add(27.76451);
            bbox.add(113.13648);
            bbox.add(27.95462);
            List<JClusterNode<AppModelTest>> resultList = jsc.getClusters(bbox, 3);
    
            System.out.println("汇聚结果:" + JSONObject.toJSONString(resultList));
    
            System.out.println("显示聚簇点下的叶子节点:");
            for (JClusterNode<AppModelTest> c : resultList) {
                if (c.isCluster()) {
                    System.out.println("汇聚节点ID:" + c.getClusterId());
                    System.out.println("叶子节点:" + JSONObject.toJSONString(jsc.getLeaves(c.getClusterId())));
                }
            }
        }
    }
    
    

    getClusters方法的第一个参数bbox是当前屏幕的边界(左上角、右下角坐标);第二个参数取当前地图的放大级别。

    运行结果

    设置地图放大层级是12时,两个点展开显示:
    在这里插入图片描述
    设置地图放大级别是3时,两个点合并成一个点显示:

    在这里插入图片描述
    GitHub源码路径:https://github.com/ylforever/elon-javasupercluster

    展开全文
  • 合并两幅地图,然后将合并之后的地图保存到工作空间。
  • 1. 2.5D地图概述1.1. 概述2.5维地图就是根据dem、dom、dlg等数据,以及真三维模型在一定高度、视角和灯光效果,按照轴侧投影的方式生成的地图。本文以臻图信息ZTMapEasy电子地图服务平台为例,详细阐述了2.5维电子...

    1.    2.5D地图概述

    1.1.    概述

    2.5维地图就是根据dem、dom、dlg等数据,以及真三维模型在一定高度、视角和灯光效果,按照轴侧投影的方式生成的地图。本文以臻图信息ZTMapEasy电子地图服务平台为例,详细阐述了2.5维电子地图关键技术以及其实现方式。

    1.2.    地图的发展和概述

    地图作为记录地理信息的一种图形语言形式是按照一定的数学法则,根据地图投影、地理坐标和比例尺,经过制图概括,在一定的载体上用各种地图符号(点、线、面状符号)和文字注记、颜色来表示一定区域内的地形、地貌、地物等地理信息。它反映各种自然和社会经济现象的空间分布、组合、联系及其动态变化。因此,地图是对地理空间信息的抽象化、符号化的描述。

    人类生活在一个真三维的现实世界里,而传统的二维地图只对处于三维空间中的各种地理对象全部进行向二维平面投影的简化处理,导致第三维方向(即垂直方向)上的几何位置信息、空间拓扑信息和部分语义信息的损失,不能完整地反映客观世界。随着计算机技术,特别是计算机图形学、网络、多媒体、虚拟现实技术、三维仿真技术的快速发展,传统的二维电子地图被注入了新的活力正在向网络化、互动化以及三维化的方向发展

    1.3.    2.5D地图概念和特点

    2.5维地图在最近几年得到了飞速的发展,如百度、搜狗等地图网站的兴起让更多的用户感觉到了真三维地图带来的震撼。

    什么是2.5维地图呢?就是根据dem、dom、dlg等数据,以及真三维模型在一定高度、视角和灯光效果,按照轴侧投影的方式生成的地图。2.5维数字地图既具有三维数字地图表现力丰富的、视觉效果好,又具备数据量小、现实速度快的优点,需要的网络环境、软硬件要求较低,满足了在远程访问下对三维地图的快速浏览访问,在传统二维地图和真三维地图很好地起到一个承上启下的作用,同时,利用三维模型制作成下游产品的2.5维数字地图,具有准确的坐标信息,还可以制作成东、南、西、北多个角度,相对意义上的三维,因此得到了许多用户的推崇。而且如果要求不高2.5维地图的制作方式可以采用可视角内进行贴面,其它不再处理的方面,这大大降低了地图的建设成本,节省大量的财力和人力,同时数据更新效率也可以大大提高。

    2.    关键技术及实现

    2.1.    制作流程

    2.5维数字地图制作的基本流程:制作基本思路,对基础数据进行原始资料收集和现场采集测绘。将整个场景按x、y两个方向,利用专业空间建模软件,分割渲染成若干张分辨率固定的栅格地图图片,最后基于专业软件进行拼合并做后期。主要分为6个阶段:

    (1)数据准备。基础数据主要是镇海三维仿真规划模型成果,该三维模型采用当今国际流行多边形建模技术,保证还原建筑真实形状。在制作模型的时候,根据采集照片对建筑物结构进行分析,对每一栋建筑进行细致的贴图,建筑外墙、窗体、装饰物的材质来自采集的照片或者精选的素材库,尽量还原建筑的真实外观。制作好建筑后,按照地形图上建筑的位置对制作好的建筑进行摆放,减少建筑与建筑之间、建筑与地表之间的位置误差。

    (2)框架数据搭建。由于已有的三维模型数据为全模,数据量巨大,因此不能将大面积的模型合并在一起渲染。首先以路网模型作为整个场景的框架,然后将需要渲染的规划管理单元建筑模型和场地模型分别合并进路网场景中,如果某个规划控制管理单元数据过多,还要可以将其再拆分。

    (3)场景设置。场景设置主要包括摄像机假设和渲染器设置,摄像机主要作用有让其按照固定的路径以一定的高度和角度运动自动运动。先将摄像机向左和向下分别旋转后再将其绑定在一条有夹角的直线路径上。这时摄像机可以沿该路径自动运行,同时也能满足轴侧无透视变形的地图渲染要求。由于在三维软件中,里面的光的热能传递却不是很明显,所以在渲染的时候,为了实现真实的场景效果,就要在渲染器中指定全局光照,为了使2.5维数字地图有良好的层次感和丰富的色彩,必须采用能够渲染全局光的渲染器。选用一定的渲染模式,能够在增强立体层次的时候既不刺眼,大大提高地图表现效果。

    (4)、图片输出。景搭建好后就可以渲染输出了。

    (5)、图片拼合。最后按照渲染的顺序将图片进行总拼。

    (6)、图面整饰。按照现场照片来进行环境的布局,对背景影像dom数据进行扭曲变形等处理;对道路、河流等实体矢量数据进行文字标注;对照照片布置绿化、草地、树木,在保证在真实的情况下对环境做美化处理,增强地图的可读性,并附上坐标信息。

    2、投影变换的算法及实现

    (1) 投影变换分类与选择

    2.5维地图为了与二维数据快速匹配定位,以及poi数据动态匹配叠加显示必须将二三维地图无缝集成,需要严格保持地物的比例以及相关几何关系,平行投影保持物体的有关比例不变,这是三维绘图中产生比例图画的方法,物体的各个面的精确视图可以由平行投影得到,因此,需要选择平行投影方式。平行投影又分为正平行投影和斜平行投影,正平行投影根据投影面与坐标轴的夹角又可分成两类:正投影(三视图)和正轴侧投影。当投影面与某一坐标轴垂直时,得到的投影为三视图,这时投影方向与这个坐标轴的方向一致。否则,得到的投影为正轴测投影。正轴侧投影是能够显示形体多个侧面的投影变换,如果投影平面不与任一坐标轴垂直,就形成正轴侧投影。正轴侧投影有正等测、正二侧和正三侧三种,当投影面与三个坐标轴之间的夹角都不相等时为正三测,正三侧投影中三个坐标分量的变化比例各不相同。

    由于在选择输出用户视图视角时,投影平面(屏幕)与三个坐标轴之间的夹角各不相同,因此我们选择正三侧投影变换模型。

    (2)求算变换矩阵进行动态匹配,建立2.5维地图与二维地图之间的逻辑对应关系。

    3、瓦片技术及影像金字塔模型

    随着webgis在各行各业不同程度的推广,瓦片式技术(tile cache)已成为臻图ZTMap系列2.5D地图数据访问的关键技术之一。tile cache是一个地图瓦片缓存器,由于预先生成了地图图片,在后端不需要读取空间数据库数据和实时渲染地图,因此大大节省了系统运行时间,加快地图响应速度,用户并发的限制将取决于硬盘的读取和网络传输。实现了平滑的移动和漫游,地图移动不再露白,放大缩小和漫游几乎不需要等待时间,而且地图图片内容也非常复杂和美观,不再受平台限制。

    一张一定比例尺下的地图可以看成是由一定大小的图片组成的,我们把这些一定大小的图片称为一个地图单元,我们为这些在一定比例尺和范围的地图单元按照一定的规则进行命名并存放在一个图库中。具体实现了地图的快速浏览有2步:

    (1)采用地图拼接机制

    采用地图拼接机制把一大块地图分割成一定大小的图片,这样在下载过程中分别下载,并在客户端进行拼接,从而体现实现地图的快速显示。

    (2)采用地图缓存机制

    为了实现地图的快速显示,采用了浏览器的缓存机制。只要访问过的地图就可以采用缓存中的地图进行显示,这样大大的加快了地图的显示速度。

    2.2.    地图功能模块

    8af0459ecb06b4b9eb69d02a74b9c493.png

    2.2.1.       基本功能

    l  放大

    用鼠标点击地图中的任意部分,或按住鼠标左键拉出一个矩形框,即可获得指定区域放大后的地图。对地图进行无级放大,随着地图的放大,系统自动显示一些相关的信息。

    l  缩小

    用鼠标点击地图中的任意部分,或按住鼠标左键拉出一个矩形框,即可获得指定区域缩小后的地图。

    对地图进行无级缩小,随着地图的缩小,地图上显示的信息将会减少以达到最好的显示效果。

    l  显示全图

    显示电子地图全貌。可以让用户方便的回到地图的初始状态。

    l  移图

    移动地图,将地图视野以外的地图移动到视野内。可按住鼠标左键任意拖动地图,使之达到理想位置。

    l  测距

    用户可以在地图上,沿着自己想要测量的线路,用鼠标单击地图,在上面画出一条直线或者折线,然后,在结束点双击,即可得到这条线所代表的实际线路的距离。

    l  三维图

    切换显示三维仿真图

    l  影像图

    叠加显示影像图

    2.2.2.       高级功能

    2.2.2.1.       地图叠加分析

    控制每个图层的显示、选择、标注等。可以打开或关闭任意一个图层;设置图层是否可选;设置每个图层中点、线、面对象的显示样式(如点符号样式、线条样式、粗细、颜色、面的填充样式等),设置每个图层的标注的字体、样式等。实现不同图层的任意叠加显示。

    2.2.2.2.       地理编码(Geocoding)

    对门牌号、地址等信息进行地理编码,自动计算匹配其地理位置,在地图上进行标注。诸如门牌地址、路口路名等描述性信息,通过地理编码功能,可以迅速将这些描述性信息转变为地理空间位置,并显示在地图上。

    2.2.2.3.       路口查询

    自动查询任意两条或多条道路的相交路口。查询与某一道路相交的所有道路以及相应的每个路口信息,对于路口在地图上进行快速显示定位。

    2.2.2.4.       地名查询

    通过输入地名,系统自动检索各类地名图层,匹配相关的地名信息。地名可以是各种主要建筑物的名称、居民小区、标志性建筑物、机关单位等。系统检索到相关结果后返回查询列表,同时在地图上进行快速高亮定位。

    2.2.2.5.       点图查询

    可以直接用鼠标在地图上点击查询,系统自动查询出点击位置的地图对象信息。

    2.2.2.6.       拉框查询

    用户可以在地图上拉一个矩形框,系统自动查询出拉框范围内的地图对象信息。

    2.2.2.7.       拉圆查询

    用户可以在地图上拉一个圆形区域,系统自动查询出圆形区域内的地图对象信息。

    2.2.2.8.       多边形查询

    用户可以在地图上通过点击鼠标绘制一个任意形状的多边形区域,系统自动搜寻出该多边形区域内的地图对象信息。

    2.3.    地图应用开发API

    n  Webservices:提供基于http协议的webservice接口,采用xml格式作为通讯格式。实现符合OGC规范的地图操作

    n  Javascript API:提供面向javascript语言的开发类库,完全基于javascript快速构建GIS应用,支持PC和手机多平台。

    n  Flex API:提供面向Flex开发环境,符合actionscript开发语言的开发包,完全基于flex快速构建GIS应用

    n  Android API:提供符合Android SDK规范的,能够在Android应用开发环境下使用Java语言开发的API

    n  IOS API:提供符合Object-C编程规范的开发包,快速构建IOS平台上的GIS应用

    n  Windows Phone API:提供能用在Windows Phone平台上调用的开发包,用于在windows移动平台上快速开发GIS应用

    3.    2.5D地图平台技术特点和优势

    本平台具有高性能,此外还具有多源数据集成、海量数据访问支持、服务器群集等高级特性;采用多级缓存结构设计;可以同时支持多种地图引擎协同工作等。

    3.1.        符合行业国际标准的地图服务

    实现跟符合标准的OGC规范的地图服务器进行地图数据的交互。使得平台能够在自身不生产存储地图数量的情况下实现以第三方OGC服务为底层数据的平台应用。同时,本平台提供的所有地图浏览和查询服务均符合OGC的相关协议规范,包括WMS、WFS、WMTS等,从平台功能性和松耦合性的设计原则上来讲,应该针对OGC的主要地图服务接口封装一套介于地图引擎应用和OGC服务器接口之间的接口,该接口根据WMS、WFS地图服务接口规范封装相关的请求细节,方便地图引擎进行调用。

    3.2.        全组件化技术

    全组件化的设计思想,作为一个成熟的Web GIS开发平台,在多个行业进行了广泛的应用。组件化设计的优点主要有以下几点:

    通过采用全组件化结构,系统的可管理性大大增强,可以实现单点登录、集中管理。可以管理分布在分布式环境下的各个服务器和服务程序。

    基于J2EE技术的组件具有自描述特性,不同组件封装了实现不同功能和不同目的的模块,从而使各个组件可以单独搭建和扩展,使系统的更新工作大大减少,兼容性得到增强。

    内置的GIS服务引擎与XTMap的数据处理和开发平台采用相同的体系结构,数据只需在服务器上进行配置即可直接使用,快速发布到Internet上。

    组件具有良好的扩充性和开放性。各个层次的组件提供了丰富的接口和功能,松散的体系结构为系统的扩充提供了足够的扩展空间,用户可以通过继承或者聚合等软件重用方法,开发特殊功能的自定义行业组件或者更高层次的通用组件,统一集成到XTMap平台中。

    3.3.        多源数据集成与海量数据快速访问

    由于XTMap的数据模型搭建的合理性、高效性和优化的特点,使得T数据兼容性较强。

    多种来源数据不仅可以在桌面系统集成,而且不需转换即可直接发布到Internet网络。包括ArcInfo coverages、ESRI shapefiles、MapInfo mif、AutoCad dxf等常用格式

    支持分析和显示各种格式的影像图 (TIFF, JPEG, GIF, ERDAS IMAGINE, MrSID图像压缩格式)

    矢量几何对象的压缩、高效的复合索引技术可以大大提高用户访问数据的速度

    3.4.        服务器群集,具有高度伸缩性

    随着应用规模的扩大,当单台服务器的处理能力(不仅包括CPU运算能力)不能满足应用需要时,往往需要将多台服务器群集起来同时提供服务。通过群集可以实现负载能力的成比例提高,同时还可以减少单点失效的危险,提高系统的稳定性。但是群集同时会带来服务器的部署、管理复杂度提高和负载平衡的问题。

    常见群集方法可以分为业务复制和业务分割两种方法。业务复制即将相同的业务复制到多台服务器上进行处理,每台服务器承担其中一部分用户的处理请求;业务分割即将业务划分为不同的部分,每一部分放到不同的服务器上去运行,比如将数据库服务、地图服务、Web服务分别放到不同的服务器上运行,可以提高性能和增强可靠性。

    XTMap主要通过四种方式提高多用户并发访问的性能:异步网络传输,减少网络阻塞;GIS服务引擎的分时操作;多应用实例并发服务;多服务器群集服务。这种特性可以满足小型工作组到大型网络服务的多种应用规模的需要。

    3.5.        客户端与服务器多级缓存结构

    服务器实现了高效的数据缓存和应用缓存,通过二次开发可以实现针对特定应用的处理缓存,随软件提供的客户端设计了巧妙的客户端缓存机制,可以大大加快地址定位和地图浏览的速度。

    可以实现多级缓存模式,对应用及其相关数据进行高速缓存,从而大幅度提高海量数据的处理能力。

    ü  应用实例缓存

    预先启动应用服务器,装载适当的数据,从而减少每次用户请求再启动应用的时间,服务器可以自动分配应用的实例。

    ü  应用数据缓存

    启动应用服务时自动加载相关数据,从而减少每次用户请求都需要重新加载和卸载数据的时间。

    ü  数据引擎缓存

    由于进行了应用缓存,数据引擎具有的缓存和调度机能可以得到充分发挥,从而大大提高数据处理的效率。

    ü  处理结果缓存

    通过将需要长时间处理或变化较小的结果预存在磁盘上,用户请求可以直接读取结果而不必每次都进行处理,从而可以减少每次处理的时间,加快用户响应的速度。

    3.6.        应用无关性的跨平台开发接口

    对于地图服务接口设计的标准性以及应用层无关性的先进理念,使得原本复杂的WEBGIS二次开发工作变得简易可行。大大缩短开发周期,提高效率。

    ü  标准的应用层接口和应用层无关性

    建立在地理信息平台基础之上的应用系统的类型是非常丰富的。这些应用系统最终将由不同行业的开发商使用不同的开发工具,设计不同的应用逻辑,所支持的其终端类型也从普通的计算机到具有文字短信功能的普通手机等,能够提供良好的应用开发接口和系统集成模型,即应用层无关性。

    在应用接口层:在HTTP协议基础之上设计标准的地图应用访问协议并提供丰富的实用功能接口函数供应用系统调用,这样的设计符合工业标准,支持异构操作系统上的应用系统。

    ü  跨平台多语言应用开发包

    跨平台多层次二次开发接口。提供从组件库到客户端脚本的多层级二次开发API,支持Javascript、Flex、Webkit、IOS、Android、Windows Mobile(Phone)等多平台和开发环境语言

    3.7.        完善的日志系统

    一个完善的日志系统是服务器端应用程序的必备重要辅助功能,为供系统管理员使用的分析模块。本子系统会在服务器访问日志的基础上,统计分析每天提交的请求数量,某地图功能被调用的次数、平均处理耗时等,最终生成分析报表。系统管理员可以根据这些结果评价系统目前的服务水平,分析系统的性能瓶颈。

    地图服务日志数据库有详细的结构文档说明,因此如果系统管理员需要分析其他的内容,二次开发人员可以根据其需求定制查询逻辑。

    4.    地图功能应用

    4.1.        二三维一体化可视化

    二三维一体化地图服务平台集成二维地图、航拍影像、三维仿真地图三种地图数据。一方面,平台集成的二维地图、航拍影像和三维仿真地图可以在不同的界面中相互切换和对应位置联动;另一方面,二维地图的兴趣点等信息可以以专题图层的方式叠加在三维仿真地图上。

    4.1.1.       二维街道地图

    对于宏观维护的城市基础地图,采用二维地图的方式进行呈现。以全市基础二维地图作为宏观范围内资产管理的数字化手段,使得楼宇等资产管理具备可视化、直观性的特点,能够更加快捷的查询和统计在行政区域、空间范围内的建筑等信息。

    基于二维地图,可以快速查看全市范围内的楼宇在地图上的分布情况,用户通过点击地图上的楼宇标识可以进一步查看该楼宇的详细属性、所包含的资产情况、资产处置记录等业务信息。

    二维基础城市地图的效果如下:

    4.1.2.       三维街道地图

    二维地图从空间区域分布角度给整个行政区域提供了基本的了解手段,但是在真实度和立体感等方面尚有不足,因此,在条件允许的情况下,可以建设三维地图,基于卫星影像图、楼宇建筑CAD图、拍照等各种基础数据和技术手段,对包括楼宇在内的基础空间对象进行三维建模。制作城市2.5D-GIS三维地图。

    2.5D地图就是以真三维地图建模数据为基础,按照一定比例对现实世界或其中一部分的一个或多个方面的三维、抽象的描述。跟真三维地图相比,2.5维地图具备以下特点:

    以栅格图片作为地图发布形式

    只能以某一个角度进行地图展现

    不能进行地图旋转等操作

    地图浏览不需要安装任何客户端浏览器控件

    对客户端显卡等硬件没有额外要求

    基于2.5D地图,可以为用户提供很轻量级的便捷的地图访问,在保持三维立体效果的同时,大大减少了开发制作成本,同时又减少了对系统软硬件的要求。效果如下:

    4.2.    社区应用举例

    4.2.1.       楼宇分布查询

    在地图上以撒点标注的方式显示楼宇企业等社区事物的分布情况,点击某个事物可以在地图上快速定位高亮显示,点击地图上的标记可以查看其详细属性信息。

    主要功能点有:

    二维地图查询浏览

    基于二维地图的楼宇撒点分布

    基于二维地图区域的楼宇分布统计

    基于2.5D地图的楼宇撒点分布

    基于2.5维地图区域的楼宇分布统计

    楼宇地图高亮定位

    楼宇业务属性信息关联查询

    4.2.2.       点位标注

    地图标注能够将三维地图上的信息点以显示名称或其他信息的方式展示,使用户可以直观的查看到相关的信息点和建筑物等信息,这将大大方便用户快速找到标志性建筑。

    4.2.3.       地址精确定位

    智慧社区应用中的人、物、事、情等信息往往具有具体的门牌地址字段,通过将这些字段信息换算为地理坐标,就能够在地图上将这些信息精确地进行标注,并实现基于空间位置区域的统计分析和对比。

    在2.5D地图中,可以将具体的地址精确定位到某一栋建筑,与二维地图相比,效果更加直观、逼真。

    展开全文
  • 本范例实现将两个地图合并为一个,并且每一个地图做为一个分组图层。
  • FLEX GIS 聚合地图

    千次阅读 2010-10-13 17:04:00
    聚合地图事实上跟之前我发的热力地图一样都是在BI商务智能 分析中 一个新颖的概念使用 GIS 增强商务智能中的分析。 之前看过ARCGIS 做的聚合 是家流感分布图。 其实他应用的领域也很广。 基本...

    聚合地图事实上跟之前我发的热力地图一样都是在BI商务智能 分析中 一个新颖的概念使用 GIS 增强商务智能中的分析。

    之前看过ARCGIS 做的聚合 是家流感分布图。

    其实他应用的领域也很广。

    基本实现了放大缩小的时候 进行气泡合并,设置气泡大小,透明度,筛选聚合,和假设分析,不同范围的数值设置不同颜色。

     

    下面贴图。

     

     

     

     

     

     

     

     

     

    展开全文
  • SuperMap系列——GIS数据之地图瓦片

    千次阅读 2019-10-28 16:42:20
    目前,地图包含的内容越来越丰富,范围也越来越广,并且常用在WebGIS中,每次前端请求出图,都需要服务器出图,导致服务器压力大,出图慢等问题,而利用瓦片地图技术可以很好的解决这个问题。那什么是地图瓦片呢?...
  • 2.5D地图GIS系统技术方案

    千次阅读 2020-03-05 18:18:07
    2.5D地图GIS概述 1.1. 概述 2.5维地图就是根据dem、dom、dlg等数据,以及真三维模型在一定高度、视角和灯光效果,按照轴侧投影的方式生成的地图。本文以电子地图服务平台为例,详细阐述了2.5维电子地图关键技术以及...
  • BIGEMAP功能对比分析图 | | | | | | 【地图服务行业专家】 BIGEMAP是全行业公认的【优秀知名软件】、高新技术企业、国内最早从事GIS领域研发的公司,为全国各
  • ArcGIS合并和拆分地图

    千次阅读 2019-02-27 15:45:14
    现在我们想要把这几个村庄的地图合并到同一张地图上,具体操作如下: 选择Geoprocessing菜单中的Merge方法,然后操作如下: 最后直接点击Ok即可。 2、地图拆分 现在我们在一张地图上有十个村的地图, 我们...
  • 研究GPU环境下梯形格网的高效LOD方法,探讨简化前后节点的重组和显存中EBO数据的更新方法,最终提出一种基于三维GIS技术的矢量地图动态LOD渲染方法。 1 简化的影响因素 1.1 地图操作对简化的影响 本文方法依据视点...
  • GIS创新实践【实验2】疫情地图制作与发布
  • gis

    2012-09-17 13:26:00
    GIS 求助编辑百科名片 GIS即地理信息系统(Geographic Information System),地理信息系统是以地理空间数据库为基础,在计算机软硬件的支持下,运用系统工程和信息科学的理论,科学管理和综合分析具有空间...
  • GIS

    2011-09-23 15:15:00
    什么是GIS  最简单地来说,GIS是以测绘测量为基础,以数据库作为数据储存和使用的数据源,以计算机编程为平台的全球空间分析即时技术。这是GIS的本质,也是核心。 物质世界中的任何事物都被牢牢地打上了时空的烙印...
  • 基于GIS和Python的百度地图街景爬取

    千次阅读 热门讨论 2020-02-22 20:18:31
    最近导师要求在一个城市设计项目里应用街景分析,于是学习并梳理了一下爬取百度地图街景的流程,简单做个总结。 1 百度全景API简介 先附上百度官方文档。 简单分析一下全景API的请求构成: ...
  • 开源GIS

    2019-03-08 10:47:35
    它提供数据的显示、编辑和分析功能,可以自动生成地图,并且能够处理地理空间数据,最后形成你期待的地图数据。它于2004年成为开源地理空间基金会的一个孵化项目。稳定版本是2.14.0 (Essen)。  QGIS是以C++、...
  • 研究GPU环境下梯形格网的高效LOD方法,探讨简化前后节点的重组和显存中EBO数据的更新方法,最终提出一种基于臻图信息三维GIS技术的矢量地图动态LOD渲染方法。1 简化的影响因素1.1 地图操作对简化的影响 本文方法依据...
  • GIS聚合图

    2020-05-18 15:28:18
    说到GIS地图大家肯东很熟悉的,因为地图都是人们常用的东西,每个人都会用到的地图,不过呢,一般GIS地图的功能才是地图中的主要的功能,要不是有主要的功能,那么地图也不会像现在的主流地图软件这么好用的,现在的...
  • 地图是人类文化的杰作,它融科学、艺术于一体,作为描述、研究人类生存环境的一种信息载体是人类生产与生活中不可缺少的一种工具。”这是陈述彭院士为《中国地图学年鉴》作序的开场语。Taylor也曾指出“当涉及应用...
  • 4 合并表格到地图属性 继续制作地图,打开Vancouver Downtown 4.ai。 1. 在AdobeIllustrator图层面板,点击目标按钮来选择所有Roads_line图层的art。 2. 从MAPublisher工具栏,点击MAP Attribute(地图属性)按钮...
  • 如何学好GIS,彻底领悟这几句话就够了!!!

    千次阅读 多人点赞 2020-11-05 11:06:57
    目 录前言GIS起源于地图学GISer心中要有地图空间数据是GIS的血液空间分析是GIS的灵魂GIS是智慧城市的操作系统 前言 **地理信息系统 (GIS)**是以可视化和分析地理配准信息为目的,用于描述和表征地球及其他地理...
  • GIS 系统

    千次阅读 2020-10-19 15:09:15
    支持网络地图服务(WMS)、网络要素服务(WFS)、网络覆盖服务(WCS)、目录服务以及地名辞典服务等一系列地理信息网络服务规范是gvSIG与其它地理信息系统的特大区别。 开发者 gvSIG Association 稳定版本 2.5.0...
  • 开源GIS,可以减少GIS软件的开发周期,降低软件开发成本,提高软件开发效率,同时可以降低GIS平台软件使用成本,促进GIS社会化和大众化,这些优势使之成为GIS研究的新热点。随着开源GIS项目越来越成熟,并且取得越开...
  • 天津矢量道路合并.DXF

    2019-06-14 16:52:26
    矢量道路合并天津市区的GIS地图.格式是DXF的,可以直接应用软件中去,是矢量格式的,
  • GIS空间索引

    万次阅读 2020-03-11 12:02:02
    GIS系统中,空间索引技术就是通过更加有效的组织方式,抽取与空间定位相关的信息组成对原空间数据的索引,以较小的数据量管理大量数据的查询,从而提高空间查询的效率和空间定位的准确性。 常见的GIS空间索引 KD...
  • 开源GIS软件初探

    千次阅读 2015-05-26 10:15:29
    谈到GIS软件,首先让我们想到的便是GIS界的龙头大哥ESRI公司旗下的ArcGIS产品,从最初接触的version 9.2到如今的version 10.1,其发展可谓风生水起。MapInfo软件也不错,可是给人的感觉是渐渐被淘汰了似的,周围使用...
  • 中科院地理所1997年GIS研究生入学试题 一、名词解释 1.拓扑关系 2.缓冲分析 3.关系数据模型 4.空间叠加 二、简答题 1、GIS的主要功能略 2、企业GIS系统的特色 三、问答题(选2) 1、GIS基本组成与主要应用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,037
精华内容 814
关键字:

gis地图合并