KOSMIndoorMap

Main.qml
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7import QtCore
8import QtQuick
9import QtQuick.Layouts
10import QtQuick.Controls as QQC2
11import QtQuick.Dialogs as Dialogs
12import org.kde.kirigami as Kirigami
13import org.kde.kpublictransport as PublicTransport
14import org.kde.kpublictransport.ui as PublicTransport
15import org.kde.kosmindoormap
16import org.kde.kosmindoormap.kpublictransport
17import org.kde.kosmindoorrouting
18import org.kde.osm.editorcontroller
19import org.kde.kirigamiaddons.formcard as FormCard
20
21Kirigami.ApplicationWindow {
22 globalDrawer: Kirigami.GlobalDrawer {
23 title: "Indoor Map"
24 titleIcon: "map-symbolic"
25 isMenu: true
26 actions: [
27 Kirigami.Action {
28 text: "Open O5M File"
29 icon.name: "document-open-symbolic"
30 onTriggered: fileDialog.open()
31 },
32 Kirigami.Action {
33 text: "Open MapCSS Stylesheet"
34 icon.name: "document-open-symbolic"
35 onTriggered: mapcssDialog.open()
36 },
37 Kirigami.Action {
38 text: "Reload Stylesheet"
39 icon.name: "view-refresh-symbolic"
40 shortcut: "F5"
41 onTriggered: {
42 var s = page.map.styleSheet;
43 page.map.styleSheet = "";
44 page.map.styleSheet = s;
45 }
46 },
47 Kirigami.Action { separator: true },
48 Kirigami.Action {
49 text: i18nc("@action", "Data Sources")
50 icon.name: "help-about-symbolic"
51 onTriggered: pageStack.pushDialogLayer(attributionPage, {}, {
52 title: i18nc("@title:window", "Data Sources")
53 })
54 },
55 Kirigami.Action {
56 id: aboutAction
57 text: "About"
58 icon.name: "help-about-symbolic"
59 onTriggered: pageStack.pushDialogLayer(aboutPage, {}, {
60 title: i18nc("@title:window", "About")
61 })
62 }
63 ]
64 }
65 contextDrawer: Kirigami.ContextDrawer {
66 id: contextDrawer
67 }
68
69 Dialogs.FileDialog {
70 id: fileDialog
71 title: "Open OSM File"
72 fileMode: Dialogs.FileDialog.OpenFile
73 nameFilters: ["o5m file (*.o5m)", "OSM XML file (*.osm *.xml)", "PBF file (*.osm.pbf)"]
74 onAccepted: page.map.mapLoader.loadFromFile(fileDialog.selectedFile);
75 }
76 Dialogs.FileDialog {
77 id: mapcssDialog
78 title: "Open MapCSS Stylesheet"
79 fileMode: Dialogs.FileDialog.OpenFile
80 nameFilters: ["MapCSS stylesheet (*.mapcss)"]
81 onAccepted: page.map.styleSheet = mapcssDialog.selectedFile
82 }
83 PublicTransport.Manager { id: ptMgr }
84 Settings {
85 id: settings
86 property alias debugMode: debugAction.checked
87 property alias stylesheet: page.map.styleSheet
88 property alias hoverMode: page.mapHoverEnabled
89 }
90
91 pageStack.initialPage: IndoorMapPage {
92 id: page
93 debug: debugAction.checked
94 routingController: routingController
95
96 actions: [
97 Kirigami.Action {
98 text: "Select Location"
99 icon.name: "search"
100 onTriggered: locationSheet.open()
101 },
102 Kirigami.Action {
103 text: "Light Style"
104 onTriggered: page.map.styleSheet = "breeze-light"
105 },
106 Kirigami.Action {
107 text: "Dark Style"
108 onTriggered: page.map.styleSheet = "breeze-dark"
109 },
110 Kirigami.Action {
111 text: "Diagnostic View"
112 onTriggered: page.map.styleSheet = "diagnostic"
113 },
114 Kirigami.Action {
115 id: debugAction
116 text: "Debug Info Model"
117 checkable: true
118 checked: false
119 },
120 Kirigami.Action {
121 id: platformAction
122 text: "Find Platform"
123 onTriggered: platformSheet.open()
124 visible: !platformModel.isEmpty
125 },
126 Kirigami.Action {
127 id: gateAction
128 text: "Find Gate"
129 onTriggered: gateSheet.open()
130 visible: !gateModel.isEmpty
131 },
132 Kirigami.Action {
133 id: amenityAction
134 text: "Find Amenity"
135 onTriggered: amenitySheet.open()
136 },
137 Kirigami.Action {
138 id: roomAction
139 text: "Find Room"
140 onTriggered: roomSheet.open()
141 },
142 Kirigami.Action {
143 id: equipmentAction
144 text: "Show Elevator Status"
145 checkable: true
146 enabled: !page.map.mapLoader.isLoading
147 onTriggered: page.queryLiveLocationData();
148 },
149 Kirigami.Action {
150 id: rentalVehicleAction
151 text: i18n("Show Rental Vehicles")
152 checkable: true
153 enabled: !page.map.mapLoader.isLoading
154 onTriggered: page.queryLiveLocationData();
155 },
157 text: i18n("Edit with iD")
158 icon.name: "document-edit"
159 onTriggered: EditorController.editBoundingBox(page.map.view.mapSceneToGeo(page.map.view.viewport), Editor.ID)
160 },
162 text: i18n("Edit with JOSM")
163 icon.name: "org.openstreetmap.josm"
164 visible: EditorController.hasEditor(Editor.JOSM)
165 onTriggered: EditorController.editBoundingBox(page.map.view.mapSceneToGeo(page.map.view.viewport), Editor.JOSM)
166 },
168 text: i18n("Edit with Vespucci")
169 icon.name: "document-edit"
170 visible: EditorController.hasEditor(Editor.Vespucci)
171 onTriggered: EditorController.editBoundingBox(page.map.view.mapSceneToGeo(page.map.view.viewport), Editor.Vespucci)
172 },
174 text: i18n("Enable hover selection")
175 icon.name: "followmouse"
176 checkable: true
177 checked: page.mapHoverEnabled
178 onToggled: page.mapHoverEnabled = !page.mapHoverEnabled
179 },
181 text: i18n("Configure routing...")
182 icon.name: "settings-configure"
183 visible: routingController.available
184 onTriggered: {
185 routingSheet.routingProfile = routingController.profile;
186 routingSheet.open();
187 }
188 }
189 ]
190
191 function queryLiveLocationData() {
192 if (rentalVehicleAction.checked || equipmentAction.checked) {
193 locationQuery.request.latitude = map.mapData.center.y;
194 locationQuery.request.longitude = map.mapData.center.x;
195 locationQuery.request.maximumDistance = map.mapData.radius;
196 locationQuery.request.types =
197 (rentalVehicleAction.checked ? (PublicTransport.Location.RentedVehicleStation | PublicTransport.Location.RentedVehicle) : 0)
198 | (equipmentAction.checked ? PublicTransport.Location.Equipment : 0);
199 } else {
200 locationQuery.clear();
201 }
202 routingController.searchRoute();
203 }
204
205 PlatformModel {
206 id: platformModel
207 mapData: page.map.mapData
208 }
209
210 Component {
211 id: platformDelegate
212 QQC2.ItemDelegate {
213 property var platform: model
214 width: ListView.view.width
215 contentItem: Row {
216 QQC2.Label { text: platform.lines.length == 0 ? platform.display : (platform.display + " - "); }
217 Repeater {
218 model: platform.lines
219 delegate: Row {
220 PublicTransport.TransportIcon {
221 id: icon
222 iconHeight: Kirigami.Units.iconSizes.small
223 visible: source != ""
224 source: {
225 switch (platform.mode) {
226 case Platform.Rail:
227 return PublicTransport.LineMetaData.lookup(modelData, platform.coordinate.y, platform.coordinate.x, PublicTransport.Line.Train, true).logo;
228 case Platform.Tram:
229 return PublicTransport.LineMetaData.lookup(modelData, platform.coordinate.y, platform.coordinate.x, PublicTransport.Line.Tramway, true).logo;
230 case Platform.Subway:
231 return PublicTransport.LineMetaData.lookup(modelData, platform.coordinate.y, platform.coordinate.x, PublicTransport.Line.Metro, true).logo;
232 }
233 return "";
234 }
235 }
236 QQC2.Label {
237 text: modelData + " "
238 visible: icon.source == ""
239 }
240 }
241 }
242 }
243 highlighted: false
244 onClicked: {
245 page.map.view.floorLevel = model.level
246 page.map.view.centerOnGeoCoordinate(model.coordinate);
247 page.map.view.setZoomLevel(19, Qt.point(page.map.width / 2.0, page.map.height/ 2.0));
248 platformSheet.close()
249 }
250 }
251 }
252
253 Kirigami.Dialog {
254 id: platformSheet
255
256 title: i18nc("@title", "Find Platform")
257
258 width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
259 height: Math.min(applicationWindow().height, Kirigami.Units.gridUnit * 32)
260
261 contentItem: ListView {
262 model: platformModel
263 clip: true
264 Layout.preferredWidth: Kirigami.Units.gridUnit * 25
265
266 section.property: "mode"
267 section.delegate: Kirigami.ListSectionHeader {
268 label: switch(parseInt(section)) {
269 case Platform.Rail: return "Railway";
270 case Platform.Subway: return "Subway";
271 case Platform.Tram: return "Tramway";
272 case Platform.Bus: return "Bus";
273 default: console.log(section, Platform.Rail); return section;
274 }
275 width: ListView.view.width
276 }
277 section.criteria: ViewSection.FullString
278
279 delegate: platformDelegate
280 }
281 }
282
283 GateModel {
284 id: gateModel
285 mapData: page.map.mapData
286 }
287
288 Kirigami.Dialog {
289 id: gateSheet
290
291 title: i18nc("@title", "Find Gate")
292
293 width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
294 height: Math.min(applicationWindow().height, Kirigami.Units.gridUnit * 32)
295
296 contentItem: ListView {
297 model: gateModel
298 Layout.preferredWidth: Kirigami.Units.gridUnit * 10
299 delegate: QQC2.ItemDelegate {
300 highlighted: false
301 width: ListView.view.width
302 contentItem: Kirigami.TitleSubtitle {
303 title: model.display
304 }
305 onClicked: {
306 page.map.view.floorLevel = model.level
307 page.map.view.centerOnGeoCoordinate(model.coordinate);
308 page.map.view.setZoomLevel(18, Qt.point(page.map.width / 2.0, page.map.height/ 2.0));
309 gateSheet.close();
310 }
311 }
312 }
313 }
314
315 AmenityModel {
316 id: amenityModel
317 mapData: page.map.mapData
318 }
319
320 AmenitySearchDialog {
321 id: amenitySheet
322 amenityModel: amenityModel
323 onAmenitySelected: (amenity) => {
324 page.map.view.floorLevel = amenity.level;
325 page.map.view.setZoomLevel(21, Qt.point(page.map.width / 2.0, page.map.height/ 2.0));
326 page.map.view.centerOnGeoCoordinate(amenity.element.center);
327 console.log(amenity.element.url);
328 }
329 }
330
331 RoomModel {
332 id: roomModel
333 mapData: page.map.mapData
334 }
335
336 RoomSearchDialog {
337 id: roomSheet
338 roomModel: roomModel
339 onRoomSelected: (room) => {
340 page.map.view.floorLevel = room.level;
341 page.map.view.setZoomLevel(21, Qt.point(page.map.width / 2.0, page.map.height/ 2.0));
342 page.map.view.centerOnGeoCoordinate(room.element.center);
343 console.log(room.element.url);
344 }
345 }
346
347 LocationQueryOverlayProxyModel {
348 id: locationModel
349 sourceModel: PublicTransport.LocationQueryModel {
350 id: locationQuery
351 manager: ptMgr
352 }
353 mapData: page.map.mapData
354 }
355
356 RealtimeEquipmentModel {
357 id: equipmentModel
358 mapData: page.map.mapData
359 realtimeModel: locationModel.sourceModel
360 onUpdate: routingController.searchRoute()
361 }
362
363 SelectLocationSheet {
364 id: locationSheet
365 publicTransportManager: ptMgr
366 onCoordinateSelected: function() {
367 page.map.mapLoader.loadForCoordinate(locationSheet.coordinate.y, locationSheet.coordinate.x);
368 page.map.view.beginTime = new Date();
369 page.map.view.endTime = new Date(page.map.view.beginTime.getTime() + 3600000);
370 // TODO timezone
371
372 settings.setValue("latitude", locationSheet.coordinate.y);
373 settings.setValue("longitude", locationSheet.coordinate.x);
374 }
375 }
376
377 RoutingController {
378 id: routingController
379 mapData: page.map.mapData
380 elevatorModel: equipmentModel
381 }
382 QQC2.BusyIndicator {
383 anchors.top: parent.top
384 anchors.right: parent.right
385 running: routingController.inProgress
386 }
387 RoutingProfileSheet {
388 id: routingSheet
389 onApplyRoutingProfile: {
390 routingController.profile = routingSheet.routingProfile;
391 routingController.searchRoute();
392 }
393 }
394
395 map.overlaySources: [ gateModel, platformModel, locationModel, equipmentModel, routingController.routeOverlay ]
396 map.timeZone: "Europe/Berlin"
397
398 header: RowLayout {
399 QQC2.Label { text: "Floor Level:" }
400 QQC2.ComboBox {
401 id: floorLevelCombo
402 model: page.map.floorLevels
403 textRole: "display"
404 currentIndex: page.map.floorLevels.rowForLevel(page.map.view.floorLevel);
405 onCurrentIndexChanged: if (currentIndex >= 0) { page.map.view.floorLevel = page.map.floorLevels.levelForRow(currentIndex); }
406 Layout.fillWidth: true
407 }
408 Connections {
409 target: page.map.view
410 function onFloorLevelChanged() { floorLevelCombo.currentIndex = page.map.floorLevels.rowForLevel(page.map.view.floorLevel); }
411 }
412
413 QQC2.Slider {
414 id: zoomSlider
415 from: 14.0
416 to: 21.0
417 live: true
418 Layout.fillWidth: true
419
420 onValueChanged: {
421 page.map.view.setZoomLevel(value, Qt.point(page.map.width / 2.0, page.map.height/ 2.0));
422 }
423 }
424 Connections {
425 target: page.map.view
426 function onZoomLevelChanged() { zoomSlider.value = page.map.view.zoomLevel; }
427 }
428 }
429
430 coordinate: Qt.point(settings.value("longitude", 11.08196), settings.value("latitude", 49.44572))
431 }
432
433 Connections {
434 target: page.map
435 function onMapDataChanged() {
436 page.queryLiveLocationData();
437 }
438 }
439
440 Component {
441 id: attributionPage
442 AttributionPage {
443 publicTransportManager: ptMgr
444 }
445 }
446
447 Component {
448 id: aboutPage
449 FormCard.AboutPage {
450 aboutData: {
451 "displayName": "KDE OSM Indoor Map",
452 "productName": "org.kde.kosmindoormap",
453 "componentName": "org.kde.kosmindoormap",
454 "shortDescription": "OSM Indoor Map Demo",
455 "homepage": "https://kde.org/",
456 "bugAddress": "submit@bugs.kde.org",
457 "version": Application.version,
458 "licenses": [
459 {
460 "name": "LGPL 2.0 or later",
461 "spdx": "LGPL-2.0-or-later"
462 }
463 ],
464 "copyrightStatement": "© 2020-2024 The KDE Community",
465 "desktopFileName": "org.kde.kosmindoormap",
466 "otherText": ""
467 }
468 }
469 }
470}
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & shortcut(StandardShortcut id)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:54:41 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.