GeoJson Viewer (QML)

GeoJson Viewer 示例演示了如何操作 MapItems,处理用户的输入输出以及与 GeoJson 文件的交互。

示例显示了一个包含各种 MapItems 的地图。MapItems 可以通过使用 QtLocation 的 GeoJsonData API 从 GeoJson 文件导入,或者通过用户使用 TapHandlers 绘制。

GeoJson 文件的示例可以在示例目录中的 data 目录找到。

要绘制 MapItem,在地图的空白部分右键单击并从出现的菜单中选择您所需的项类型。接下来的点击将定义选定的项。示例允许绘制 MapCirclesMapRectanglesMapPolygonsMapPolylines。完全由两个点定义的项,例如圆形和矩形,可以通过鼠标左键两次点击来绘制。定义为多个点的项,例如多边形和折线,可以通过任意数量的鼠标左键点击来创建,并通过鼠标右键完成。以这种方式绘制的项将保存为点、多边形和折线以符合 GeoJson 规范,请参阅 https://geojson.org/

运行示例

要从 Qt Creator 运行示例,打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例

创建 MapView

首先,我们创建一个基础地图,所有项都可以放置在其中。我们利用一个 MapView 元素,它结合了一个基本 Map 和输入处理(鼠标滚轮、拖动等)。可以使用 map 属性访问其下 Map。如果 MapView 中缺少属性,最有可能通过 MapView.map 访问。

MapView {
    id: view
    anchors.fill: parent
    map.plugin: Plugin { name: "osm" }
    map.zoomLevel: 4
    map.center: QtPositioning.coordinate(3, 8)
}

设置 GeoJson 模型 / 显示 MapItems

为了在地图上显示文件内容,我们将使用一种称为 Model/View 编程 的设计模式。首先,我们需要设置一个合适的视图,在这个例子中是一个 MapItemView 元素。其父元素必须设置为 MapView 的底层地图,以正确显示其中放置的所有项。

MapItemView {
    id: miv
    parent: view.map
}

接下来,我们需要一个适当的模型,代表一个 GeoJSON 文档。为此,QtLocation 提供了可以读取和写入 GeoJSON 文件的 GeoJsonData 元素。它可以轻松实例化

GeoJsonData {
    id: geoDatabase
    sourceUrl: ":/data/11-full.json"
}

并将其分配给MapItemView

model: geoDatabase.model

文件11-full.json在启动时被加载作为示例。

最后我们需要一个代表,将模型数据转换为项目表示,填充MapItemView

delegate: GeoJsonDelegate {}

GeoJsonDelegate元素在文件GeoJsonDelegate.qml中声明。它是一个DelegateChooser元素,用于考虑不同几何类型的多样特性。

DelegateChooser {
    id: dc
    role: "type"
}

DelegateChooser包含每个在GeoJson文件中可以找到的几何类型的一个DelegateChoice。属性role将与DelegateChoice.roleValue进行匹配,以确定正确的代理。

例如,GeoJson中以"type":"Point"描述的点,在MapItemView上表示为MapCircle

DelegateChoice {
    roleValue: "Point"
    delegate: MapCircle {
        property string geojsonType: "Point"
        property var props: modelData.properties
        geoShape: modelData.data
        radius: (props && props.radius) || 20*1000
        border.width: 2
        border.color: hh.hovered ? "magenta" : Qt.darker(color)
        opacity: dc.defaultOpacity
        color: (props && props.color) || (parent && parent.props && parent.props.color) || dc.defaultColor
    }
}

MapCircle的属性,如colorradius尝试从模型数据属性中的GeoJson文件中读取。然而,这并不是GeoJson的严格标准,并为所有属性设置了回退值。

将MapItems写入GeoJson

要将MapItems写入GeoJson文件,我们可以使用指定的文件名调用GeoJsonData::saveAs函数。这会将当前模型中的所有项目写入指定的文件。如果需要将其他项目写入文件,必须首先使用函数GeoJsonData::addItemGeoJsonData::setModelToMapContents将它们添加到模型中。

geoDatabase.saveAs(fileWriteDialog.selectedFile)

用户与MapItems的交互

要处理用户交互,我们将使用PointHandlers。它们特别适合此任务,因为它们与底层项的形状完全一致,与MouseArea(它始终覆盖一个方形区域)相反。从GeoJson文件导入的MapItems在其代理中直接获得自己的HoverHandlerTapHandler

TapHandler {
    onTapped: {
        if (props !== undefined)
            console.log(props.name)
        else if (parent.parent.geojsonType == "MultiPoint")
            console.log(parent.parent.props.name)
        else
            console.log("NO NAME!", props)
    }
}
HoverHandler {
    id: hh
}

TapHandler用于在项目被点击时在控制台上写入有关项目的一些信息。该HoverHandler用于突出显示鼠标指针下方的项目。这是通过描述依赖于HoverHandler的hovered属性/状态的属性border.color来实现的。

添加新项目

MapView组合使用HoverHandlerTapHandler允许我们通过用户的鼠标移动和点击来做出反应。

如果TapHandler发出singleTapped信号,我们将在LeftButton上创建或修改一个新的MapItem,并在RightButton上完成MapItem。如果没有要完成的项目,则RightButton将打开一个菜单。

onSingleTapped: (eventPoint, button) => {
    lastCoordinate = view.map.toCoordinate(tapHandler.point.position)
    if (button === Qt.RightButton) {
        if (view.unfinishedItem !== undefined) {
            view.finishGeoItem()
        } else
            mapPopupMenu.show(lastCoordinate)
    } else if (button === Qt.LeftButton) {
        if (view.unfinishedItem !== undefined) {
            if (view.unfinishedItem.addGeometry(view.map.toCoordinate(tapHandler.point.position), false)) {
                view.finishGeoItem()
            }
        }
    }
}

使用pointChanged信号暂时更新MapItem,为用户提供预览。

HoverHandler {
    id: hoverHandler
    property variant currentCoordinate
    grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType

    onPointChanged: {
        currentCoordinate = view.map.toCoordinate(hoverHandler.point.position)
        if (view.unfinishedItem !== undefined)
            view.unfinishedItem.addGeometry(view.map.toCoordinate(hoverHandler.point.position), true)
    }
}

Mapitems由定义在单独的qml文件中的原型生成。它们使用createComponent函数创建,并通过addMapItem方法添加到地图中。然后存储对新项目的引用,以便用户进行进一步操作。

function addGeoItem(item)
{
    var co = Qt.createComponent('mapitems/'+item+'.qml')
    if (co.status === Component.Ready) {
        unfinishedItem = co.createObject(map)
        unfinishedItem.setGeometry(tapHandler.lastCoordinate)
        unfinishedItem.addGeometry(hoverHandler.currentCoordinate, false)
        view.map.addMapItem(unfinishedItem)
    } else {
        console.log(item + " is not supported right now, please call us later.")
    }
}

将项目添加到Map足以显示该项目。但是,为了进一步使用项目(例如,将其保存到文件中),必须将其添加到模型中。这在进行编辑后完成。

function finishGeoItem()
{
    unfinishedItem.finishAddGeometry()
    geoDatabase.addItem(unfinishedItem)
    map.removeMapItem(unfinishedItem)
    unfinishedItem = undefined
}

删除项目

要从地图中删除所有项目,我们只需调用GeoJsonData对象的reset函数。

function clearAllItems()
{
    geoDatabase.clear();
}

代码示例 @ code.qt.io

© 2024 Qt公司。本文档的贡献权归各自的拥有者所有。所提供的文档根据Free Software Foundation发布、根据GNU自由文档许可证第1.3版本条款授权使用。Qt及其相关标志是芬兰及其它国家的Qt公司的商标。所有其他商标均为其各自所有者的财产。