纯QWidget绘制实现电子地图控件/非qml非web/多线程下载和加载瓦片/支持各种图形

纯QWidget绘制实现电子地图控件/非qml非web/多线程下载和加载瓦片/支持各种图形

一、前言说明

之前做的地图组件,耗费了巨大的时间精力,前后完善了五年之多,能够想到的应用场景几乎都实现了,也有不少的用户,现场实际需求也不断反馈,不断的修改和增加功能,尽管优点很多,依然有个巨大缺点就是依赖浏览器控件,性能肯定是要打折扣的,毕竟有些嵌入式板子甚至老的开发环境,不一定有浏览器控件,就算有,在嵌入式板子环境上或者一些国产硬件的系统上,配置比较低,在浏览器上运行的网页,性能指数级下降,甚至一些环境连GPU都没有,老板为了节省成本,尽量选一些配置低的板子,所以也没有一种可能用QWidget绘制来实现呢,这样性能极好,而且控制度极高,qt的painter非常灵活可靠。

经过大量的尝试改造,总算在今年实现了这个地图控件,不依赖浏览器控件,也不依赖qml,有些人用的Qt自带的qml的location组件来实现的,这个尽管方便,但是性能也低,因为嵌入式环境配置低的板子,根本无法正常跑起来qml,别提要新版的Qt才有qlocaltion组件。用qwidget来实现有两个最大难点,一个是如何将地理坐标映射到像素绘制坐标,一个是如何快速的加载瓦片多线程绘制,这个必须采用多个分层绘制的机制。还有一个难点是绘制的图形有两大类,一种是面积大小自适应的比如多边形,一种是不需要自适应的一直是固定大小的比如标注点,这两种都需要在固定的经纬度区域,拖动移动要自动移动的对应的位置。将地理坐标映射到像素绘制坐标这个是最难的,整个地图组件的绘制要不断的将经纬度坐标转像素坐标,识别区域要将像素坐标转经纬度坐标。

二、效果图

在这里插入图片描述


在这里插入图片描述

三、相关代码

#include"frmoverlay.h"#include"ui_frmoverlay.h"#include"qthelper.h"#include"maphelper.h"#include"overlayhelper.h"#include"magicfish.h" frmOverlay::frmOverlay(QWidget *parent):QWidget(parent),ui(new Ui::frmOverlay){ ui->setupUi(this);this->initForm();this->initConfig();this->loadMap();}frmOverlay::~frmOverlay(){delete ui;}void frmOverlay::initForm(){ lng =121.424362; lat =31.175942;connect(ui->mapWidget,SIGNAL(receivePoint(qreal, qreal)),this,SLOT(receivePoint(qreal, qreal)));connect(ui->cboxFlag->lineEdit(),SIGNAL(textChanged(QString)),this,SLOT(textChanged(QString)));}void frmOverlay::initConfig(){MapHelper::loadTileSource(ui->cboxTileSource, AppConfig::OverlaySource);connect(ui->cboxTileSource,SIGNAL(currentIndexChanged(int)),this,SLOT(saveConfig()));connect(ui->cboxTileSource,SIGNAL(currentIndexChanged(int)),this,SLOT(loadMap()));MapHelper::loadTileType(ui->cboxTileType, AppConfig::OverlayType);connect(ui->cboxTileType,SIGNAL(currentIndexChanged(int)),this,SLOT(saveConfig()));connect(ui->cboxTileType,SIGNAL(currentIndexChanged(int)),this,SLOT(loadMap())); ui->cboxOffline->setCurrentIndex(AppConfig::OverlayOffline);connect(ui->cboxOffline,SIGNAL(currentIndexChanged(int)),this,SLOT(saveConfig()));connect(ui->cboxOffline,SIGNAL(currentIndexChanged(int)),this,SLOT(loadMap())); ui->cboxCache->setCurrentIndex(AppConfig::OverlayCache);connect(ui->cboxCache,SIGNAL(currentIndexChanged(int)),this,SLOT(saveConfig()));connect(ui->cboxCache,SIGNAL(currentIndexChanged(int)),this,SLOT(loadMap()));}void frmOverlay::saveConfig(){ AppConfig::OverlaySource = ui->cboxTileSource->itemData(ui->cboxTileSource->currentIndex()).toInt(); AppConfig::OverlayType = ui->cboxTileType->itemData(ui->cboxTileType->currentIndex()).toInt(); AppConfig::OverlayOffline = ui->cboxOffline->currentIndex(); AppConfig::OverlayCache = ui->cboxCache->currentIndex();AppConfig::writeConfig();}void frmOverlay::reset(){ index =1; flag ="overlay1"; ui->cboxFlag->clear(); ui->cboxFlag->lineEdit()->setText(flag);}void frmOverlay::loadMap(){this->reset();int tileSource = ui->cboxTileSource->itemData(ui->cboxTileSource->currentIndex()).toInt(); ui->mapWidget->setTileSource(TileSource(tileSource));int tileType = ui->cboxTileType->itemData(ui->cboxTileType->currentIndex()).toInt(); ui->mapWidget->setTileType(TileType(tileType)); QString offlinePath =TileHelper::getOfflinePath(TileSource(tileSource)); ui->mapWidget->setOffline(ui->cboxOffline->currentIndex()==1); ui->mapWidget->setOfflinePath(offlinePath); QString cachePath =(ui->cboxCache->currentIndex()==1?QtHelper::appPath()+"/mapcache":""); ui->mapWidget->setCachePath(cachePath);}void frmOverlay::receivePoint(qreal lng, qreal lat){this->lng = lng;this->lat = lat; ui->txtLng->setText(QString::number(lng,'f',6)); ui->txtLat->setText(QString::number(lat,'f',6));}void frmOverlay::addIndex(){ index++; QString text =QString("overlay%1").arg(index); QString item =QString("overlay%1").arg(index -1); ui->cboxFlag->addItem(item); ui->cboxFlag->lineEdit()->setText(text);}void frmOverlay::textChanged(const QString &arg1){if(!arg1.isEmpty()){this->flag = arg1;}}void frmOverlay::on_btnAddMarker_clicked(){ QString file =":/image/marker.png";//QString file = ":/image/gif_person.gif"; OverlayBase *marker = ui->mapWidget->addMarker(flag, lng, lat, file);OverlayHelper::updateText(marker,"标注点文本","#000000",4,0,"");this->addIndex();}void frmOverlay::on_btnDeleteMarker_clicked(){ ui->mapWidget->deleteOverlay(flag);}void frmOverlay::on_btnUpdateMarker_clicked(){ QString file =":/image/gif_person.gif"; OverlayBase *marker = ui->mapWidget->updateMarker(flag, lng, lat, file);OverlayHelper::updateText(marker,"新的文本底部居中","#ff0000",8,0,"",20);}void frmOverlay::on_btnRotateMarker_clicked(){ ui->mapWidget->updateMarker(flag,NULL,NULL,QString(),QPixmap(),30);}void frmOverlay::on_btnAddLabel_clicked(){ ui->mapWidget->addLabel(flag, lng, lat,"#ff0000","测试文本",2);this->addIndex();}void frmOverlay::on_btnUpdateLabel_clicked(){ ui->mapWidget->updateLabel(flag, lng, lat,"#00ffff","新的文本",8,20);}void frmOverlay::on_btnAddPolyline_clicked(){ QPointF point(lng, lat); QList<QPointF> points =MapHelper::getRandPoints(point,4,0.1); ui->mapWidget->addPolyline(flag, points,"#ff0000",3);this->addIndex();}void frmOverlay::on_btnUpdatePolyline_clicked(){ ui->mapWidget->updatePolyline(flag,QStringList(),QColor(0,100,100,180),5);}void frmOverlay::on_btnAddPolygon_clicked(){ QPointF point(lng, lat); QList<QPointF> points =MapHelper::getRandPoints(point,3,0.15); ui->mapWidget->addPolygon(flag, points,"#00ff00",2,QColor(0,255,0,100));this->addIndex();}void frmOverlay::on_btnUpdatePolygon_clicked(){ ui->mapWidget->updatePolygon(flag,QStringList(), Qt::yellow,4,QColor(250,200,89,150));}void frmOverlay::on_btnAddRectangle_clicked(){ QPointF point(lng, lat); QList<QPointF> points =MapHelper::getRandPoints(point,1,0.15); ui->mapWidget->addRectangle(flag, points.at(0), points.at(1),"#008888",2,QColor(0,255,0,100));this->addIndex();}void frmOverlay::on_btnUpdateRectangle_clicked(){ ui->mapWidget->updateRectangle(flag,QPointF(),QPointF(), Qt::red,3,QColor(255,0,89,150));}void frmOverlay::on_btnAddCircle_clicked(){//根据当前缩放级别获取合适的长度int distance =MapHelper::getDistance(ui->mapWidget->getZoom()); qreal radius =MapHelper::getRadius(distance); ui->mapWidget->addCircle(flag, lng, lat, radius,"#0000ff",3,QColor(0,0,255,50));this->addIndex();}void frmOverlay::on_btnUpdateCircle_clicked(){ qreal radius =MapHelper::getRadius(8000); ui->mapWidget->updateCircle(flag, lng, lat, radius,"#ff0000");}void frmOverlay::on_btnAddWidget_clicked(){ MagicFish *widget =newMagicFish(ui->mapWidget); ui->mapWidget->addWidget(flag, lng, lat, widget);this->addIndex();}void frmOverlay::on_btnUpdateWidget_clicked(){ ui->mapWidget->updateWidget(flag, lng, lat);}void frmOverlay::on_btnAddBoundary_clicked(){on_btnClearOverlay_clicked(); QString fileName =QtHelper::getOpenFileName("*.txt",QtHelper::appPath()+"/mapboundary");if(!fileName.isEmpty()){ QFile file(fileName);if(file.open(QFile::ReadOnly | QFile::Text)){ QString points = file.readAll(); ui->mapWidget->addBoundary("boundary", points,"#ff55ff",2,QColor(255,100,255,100));}}}void frmOverlay::on_btnAddOverlays_clicked(){//批量添加可以先禁用重绘 OverlayHelper::redraw =false;on_btnClearOverlay_clicked(); ui->mapWidget->setZoom(10); ui->mapWidget->setCenter(121.424362,31.175942);for(int i =0; i <1000;++i){ qreal lng =QtHelper::getRandFloat(121.135250,121.77727); qreal lat =QtHelper::getRandFloat(30.99056,31.43569); qreal r =QtHelper::getRandFloat(0.02,0.07); QColor color =QtHelper::getRandColor(); QColor bgColor = color; bgColor.setAlpha(150); ui->mapWidget->addCircle(QString("circle%1").arg(i), lng, lat, r, color,3, bgColor);}//添加完成后启用重绘 OverlayHelper::redraw =true;}void frmOverlay::on_btnDeleteOverlay_clicked(){ ui->mapWidget->deleteOverlay(flag);}void frmOverlay::on_btnDeleteGroup_clicked(){ ui->mapWidget->deleteGroup("circle");}void frmOverlay::on_btnClearOverlay_clicked(){this->reset(); ui->mapWidget->clearOverlay();}void frmOverlay::on_btnVisibleOverlay_clicked(){if(ui->btnVisibleOverlay->text()=="显示所有"){ ui->mapWidget->setOverlayVisible("",true); ui->btnVisibleOverlay->setText("隐藏所有");}else{ ui->mapWidget->setOverlayVisible("",false); ui->btnVisibleOverlay->setText("显示所有");}}

四、相关地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.ZEEKLOG.net/feiyangqingyun/article/details/97565652
  4. 文件地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:bin_mapwidget.zip

五、功能特点

  1. 支持各种地图源,包括天地图、高德地图、腾讯地图、谷歌地图、微软地图等。
  2. 标准WGS-84地球坐标系,采用默卡托投影,可以拓展其他坐标系和投影规则。
  3. 支持在线和离线两种场景需求,可以自定义在线瓦片地址格式和离线瓦片地址格式。
  4. 多线程下载和加载瓦片图片文件,多线程绘制,自动缓存瓦片文件。
  5. 在线模式下,可以开启是否缓存文件,指定缓存路径,将下载的瓦片文件存放到本地,默认优先从缓存文件查找,如果存在缓存文件则加载缓存文件,不存在则联网下载。
  6. 可以拖动地图,鼠标滚轮放大和缩小地图,以鼠标所在位置作为缩放中心点,提供缩放控件手动单击进行操作。
  7. 多图层机制,支持多个瓦片叠加图层和图形绘制图层,全部采用双缓冲技术,所有的图形和瓦片全部绘制到一个图片文件上,最终再将图片文件绘制到地图控件。不可见区域的图层包括覆盖物不会触发绘制,降低CPU占用。
  8. 预加载机制,默认绘制的图层大小以当前区域往四周放大两倍,这样在鼠标拖动和缩放的时候,不会看到明显的加载过程,体验更佳。
  9. 内置了多种图形覆盖物,包括标注点、折线、多边形、矩形、圆形等,可以设置边框颜色粗细、填充颜色和透明度等参数。
  10. 标注点支持旋转角度和提示文本,其中提示文本可以设置在标注点的相对位置,位置包括左侧、右侧、上侧、下侧、中间、左上角、右上角、左下角、右下角。标注点本身也可以设置相对位置,默认按照底部居中对齐,一般圆形图标可以设置中心点对齐。标注点图片支持gif动图,可以动态切换静态图和动图。
  11. 所有的覆盖物可以动态更新前景色、颜色粗细、背景颜色、颜色透明度等。
  12. 支持删除单个覆盖物、删除一种类型的覆盖物、删除所有覆盖物、隐藏指定覆盖物等。
  13. 可以动态启动禁用比例尺、十字线、缩放控件、地图拖曳、键盘操作、滚轮缩放、双击放大等特性。
  14. 可以任意指定经纬度区域进行瓦片拼接保存成图片文件,也可以直接对整个可视区域或者缓存区域的地图图片文件保存。支持任意多边形轮廓保存成图片,比如某个行政区的瓦片保存。
  15. 覆盖物可以动态设置zindex层叠顺序,值越大,越显示在前面,内部维护着一个zindex表,默认按照添加的先后顺序增加,后面添加的显示在前面,主动设置后,按照设置的zindex来绘制。
  16. 支持将QWidget对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。
  17. 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。
  18. 默认开启缓存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。
  19. 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。
  20. 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。
  21. 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,支持极低性能的嵌入式环境。
  22. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。

Read more

Cogito-v1-preview-llama-3B效果展示:多模态思维链(CoT)生成可视化

Cogito-v1-preview-llama-3B效果展示:多模态思维链(CoT)生成可视化 最近在探索各种开源大模型时,我遇到了一个挺有意思的模型——Cogito-v1-preview-llama-3B。这个模型最吸引我的地方,是它号称能在回答问题时,把思考过程“可视化”出来。这听起来有点玄乎,但实际用下来,发现它确实有点东西。 简单来说,Cogito-v1-preview-llama-3B是一个只有30亿参数的小模型,但它有个特别的能力:不仅能直接给出答案,还能在回答前先“自我反思”一番,然后把整个思考链条展示给你看。这种“思维链”功能,通常只在那些动辄几百亿参数的大模型里才能看到,现在居然在一个3B的小模型上实现了,这本身就挺让人惊讶的。 我花了一些时间测试这个模型,发现它的效果确实超出了我的预期。它不仅在各种标准测试中表现不错,更重要的是,它的“可视化思考”功能,让我们能真正看到模型是怎么一步步推理出答案的。这对于理解模型的决策过程、排查错误,甚至教学演示,都很有价值。 1. 模型核心能力概览 Cogito-v1-preview-llama-3B虽然参数规模不大,

Qwen-Image-2512极速文生图:新手也能玩转的AI绘画工具

Qwen-Image-2512极速文生图:新手也能玩转的AI绘画工具 Qwen-Image-2512 极速文生图创作室,不是又一个需要调参、等半天、看运气的AI画图工具。它是一台开箱即用的“灵感喷射器”——输入一句话,按下按钮,3秒后高清画面就出现在你眼前。没有模型下载、没有环境报错、没有显存崩溃,连电脑刚装完系统的新手,也能在5分钟内生成第一张属于自己的AI艺术作品。 它背后是阿里通义千问团队打磨的 Qwen/Qwen-Image-2512 模型,但真正让它与众不同的,是那一套为“人”而设计的工程化思维:不堆参数,不炫技术,只做一件事——让中文用户,用最自然的语言,最快拿到最满意的结果。 1. 为什么说这是“新手友好型”文生图工具? 很多AI绘画工具对新手并不友好:要查采样器、调CFG值、选分辨率、试十几遍才能出一张像样的图。Qwen-Image-2512 则反其道而行之——它主动把复杂性藏起来,把确定性交到你手上。 1.1 不用学术语,直接说人话 你不需要知道什么是“Euler

Qwen-Image-2512 V2版 - 细节拉满,更真实的AI绘画体验 ComfyUI+WebUI 一键整合包下载

Qwen-Image-2512 V2版 - 细节拉满,更真实的AI绘画体验 ComfyUI+WebUI 一键整合包下载

Qwen-Image-2512 是 Qwen-Image 文生图基础模型的 12 月更新版本,这是一个最新的文本生成图像模型,特点是 画面更真实、细节更精致,提升了人物与自然细节的真实感,适合在创意设计、教育展示、内容生产等领域使用。 今天分享的 Qwen-Image-2512 V2版 一键包基于阿里最新开源的 Qwen-Image-2512 的FP8量化版(同时支持BF16),支持消费级显卡最低12G显存流畅运行,支持更适合小白操作的WebUI模式和专业选手的ComfyUI两种模式。 相比较上个版本,V2版因使用精度更高的FP8模型,所以在生成效果上更好,同时对硬件的要求也更高,大家根据需要选择适合自己的版本。 下载地址:点此下载   模型特点 更真实的人物表现:相比旧版本,人物的面部细节、表情和环境都更自然,不再有明显的“AI感”。   更精细的自然细节:风景、动物毛发、水流等元素渲染更逼真,层次感更强。   更准确的文字渲染:在生成带文字的图像(如海报、PPT)时,排版和字体更清晰,图文融合更好。   更强的整体性能:

重磅升级| Copilot Studio 一站式智能体平台,助力企业智能体化转型

近期,微软 lgnite 大会重磅宣布 Microsoft Copilot Studio 全面升级,集成智能体构建、自动化与治理,支持多模型和跨平台集成,强化安全与可控性。企业可一站式打造高效、合规,且可扩展的智能体系统,推动流程重塑与创新,加速 AI技术价值在实际业务中的落地转化。 当前,越来越多的企业正加速迈向“智能体化”业务转型。AI 技术不再只是概念,而是通过自动化现有流程、利用智能体提升生产力、重塑业务职能,带来可衡量的实际成效。Microsoft Copilot Studio 作为智能 Microsoft Copilot 副驾驶®的智能体平台,提供了一站式的托管解决方案,助力企业高效落地 AI 技术能力。 Microsoft Copilot Studio 让全球企业能够快速将 AI 成果应用于实际业务。它支持企业通过智能体流程自动化,打造专属的单一场景智能体,解决具体问题;也能开发多智能体协作方案,