在搭建如OpenStreetMap等瓦片服务器的过程中,我们会关心当前服务器上哪些瓦片已经存在(被渲染了)、哪些瓦片被访问量大,哪些少。
统计瓦片的历史记录,可以直接扫描缓存,或者通过数据库来完成。如果能够把统计数据直接叠加到地图上显示,就更棒了!今天,就来动手制作一个webService,用来生成每个瓦片的访问情况图。
演示网址参见
http://www.goldenhawking.org:8088/subpages/heat.html
#1.接口设计
我们希望,这个服务本身可以作为一个图层与现有的OpenStreetMap地图无缝契合。因此,它应该也是一个瓦片服务器。通过接口访问:

http://server_address/cgi-bin/tilehis.fcgi?x=138&y=221&z=7

将返回一个透明的PNG图片,用不同色彩,显示该瓦片比例尺以下4级的渲染情况。

#2.数据库
数据库中,存储了各个瓦片的历史记录。数据库直接利用OpenStreetMap的PostGIS数据库,表如下:

CREATE TABLE tilehis
(x integer NOT NULL,y integer NOT NULL,z integer NOT NULL,counts integer NOT NULL DEFAULT 1,CONSTRAINT tilehis_pkey PRIMARY KEY (x, y, z)USING INDEX TABLESPACE tbspace_his
)
WITH (OIDS = FALSE
)

共4列,分别为x,y,z与访问次数。
在瓦片服务中,会根据每次客户端对瓦片的访问,为数据库中的记录加一。

#3.fcgi 功能实现
我们采用基于Qt的多线程并发fcgi框架
##3.1 主函数与工作线程
主函数(main.cpp)负责初始化fcgi环境,启动线程池,并进入消息循环。

#include <QCoreApplication>
#include <QList>
#include <iostream>
#include <QDebug>
#include <fcgi_stdio.h>
#include "listenthread.h"
using namespace std;
const int thread_count = 4;
int main(int argc, char *argv[])
{//由于Qt在fcgi下,采用console设置,所以为QCoreApplication而非QApplicationQCoreApplication a(argc, argv);//初始化fcgiFCGX_Init();//初始化线程池QList<listenThread *> threadpool;for (int i=0;i<thread_count;++i)threadpool.push_back(new listenThread(&a));foreach (listenThread * t, threadpool)t->start();return a.exec();
}

在主函数中,启动四个线程,用于响应用户连接。具体的工作由 listenthread 完成。在listenthread中,存在一个Mutex,用于保护数据库创建过程中的线程安全。

listenthread.h

#ifndef LISTENTHREAD_H
#define LISTENTHREAD_H
#include <QThread>
#include <QMutex>
struct FCGX_Request;
class listenThread : public QThread
{Q_OBJECT
public:explicit listenThread(QObject *parent = 0);
protected:static QMutex  m_mutex;void run();void deal_client(FCGX_Request * request);};#endif // LISTENTHREAD_H

##3.2 不断接受并处理请求
在listenThread的实现中,入口点为Qthread::run。

void listenThread::run()
{//采用一个mutex保护数据库创建过程。Qt的数据库创建过程需要保护,创建后基本就安全了(对PostgreSQL而言)。m_mutex.lock();QString dbName = QString("RThread%1").arg(quint64(this));QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL",dbName);//Sql Connectionsif (db.isValid()){db.setHostName("127.0.0.1");db.setDatabaseName("tilehis");db.setUserName("pi");db.setPassword("**********");db.open();}m_mutex.unlock();//检查创建效果if (db.isOpen()==false){qCritical()<<db.lastError().text();quit();return;}//开始FCGI事件循环FCGX_Request request;FCGX_InitRequest(&request, 0, 0);int rc = FCGX_Accept_r(&request);while (rc >=0){//执行业务逻辑deal_client(&request);FCGX_Finish_r(&request);rc = FCGX_Accept_r(&request);}//拆除数据库连接m_mutex.lock();db.close();QSqlDatabase::removeDatabase(dbName);m_mutex.unlock();quit();
}

这里需要注意的是,run运行在QThread管理的独立线程里。数据库对象的建立与拆除,都是在线程内进行的。Qt虽然号称支持多线程的数据库访问,但是数据库的创建(addDatabase)阶段,仍旧需要保护。否则,笔者实测会出错。
一旦数据库建立,即可不断接受cgi连接。
##3.3 实现响应:数据库查询与OpenCV图片生成
在响应函数deal_client中,我们获取请求内容,并查询数据库中瓦片的访问历史,从而生成一个半透明的叠加图片。

void listenThread::deal_client(FCGX_Request * request)
{//3.3.1 获得请求字符串,类似 x=374&y=378&z=13 这样的字符串。const char * const query_string=FCGX_GetParam("QUERY_STRING",request->envp);//3.3.2 利用Qt强大的字符功能,直接把请求变为字典。QHash < QString, QString> values;QString str = QString::fromUtf8(query_string) ;QStringList lst = str.split("&",QString::SkipEmptyParts);foreach (QString pai, lst){int pd = pai.indexOf("=");if (pd>0 && pd < pai.length()){QString key = pai.left(pd);QString v = pai.mid(pd+1);values[key.trimmed().toUpper()]  = v;}}//3.3.2 获得 x,y,z 参数int x = values["X"].toInt();int y = values["Y"].toInt();int z = values["Z"].toInt();if (z>18) z = 18;if (z<0) z = 0;int maxsz = 1<<z;if (x<0) x = 0; if (x>=maxsz) x = maxsz - 1;if (y<0) y = 0; if (y>=maxsz) y = maxsz - 1;//3.3.3 获得本线程对应的数据库连接QString dbName = QString("RThread%1").arg(quint64(this));QSqlDatabase db = QSqlDatabase::database(dbName);//3.3.4 准备显示在瓦片上的文字QString urls = QString("%1/%2/%3.png").arg(z).arg(x).arg(y);//3.3.5 OpenCV部分,为本线程准备一副透明的图片(PNG)//注意,为了避免次次进行初始化,使用了静态对象。static cv::Mat mat_raw(256,256, CV_8UC4);static bool inited=false;if (inited==false){creatAlphaMat(mat_raw);inited = true;}//3.3.5.1 从静态对象生成本会话需要的图片 cv::Mat mat;mat_raw.copyTo(mat);//3.3.5.2 一个队列,存储需要显示的文本。std::vector<std::string> string_lists;string_lists.push_back(urls.toStdString());//3.3.5.3 运行SQL获得本瓦片总的被访问次数if (db.isOpen()){QSqlQuery query(db);query.setForwardOnly(true);query.prepare("select sum (counts) as subct from tilehis where x= ? and y=? and z=?");query.addBindValue(x);query.addBindValue(y);query.addBindValue(z);if (query.exec()){if (query.next()){QString ct = query.value(0).toString();std::string strCt = "Total Access:" +ct.toStdString();string_lists.push_back(strCt);}}}//3.3.5.4 运行SQL获得本瓦片内部的4级比例尺各层访问次数,并绘制热力图if (db.isOpen()){QSqlQuery query(db);query.setForwardOnly(true);for (int zo =1; zo<5 ;++zo){if (z + zo >18)continue;int  x_left = x * (1<<zo);int  x_right = (x+1) * (1<<zo);int  y_left = y * (1<<zo);int  y_right = (y+1) * (1<<zo);query.prepare("select x,y,sum (counts) as subct from tilehis where x>= ? and x< ? and  y>=? and y<? and  z=? group by x,y");query.addBindValue(x_left);query.addBindValue(x_right);query.addBindValue(y_left);query.addBindValue(y_right);query.addBindValue(z+zo);if (query.exec()){int step = 256 / (1<<zo);while (query.next()){int sx = query.value(0).toInt() - x_left;int sy = query.value(1).toInt() - y_left;int sa = query.value(2).toInt();//色彩表,按照热度,从蓝色到红色映射。int colr = sa > 255 ? (sa>255+128?255 : sa -255+128):0;if (colr>255) colr = 255;int colg = sa > 128 && sa <=255? sa:0;if (colg>255) colg = 255;int colb = sa <= 128? sa+128:0;if (colb>255) colb = 255;int gama = 32 + zo*16;//openCV 绘制矩形cv::rectangle(mat, cv::Point(sx*step,sy*step), cv::Point((sx+1)*step-1,(sy+1)*step-1),cv::Scalar(colb,colg,colr,gama),(int)-1);}}}}//3.3.5.5 写入文字size_t szv = string_lists.size();if (szv){       for (int j = 0;j<szv;++j){std::string text = string_lists[j];int fontFace = cv::FONT_HERSHEY_PLAIN;double fontScale = 1;int thickness = 2;int baseline = 0;cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, &baseline);baseline += thickness;cv:: Point textOrg(3,(textSize.height)+3+j*textSize.height*1.5);cv::rectangle(mat,cv::Point(0,0) ,cv:: Point(255,255),cv::Scalar(0,0,255,128));cv::rectangle(mat,textOrg + cv::Point(0,baseline),textOrg + cv::Point(textSize.width,-textSize.height),cv::Scalar(255,255,255,128),(int)-1);         cv::putText(mat,text,textOrg,fontFace,fontScale,cv::Scalar(192,0,0,192),thickness,8);}}//3.3.6 压缩生成PNGvector<uchar> buf; // Memory buffervector<int> params;params.push_back( cv::IMWRITE_PNG_COMPRESSION );params.push_back( 9 ); // Quality of compressioncv::imencode(".png", mat, buf, params );//3.3.7 向客户端返回PNGFCGX_FPrintF(request->out,"Content-type: image/png\n\n");FCGX_PutStr((const char *)buf.data(),buf.size(),request->out);
}

这里需要注意到,我们使用了OpenCV而不是Qt的QIMage,主要是因为fcgi程序没有GUI支持。如果硬是开启Qt的gui支持,笔者测试会出现问题。
#4 运行效果
我们使用两个图层叠加,实现地图的热力图效果。
具体的演示代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html style="height: 100%; width: 100%;">
<head><meta content="text/html; charset=UTF8" http-equiv="content-type"><title>OpenStreetMap</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="baidu-site-verification" content="tdO4FTbzxO"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="pragma" content="no-cache"><meta http-equiv="expires" content="0"><link href="/ol.css" rel="stylesheet" type="text/css"><script src="/ol.js"></script>
</head>
<body style="height: 98%; width: 99%;">
<div id="osm_map" class="map" style="width: 98%; height: 88%; text-align: center; color: rgb(255, 102, 102);">
</div>
<script>var map = new ol.Map({layers: [new ol.layer.Tile({source: new ol.source.OSM({url:"/cgi-bin/get_tile.fcgi?z={z}&x={x}&y={y}"})}),new ol.layer.Tile({source: new ol.source.OSM({url:"/cgi-bin/tile_his.fcgi?z={z}&x={x}&y={y}"})})],target: 'osm_map',controls: ol.control.defaults({rotateOptions:({autoHide:true})}).extend([new ol.control.MousePosition({projection:'EPSG:4326'}),new ol.control.ScaleLine({units: 'metric'}),new ol.control.ZoomToExtent ({})]),view: new ol.View({center: ol.proj.fromLonLat([114.395592, 30.5216412]),zoom: 0,maxZoom: 18})});
</script>
</body>
</html>

其中,在OpenLayers中,插入了2个瓦片图层。get_tile.fcgi 是底图,tile_his.fcgi是覆盖热力图。

其效果:

#5 Qt FCGI性能

我们使用了一个树莓派2代进行测试,发现性能还是不错的!由于全部是C的代码,即使在开启了PostgreSQL的Arm 1G内存下,访问效率仍旧很高。

在近期的各类应用中,我们尝试了使用Qt+FCGI实现了各种业务。包括数据下载、上传、图片处理、设备控制,对应熟悉C++、Qt的Native开发者来说,是架构转型的又一种解决方案。

使用openCV+Qt+fcgi 为OpenStreetMap瓦片添加热力图图层相关推荐

  1. OpenCV+Qt+CMake安装+十种踩坑

    平台:win10 x64+opencv-3.4.1 + qt-x86-5.9.0 + cmake3.13.4 x64 OpenCV+Qt+CMake安装,及目前安装完后打包:mingw32-make时 ...

  2. 利用Opencv+Qt打开摄像头

    利用Opencv+Qt添加摄像头 1.opencv包 添加链接描述 2.安装Qt 3.代码 #pragma once#include <QWidget> #include "ui ...

  3. 利用opencv+QT打开并显示图片

    ``` // 利用opencv+QT打开并显示图片// 头文件在添加 #include "qlabel.h" // opencv #include <opencv2\core ...

  4. 四小时学习opencv+qt系列(第二天)

    每天四小时学习opencv+qt系列(第二天) 顺便提一下,我觉得收获最大的就是上一篇博客中使用到的QSettings这个类,可以对设置进行保存与初始化,而我原来没有用到这个类的时候用的是将设置保存在 ...

  5. (成功案例超详细保姆级)vs2019 opencv qt创建动态库被C#调用

    之前有个项目关于图像处理,既用到了opencv,还二次开发了相机提供的动态库.一开始我是用QT写的,然后发现人家只需要我提供一个库函数调用就可以了,但是他是用C#写的.没办法,到处找资料,但是没有找到 ...

  6. 成功案例超详细-vs2019 opencv qt创建动态库被C#调用

    之前有个项目关于图像处理,既用到了opencv,还二次开发了相机提供的动态库.一开始我是用QT写的,然后发现人家只需要我提供一个库函数调用就可以了,但是他是用C#写的.没办法,到处找资料,但是没有找到 ...

  7. 四小时学习opencv+qt系列(第六天)

    四小时学习opencv+qt系列(第六天) 一.图形视图框架 三大类:场景(QGraphicsScene类)视图(QGraphicsView类)图形对象元素(QGraphicsItem及其子类) 1. ...

  8. OpenCV特征点检测匹配图像-----添加包围盒

    OpenCV特征点检测匹配图像-----添加包围盒 最终效果: 其实这个小功能非常有用,甚至加上只有给人感觉好像人脸检测,目标检测直接成了demo了,主要代码如下: // localize the o ...

  9. Qt Creator在属性之间添加绑定

    Qt Creator在属性之间添加绑定 在属性之间添加绑定 在属性之间添加绑定 要动态更改对象的行为,可以在两个对象的属性之间创建绑定.为了创建属性绑定,为属性分配了一个JavaScript表达式,该 ...

最新文章

  1. 前端开发基础7(Bootstrap框架)
  2. solaris下使用USB 海量存储设备
  3. 福利继续:赠书《Spring Cloud微服务-全栈技术与案例解析》
  4. [云炬创业基础笔记]第七章创业资源测试4
  5. 初学Java,这些框架你要掌握
  6. Android10创建文件Permission denied 失败
  7. 实验项目 3-4:一元多项式的乘法与加法运算
  8. java logging 格式化_java.util.logging.Logger使用详解 (转)
  9. Apache用户认证、默认虚拟主机、域名301跳转
  10. Pandas时间索引的骚操作
  11. 图嵌入(一)--综述
  12. PHP设计模式——原型模式
  13. 设置HTML元素的透明度
  14. 机器学习数学基础(1)-回归、梯度下降
  15. 有向图的拓补排序算法
  16. 市场调研报告-加工食品包装市场现状及未来发展趋势
  17. 服务器名称显示 n a,EXCEL技巧 怎样消除vlookup找不到目标时出现的#N/A
  18. 如何修改云服务器的远程连接密码?
  19. 【R语言文本挖掘】:n-grams和相关性计算
  20. 2022年中国互联网数据中心(IDC)行业产业链及市场现状分析(附国家绿色数据中心公示名单)[图]

热门文章

  1. 006.Sql条件查询
  2. date比较大小 mybatis_mybatis 日期比较
  3. 【Python基础】02 Python基础语法
  4. 整理Glide方法使用含义(毛玻璃效果,实现圆角等)
  5. linux中的ubiq命令用途,Linux uniq 命令
  6. z420开机无法进入BIOS,出现A9和光标
  7. 用友nc阻止java运行_用友NC网页版进不去,应用程序已被安全设置阻止..._安全工程师_帮考网...
  8. 【MFC】学生数据管理-广州大学程序设计课程设计报告
  9. 加密聊天解决方案——木星文
  10. Python3.8的下载与安装