GeoJson Viewer (QML)▲
The example displays a map with various MapItems. The MapItems are either imported from a GeoJson file, using the GeoJsonData API of QtLocation or drawn by the user using TapHandlers.
Examples for GeoJson files can be found in the directory data within the example directory.
To draw a MapItem, right click on an empty part of the map and select an item type of your choice in the appearing menu. The next clicks will define the chosen item. The example allows to draw MapCircles, MapRectangles, MapPolygons and MapPolylines. Items that are fully defined by two points, i.e. circles and rectangles, are drawn with two clicks of the left mouse button. Items that are defined by multiple points, i.e. polygons and polylines, are created by an arbitrary amount of left button clicks and completed with the right mouse button. Items drawn this way are saved as points, polygons and polylines to fit the GeoJson specification, see https://geojson.org/.
Running the Example▲
To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.
Creating a MapView▲
First we create a base map on which all items can be placed on. We take advantage of a MapView element that combines a basic Map with input handling (mouse wheel, drag, etc.). The underlying Map can be accessed with map property. If you miss a property in MapView it can be most likely accessed with MapView.map.
MapView {
id
:
view
anchors.fill
:
parent
map.plugin
:
Plugin {
name
:
"osm"
}
map.zoomLevel
:
4
map.center
:
QtPositioning.coordinate(3
, 8
)
}
Setting up the GeoJson Model / Display MapItems▲
In order to display file contents on the map we will use a design pattern known as Model/View Programming. First we need to set up a suitable view, in this example a MapItemView element. Its parent must be set to the underlying map of the MapView to correctly display all items placed in it.
MapItemView {
id
:
miv
parent
:
view.map
}
Next we need a suitable model, representing a GeoJSON document. For this purpose QtLocation offers the GeoJsonData element that can read and write GeoJSON files. It can be easily instantiated
GeoJsonData {
id
:
geoDatabase
sourceUrl
:
":/data/11-full.json"
}
and assigned to the MapItemView.
model
:
geoDatabase.model
The file 11-full.json is loaded on start-up as an example.
Finally we need a delegate, translating the model data into a representation of items, filling the MapItemView.
delegate
:
GeoJsonDelegate {}
The GeoJsonDelegate element is declared in the file GeoJsonDelegate.qml. It is a DelegateChooser element, to take into account the varying properties of different geometry types.
DelegateChooser {
id
:
dc
role
:
"type"
}
The DelegateChooser contains a DelegateChoice for each geometry type that can be found in a GeoJson file. The property role will be matched with DelegateChoice.roleValue to determine the correct delegate.
As an example, a point, described with "type":"Point" in GeoJson, is represented by a MapCircle on the MapItemView:
DelegateChoice {
roleValue
:
"Point"
delegate
:
MapCircle {
property
string
geojsonType
:
"Point"
property
var props
:
modelData.properties
geoShape
:
modelData.data
radius
:
(props &
amp;&
amp; props.radius) ||
20
*
1000
border.width
:
2
border.color
:
hh.hovered ? "magenta"
:
Qt.darker(color
)
opacity
:
dc.defaultOpacity
color
:
(props &
amp;&
amp; props.color) ||
(parent
&
amp;&
amp; parent.props &
amp;&
amp; parent.props.color) ||
dc.defaultColor
}
}
Properties of the MapCircle, such as color or radius are attempted to be read from the GeoJson file that is available in form of the modelData property. However, this is not a strict standard of GeoJson and fallback values are set for all properties.
Writing MapItems to GeoJson▲
To write MapItems to a GeoJson file we can simply call the GeoJsonData::saveAs function with the designated filename. This writes all items in the current model to the designated file. Any other items that should be written to the file have to be added to the model first using the function GeoJsonData::addItem or GeoJsonData::setModelToMapContents.
geoDatabase.saveAs(fileWriteDialog.selectedFile)
User Interaction with MapItems▲
To handle user interactions we will use PointHandlers. They are especially well suited for the task as they conform to the exact shape of the underlying item, in contrast to MouseArea, which always covers a square shape. MapItems that are imported from a GeoJson file get their own HoverHandler and TapHandler directly in the delegate:
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
}
The TapHandler is used to write some information about the item on the console when the item is tapped. The HoverHandler is used to highlight items that lie beneath the mouse pointer. This is implemented by describing the property border.color depending on the property / state hovered of the HoverHandler.
Adding new Items▲
A combination of HoverHandler and TapHandler for the MapView allows us to react to mouse movements and clicks by the user.
If the TapHandler emits a singleTapped signal, we will create or modify a new MapItem on LeftButton and finish the MapItem on RightButton. If there is no item to finish then the RightButton will open a menu.
onSingleTapped
:
(eventPoint, button
) =&
gt; {
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
(
)
}
}
}
}
The pointChanged signal is used to temporarily update a MapItem, giving the user a preview.
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 are generated from prototypes that are defined in separate qml files. They are created using the createComponent function and added to the map with addMapItem. A reference to the new item is stored for further manipulation by the user.
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."
)
}
}
Adding the item to the Map is sufficient to display the item. However, in order to further use the item (e.g. saving it to a file), it has to be added to the model. This is done after editing is finished:
function
finishGeoItem()
{
unfinishedItem.finishAddGeometry()
geoDatabase.addItem(unfinishedItem)
map.removeMapItem(unfinishedItem)
unfinishedItem =
undefined
}
Removing Items▲
To remove all items from the map, we simply call the reset function of the GeoJsonData object
function
clearAllItems()
{
geoDatabase.clear();
}