SkeyeGisMap 中实现自定义形状需要继承自 MapShapeNode, 也可继承任何已有的 Map*Node。
当然, 我们这里为了说明流程, 选择从头实现一个新的形状。
class CustomShape : public QSGNode, public MapShapeNode
注意, 当一个地图节点想要被渲染, 它还需要继承 QSG*Node。
因为是一个新的形状, 所以增加一个新的类型给 MapShapeNode:
static const MapShapeNode::ShapeType CustomShapeType = MapShapeNode::ShapeType::MapUserShape + 1;
现在我们开始实现这个新形状:
CustomShape(QQuickItem *item, MapRootNode *root) : MapShapeNode(CustomShapeType)
{
m_root = root;
m_text = new MapTextNode(item, root, QPointF(200, 200), QFont("微软雅黑", 20, QFont::Bold)
, u8"这是一个自定义形状\n使用QPainterPathStroker\n可以自由移动它\nGood luck~"
, Qt::white, QTextOption(Qt::AlignCenter)
, Qt::yellow, MapTextNode::TextStyle::Outline, Qt::black);
m_text->setLineHeight(40);
m_text->setFlag(OwnedByParent);
QPainterPathStroker stroker;
stroker.setCapStyle(Qt::RoundCap);
stroker.setJoinStyle(Qt::RoundJoin);
stroker.setDashPattern(Qt::DashDotLine);
stroker.setWidth(10);
QPainterPath path;
auto rect = m_text->boundingRect();
rect.setSize(rect.size() / root->scale() + QSizeF(stroker.width(), stroker.width()));
rect.moveCenter(m_text->position());
path.addRect(rect);
QPainterPath outlinePath = stroker.createStroke(path);
auto polygons = outlinePath.toSubpathPolygons().toVector();
for (auto &polygon: polygons) {
if (maputil::judgeOrder(polygon))
std::reverse(polygon.begin(), polygon.end());
}
m_multiPolygon = new MapMultiPolygonNode(polygons, "red", 2, "#8CFFFB");
m_multiPolygon->setFlag(OwnedByParent);
m_text->appendChildNode(m_multiPolygon);
appendChildNode(m_text);
}
首先, 我们创建一个地图文本节点, 参数比较多(当然很多都有默认值)。
接着, 我们的想法是给这个文本加上一些不同的边框。
Qt 中提供了相当丰富的绘制工具, 这里直接使用 QPainterPathStroker 生成了一个虚线边框。
注意, rect.setSize(rect.size() / root->scale() + QSizeF(stroker.width(), stroker.width()));, 因为 MapRootNode 默认可缩放(即跟随地图缩放), 因此需要除去缩放才能算出正确的边框矩形。
接着, 我们将生成的边框转换为多边形, MapMultiPolygonNode 用于绘制多个多边形或多个带孔多边形, 并且需要保证多边形的环绕方向, MapMultiPolygonNode 使用OGC的环绕标准, 即顺时针内环, 逆时针外环。
因为生成的边框多边形为顺时针, 所以还需要翻转方向。
然后添加到子节点即可, 注意, 这里的边框添加到文本的子节点是因为边框同样需要变换正方向, 而直接成为子节点可以直接继承变换矩阵。
最后我们处理事件即可:
void translate(const QPointF &offset)
{
auto polygons = m_multiPolygon->polygons();
for (auto &polygon: polygons) {
polygon.translate(offset);
}
m_multiPolygon->setPolygons(polygons);
m_text->setPosition(m_text->position() + offset);
}
virtual bool contains(const QPointF &position) override
{
return m_text->boundingPolygon().containsPoint(position, Qt::OddEvenFill);
}
virtual void mousePresseEvent(MapMouseEvent *event) override
{
MapShapeNode::mousePresseEvent(event);
m_startPosition = event->displayCoord();
m_text->setBackground(Qt::green);
event->accepted();
}
virtual void mouseMoveEvent(MapMouseEvent *event) override
{
MapShapeNode::mouseMoveEvent(event);
translate(event->displayCoord() - m_startPosition);
m_startPosition = event->displayCoord();
event->accepted();
}
virtual void mouseReleaseEvent(MapMouseEvent *event) override
{
MapShapeNode::mouseReleaseEvent(event);
m_text->setBackground(Qt::yellow);
event->accepted();
}
关于事件处理见之前的文章: https://openskeye.blog.csdn.net/article/details/127517747
这里主要提一下 virtual bool contains(const QPointF &position), 只有返回 true 时才会接收事件, 我们判断鼠标是否进入文本区域即可。

源码地址(dynamictarget): https://gitee.com/visual-opening/skeyegismap/tree/master/coremap/example