openStreetMap数据分析举例-使用Qt统计城市科技指数排名

openstreetmap是一种完全开放的地理信息系统,数据由个人、公司免费捐赠、维护。在这个博客的前文中,我们大多围绕搭建地图环境展开讨论。实际上,它更具价值的是数据本身。今天,我们来看使用Qt5分析openstreetmap数据库(样本为2019-01导入全球数据),获得城市科技指数这个自定义指标。

openSteetMap详细知识、数据、虚拟机见:
http://www.goldenhawking.org:8088/
https://www.openstreetmap.org
https://planet.openstreetmap.org/

数据库的搭建见本博客其他博文。专栏:
https://blog.csdn.net/goldenhawking/column/info/22354

1 openstreetmap体现城市科技发达程度

在各类官方、非官方的统计学报告中,通过各种角度对城市进行排名。 有根据GDP的,根据绿化的,等等。openStreetMap作为在国内并不很流行的GIS,因为主要数据都来自不受共同利益约束的个人、小公司,因而更能体现一个地方的科技发达程度。

  • 地标数量越多,说明为其贡献数据的独立个体越多。
  • 这些个体可能是城市的原住民,或者和城市有关的人(游客、学生、社团)。
  • 由于编辑、维护这些数据需要一定的技术基础,比如要会上网、打字,还要有一些基础的地理知识,我们可以认为贡献者应该代表了同时具备初中以上文化,且对计算机技术比较熟练的人。
  • 综上,一个地方地标越多,说明与该地相关的具有基础科技知识的人越多——而人,是未来一个城市发展的希望。

2 设计相对公平的统计原理

各个城市的大小不一样,形状也不同。如果直接用外接多边形(行政区)进行统计,显然,面积越大的城市越占优势。我们用城市中心区域500平方公里圆形范围(12.616千米半径)内的地标个数为统计方法,主要理由:

  • 大部分城市的市区应该都能填充12千米为半径的圆,即使这个城市的形状非常奇怪(如长条型)
  • OpenStreetMap的城市要素(如place=city)point基本全部安排在最繁华的地点,因此,可以为这个圆找到非常棒的中心。
  • 两个城市市区中心、CBD之间距离一般大于20公里。

因此,统计的方法为查找各个地级市最繁华的半径为12公里的圆型区域中,地标有多少个。

3 SQL语句设计

PostgreSQL支持地理计算,以下语句会直接搜索出距离中心点最近的5.642千米内的地标ID(100平方公里的圆)。PostGIS允许SQL语句中加入地理空间计算,对集合的子交并补支持的非常好。

 select distinct osm_id from planet_osm_line where 
  ST_Covers(
  	ST_Transform(
  		ST_SetSRID(
  			ST_MakeBox2D(
  				ST_Point(112.556-0.5, 37.8562-0.5),ST_Point(112.556+0.3, 37.8562+0.3)
 		),4326),
 	3857),
 	way) 
  and ST_Distance(
  	ST_Transform(
  		ST_SetSRID(
  			ST_MakePoint(112.556, 37.8562), 
		4326),
	3857),
  way) < 12616

4 地理数据获得

可以通过CSDN资源
https://download.csdn.net/download/u012266955/10664500
获得CSV格式的城市经纬度。由于该城市经纬度是行政区域的中心,因而需要和OpenStreetMap的位置进行矫正,以便找到最繁华的市中心。
该数据类似:
原始数据
我们根据城市的名字,加上行政中心经纬度,使用名称在数据库搜索:

select name, St_X(St_Transform(way,4326)),St_Y(St_Transform(way,4326)) ,
ST_Distance(
	ST_Transform(
		ST_SetSRID(
			ST_MakePoint(116.753, 38.2658), 
		4326),
	3857),
way) as dis 
from planet_osm_point where 
ST_Covers(
	ST_Transform(
		ST_SetSRID(
			ST_MakeBox2D(
				ST_Point(116.753-2, 38.2658-2),ST_Point(116.753+2, 38.2658+2))
		,4326)
	,3857),
way)
and place in ('city','town','suburb')  
and (name like '沧州%' or name like '沧州市%')  
order by admin_level, dis asc

5 统计结果

统计结果如下:

顺序城市省份命名地点个数
1广州广东19237
2北京北京15733
3上海上海14878
4珠海广东12470
5深圳广东12134
6杭州浙江11178
7南昌江西9208
8济南山东6593
9合肥安徽6463
10南通江苏6457
11成都四川6446
12南京江苏6335
13苏州江苏5849
14武汉湖北5667
15青岛山东4547
16临沂山东4511
17佛山广东4389
18西安陕西4197
19天津天津4056
20长沙湖南3939
21兰州甘肃3697
22潍坊山东3222
23江门广东3162
24福州福建3028
25长春吉林2707
26无锡江苏2683
27唐山河北2597
28汕头广东2593
29清远广东2572
30昆明云南2455
31常州江苏2439
32厦门福建2154
33中山广东2071
34湘潭湖南2004
35鸡西黑龙江1975
36洛阳河南1954
37大连辽宁1832
38张家口河北1785
39平顶山河南1725
40宁波浙江1706
41泰安山东1700
42贵阳贵州1642
43石家庄河北1597
44哈尔滨黑龙江1536
45九江江西1500
46遵义贵州1405
47宁德福建1391
48蚌埠安徽1376
49镇江江苏1374
50桂林广西1374

6 源代码

细节主要包括:

  • 使用opensteetmap中的多组地名进行地标中心选取,每个城市都取最多地标的中心作为参照。
  • 只统计具有name属性的地标。以防很多自动标注工具批量生成的内容干扰统计。
#include <QCoreApplication>
#include <QFile>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QTextStream>
#include <QVector>
#include <cassert>

QVector<QVector<double> > correctPosition(const double lon, const double lat,
								const QString & name1, const QString & name2);
qint64 queryCount(const double lon, const double lat, const QString & filter);

int main(int argc, char *argv[]) {
	QCoreApplication a(argc, argv);
	QTextStream st_out(stdout,QIODevice::WriteOnly);
	try {
		//打开数据库,准备选择
		QSqlDatabase dbq = QSqlDatabase::addDatabase("QPSQL","TEST");
		if (!dbq.isValid())
			throw dbq.lastError();
		dbq.setHostName("127.0.0.1");
		dbq.setPort(5433);
		dbq.setUserName("archosm");
		dbq.setPassword("archosm");
		dbq.setDatabaseName("gis");
		if (!dbq.open())
			throw dbq.lastError();

		//读取CSV数据,https://download.csdn.net/download/u012266955/10664500
		QFile f_city(":/ctz_city_type.csv");
		QFile f_city_out("ctz_city_type.csv");
		if (!f_city.open(QIODevice::ReadOnly))
			throw f_city.errorString();
		if (!f_city_out.open(QIODevice::WriteOnly))
			throw f_city_out.errorString();
		QTextStream csv_in(&f_city);
		QTextStream csv_out(&f_city_out);
		csv_out<<"city,provice,citycode,citytype,cityclass,citype,conspeople,"
				 "cityname,cityym,lng_gd,lat_gd,spare,corr_lon,corr_lat,osm_count_1000km^2,url\n";
		//skip first line(titles)
		csv_in.readLine();
		while (!csv_in.atEnd())
		{
			const QString line = csv_in.readLine();
			const QStringList lst_elems = line.split(",");
			if (lst_elems.size()<11)
				continue;
			const double lon = lst_elems.at(9).toDouble();
			const double lat = lst_elems.at(10).toDouble();
			if (lon < 40 || lon >140 || lat<5 || lat >65)
				throw QString("Bad Input lat lon.");

			const QVector<QVector<double> > corrected =
					correctPosition(lon,lat,lst_elems.first(),lst_elems.at(7));
			qint64 best_hit = 0;
			double best_lat = lat, best_lon = lon;
			foreach(const QVector<double> & cter, corrected)
			{
				const qint64 count = queryCount(cter[0],cter[1]," name is not null ");
				if (count>best_hit)
				{
					best_hit = count;
					best_lon = cter[0];
					best_lat = cter[1];
				}
			}

			foreach (const QString & v, lst_elems) {
				csv_out<<v<<",";
			}
			csv_out<<best_lon<<","<<best_lat<<","<<best_hit<<",";
			csv_out<<QString("\"https://www.openstreetmap.org/#map=10/%2/%1\"")
					 .arg(best_lon).arg(best_lat)<<"\n";
			st_out << lst_elems.at(0)<<":"<<best_hit << "\n";
			st_out.flush();
		}


		f_city.close();
		f_city_out.close();


	}
	catch (QString err) {
		st_out << err << "\n";
		st_out.flush();
	}
	catch (QSqlError err) {
		st_out << err.text() << "\n";
		st_out.flush();
	}


	a.processEvents();
	return 0;
}

qint64 queryCount(const double lon, const double lat, const QString & filter)
{
	//2000 km^2圆形区域内。
	QTextStream st_out(stdout,QIODevice::WriteOnly);
	static const QString s(
				"select count(osm_id) from ("
				"select distinct osm_id from ("
				"select distinct osm_id from planet_osm_line where ST_Covers("
				"ST_Transform(ST_SetSRID(ST_MakeBox2D(ST_Point(%1-0.5, %2-0.5),ST_Point(%1+0.3, %2+0.3)),4326),3857),way) "
				"and ST_Distance(ST_Transform(ST_SetSRID(ST_MakePoint(%1, %2), 4326),3857),way) < 12616 and (%3) "
				" union "
				"select distinct osm_id from planet_osm_polygon where ST_Covers("
				"ST_Transform(ST_SetSRID(ST_MakeBox2D(ST_Point(%1-0.5, %2-0.5),ST_Point(%1+0.3, %2+0.3)),4326),3857),way) "
				"and ST_Distance(ST_Transform(ST_SetSRID(ST_MakePoint(%1, %2), 4326),3857),way) < 12616 and (%3) "
				" union "
				"select distinct osm_id from planet_osm_point where ST_Covers("
				"ST_Transform(ST_SetSRID(ST_MakeBox2D(ST_Point(%1-0.5, %2-0.5),ST_Point(%1+0.3, %2+0.3)),4326),3857),way) "
				"and ST_Distance(ST_Transform(ST_SetSRID(ST_MakePoint(%1, %2), 4326),3857),way) < 12616 and (%3) "
				" union "
				"select distinct osm_id from planet_osm_roads where ST_Covers("
				"ST_Transform(ST_SetSRID(ST_MakeBox2D(ST_Point(%1-0.5, %2-0.5),ST_Point(%1+0.3, %2+0.3)),4326),3857),way) "
				"and ST_Distance(ST_Transform(ST_SetSRID(ST_MakePoint(%1, %2), 4326),3857),way) < 12616 and (%3) "
				") all_eles) joined_uniq;"
				);
	QSqlQuery query(QSqlDatabase::database("TEST"));
	QString sql = s.arg(lon).arg(lat).arg(filter);
	//st_out<<sql;
	if (query.exec(sql)==false)
		throw query.lastError();
	qint64 count = 0;
	if (query.next())
		count = query.value(0).toLongLong();

	return count;
}
QVector<QVector<double> > correctPosition(const double lon, const double lat, const QString & name1, const QString & name2)
{
	QVector<QVector<double> > res;
	QVector<double> lonlat_raw{lon,lat};
	//res<<lonlat;
	static const QString s(
				"select St_X(St_Transform(way,4326)),St_Y(St_Transform(way,4326)) ,"
				"ST_Distance(ST_Transform(ST_SetSRID(ST_MakePoint(%1, %2), 4326),3857),way) as dis,name from planet_osm_point"
				" where ST_Covers("
				"ST_Transform(ST_SetSRID(ST_MakeBox2D(ST_Point(%1-2, %2-2),ST_Point(%1+2, %2+2)),4326),3857),way) "
				" %5 and (name ='%3' or name ='%4') "
				" order by admin_level, dis asc "
				);
	QTextStream st_out(stdout,QIODevice::WriteOnly);
	QTextStream st_in(stdin,QIODevice::ReadOnly);
	QSqlQuery query(QSqlDatabase::database("TEST"));
	QString sql = s.arg(lon).arg(lat).arg(name1).arg(name2)
			.arg(" and place in ('city','town','suburb') ");
	//st_out<<"\n"<<sql;
	if (query.exec(sql)==false)
		throw query.lastError();
	while (query.next())
	{
		QVector<double> lonlat{0,0};
		lonlat[0] = query.value(0).toDouble();
		lonlat[1] = query.value(1).toDouble();
		res<<lonlat;
	}
	if (res.size()==0)
	{
		sql = s.arg(lon).arg(lat).arg(name1).arg(name2)
				.arg(" and place is not null ");
		if (query.exec(sql)==false)
			throw query.lastError();
		while (query.next())
		{
			QVector<double> lonlat{0,0};
			lonlat[0] = query.value(0).toDouble();
			lonlat[1] = query.value(1).toDouble();
			res<<lonlat;
		}
	}
	if (res.size()==0)
	{
		sql = s.arg(lon).arg(lat).arg(name1).arg(name2)
				.arg("  ");
		if (query.exec(sql)==false)
			throw query.lastError();
		while (query.next())
		{
			QVector<double> lonlat{0,0};
			lonlat[0] = query.value(0).toDouble();
			lonlat[1] = query.value(1).toDouble();
			res<<lonlat;
		}
	}
	if (res.size()==0)
	{
		res<<lonlat_raw;
		st_out<<"\n"<<sql<<" \nNot corrented\n";
	}
	return res;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁劲犇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值