如何对互联网上挖掘到的POI数据进行去重
如何对互联网上挖掘到的POI数据进行去重
一、需求
随着互联网的发展,电子地图被越来越多地使用。在制作电子点图时,需要使用大量的POI数据(POI,Point of Interest,兴趣点),这些地址信息小则一家店铺、一个公交站等,大则一座公园、住宅小区等。目前这些POI数据的来源主要有两种渠道,一种渠道是一些政府机构、商业地图服务厂商去现场实地勘测,记录下各个兴趣点以及他们的经纬度等信息。另一种渠道来源于从海量的互联网数据中挖掘。本发明就是针对后一种,当互联网中挖掘出海量的POI数据后,这些数据因为来源不同、更新时间不通、格式不同等,因此需要对其进行去重,以得到一份最有价值的POI数据,以补充到被绘制的地图中。
二、技术方案
2.1 当前问题
我以一个住宅小区(“杭州市上城区的向阳新村”)为例,来详细分析目前的问题。目前我们采集到的POI数据非常杂,总共有68条数据:
POI名称 | 经度 | 维度 | 采集时间 |
---|---|---|---|
向阳新村(东门) | 120.163176 | 30.2600017 | 2020 |
向阳新村3幢-4单元 | 120.162343 | 30.2596995 | 2016 |
向阳新村 | 120.162693 | 30.2597728 | 2019 |
向阳新村2号楼 | 120.16305 | 30.259905 | 2021 |
向阳新村3幢-1单元 | 120.162552 | 30.2599384 | 2018 |
长生路小区向阳新村1栋 | 120.16291 | 30.2596244 | 2018 |
向阳新村3幢-3单元 | 120.162335 | 30.2598785 | 2018 |
向阳新村3幢-3单元 | 120.162335 | 30.2598705 | 2016 |
向阳新村3幢-2单元 | 120.162435 | 30.2599105 | 2016 |
向阳新村3幢-4单元 | 120.162346 | 30.2597015 | 2021 |
向阳新村2号楼 | 120.163147 | 30.259862 | 2015 |
向阳新村3幢-1单元 | 120.162566 | 30.2599465 | 2020 |
向阳新村 | 120.163128 | 30.259864 | 2022 |
向阳新村3号楼 | 120.162465 | 30.2596134 | 2016 |
向阳新村3幢-1单元 | 120.162552 | 30.2599294 | 2017 |
向阳新村3幢-2单元 | 120.162502 | 30.2596114 | 2018 |
向阳新村3号楼 | 120.162545 | 30.2596164 | 2018 |
向阳新村3号楼 | 120.162559 | 30.2596245 | 2020 |
向阳新村3号楼 | 120.162545 | 30.2596164 | 2018 |
向阳新村3幢-4单元 | 120.16236 | 30.2597112 | 2020 |
向阳新村3号楼 | 120.162465 | 30.2596134 | 2015 |
向阳新村 | 120.162693 | 30.2597728 | 2019 |
向阳新村3幢-4单元 | 120.162346 | 30.2596995 | 2017 |
长生路小区向阳新村1栋 | 120.162909 | 30.2595964 | 2016 |
向阳新村(西门) | 120.162431 | 30.2596023 | 2020 |
向阳新村3号楼 | 120.162462 | 30.2596124 | 2017 |
长生路小区向阳新村1栋 | 120.162912 | 30.2595954 | 2017 |
向阳新村3幢-1单元 | 120.162552 | 30.2599384 | 2021 |
向阳新村2号楼 | 120.162863 | 30.2599184 | 2016 |
向阳新村3号楼 | 120.162412 | 30.2596555 | 2015 |
长生路小区向阳新村2-3栋 | 120.162422 | 30.2597723 | 2019 |
向阳新村3幢-1单元 | 120.162502 | 30.2596114 | 2018 |
向阳新村 | 120.162726 | 30.259792 | 2021 |
长生路小区向阳新村1幢 | 120.162817 | 30.259629 | 2021 |
长生路小区向阳新村2-3栋 | 120.162406 | 30.2597275 | 2021 |
向阳新村2号楼 | 120.163171 | 30.2599827 | 2020 |
向阳新村3幢-2单元 | 120.162435 | 30.2599195 | 2018 |
向阳新村3幢-3单元 | 120.162502 | 30.2596114 | 2018 |
向阳新村-西门 | 120.162432 | 30.2596023 | 2019 |
向阳新村3号楼 | 120.162368 | 30.2596755 | 2021 |
向阳新村3幢-2单元 | 120.162435 | 30.2599195 | 2021 |
向阳新村3幢-3单元 | 120.162335 | 30.2598705 | 2015 |
向阳新村-东门 | 120.163174 | 30.2600037 | 2019 |
向阳新村3幢 | 120.162462 | 30.2597824 | 2019 |
向阳新村3幢-4单元 | 120.162502 | 30.2596114 | 2018 |
长生路小区向阳新村1栋 | 120.162909 | 30.2595964 | 2015 |
向阳新村2号楼 | 120.163148 | 30.259862 | 2017 |
向阳新村3幢-1单元 | 120.16255 | 30.2599304 | 2016 |
向阳新村3幢-2单元 | 120.162435 | 30.2599105 | 2015 |
向阳新村2号楼 | 120.163147 | 30.259862 | 2016 |
向阳新村3幢-3单元 | 120.162349 | 30.2598881 | 2020 |
向阳新村3幢-2单元 | 120.162435 | 30.2599115 | 2017 |
向阳新村3幢-3单元 | 120.162335 | 30.2598705 | 2017 |
长生路小区向阳新村2-3栋 | 120.162404 | 30.2597275 | 2016 |
长生路小区向阳新村2-3栋 | 120.16242 | 30.2597373 | 2020 |
向阳新村3幢-1单元 | 120.16255 | 30.2599304 | 2015 |
向阳新村3号楼 | 120.162412 | 30.2596555 | 2016 |
向阳新村3幢-2单元 | 120.162449 | 30.2599293 | 2020 |
长生路小区向阳新村2-3栋 | 120.162406 | 30.2597275 | 2017 |
向阳新村2幢 | 120.162904 | 30.2599032 | 2019 |
向阳新村3幢-3单元 | 120.162335 | 30.2598785 | 2021 |
长生路小区向阳新村2-3栋 | 120.162404 | 30.2597275 | 2015 |
向阳新村2号楼 | 120.162863 | 30.2599184 | 2015 |
长生路小区向阳新村1栋 | 120.162974 | 30.2597533 | 2019 |
向阳新村(西门) | 120.162417 | 30.2595925 | 2018 |
向阳新村3幢-4单元 | 120.162343 | 30.2596995 | 2015 |
长生路小区向阳新村1栋 | 120.162925 | 30.2596332 | 2020 |
向阳新村3幢-4单元 | 120.162346 | 30.2597015 | 2018 |
分析上述数据,我们可以看出几个问题,这些问题就是我们核心要去解决的:
- 问题一:相同名称的POI数据非常多,比如有多条:“向阳新村3号楼”,但他们的经纬度数据却不同:
POI名称 | 经度 | 维度 | 采集时间 |
---|---|---|---|
向阳新村3号楼 | 120.1624648 | 30.25961336 | 2016 |
向阳新村3号楼 | 120.162545 | 30.25961636 | 2018 |
向阳新村3号楼 | 120.1625587 | 30.25962455 | 2020 |
向阳新村3号楼 | 120.162545 | 30.25961636 | 2018 |
向阳新村3号楼 | 120.1624648 | 30.25961336 | 2015 |
向阳新村3号楼 | 120.1624618 | 30.25961236 | 2017 |
向阳新村3号楼 | 120.1624124 | 30.25965553 | 2015 |
向阳新村3号楼 | 120.1623684 | 30.25967553 | 2021 |
向阳新村3号楼 | 120.1624124 | 30.25965553 | 2016 |
- 问题二:对于地图渲染而言,尤其是缩放级数没有那么高的场景(比如缩放级数不超过20级),精确到某幢楼的某个单元其实没啥必要。比如上述数据中的“向阳新村3幢-1单元”、“向阳新村3幢-2单元”、“向阳新村3幢-3单元”、“向阳新村3幢-4单元”都可以归一到“向阳新村3幢”。
2.2 步骤一,粗略去重:对于同一较大区域内的所有POI数据,按照POI名称来去重
还是以上述问题一为例
POI名称 | 经度 | 维度 | 采集时间 |
---|---|---|---|
向阳新村3号楼 | 120.1624648 | 30.25961336 | 2016 |
向阳新村3号楼 | 120.162545 | 30.25961636 | 2018 |
向阳新村3号楼 | 120.1625587 | 30.25962455 | 2020 |
向阳新村3号楼 | 120.162545 | 30.25961636 | 2018 |
向阳新村3号楼 | 120.1624648 | 30.25961336 | 2015 |
向阳新村3号楼 | 120.1624618 | 30.25961236 | 2017 |
向阳新村3号楼 | 120.1624124 | 30.25965553 | 2015 |
向阳新村3号楼 | 120.1623684 | 30.25967553 | 2021 |
向阳新村3号楼 | 120.1624124 | 30.25965553 | 2016 |
针对这一问题,我们可以把整个世界平铺成一个平面,然后把平铺后的世界地图按照一定长宽值切割成诸多矩形。然后看这些POI数据的经纬度落在哪些个矩形中,然后在同一矩形中,按POI数据的名称进行去重。 还是以“向阳新村”为例,下图是我们按照一定的长宽值将世界切成很多矩形后,“向阳新村”周边的四个矩形:分别标记是A、B、C、D。 然后对上述的“向阳新村3号楼”的经纬度映射他们的落位信息,发现它们都是落在了区域B,那么我们就可以对该区域中所有的相同名称的POI数据去重。至于去重后“向阳新村3号楼”的具体经纬度取值很简单,对进度和维度取个平均值就行。

当然,在这一步骤中,把平铺后的世界切割按照一定的长宽值切割后矩形,这个长宽值的选择粒度很重要。如果粒度太大,长10公里、宽5公里,这么一个区域内相同的名称的POI数据会很多,比如:肯德基、兰州拉面这些。如果粒度太小,比如长5米、宽5米的,那么又起不到去重的效果。 此外,我们在具体工程实现的上,还会做如下一些优化:
- 对平铺的世界地图的切割,直接采用了geohash6算法,geohash6的尺寸是:1.2km x 609.4 km,这个效果相对最佳。
- 在对POI数据去重时,会对POI数据的名称做一些清洗,比如去重标点符号,例如:“向阳新村(东门)”和“向阳新村-东门”都被清洗成“向阳新村东门”。
- 此外,还可以将POI数据中的电话号码作为分组字段,甚至可以优先电话号码。
2.3 步骤二,更小范围的去重:对于同一较小区域内的所有POI数据,取中心点的数据
我们还是以“向阳新村”为例,经过步骤一的补充,目前的数据情况如下:
POI名称 | 经度 | 维度 | 采集时间 |
---|---|---|---|
向阳新村 | 120.163128 | 30.259864 | 2022 |
向阳新村(西门) | 120.162431 | 30.259602 | 2020 |
向阳新村2号楼 | 120.16305 | 30.259905 | 2021 |
向阳新村3幢2单元 | 120.162435 | 30.25992 | 2021 |
向阳新村(东门) | 120.163176 | 30.260002 | 2020 |
长生路小区向阳新村1幢 | 120.162817 | 30.259629 | 2021 |
长生路小区向阳新村2-3栋 | 120.162406 | 30.259728 | 2021 |
向阳新村3幢 | 120.162462 | 30.259782 | 2019 |
向阳新村3幢4单元 | 120.162346 | 30.259702 | 2021 |
向阳新村3幢1单元 | 120.162552 | 30.259938 | 2021 |
向阳新村2幢 | 120.162904 | 30.259903 | 2019 |
长生路小区向阳新村1栋 | 120.162925 | 30.259633 | 2020 |
向阳新村3号楼 | 120.162368 | 30.259676 | 2021 |
向阳新村3幢3单元 | 120.162335 | 30.259879 | 2021 |
从上述数据中我们就可以看出,目前就是前文所提的问题二的情况。 这里我们采用更小区域粒度内找中心点的方案。 如果你咨询AI的话,它往往会告诉你使用KMeans等方案来实现,但据我各种尝试效果并没有预期得那么那么好,并且因为使用了KMeans等机器学习的方法后,效率太挺慢。最终我取巧:直接在以GeoHash8粒度来去重,保留名称最小的那条数据。
2.4 最终代码
最终代码就很简单了,一条SQL搞定:
WITH ranked_poi AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY
CASE WHEN is_not_blank(tel) THEN tel
ELSE CONCAT(clean_all_punctuation(name), geohash(longitude, latitude, 6))
END
ORDER BY ds DESC
) AS rn
FROM poi_china_detail_merged
WHERE
is_not_blank(name)
AND longitude BETWEEN 72.004 AND 137.8347
AND latitude BETWEEN 0.8293 AND 55.8271
),
filtered_poi AS (
SELECT *,
geohash(longitude, latitude, 8) AS geohash8,
ROW_NUMBER() OVER (
PARTITION BY geohash(longitude, latitude, 8)
ORDER BY LENGTH(name)
) AS georank
FROM ranked_poi
WHERE rn = 1
)
INSERT OVERWRITE TABLE poi_china_detail
SELECT
clean_poi_name(name) as name,
clean_poi_name(address) as address,
admin1,
admin2,
admin3,
ROUND(longitude, 6) as longitude,
ROUND(latitude, 6) as latitude,
zip_code,
tel,
big_category,
mid_category,
sub_category,
ds
FROM filtered_poi
WHERE georank = 1
;