您的位置:首页 > 其它

Gis分析POI空间聚合设计思路及示例实现源码

2010-06-09 17:01 666 查看
前些时候,公司领导让我们提前预言商业智能gis分析以及物流交通相关的业务,并提供了几个国外的业务网站做参考,其中一个就是我上片文章提到的spacialKey(有兴趣的哥们可以读一下,不知道我的那个过期了没有。申请测试key也很方便。http://blog.csdn.net/chaiqi/archive/2010/06/01/5640144.aspx)。无论是gis分析、还是交通统计。这里面有个重要的技术就是 POI空间聚合。从网上找了些资料,大多是E文的,而且说的都巨复杂,不容易理解。

多亏了google,哥们后来无意中发现了一个esri做的一个聚合示例。 http://www.esrichina-bj.cn/market/H1N1/H1N1.swf ,哈哈如获至宝,通过httpwatch找到数据来源,发现他是通过客户端做聚合运算的。flex的引擎下几百个点的运算速度相当不错。而且聚合的效果也相当的明显。唯一不足的是,他是相对的屏幕坐标进行聚合运算的。也就是最大也就是1440*900(呵呵这是我的电脑分辨率)。所以每次移动屏幕,聚合的点都会因为吸收或者抛弃屏幕外新的POI而发生聚合变化,变来变去给人的感受不是很爽。而且从分析来看,这种运算是根据每次拖动地图的px来进行,也就是稍稍的拖动一次地图就会有大规模的聚合运算(代表大的for循环)。当然如果用js来实现的话可远远达不到这个效果。



有了参考的样本和示例,呵呵,信心增强了不少。这世界上最难得就是创新,只要见别人实现过,研究研究就有门。

注:gis圈小的很,正好有个特好的哥们在esir(哈,我们以前还一个宿舍住过呢) 正好要了些算法看了看。

这次预言开发我决定用flex试试看以前没有用过flex,初次接触起来感觉也还凑活,有点像java和js的结合体,都是高级预言算法语法也基本相通没啥难的。导入eclips工程,算法还是基本都能看明白的。梳理了一遍,并不是很复杂。

我对算法做了一部分改进。目的是解决拖动时候,聚合不停变化,给人不舒服的感觉,其实也就做了3步:

1、去掉了拖动地图聚合运算相应。改为了缩放比例尺时请求运算,这样能增加效率,甚至日后可以都放到服务端算提前聚合,这样速度就快多了。

2、聚合时用到的相对屏幕坐标,改为相对比例尺的原点坐标。这样就不会变来变去了。

3、更换了我们的flex 地图api 显示模式 ,增加了部分功能。

呵呵,个人感觉比esir做的要好。改天要些大数据,搞个 国家厕所分布图。

先给大家来点直观的插图:



点击可查看聚合得点。以及POI的位置,这点学spaclateKey的呵呵。



聚合前 3个聚合圆



聚合后。呵呵,增加了比例尺缩放效果后非常明显,能看到相邻的点快速进行了合并。





其实,空间聚合所白了也就以下几步骤,有点基础的朋友相信看看就会明白的。 不清楚email给我哈。

第一步、针对基础数据进行初步聚合。

遍历POI数据转化为相对比例尺原点的屏幕坐标(要保证每个POI的屏幕数据都是唯一的),这里还不能用经纬度,因为经纬度相对比例尺来说都是固定的。但是的原点屏幕坐标相对原点是相同的。但不同比例尺下不同。循环过程中根据聚合的直径范围判断将POI放入不同的聚合数组对象中,并计算这些POI点的中心点(根据算法,他会像点多放偏移)。这样就初步得到多个聚合数组,并且记录的聚合中心点也是相对POI数量有侧重的。

相对的Flex代码如下:

private function assignMapPointsToClusters() : void
{
m_orig = new Dictionary();
//循环所有POI
for each ( var tcar:TCar in sink )
{
//经纬度转屏幕像素(固定屏幕像素)
var sxy:SE_Point = getPixelCoord(tcar.lnglat);
//根据直径的精度,进行聚合 Convert to cluster x/y values.
var cx : int = sxy.x / m_diameter;
var cy : int = sxy.y / m_diameter;
//cx位移19位和cy组成Key  cluster dictionary key.
//var ci : int = (cx << 20) | cy; //位移20位也不够用
var ci:String = String(cx)+String(cy);
//               trace(cx+","+cy+"索引号:"+ci);
//根据key找聚合对象 Find existing cluster
var cluster : Cluster = m_orig[ci];
if(cluster){ //如果聚合对象已存在
//平均聚合点和单签点的距离 Average centroid values based on new map point.
cluster.lng = (cluster.lng + tcar.lnglat.lng) / 2;
cluster.lat = (cluster.lat + tcar.lnglat.lat) / 2;
cluster.x = (cluster.x + sxy.x) / 2;
cluster.y = (cluster.y + sxy.y) / 2;
//聚合POI数量累加 Increment the number map points in that cluster.
cluster.n++;
cluster.tcars.addItem(tcar);
}else{ //如果不存在
//没有的话,创建并赋值 Not found - create a new cluster as that index.
m_orig[ci] = new Cluster( sxy.x, sxy.y, cx, cy);
m_orig[ci].lng = tcar.lnglat.lng;
m_orig[ci].lat = tcar.lnglat.lat;
Cluster( m_orig[ci]).tcars.addItem(tcar);
}

}

}


第二步、对临近的聚合圆进行聚合

完成了对基础POI的聚合后,我们得到就是带有数量的多个聚合圆。这对我们在地图上显示来说还是比较杂乱比较多。我们需要再次针对这些聚合圆进行聚合。根据四面八方的原则,再聚合圆的基础上在进行聚合直径的扩充。如果发现邻近的聚合圆存在就合并,并根据数量偏移聚合圆的位置(当然靠近数量多的一方哈)。这个循环直到发现每个聚合圆的直径都没有接触到附近的聚合圆为止。

相对应的Flex如下:

/**
* 1.2判断相邻的两个聚合圆是否也需要聚合。
*/
private function mergeOverlappingClusters() : void
{
m_overlapExists = false;
// 建立一个新的dic存储圆圆聚合后的圆 Create a new set to hold non-overlapping clusters.
const dest/*<int,Cluster>*/ : Dictionary = new Dictionary();
// 循环所有聚合圆,进行再聚合
for each( var cluster : Cluster in m_orig )
{
//忽略空的聚合数组
if( cluster.n === 0 )
{
continue;
}
//8个方向寻找邻近可聚合的圆 Search all immediately adjacent clusters.
searchAndMerge( cluster,  1,  0 );
searchAndMerge( cluster, -1,  0 );
searchAndMerge( cluster,  0,  1 );
searchAndMerge( cluster,  0, -1 );
searchAndMerge( cluster,  1,  1 );
searchAndMerge( cluster,  1, -1 );
searchAndMerge( cluster, -1,  1 );
searchAndMerge( cluster, -1, -1 );
//得到新的聚合圆 Find the new cluster centroid values.
var cx : int = cluster.x / m_diameter;
var cy : int = cluster.y / m_diameter;
cluster.cx = cx;
cluster.cy = cy;
//var ci : int = (cx << 20) | cy;
var ci:String = String(cx)+String(cy);

//保存到新的聚合圆数组
dest[ci] = cluster;
}
//复制给原始的聚合圆数组
m_orig = dest;
}

/**
* 1.3 寻找邻近直径内是否有可聚合圆,并聚合
**/
private function searchAndMerge(cluster:Cluster,ox:int,oy:int) : void
{
//获得邻近块,就是扩大聚合范围。cx+1 = x+一个直径
const cx : int = cluster.cx + ox;
const cy : int = cluster.cy + oy;
//算出邻近块的聚合圆的ci
//const ci : int = (cx << 20) | cy;
const ci:String = String(cx)+String(cy);

//邻近圆是否存在,直径内则合并
const found : Cluster = Cluster(m_orig[ci]);
if( found && found.n )
{
const dx : Number = found.x - cluster.x;
const dy : Number = found.y - cluster.y;
const dd : Number = Math.sqrt(dx * dx + dy * dy);
if( dd < m_diameter )
{
m_overlapExists = true; //设置聚合状态
//                    trace("发生两圆聚合:"+cluster.x+","+cluster.y+"和"+found.x+","+found.y);
//2圆聚合、经纬度靠近点多方
merge( cluster, found );
}
}
}

/**
* 1.4 两聚合圆进行聚合,并靠近点多的圆
* The more map points a cluster has, the less it moves.
*/
private function merge(lhs : Cluster, rhs : Cluster):void {

const nume : Number = lhs.n + rhs.n;
lhs.x = (lhs.n * lhs.x + rhs.n * rhs.x ) / nume;
lhs.y = (lhs.n * lhs.y + rhs.n * rhs.y ) / nume;
lhs.lng = (lhs.n * lhs.lng + rhs.n * rhs.lng ) / nume;
lhs.lat = (lhs.n * lhs.lat + rhs.n * rhs.lat ) / nume;

for each ( var tcar:TCar in rhs.tcars ) {
lhs.tcars.addItem(tcar);
}

lhs.n += rhs.n; // merge the map points
rhs.n = 0; // marke the cluster as merged.

rhs.tcars.removeAll();

}


其实以上的第一步和第二步,如果客户端压力大的话(如果是大数据量)最好放到服务端来做。64位的Dell2950 Linux服务器可不是开玩笑的,眨眼之间1-18级别的聚合都可以完成。我之所以用flex做主要是为了方便调试,因为一会还要用新的 flexapi做地图显示。

第三步,范围判断显示聚合圆

剩下的就最简单多了。你可以服务端来算、也可以客户端来算。就是根据屏幕经纬度区间去获取需要显示的聚合圆显示。因为我们对屏幕做了聚合,所以一般情况下,聚合圆的数量不会太多,当然如果18级别,你又是几十万的数据那您还是放到服务端好了。也不用oracle或者postSql的spatial。直接内存循环速度就没有问题。

/**
* 2、根据屏幕范围显示绘制圆图
**/
public function showClustersOnMap():void {
var bounds:SE_LngLatBounds = map.getLngLatBounds();//屏幕四角坐标
//循环聚合点,显示与地图上
for each ( var cluster : Cluster in m_orig ){
if (bounds.containsLngLat(new SE_LngLat(cluster.lng, cluster.lat))) {
var cs:ClusterSymbol = new ClusterSymbol(cluster.tcars);
cs.radius = cluster.n > 1 ? 20 : 8; //如果数量大于一则画大圆,否则小圆
cs.map = map;
cs.draw(cluster);
cs.lnglat.lng = cluster.lng;
cs.lnglat.lat = cluster.lat;
map.addOverLay(cs);
source.addItem(cs); //图形增加到聚合数组中
cs.addEventListener(SE_OverlayMouseEvent.SE_OVERLAY_MOUSE_CLICK, clusterClickedHandler);
}
}
}


这样以上三步基本上就搞成了,估计大部分的聚合方式都类似于此。关于控件聚合核心的代码我都粘贴出来共享了,有点经验的朋友看起来应该不算困难。考虑到公司问题我不便把工程粘贴出来。有兴趣的朋友可以站内留言我。

csdn不能上传,可以访问demo地址:http://map.mapenjoy.com/tool/converge.swf
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: