目录
本文以geojson转mif文件为例,介绍如何使用qt读取json文件。整个工程上传在我的资源Qt程序,将三沙市的GeoJson格式数据转化为mif文件-C++文档类资源-CSDN下载
理论
Json的两种基本数据类型是对象(object)和数组(array)。Json格式的对象用{}标识,而数组用[]标识。
Qt提供三种数据类型来描述Json: QJsonObject QJsonArray QJsonValue。前两者分别代表对象和数组,第三个QJsonValue的使用场景如下:
考虑一个典型的Json对象 {"x": "1"}
你可以用QJsonObject::value("x")来索引"1"。而value这个函数的返回值类型就是QJsonValue。你可以通过QJsonValue::toString()进一步将这个返回值变为字符串型。
考虑一个典型的Json对象 {"x": {"1", "t"}}
你可以用QJsonObject::value("x")来索引{"1","t"}。而value这个函数的返回值类型还是QJsonValue。你可以通过QJsonValue::toObject()进一步将这个返回值变为{"1","t"}实际代表的QJsonObject型。
考虑一个典型的Json对象 {"x": ["1", "t"]}。
你可以用QJsonObject::value("x")来索引["1","t"]。而value这个函数的返回值类型还是QJsonValue。你可以通过QJsonValue::toArray()进一步将这个返回值变为["1","t"]实际代表的QJsonArray型。
总结起来说,当你从一个文本文件里读取json时,返回的结果类型是QJsonObject。当你通过value函数从QJsonObject类型的变量中获取结果时,结果类型为QJsonValue;
当你通过at函数从QJsonArray类型的变量中获取结果时,结果类型也为QJsonValue。而QJsonValue可以向QJsonObject和QJsonArray转化。
接下来通过一个示例展示Qt读取GeoJson文件的操作。
示例
1分析
GeoJson本质就是一个含有地理信息的Json文件。但是不同的信息源产生的GeoJson格式略有不同。关于GeoJson的描述可参考GeoJSON结构解析_GADFLYGIS的博客-CSDN博客,这里不赘述。以高德-阿里联合发布的数据为例https://datav.aliyun.com/portal/school/atlas/area_selector,其格式如下:
{
"type":"....",
"features":[
{
"type":"...",
"properties":{...},
"geometry": {
"type":"MultiPolygon",
"coordinates": [
[[[x,y],[..],[...]]],
[[[x,y],[..],[...]]],
.......
]
}
},
{
},
{
},
.....
]
}
根节点下面有若干成员,其中最重要的是features。features索引了一个数组。每个数组的成员又是一个json对象。一般来说,这个对象的成员里面最重要的是geometry项。其type指明了几何形状的类型。"type":"MultiPolygon",表明几何形状由多个多边形组成。coordinates存储每个多边形的定点经纬度。这里有个反直觉的地方:每个顶点经纬度构成一个数组,需要一重方括号;同一个多边形包含多个顶点,也需要一重方括号;多个多边形又构成一个数组,又需要一重方括号。所以coordinates总共需要三重括号。但是在实际数据文件中,多边形用两重括号包括了顶点数组。所以实际上coordinates总共用了四重括号。
2代码
根据以上分析,我写了一个Qt程序来将GeoJson格式的南海诸岛地理信息转化为mif格式。
读取json文件
QFile fp("SouthSea.json");
if(fp.open(QIODevice::ReadOnly))
{
QJsonObject jsonObj = jsonFromQba(fp.readAll());
fp.close();
QJsonArray jsonArr = jsonObj.value("features").toArray();
std::list<QPolygonF> lstIslands = lstGetPolygon(jsonArr);
vWriteMifFile(lstIslands);
}
如同前面分析的,根节点是一个对象object。 jsonObj.value("features")获取对象的成员,并将其转化为QJsonArray。
随后函数lstGetPolygon将QJsonArray转化为一个QPolygonF的集合。
std::list<QPolygonF> lstGetPolygon(const QJsonArray & jsonArr)
{
std::list<QPolygonF> lstRet;
for(const auto & jsonVal: jsonArr)
{
QJsonValue jsonGeom = jsonVal.toObject().value("geometry");
if (jsonGeom.isObject())
{
QJsonValue jsonValCood = jsonGeom.toObject().value("coordinates");
QJsonArray jsonArrCoord = jsonValCood.toArray();
std::list<QPolygonF> lstPloygon = lstPolygonFromArray(jsonArrCoord);
lstRet.splice(lstRet.end(), lstPloygon);
}
}
return lstRet;
}
由于features索引了一个数组,所以要通过 for(const auto & jsonVal: jsonArr)遍历其所有元素。
数组的每一个元素格式都是{ "type": "Feature", "properties": {}, "geometry": {},......}。为了提取经纬度信息,接下来采用value("geometry")函数返回QJsonValue,并利用函数将toObject()转化为QJsonObject。然后获取坐标"coordinates"。
"coordinates": [
[[[x,y],[..],[...]]],
[[[x,y],[..],[...]]],
.......]
coordinates是数组,所以采用函数toArray()将其转化为QJsonArray,随后利用函数lstPolygonFromArray(接下来会介绍)返回QPolygonF的集合。由于features索引的数组中含有多个元素,以上只是处理了其中一个元素,所以还要用splice函数把每个元素对应的集合拼接到同一个集合lstRet中,作为返回值。
//下面的函数将geoJson的coordinates的坐标解析出来
std::list<QPolygonF> lstPolygonFromArray(const QJsonArray & jsonArr)
{
std::list<QPolygonF> lstRet;
//jsonArr是多元数组元素,每一个元素是一个PolygonF
for(const auto & jsonVal: jsonArr)
{
auto jsonPolygon = jsonVal.toArray();
if(1 == jsonPolygon.size())
{
QPolygonF plgn;
auto jsonLine = jsonPolygon.at(0).toArray();
for(const auto & jsonVertex: jsonLine)
{
auto jsonLongLat = jsonVertex.toArray();
if(jsonLongLat.size() == 2)
plgn<<QPointF(jsonLongLat.at(0).toDouble(), jsonLongLat.at(1).toDouble());
}
lstRet.push_back(plgn);
}
}
return lstRet;
}
lstPolygonFromArray的输入参数jsonArr是一个数组类型。所以要用for(const auto & jsonVal: jsonArr)遍历该数组。
每个数组的元素又是一个数组,如下形式:
[[[x,y],[..],[...]]]
正如前面所说,“在实际数据文件中,多边形用两重括号包括了顶点数组”,所以对于jsonArr的每个元素来说,第一重括号内只有一个元素。要想“拨开”第一层括号,只要用at(0)即可。随后用for(const auto & jsonVertex: jsonLine)遍历第二层括号。括号内是一个个二元数组jsonLongLat 。于是用jsonLongLat.at(0)和jsonLongLat.at(1)获取二元数组的经纬度,并将经纬度作为顶点信息存入多边形plgn。当一个多边形(也即是[[[x,y],[..],[...]])被遍历完毕后,将plgn放入lstRet。本函数遍历完毕后,完成了对一个"coordinates"的解析。"features"索引一个数组,数组的每个元素都含有一个"coordinates",所以一个GeoJson文件将对"features"的所有"coordinates"进行处理,返回的是若干多边形的集合。
void vWriteMifFile(const std::list<QPolygonF> & lstPolygon)
{
QFile fp("SouthSea.mif");
if(fp.open(QFile::WriteOnly))
{
QString qstrRegion = QString("Region %1\n").arg(lstPolygon.size());
fp.write(qstrRegion.toLatin1());
for(const auto & polygon: lstPolygon)
{
QString qstrPoints = QString(" %1\n").arg(polygon.size());
fp.write(qstrPoints.toLatin1());
for(const auto & pnt: polygon)
{
int iLong = pnt.x() * 1000000;
int iLat = pnt.y() * 1000000;
QString qstrPnt =
QString("%1.%2 %3.%4\n").
arg(iLong/1000000).arg(iLong%1000000, 6, 10, QChar('0')).
arg(iLat / 1000000).arg(iLat % 1000000, 6, 10, QChar('0'));
fp.write(qstrPnt.toLatin1());
}
}
fp.close();
}
}
vWriteMifFile函数产生mif文件。
mif 文件的第一行是以"Region"开头。所谓region就是刚才说的多边形。region后面是多边形的个数。随后是逐个多边形的经纬度信息。
每个多边形的信息的第一行是其顶点总数,接下来是各个顶点经纬度。下图是一个mif文件的开头部分截图。