From b50004900d9671f9e9abd0284a25496a5fe69795 Mon Sep 17 00:00:00 2001 From: twi-devel Date: Tue, 13 Oct 2026 11:09:06 +0200 Subject: [PATCH] Copies intermediate migration results to repo --- .../SituationMap/ArcGISQt5Hack/CMakeLists.txt | 15 + .../ArcGISQt5Hack/arcgisqt5hack.cpp | 21 + .../ArcGISQt5Hack/arcgisqt5hack.version | 3 + .../ElevationMap/elevation_map.cpp | 165 + .../SituationMap/ElevationMap/elevation_map.h | 168 + .../ElevationMap/elevation_map_helper.h | 88 + .../ElevationMap/elevation_map_manager.cpp | 531 + .../ElevationMap/elevation_map_manager.h | 84 + .../line_of_sight_calculation.cpp | 229 + .../ElevationMap/line_of_sight_calculation.h | 92 + .../situationmapelevationmap_export.h | 21 + .../SituationMap/EvalApps/CMakeLists.txt | 9 + .../EvalApps/Viewshed3D/CMakeLists.txt | 43 + .../EvalApps/Viewshed3D/LocationViewshed.pro | 58 + .../EvalApps/Viewshed3D/ViewshedLocation.cpp | 274 + .../EvalApps/Viewshed3D/ViewshedLocation.h | 99 + .../SituationMap/EvalApps/Viewshed3D/main.cpp | 244 + .../FunctionDialog/CMakeLists.txt | 46 + .../FunctionDialog/function_dialog.cpp | 8906 +++++++++++++++++ .../FunctionDialog/function_dialog.h | 289 + .../FunctionDialog/function_dialog_export.h | 21 + .../FunctionDialog/simple_esri_layer.cpp | 378 + .../FunctionDialog/simple_esri_layer.h | 79 + .../FunctionDialog/state_icon_a.xpm | 156 + .../FunctionDialog/state_icon_b.xpm | 216 + .../FunctionDialog/state_icon_c.xpm | 110 + .../SituationMap/FunctionDialog/symbole2.csv | 445 + .../SituationMap/GeometryModel/CMakeLists.txt | 42 + .../GeometryModel/geometry_model.cpp | 38 + .../GeometryModel/geometry_model.dtd | 400 + .../GeometryModel/geometry_model.h | 1028 ++ .../GeometryModel/geometry_model_export.h | 21 + .../GeometryModel/geometry_model_factory.cpp | 58 + .../GeometryModel/geometry_model_factory.h | 59 + .../GeometryModel/geometry_model_q_dom.cpp | 2711 +++++ .../GeometryModel/geometry_model_q_dom.h | 374 + .../GeometryModel/geometry_node_visitor.cpp | 75 + .../GeometryModel/geometry_node_visitor.h | 75 + .../GeometryModel/geometry_pixelTextBox.cpp | 216 + .../GeometryModel/geometry_pixelTextBox.h | 69 + .../abstract_graphical_object.cpp | 556 + .../abstract_graphical_object.h | 213 + .../GraphicalObjects/ellipse_parameters.cpp | 96 + .../GraphicalObjects/ellipse_parameters.h | 95 + .../graphical_object_attributes.cpp | 190 + .../graphical_object_attributes.h | 88 + .../graphical_object_cube.cpp | 162 + .../GraphicalObjects/graphical_object_cube.h | 100 + .../graphical_object_ellipse.cpp | 344 + .../graphical_object_ellipse.h | 121 + .../graphical_object_model_scene_marker.cpp | 169 + .../graphical_object_model_scene_marker.h | 111 + .../graphical_object_multi_path.cpp | 323 + .../graphical_object_multi_path.h | 105 + .../graphical_object_path.cpp | 260 + .../GraphicalObjects/graphical_object_path.h | 78 + .../graphical_object_picture_marker.cpp | 572 ++ .../graphical_object_picture_marker.h | 153 + .../graphical_object_range.cpp | 163 + .../GraphicalObjects/graphical_object_range.h | 87 + .../graphical_object_renderer_attributes.cpp | 135 + .../graphical_object_renderer_attributes.h | 102 + .../graphical_object_scene_marker.cpp | 204 + .../graphical_object_scene_marker.h | 137 + .../graphical_object_text.cpp | 340 + .../GraphicalObjects/graphical_object_text.h | 125 + .../GraphicalObjects/stub_graphical_object.h | 69 + .../GraphicalObjects/tactical_appearance.cpp | 622 ++ .../GraphicalObjects/tactical_appearance.h | 117 + .../layer_aggregation_widget.cpp | 603 ++ .../layer_aggregation_widget.h | 110 + .../participant_aggregation_dialog.cpp | 105 + .../participant_aggregation_dialog.h | 79 + .../SituationMap/LocalSrv/CMakeLists.txt | 37 + .../SituationMap/LocalSrv/local_server.cpp | 519 + .../SituationMap/LocalSrv/local_server.h | 128 + .../LocalSrv/local_server_export.h | 21 + .../Viewer/SituationMap/MIGRATION_STATUS.md | 81 + .../abstract_effect_simulation_impl.cpp | 59 + .../MapImpl/abstract_effect_simulation_impl.h | 52 + .../MapImpl/abstract_map_object_impl.cpp | 1206 +++ .../MapImpl/abstract_map_object_impl.h | 443 + .../MapImpl/coord_translation_functor_impl.h | 207 + .../MapImpl/coordinate_translation.cpp | 358 + .../MapImpl/coordinate_translation.h | 191 + .../MapImpl/coordinate_translation_thread.cpp | 82 + .../MapImpl/coordinate_translation_thread.h | 79 + .../MapImpl/geometry_model_renderer_esri.cpp | 1197 +++ .../MapImpl/geometry_model_renderer_esri.h | 95 + .../Viewer/SituationMap/MapImpl/grid_impl.cpp | 220 + .../Viewer/SituationMap/MapImpl/grid_impl.h | 61 + .../MapImpl/jump_point_custom_view.cpp | 76 + .../MapImpl/jump_point_custom_view.h | 48 + .../MapImpl/jump_point_custom_view.ui | 80 + .../SituationMap/MapImpl/jump_point_model.cpp | 166 + .../SituationMap/MapImpl/jump_point_model.h | 105 + .../SituationMap/MapImpl/jump_point_namer.cpp | 41 + .../SituationMap/MapImpl/jump_point_namer.h | 39 + .../SituationMap/MapImpl/jump_point_namer.ui | 46 + .../MapImpl/jump_point_widget.cpp | 445 + .../SituationMap/MapImpl/jump_point_widget.h | 103 + .../MapImpl/layer_control_widget.cpp | 1134 +++ .../MapImpl/layer_control_widget.h | 125 + .../MapImpl/layer_factory_impl.cpp | 3049 ++++++ .../SituationMap/MapImpl/layer_factory_impl.h | 329 + .../MapImpl/location_display_impl.cpp | 245 + .../MapImpl/location_display_impl.h | 61 + .../MapImpl/location_to_elevation_bulk.cpp | 218 + .../MapImpl/location_to_elevation_bulk.h | 212 + .../MapImpl/location_to_elevation_helper.cpp | 198 + .../MapImpl/location_to_elevation_helper.h | 81 + .../SituationMap/MapImpl/logical_layer.cpp | 802 ++ .../SituationMap/MapImpl/logical_layer.h | 190 + .../Viewer/SituationMap/MapImpl/map_impl.cpp | 5811 +++++++++++ .../Viewer/SituationMap/MapImpl/map_impl.h | 519 + .../Viewer/SituationMap/MapImpl/map_layer.cpp | 325 + .../Viewer/SituationMap/MapImpl/map_layer.h | 94 + .../SituationMap/MapImpl/measurement_impl.cpp | 428 + .../SituationMap/MapImpl/measurement_impl.h | 66 + .../MapImpl/object_container_impl.cpp | 134 + .../MapImpl/object_container_impl.h | 105 + .../MapImpl/object_indicator_impl.cpp | 517 + .../MapImpl/object_indicator_impl.h | 114 + .../MapImpl/overlay_handler_impl.cpp | 644 ++ .../MapImpl/overlay_handler_impl.h | 108 + .../MapImpl/overview_map_handler_impl.cpp | 350 + .../MapImpl/overview_map_handler_impl.h | 113 + .../pixel_based_textbox_handler_impl.cpp | 562 ++ .../pixel_based_textbox_handler_impl.h | 221 + .../screen_location_elevation_helper_base.cpp | 144 + .../screen_location_elevation_helper_base.h | 77 + .../MapImpl/screen_to_location_helper.cpp | 122 + .../MapImpl/screen_to_location_helper.h | 71 + .../SituationMap/MapImpl/sidc_symbol_impl.cpp | 226 + .../SituationMap/MapImpl/sidc_symbol_impl.h | 145 + .../SituationMap/MapImpl/sight_fan_impl.cpp | 801 ++ .../SituationMap/MapImpl/sight_fan_impl.h | 184 + .../MapImpl/terrain_profile_impl.cpp | 414 + .../MapImpl/terrain_profile_impl.h | 78 + .../MapImpl/terrain_profile_processor.cpp | 365 + .../MapImpl/terrain_profile_processor.h | 125 + .../Viewer/SituationMap/MapImpl/tms_layer.cpp | 213 + .../Viewer/SituationMap/MapImpl/tms_layer.h | 50 + .../MapImpl/viewshed_analysis.cpp | 120 + .../SituationMap/MapImpl/viewshed_analysis.h | 64 + .../SituationMap/MapImpl/viewshed_base.cpp | 27 + .../SituationMap/MapImpl/viewshed_base.h | 62 + .../MapImpl/viewshed_geo_processing.cpp | 569 ++ .../MapImpl/viewshed_geo_processing.h | 123 + ...shed_geo_processing.h_ConsultingBuchta.txt | 116 + .../SituationMap/MapImpl/viewshed_impl.cpp | 880 ++ .../SituationMap/MapImpl/viewshed_impl.h | 91 + .../MapObjects/_single_fire_central.h | 58 + .../abstract_multi_point_map_object.h | 65 + .../abstract_single_point_map_object.h | 63 + .../SituationMap/MapObjects/air_support.h | 38 + .../SituationMap/MapObjects/area_fire.h | 90 + .../MapObjects/biological_agent.h | 46 + .../MapObjects/building_outline.h | 58 + .../SituationMap/MapObjects/chemical_agent.h | 46 + .../Lts/Viewer/SituationMap/MapObjects/cube.h | 44 + .../SituationMap/MapObjects/danger_area.h | 37 + .../SituationMap/MapObjects/functional_area.h | 48 + .../SituationMap/MapObjects/geo_image.h | 146 + .../SituationMap/MapObjects/geometry.cpp | 75 + .../Viewer/SituationMap/MapObjects/geometry.h | 46 + .../Viewer/SituationMap/MapObjects/hit_line.h | 58 + .../SituationMap/MapObjects/line_of_sight.h | 64 + .../MapObjects/map_object_attributes.h | 35 + .../MapObjects/map_object_indicator.h | 80 + .../Lts/Viewer/SituationMap/MapObjects/mine.h | 53 + .../SituationMap/MapObjects/mine_field.h | 90 + .../SituationMap/MapObjects/nine_liner.h | 112 + .../SituationMap/MapObjects/nuclear_agent.h | 98 + .../SituationMap/MapObjects/participant.h | 279 + .../Viewer/SituationMap/MapObjects/polygon.h | 39 + .../Viewer/SituationMap/MapObjects/polyline.h | 56 + .../SituationMap/MapObjects/single_fire.h | 61 + .../SituationMap/MapObjects/sound_area.h | 123 + .../Lts/Viewer/SituationMap/MapObjects/text.h | 53 + .../SituationMap/MapObjects/virtual_mine.h | 111 + .../MapObjectsImpl/CMakeLists.txt | 110 + .../_single_fire_central_impl.cpp | 415 + .../_single_fire_central_impl.h | 116 + .../abstract_multi_point_map_object_impl.cpp | 287 + .../abstract_multi_point_map_object_impl.h | 83 + .../abstract_single_point_map_object_impl.cpp | 175 + .../abstract_single_point_map_object_impl.h | 82 + .../MapObjectsImpl/air_support_impl.cpp | 587 ++ .../MapObjectsImpl/air_support_impl.h | 119 + .../MapObjectsImpl/area_fire_impl.cpp | 820 ++ .../MapObjectsImpl/area_fire_impl.h | 108 + .../MapObjectsImpl/basic_path_object_impl.cpp | 1112 ++ .../MapObjectsImpl/basic_path_object_impl.h | 295 + .../MapObjectsImpl/biological_agent_impl.cpp | 156 + .../MapObjectsImpl/biological_agent_impl.h | 83 + .../MapObjectsImpl/building_outline_impl.cpp | 269 + .../MapObjectsImpl/building_outline_impl.h | 94 + .../MapObjectsImpl/chemical_agent_impl.cpp | 129 + .../MapObjectsImpl/chemical_agent_impl.h | 78 + .../SituationMap/MapObjectsImpl/cube_impl.cpp | 166 + .../SituationMap/MapObjectsImpl/cube_impl.h | 76 + .../MapObjectsImpl/danger_area_impl.cpp | 105 + .../MapObjectsImpl/danger_area_impl.h | 68 + .../MapObjectsImpl/functional_area_impl.cpp | 179 + .../MapObjectsImpl/functional_area_impl.h | 97 + .../MapObjectsImpl/geo_image_impl.cpp | 371 + .../MapObjectsImpl/geo_image_impl.h | 101 + .../MapObjectsImpl/hit_line_impl.cpp | 177 + .../MapObjectsImpl/hit_line_impl.h | 82 + .../MapObjectsImpl/indication_container.cpp | 575 ++ .../MapObjectsImpl/indication_container.h | 115 + .../MapObjectsImpl/line_of_sight_impl.cpp | 205 + .../MapObjectsImpl/line_of_sight_impl.h | 82 + .../map_object_attributes_impl.cpp | 111 + .../map_object_attributes_impl.h | 65 + .../map_object_indicator_impl.cpp | 289 + .../map_object_indicator_impl.h | 101 + .../MapObjectsImpl/mine_field_impl.cpp | 381 + .../MapObjectsImpl/mine_field_impl.h | 110 + .../SituationMap/MapObjectsImpl/mine_impl.cpp | 241 + .../SituationMap/MapObjectsImpl/mine_impl.h | 77 + .../MapObjectsImpl/nine_liner_impl.cpp | 388 + .../MapObjectsImpl/nine_liner_impl.h | 108 + .../MapObjectsImpl/nuclear_agent_impl.cpp | 428 + .../MapObjectsImpl/nuclear_agent_impl.h | 106 + .../MapObjectsImpl/participant_impl.cpp | 1237 +++ .../MapObjectsImpl/participant_impl.h | 293 + .../MapObjectsImpl/path_container.cpp | 370 + .../MapObjectsImpl/path_container.h | 110 + .../MapObjectsImpl/polygon_impl.cpp | 75 + .../MapObjectsImpl/polygon_impl.h | 69 + .../MapObjectsImpl/polyline_impl.cpp | 115 + .../MapObjectsImpl/polyline_impl.h | 81 + .../MapObjectsImpl/position_marker.xpm | 22 + .../MapObjectsImpl/single_fire_impl.cpp | 323 + .../MapObjectsImpl/single_fire_impl.h | 106 + .../situation_map_objects_export.h | 21 + .../MapObjectsImpl/sound_area_impl.cpp | 193 + .../MapObjectsImpl/sound_area_impl.h | 89 + .../SituationMap/MapObjectsImpl/text_impl.cpp | 149 + .../SituationMap/MapObjectsImpl/text_impl.h | 76 + .../MapObjectsImpl/virtual_mine_impl.cpp | 494 + .../MapObjectsImpl/virtual_mine_impl.h | 102 + .../Viewer/SituationMap/MathUtils/clipper.cpp | 4691 +++++++++ .../Viewer/SituationMap/MathUtils/clipper.hpp | 411 + .../Viewer/SituationMap/MathUtils/curve.cpp | 736 ++ .../Lts/Viewer/SituationMap/MathUtils/curve.h | 288 + .../MathUtils/geometry_engine.cpp | 409 + .../SituationMap/MathUtils/geometry_engine.h | 86 + .../SituationMap/MathUtils/math_const.h | 47 + .../SituationMap/MathUtils/matrix2d.cpp | 114 + .../Viewer/SituationMap/MathUtils/matrix2d.h | 413 + .../SituationMap/MathUtils/path_rasterer.cpp | 99 + .../SituationMap/MathUtils/path_rasterer.h | 94 + .../SituationMap/MathUtils/simple_helper.cpp | 207 + .../SituationMap/MathUtils/simple_helper.h | 130 + .../SituationMap/ModuleTests/CMakeLists.txt | 49 + .../log4cxxSituationMapModuleTests.props | 159 + .../SituationMap/ModuleTests/sit_map.cpp | 347 + .../Viewer/SituationMap/ModuleTests/sit_map.h | 58 + .../ModuleTests/situation_map_test_main.cpp | 3518 +++++++ .../ModuleTests/situation_map_test_main.h | 118 + .../Viewer/SituationMap/Portal/CMakeLists.txt | 34 + .../Lts/Viewer/SituationMap/Portal/portal.cpp | 351 + .../Lts/Viewer/SituationMap/Portal/portal.h | 80 + .../SituationMap/Portal/portal_export.h | 21 + .../Viewer/SituationMap/Resources/compass.png | Bin 0 -> 2413 bytes .../Viewer/SituationMap/Resources/compass.svg | 81 + .../SituationMap/Resources/preview_aerial.png | Bin 0 -> 260269 bytes .../Resources/preview_topographic.png | Bin 0 -> 179683 bytes .../Resources/pushPinRedClipArt.png | Bin 0 -> 17793 bytes .../SituationMap/Resources/situation_map.qrc | 7 + .../SituationMap/Rubberbands/README.txt | 34 + .../Viewer/SituationMap/Rubberbands/abort.xpm | 149 + .../Rubberbands/abstract_event_dispatcher.cpp | 291 + .../Rubberbands/abstract_event_dispatcher.h | 234 + .../Viewer/SituationMap/Rubberbands/apply.xpm | 110 + .../Rubberbands/constrainsMarker.xpm | 200 + .../Rubberbands/drawable_ellipse.cpp | 127 + .../Rubberbands/drawable_ellipse.h | 72 + .../Rubberbands/drawable_line.cpp | 109 + .../SituationMap/Rubberbands/drawable_line.h | 68 + .../Rubberbands/drawable_painter_path.cpp | 152 + .../Rubberbands/drawable_painter_path.h | 75 + .../Rubberbands/drawable_smooth_path.cpp | 127 + .../Rubberbands/drawable_smooth_path.h | 77 + .../Rubberbands/event_delegater.cpp | 72 + .../Rubberbands/event_delegater.h | 72 + .../Rubberbands/event_dispatcher.cpp | 626 ++ .../Rubberbands/event_dispatcher.h | 140 + .../Rubberbands/event_mouse_navigator.cpp | 643 ++ .../Rubberbands/event_mouse_navigator.h | 224 + .../SituationMap/Rubberbands/gestures.cpp | 106 + .../SituationMap/Rubberbands/gestures.h | 72 + .../SituationMap/Rubberbands/polygon.xpm | 52 + .../SituationMap/Rubberbands/rubberband.cpp | 309 + .../SituationMap/Rubberbands/rubberband.h | 319 + .../Rubberbands/rubberband_3d.cpp | 116 + .../SituationMap/Rubberbands/rubberband_3d.h | 214 + .../Rubberbands/rubberband_arrow.cpp | 359 + .../Rubberbands/rubberband_arrow.h | 130 + .../Rubberbands/rubberband_base.cpp | 654 ++ .../Rubberbands/rubberband_base.h | 500 + .../Rubberbands/rubberband_base_internal.h | 118 + .../Rubberbands/rubberband_corridor.cpp | 991 ++ .../Rubberbands/rubberband_corridor.h | 239 + .../Rubberbands/rubberband_drawable.cpp | 155 + .../Rubberbands/rubberband_drawable.h | 214 + .../Rubberbands/rubberband_ellipse.cpp | 455 + .../Rubberbands/rubberband_ellipse.h | 95 + .../Rubberbands/rubberband_ellipse_handle.cpp | 236 + .../Rubberbands/rubberband_ellipse_handle.h | 96 + .../Rubberbands/rubberband_factory.cpp | 634 ++ .../Rubberbands/rubberband_factory.h | 290 + .../Rubberbands/rubberband_frame_handle.cpp | 314 + .../Rubberbands/rubberband_frame_handle.h | 153 + .../Rubberbands/rubberband_free_hand.cpp | 267 + .../Rubberbands/rubberband_free_hand.h | 102 + .../Rubberbands/rubberband_handle.cpp | 70 + .../Rubberbands/rubberband_handle.h | 100 + .../Rubberbands/rubberband_handle_proxy.cpp | 167 + .../Rubberbands/rubberband_handle_proxy.h | 104 + .../Rubberbands/rubberband_internal.cpp | 98 + .../Rubberbands/rubberband_internal.h | 98 + .../Rubberbands/rubberband_line_handle.cpp | 231 + .../Rubberbands/rubberband_line_handle.h | 81 + .../rubberband_multi_ellipse_feature.cpp | 1747 ++++ .../rubberband_multi_ellipse_feature.h | 254 + .../rubberband_multi_point_feature.cpp | 1137 +++ .../rubberband_multi_point_feature.h | 209 + .../rubberband_multi_point_feature_3d.cpp | 660 ++ .../rubberband_multi_point_feature_3d.h | 128 + .../Rubberbands/rubberband_point_cluster.cpp | 133 + .../Rubberbands/rubberband_point_cluster.h | 102 + .../Rubberbands/rubberband_point_feature.cpp | 311 + .../Rubberbands/rubberband_point_feature.h | 99 + .../rubberband_point_feature_3d.cpp | 272 + .../Rubberbands/rubberband_point_feature_3d.h | 104 + .../Rubberbands/rubberband_point_handle.cpp | 390 + .../Rubberbands/rubberband_point_handle.h | 116 + .../Rubberbands/rubberband_polyline_width.cpp | 318 + .../Rubberbands/rubberband_polyline_width.h | 88 + .../Rubberbands/rubberband_rectangle.cpp | 1913 ++++ .../Rubberbands/rubberband_rectangle.h | 274 + .../Rubberbands/rubberband_smooth_line.cpp | 93 + .../Rubberbands/rubberband_smooth_line.h | 72 + .../Rubberbands/rubberband_state_select.cpp | 572 ++ .../Rubberbands/rubberband_state_select.h | 180 + .../Rubberbands/rubberband_transform.cpp | 92 + .../Rubberbands/rubberband_transform.h | 99 + .../Rubberbands/rubberband_transform_move.cpp | 48 + .../Rubberbands/rubberband_transform_move.h | 67 + .../rubberband_transform_rotate.cpp | 62 + .../Rubberbands/rubberband_transform_rotate.h | 77 + .../rubberband_transform_scale.cpp | 47 + .../Rubberbands/rubberband_transform_scale.h | 64 + .../Rubberbands/sphere_pattern.xpm | 32 + .../Rubberbands/transient_tactical_symbol.cpp | 88 + .../Rubberbands/transient_tactical_symbol.h | 79 + .../Viewer/SituationMap/Rubberbands/undo.xpm | 134 + .../SituationMap/Rubberbands/winkel_icon.xpm | 84 + .../SituationMap/SideBar/CMakeLists.txt | 44 + .../Viewer/SituationMap/SideBar/side_bar.cpp | 416 + .../Viewer/SituationMap/SideBar/side_bar.h | 102 + .../SituationMap/SideBar/side_bar_export.h | 21 + .../SituationMap/SideBar/side_bar_item.cpp | 42 + .../SituationMap/SideBar/side_bar_item.h | 51 + .../SituationMap/SideBar/side_bar_model.cpp | 123 + .../SituationMap/SideBar/side_bar_model.h | 67 + .../SituationMap/SymbologyGui/CMakeLists.txt | 46 + .../SymbologyGui/base_symbol_filter_proxy.cpp | 339 + .../SymbologyGui/base_symbol_filter_proxy.h | 142 + .../base_symbol_lookup_dialog.cpp | 210 + .../SymbologyGui/base_symbol_lookup_dialog.h | 76 + .../base_symbol_lookup_widget.cpp | 481 + .../SymbologyGui/base_symbol_lookup_widget.h | 104 + .../SymbologyGui/base_symbol_lookup_widget.ui | 33 + .../SymbologyGui/field_table_model.cpp | 126 + .../SymbologyGui/field_table_model.h | 47 + .../symbol_modification_dialog.cpp | 137 + .../SymbologyGui/symbol_modification_dialog.h | 86 + .../symbol_modification_widget.cpp | 1298 +++ .../SymbologyGui/symbol_modification_widget.h | 135 + .../Viewer/SituationMap/Tests/CMakeLists.txt | 14 + .../Tests/CrashDictSymbol/CrashDictSymbol.cpp | 391 + .../Tests/CrashDictSymbol/CrashDictSymbol.pro | 47 + .../Tests/EsriMapTouchTest/CMakeLists.txt | 23 + .../abstract_event_dispatcher.cpp | 311 + .../abstract_event_dispatcher.h | 232 + .../event_mouse_navigator.cpp | 495 + .../EsriMapTouchTest/event_mouse_navigator.h | 154 + .../Tests/EsriMapTouchTest/gestures.cpp | 102 + .../Tests/EsriMapTouchTest/gestures.h | 71 + .../Tests/EsriMapTouchTest/main.cpp | 83 + .../GraphicsViewTouchTest/CMakeLists.txt | 20 + .../Tests/GraphicsViewTouchTest/main.cpp | 86 + .../LineOfSightEvaluation/CMakeLists.txt | 37 + .../lineOfSightEvaluation.cpp | 330 + .../QGraphicsItem_FillBug.cpp | 267 + .../QGraphicsItem_FillBug.pro | 47 + .../Tests/RdeExample_Viewshed/CMakeLists.txt | 32 + .../Tests/RdeExample_Viewshed/Viewshed.cpp | 556 + .../Tests/RdeExample_Viewshed/Viewshed.h | 87 + .../Tests/RdeExample_Viewshed/main.cpp | 92 + .../Tests/SitMapTestApp/CMakeLists.txt | 69 + .../SitMapTestApp/log4cxx_sitMapTestApp.props | 165 + .../SituationMap/Tests/SitMapTestApp/main.cpp | 67 + .../Tests/SitMapTestApp/mainwindow.cpp | 2702 +++++ .../Tests/SitMapTestApp/mainwindow.h | 201 + .../Tests/SitMapTestApp/mapsettings.ini | 241 + .../Tests/SitMapTestApp/nglsettings.cpp | 222 + .../Tests/SitMapTestApp/nglsettings.h | 63 + .../Tests/SitMapTestApp/ui/mainwindow.ui | 594 ++ .../Tests/SituationMap/CMakeLists.txt | 53 + .../SituationMap/log4cxx_sitmapMain.props | 175 + .../Tests/SituationMap/sit_map.cpp | 688 ++ .../SituationMap/Tests/SituationMap/sit_map.h | 50 + .../Tests/SituationMap/sit_map_main.cpp | 119 + .../Tests/exampleSvgRenderer/CMakeLists.txt | 31 + .../Tests/exampleSvgRenderer/exampleMain.cpp | 108 + .../SituationMap/UnitTests/CMakeLists.txt | 52 + .../log4cxxSituationMapUnitTest.props | 167 + .../SituationMap/UnitTests/unit_test.cpp | 3637 +++++++ .../Viewer/SituationMap/UnitTests/unit_test.h | 142 + .../UnitTests/virtual_inheritances.cpp | 252 + .../UnitTestsGeoModel/CMakeLists.txt | 47 + .../log4cxxGeometryModelUnitTest.props | 129 + .../UnitTestsGeoModel/unit_test.cpp | 807 ++ .../UnitTestsGeoModel/unit_test.h | 66 + .../SituationMap/Utils/mouse_move_overlay.cpp | 102 + .../SituationMap/Utils/mouse_move_overlay.h | 57 + .../SituationMap/Utils/movement_track.cpp | 268 + .../SituationMap/Utils/movement_track.h | 85 + .../Utils/movement_track_manager.cpp | 338 + .../Utils/movement_track_manager.h | 114 + .../Utils/terrain_profile_dialog.cpp | 75 + .../Utils/terrain_profile_dialog.h | 61 + .../Utils/terrain_profile_dialog.ui | 111 + .../SituationMap/admin_scripts/CMakeLists.txt | 11 + .../admin_scripts/ctc_check_situation_map.sh | 83 + .../admin_scripts/ctc_find_getlogger.sh | 29 + .../ctc_operator_app_removal_tool.sh | 18 + .../SituationMap/situation_map_export.h | 34 + 444 files changed, 122986 insertions(+) create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.version create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_helper.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.h create mode 100644 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/situationmapelevationmap_export.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/LocationViewshed.pro create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/main.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog_export.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_a.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_b.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_c.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/symbole2.csv create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.dtd create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_export.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/stub_graphical_object.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server_export.h create mode 100644 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MIGRATION_STATUS.md create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coord_translation_functor_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.ui create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.ui create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h_ConsultingBuchta.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/_single_fire_central.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_multi_point_map_object.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_single_point_map_object.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/air_support.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/area_fire.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/biological_agent.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/building_outline.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/chemical_agent.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/cube.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/danger_area.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/functional_area.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geo_image.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/hit_line.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/line_of_sight.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_attributes.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_indicator.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine_field.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nine_liner.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nuclear_agent.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/participant.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polygon.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polyline.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/single_fire.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/sound_area.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/text.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/virtual_mine.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/position_marker.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/situation_map_objects_export.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.hpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/math_const.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/log4cxxSituationMapModuleTests.props create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal_export.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/compass.png create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/compass.svg create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/preview_aerial.png create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/preview_topographic.png create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/pushPinRedClipArt.png create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/situation_map.qrc create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/README.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abort.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/apply.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/constrainsMarker.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/polygon.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base_internal.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/sphere_pattern.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/undo.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/winkel_icon.xpm create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_export.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.ui create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.pro create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/main.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/main.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/lineOfSightEvaluation.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.pro create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/main.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/log4cxx_sitMapTestApp.props create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/main.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mapsettings.ini create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/ui/mainwindow.ui create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/log4cxx_sitmapMain.props create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map_main.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/exampleMain.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/log4cxxSituationMapUnitTest.props create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/virtual_inheritances.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/log4cxxGeometryModelUnitTest.props create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.cpp create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.h create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.ui create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/CMakeLists.txt create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_check_situation_map.sh create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_find_getlogger.sh create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_operator_app_removal_tool.sh create mode 100755 qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/situation_map_export.h diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/CMakeLists.txt new file mode 100755 index 000000000..d856f41bb --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# @RDE_HEADER@ +# Small hack to provide the missing smybol to 'typeinfo for std::bad_array_new_length@Qt_5' +# $Id$ +# $HeadURL$ +# $Author$ +# + +IF(UNIX) + NGL_ADD_LIBRARY(WS SV AAR ArcGISQt5Hack SHARED arcgisqt5hack.cpp arcgisqt5hack.version) # SV,AAR for CES - expand polygon + NGL_TARGET_LINK_LIBRARIES(ArcGISQt5Hack PRIVATE "-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/arcgisqt5hack.version") +ELSE() + NGL_ADD_LIBRARY(WS SV AAR ArcGISQt5Hack STATIC arcgisqt5hack.cpp) # SV,AAR for CES - expand polygon +ENDIF() +NGL_INSTALL_TARGET(ArcGISQt5Hack) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.cpp new file mode 100755 index 000000000..5d0c7516e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.cpp @@ -0,0 +1,21 @@ +/** + * @RDE_HEADER@ + * Small hack to provide the missing smybol to 'typeinfo for std::bad_array_new_length@Qt_5' + * $Id$ + * $HeadURL$ + * $Author$ + */ +#ifdef WIN32 +__declspec(dllexport) int foo42(int) { return 42;} // just a dummy to get windows build running +#else +#include + +extern "C++" +{ +__attribute__((visibility("default"))) void operator delete(void* ptr, std::size_t) noexcept +{ + ::operator delete(ptr); +} +} + +#endif diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.version b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.version new file mode 100755 index 000000000..069dfeeca --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ArcGISQt5Hack/arcgisqt5hack.version @@ -0,0 +1,3 @@ +Qt_5 { + *; +}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.cpp new file mode 100755 index 000000000..be3be3aed --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.cpp @@ -0,0 +1,165 @@ +// Stub implementation without GDAL dependency for Qt6 migration +#include "elevation_map.h" +#include "elevation_map_helper.h" + +#include +#include +#include +#include +#include + +#include + +using namespace SituationMap; + +class ElevationMap::Internal +{ +public: + Internal() = default; + ~Internal() = default; +}; + +ElevationMap::ElevationMap(const QFileInfo &fileInfo) + : m_internal(new Internal()) + , m_aproximation(true) + , m_valid(false) + , m_logger(getLogger("ElevationMap")) +{ + m_fileName = fileInfo.absoluteFilePath(); + + INFO_LOG(m_logger) << " ctor ElevationMap: map of file '" << m_fileName << "'" + << ", this=" << this; + + if (!fileInfo.exists()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << m_fileName << "\' doesn't exists."; + return; + } + + if (!fileInfo.isReadable()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << m_fileName << "\' isn't readable."; + return; + } + + auto cb = [this]() + { + return readFromFile(); + }; + auto cbFinished = [this]() + { + NOTICE_LOG(m_logger) << " Finished reading elevation map of file '" << m_fileName << "'" + << ", this=" << this; + }; + QObject::connect(&m_gdalReadWatcher, &QFutureWatcher::finished, + &m_gdalReadWatcher, cbFinished); + m_gdalReadWatcher.setFuture(QtConcurrent::run(cb)); +} + +ElevationMap::~ElevationMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_gdalReadWatcher.cancel(); + m_gdalReadWatcher.waitForFinished(); + + delete m_internal; + m_internal = nullptr; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool ElevationMap::waitForInitialization() +{ + m_gdalReadWatcher.waitForFinished(); + return m_valid; +} + +bool ElevationMap::isValid() const +{ + return m_valid; +} + +QString ElevationMap::filename() const +{ + return m_fileName; +} + +QRectF ElevationMap::extent() const +{ + return QRectF(); +} + +QPair ElevationMap::elexelSize() const +{ + return qMakePair(0.0, 0.0); +} + +double ElevationMap::elevation(double lon, double lat) const +{ + return 0.0; +} + +double ElevationMap::distance(const QPointF &start, const QPointF &end) const +{ + return 0.0; +} + +double ElevationMap::profileDistance(const QPointF &start, const QPointF &end) const +{ + return 0.0; +} + +unsigned int ElevationMap::elevationProfile(const QPointF &start, const QPointF &end, Vec3dVector &elevProfile) const +{ + elevProfile.clear(); + return 0; +} + +bool ElevationMap::contains(const QPointF &point) const +{ + return false; +} + +bool ElevationMap::intersects(const QPolygonF &polygon) const +{ + return false; +} + +void ElevationMap::LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile) +{ + losProfile.clear(); +} + +int ElevationMap::getElevation(int column, int row) const +{ + return 0; +} + +double ElevationMap::getElevation(double lon, double lat) const +{ + return 0.0; +} + +double ElevationMap::getOrSubstituteElevation(int column, int row, double substitute) const +{ + return substitute; +} + +void ElevationMap::getSamplePoints(const QPointF &start, const QPointF &end, Vec3dVector &sPnts) const +{ + sPnts.clear(); +} + +int ElevationMap::clipp2Map(const QPointF &start, const QPointF &end, QPointF &cStart, QPointF &cEnd) const +{ + cStart = start; + cEnd = end; + return 0; +} + +bool ElevationMap::readFromFile() +{ + // Stub implementation - GDAL removed for Qt6 migration + ERROR_LOG(m_logger) << LOGFUNCTION << " GDAL support not available in Qt6 build"; + m_valid = false; + return false; +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.h new file mode 100755 index 000000000..fe3212be3 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map.h @@ -0,0 +1,168 @@ +/** + * @RDE_HEADER@ + * adapting GeISiR source to SituationMap + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "elevation_map_helper.h" +#include "situationmapelevationmap_export.h" + +#include + +#include + +#include +#include +#include +#include + +class QFileInfo; +namespace SituationMap +{ + + +//------------------------------------------------------------------------------------------------------------ +// ElevationMap +//------------------------------------------------------------------------------------------------------------ + +class SITUATIONMAPELEVATIONMAP_EXPORT ElevationMap final +{ + + friend class ElevationMapManager; + +public: + ElevationMap() = delete; + ElevationMap(const ElevationMap &) = delete; + ElevationMap &operator = (const ElevationMap &) = delete; + + explicit ElevationMap(const QFileInfo &fileInfo); + + ~ElevationMap(); + + bool waitForInitialization(); + + bool isValid() const; + + QString filename() const; + + QRectF extent() const; + + QPair elexelSize() const; + + double elevation(double lon, double lat) const; // rad + + double distance(const QPointF &start, const QPointF &end) const; + + double profileDistance(const QPointF &start, const QPointF &end) const; + + unsigned int elevationProfile(const QPointF &start, const QPointF &end, Vec3dVector &elevProfile) const; + + void interpolation(bool on) { m_aproximation = on; } + bool interpolation() const { return m_aproximation; } + + bool contains(const QPointF &point) const; + bool intersects(const QPolygonF &polygon) const; + + // Line of Sight Profile: berücksichtigt Erdkrümmung und WGS84 Erdellipsoid + void LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile); + +protected: + + int getElevation(int column, int row) const; + double getElevation(double lon, double lat) const; + + double getOrSubstituteElevation(int column, int row, double substitute) const; + + // getSamplePoints rastert den Vector( start, end) in Lat/Lon entsprechend dem Höhenraster + void getSamplePoints(const QPointF &start, const QPointF &end, Vec3dVector &sPnts) const; + + int clipp2Map(const QPointF &start, const QPointF &end, QPointF &cStart, QPointF &cEnd) const; + + bool readFromFile(); + + struct Internal; + + Internal *m_internal; + QString m_fileName; + bool m_aproximation; + bool m_valid; + QFutureWatcher m_gdalReadWatcher; + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; // ElevationMap + + +} // SituationMap + + + +/* ----------------------------------------------------------------------------------------------- +** CVS-Id/Release ...: $Header$ +** ----------------------------------------------------------------------------------------------- +** +** Historie: +** +** $Log: not supported by cvs2svn $ +** Revision 1.20 2006/09/26 07:51:12 Jörg +** Syntax Fehler unter Suse Linux 10.1 behoben. +** +** Revision 1.19 2006/03/24 09:05:24 klaus +** Elevationprofile: Begrenzung auf Kartengröße aufgehoben ( Clipping) +** +** Revision 1.18 2005/11/09 11:12:27 klaus +** Anpassung an Linux: Kommentar hinter #endif mit "//" versehen +** +** Revision 1.17 2005/08/22 14:15:02 joerg +** typeId() +** +** Revision 1.16 2005/08/12 09:37:44 joerg +** Koordinaten-Umprojezieren bei Wechsel der Projektion +** +** Revision 1.15 2005/04/15 13:04:35 joerg +** Resource Control +** +** Revision 1.14 2005/03/04 14:29:13 klaus +** Elevationmap mit CachedTileList versehen und automatischem Entladen von Tiles +** +** Revision 1.13 2005/03/03 12:37:14 klaus +** dynamischen Laden und Entladen der ElevationTiles für Reliefmap +** +** Revision 1.12 2005/03/01 12:33:12 klaus +** Reliefmap eingeführt, aus Elevationmap entfernt +** +** Revision 1.11 2005/02/25 15:26:41 klaus +** Ergänzung um Reliefkarte +** +** Revision 1.10 2005/02/10 14:13:21 klaus +** Bias in Basisklasse MapObject überführt +** +** Revision 1.9 2005/02/10 13:51:01 klaus +** Bias für SightFans eingeführt +** +** Revision 1.8 2005/02/09 16:53:06 klaus +** 1. Version der SightFans +** +** Revision 1.7 2005/01/26 12:25:41 klaus +** todos bearbeitet, Fehlerrückmeldnugen +** +** Revision 1.6 2004/12/14 09:43:25 klaus +** 7. Erweiterung für Höhenkarten +** +** Revision 1.5 2004/12/07 11:07:20 klaus +** 5. Erweiterung für Höhenkarten +** +** Revision 1.4 2004/12/03 16:24:12 klaus +** 4. Erweiterung für Höhenkarten +** +** Revision 1.3 2004/11/30 13:15:28 klaus +** 3. Erweiterung für Höhenkarten +** +** Revision 1.2 2004/11/23 14:36:14 klaus +** 2. Erweiterung für Höhenkarten +** +** +** ----------------------------------------------------------------------------------------------- */ + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_helper.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_helper.h new file mode 100755 index 000000000..75655f15b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_helper.h @@ -0,0 +1,88 @@ +/** + * @RDE_HEADER@ + * Some functions and struct for ElevationMap/LineOfSight (LOS) + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include + +#include +#include + + +#include +#include + +namespace SituationMap +{ + +inline bool isInIntervall(double x, double x1, double x2) +{ + return (((x > x1) && (x < x2)) || (((x > x2) && (x < x1)))); +} // inline bool isInIntervall(double x, double x1, double x2) + + +inline double radiusWGS84(double lat) +{ + double sinLAT = sin(lat); + return (MathConst::WGS84_Major_a * sqrt(1.0 - MathConst::WGS84_SQR_EXC * sinLAT * sinLAT)); +} // inline double radiusWGS84(double lat) + + +inline QPointF distance_in_meter_WGS84(const QPointF &start, const QPointF &end) // start, end in LonLat +{ + // todo: prüfen ob Distanz zu groß, dann verbesserte Berechnung !! + double lat = (end.y() + start.y()) / 2.0; + double faktorLon = MathConst::WGS84_Major_a * cos(lat); // radiusWGS84(lat) * 2.0 * PI; + double faktorLat = MathConst::WGS84_Minor_b; + + return QPointF(faktorLon * (end.x() - start.x()), faktorLat * (end.y() - start.y())); + +} // QPointF distance_in_meter_WGS84(const QPointF &start, const QPointF &end) + + +inline double squareLength(const QPointF &v) +{ + return v.x() * v.x() + v.y() * v.y(); +} // inline double squareLength(const QPoint & v) + + +inline double length(const QPointF &v) +{ + return sqrt(squareLength(v)); +} // inline double length(const QPoint & v) + + +struct LOSentry // Defines 1 Entry for Line of Sight +{ + LOSentry() = default; + LOSentry(double dist, double height) : _dist(dist), _height(height) { ; } + + double &dist() { return _dist; } + double &height() { return _height; } + + double dist() const { return _dist; } + double height() const { return _height; } + + bool isUndefined() const { return std::isnan(_height); } + +private: + + double _dist = 0; // Distance from Start + double _height = -NAN; + +}; /* LOSentry */ + + +using Point3D = LtsUtils::QPoint3D; ///< x = lon ; y = lat , z = elevation/altitude in meters +using LOSVector = QVector; +using Vec2dVector = QVector; +using Vec3dVector = QVector; + + +} // SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.cpp new file mode 100755 index 000000000..0394f5c1e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.cpp @@ -0,0 +1,531 @@ +/** + * @RDE_HEADER@ + * Manage and combine several ElevationMap + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +#include "elevation_map_manager.h" +#include "elevation_map.h" + +// Qt +#include +#include +#include +#include + +// system +#include + +namespace SituationMap +{ + +static long s_counter{0}; + +//------------------------------------------------------------------------------------------------------------ +// ElevationMapManager::Private +//------------------------------------------------------------------------------------------------------------ + +class ElevationMapManager::Private +{ +public: + // fine to rough + std::vector m_elevationMapCont; + QRectF m_simpleExtent; +}; + + +//------------------------------------------------------------------------------------------------------------ +// ElevationMapManager +//------------------------------------------------------------------------------------------------------------ +ElevationMapManager::ElevationMapManager() + : BaseClass() + , d(new Private()) + , m_logger(getLogger("ElevationMapManager")) +{ + ++s_counter; + TRACE_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this; +} + +ElevationMapManager::~ElevationMapManager() +{ + --s_counter; + TRACE_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this; + cleanupCurrentContent(); + delete d; + d = nullptr; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this; +} +int ElevationMapManager::init(const QStringList &filePaths_rough_to_fine) +{ + NOTICE_LOG(m_logger) << LOGFUNCTION << " START" << " filePaths_rough_to_fine=" << filePaths_rough_to_fine.join(QStringLiteral(" ; ")); + + cleanupCurrentContent(); + + d->m_elevationMapCont.reserve(filePaths_rough_to_fine.size()); + int cnt = 0; + for (const QString &filePath : filePaths_rough_to_fine) + { + QFileInfo fileInfo(filePath); + + const QString &fileName = fileInfo.absoluteFilePath(); + + if (!fileInfo.exists()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << fileName << "\' doesn't exists."; + continue; + } + + if (!fileInfo.isReadable()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << fileName << "\' isn't readable."; + continue; + } + + // ctor will spawn thread via QtConcurrent and read file async: + d->m_elevationMapCont.insert(d->m_elevationMapCont.begin(), new ElevationMap(fileInfo)); + ++cnt; + } // for + + NOTICE_LOG(m_logger) << LOGFUNCTION << " END" << " filePaths_rough_to_fine=" << filePaths_rough_to_fine.join(QStringLiteral(" ; ")) + << ", following number of files are going to be loaded: " << cnt; + return cnt; +} + +void ElevationMapManager::cleanupCurrentContent() +{ + qDeleteAll(d->m_elevationMapCont); + d->m_elevationMapCont.clear(); +} + + + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + + +bool ElevationMapManager::waitForInitialization() +{ + for (ElevationMap *elevationMap : d->m_elevationMapCont) + elevationMap->waitForInitialization(); + return isValid(); +} + +bool ElevationMapManager::isValid() const +{ + bool allAreValid = true; + for (const ElevationMap *elevationMap : d->m_elevationMapCont) + allAreValid = allAreValid && elevationMap->isValid(); + +// FATAL_LOG(m_logger) << LOGFUNCTION << " allAreValid = " << allAreValid; + return allAreValid; +} + +double ElevationMapManager::elevation(double lon_rad, double lat_rad, bool *bIsCovered) const +{ + // assume, that all children are ready + for (const ElevationMap *elevationMap : d->m_elevationMapCont) + { +// qCritical() << " EMM::elevation: " +// << " lon_rad=" << lon_rad +// << " lat_rad=" << lat_rad +// << " (try: " << elevationMap->filename() << ")..."; + + double singleResult = elevationMap->elevation(lon_rad, lat_rad); + + if (singleResult > -300) // -300 -> ausserhalb der Höhenkarte !! + { + // qCritical() << " EMM::elevation: elevation= " << singleResult << " (taken from: " << elevationMap->filename() << ")"; + if (bIsCovered) + *bIsCovered = true; + return singleResult; + } + } + +// DEBUG_LOG(m_logger) << LOGFUNCTION << " invalid= " +// << " lon_rad=" << lon_rad +// << " lat_rad=" << lat_rad; + if (bIsCovered) + *bIsCovered = false; + return std::numeric_limits::min(); +} + +double ElevationMapManager::elevation(double lon_rad, double lat_rad, double defaultValue, bool *bIsCovered) const +{ + bool bIsCoveredLocal = false; + const double elevationValue = elevation(lon_rad, lat_rad, &bIsCoveredLocal); + + // in any case: if provided, transfer isCovered back to caller + if (bIsCovered) + *bIsCovered = bIsCoveredLocal; + + // which value to be returned? + if (!bIsCoveredLocal) + return defaultValue; + + return elevationValue; +} + + +QRectF ElevationMapManager::simpleUnitedExtent() const +{ + if (d->m_simpleExtent.isNull()) + { + bool bFirst = true; + for (const ElevationMap *elevationMap : d->m_elevationMapCont) + { + const QRectF &singleExtent = elevationMap->extent(); + if (bFirst) + { + bFirst = false; + d->m_simpleExtent = singleExtent; +// qCritical() << " bFirst.left " << singleExtent.left() << " bFirst.right " << singleExtent.right(); +// qCritical() << " bFirst.top " << singleExtent.top() << " bFirst.bottom " << singleExtent.bottom(); + + } else { + // prefer following instead QRectF::united (top vs. bottom): +// qCritical() << " next.left " << singleExtent.left() << " secnextright " << singleExtent.right(); +// qCritical() << " next.top " << singleExtent.top() << " next.bottom " << singleExtent.bottom(); + d->m_simpleExtent.setLeft (std::min(d->m_simpleExtent.left(), singleExtent.left())); + d->m_simpleExtent.setTop (std::max(d->m_simpleExtent.top(), singleExtent.top())); // top - >use max !!! + d->m_simpleExtent.setBottom(std::min(d->m_simpleExtent.bottom(), singleExtent.bottom())); // bottom -> use min + d->m_simpleExtent.setRight (std::max(d->m_simpleExtent.right(), singleExtent.right())); + } + } // for + } + + return d->m_simpleExtent; +} + +bool ElevationMapManager::contains(const QPointF &point) const +{ + for (const ElevationMap *elevationMap : d->m_elevationMapCont) + { + if (elevationMap->contains(point)) + return true; + } + return false; +} + +void ElevationMapManager::LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->m_elevationMapCont.empty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no elevationMap"; + return; + } + + waitForInitialization(); + + QRectF simpleExtentLocal = simpleUnitedExtent(); + +// qCritical() << " simpleExtent.left " << simpleExtentLocal.left() << " simpleExtent.right " << simpleExtentLocal.right(); +// qCritical() << " simpleExtent.top " << simpleExtentLocal.top() << " simpleExtent.bottom " << simpleExtentLocal.bottom(); + + if (!simpleExtentLocal.contains(start)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " start coordinate not within area" + << ", start: x=" << start.x() << " ; y=" << start.y() + << ", area: " + << " left " << simpleExtentLocal.left() << ", right " << simpleExtentLocal.right() + << " top " << simpleExtentLocal.top() << ", bottom " << simpleExtentLocal.bottom() + ; + return; // ausserhalb der Höhenkarte !! + } + + bool clipped = false; + double totalLng; + QPointF startG = start; + QPointF endG = end; + + if (!simpleExtentLocal.contains(end)) // Clippen + { + clipped = true; + totalLng = length(endG - startG); + if (endG.x() < simpleExtentLocal.left()) + { + double m = (endG.y() - startG.y()) / (endG.x() - startG.x()); + double x = simpleExtentLocal.left(); + endG.setX(x); + endG.setY(startG.y() + m * (x - startG.x())); + } + else if (endG.x() > simpleExtentLocal.right()) + { + double m = (endG.y() - startG.y()) / (endG.x() - startG.x()); + double x = simpleExtentLocal.right(); + endG.setX(x); + endG.setY(startG.y() + m * (x - startG.x())); + } + if (endG.y() < simpleExtentLocal.bottom()) + { + double m = (endG.x() - startG.x()) / (endG.y() - startG.y()); + double y = simpleExtentLocal.bottom(); + endG.setY(y); + endG.setX(startG.x() + m * (y - startG.y())); + } + else if (endG.y() > simpleExtentLocal.top()) + { + double m = (endG.x() - startG.x()) / (endG.y() - startG.y()); + double y = simpleExtentLocal.top(); + endG.setY(y); + endG.setX(startG.x() + m * (y - startG.y())); + } + } + + + // Korrektursteigung aufgrund WGS84 Erdellipsoid, Lineare Approximation, Fehler bis 200 km: < 1.0m + double startRadius = radiusWGS84(startG.y()); + double corrM = (radiusWGS84(endG.y()) - startRadius) / length(endG - startG); + + // + // getSamplePoints(start, end, elevProfile); + // find best elevationMap for equested path {startG, endG} + // + + // 1st find maps covering the desired pathG: + const QPolygonF pathG( QVector{startG, endG} ); + + std::vector coveredElevationMaps; + coveredElevationMaps.reserve(d->m_elevationMapCont.size()); + for (auto curItem : std::as_const(d->m_elevationMapCont)) + { + if (curItem && curItem->intersects(pathG)) + coveredElevationMaps.push_back(curItem); + } // for + + if (m_logger.isDebugEnabled()) + { + DEBUG_LOG(m_logger) << " coveredElevationMap.size() = " << coveredElevationMaps.size(); + for (auto curItem : std::as_const(coveredElevationMaps)) + DEBUG_LOG(m_logger) << " coveredElevationMap = " << curItem->filename(); + } + + // if no maps covers the desired path, stay on old approach: use most bottom elevation map: + if (coveredElevationMaps.empty()) + coveredElevationMaps = d->m_elevationMapCont; + + // 2nd: find elevation map with highest resolution to achieve best results: + using ELE_VEC_IT = std::vector::iterator; + ELE_VEC_IT eleIt = coveredElevationMaps.begin(); + for (ELE_VEC_IT curItem = coveredElevationMaps.begin()+1; curItem != coveredElevationMaps.end(); ++curItem) + { + ElevationMap *elePtr = *curItem; + if (!elePtr) + continue; + + if (elePtr->elexelSize().first < (*eleIt)->elexelSize().first) + eleIt = curItem; + } // for + + Q_ASSERT_X((*eleIt), "ElevationMapManager::LOSProfile", "eleIt failed"); + + Vec3dVector elevProfile; + (*eleIt)->getSamplePoints(start, end, elevProfile); + + INFO_LOG(m_logger) << "Using ElevationMap '" << (*eleIt)->filename() << "'" + << " (of size = " << d->m_elevationMapCont.size() << ")" + << " with_elexelSize " << (*eleIt)->elexelSize().first + << " -> # SamplePoints = " << elevProfile.size(); + + for (Vec3dVector::iterator itr = elevProfile.begin(); itr != elevProfile.end(); ++itr) + { + double dist = length(distance_in_meter_WGS84(start, itr->toPointF())); + double height = elevation(itr->x(), itr->y(), nullptr); // use elevation over all seperate elevationMaps + height += corrM * dist; // WGS84 Erdellipsoid Korrektur + height -= (startRadius * (1 - cos(atan(dist / startRadius)))); // Einbeziehen der Erdkruemmung + // alternativ als Naeherung: height -= ( dist * dist) / (startRadius * 2.0); + losProfile.push_back(LOSentry(dist, height)); + } + + if (clipped) + losProfile.push_back(LOSentry(totalLng, -NAN)); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +ElevationMapManager::LineOfSight ElevationMapManager::lineOfSightCalculation(const LOSVector &losProfile, + double centerHat, + double objHat) const +{ + // Analog zu SightFanImpl::calculateSightBeam + + + if (losProfile.size() < 2) // Startpunkt undefiniert oder ausserhalb der Karte + { + return LineOfSight::LOS_ERROR; + } + + LOSVector::const_iterator itr = losProfile.begin(); + const double centerHeight = itr->height() + centerHat; + + double d1 = 0.0; + double height1 = itr->height(); + +// // überspringen bis _innerRadius erreicht ist ! +// while( ++itr != losProfile.end()) +// { +// double d2 = itr->dist(); // *EarthRadius; +// double height2 = itr->height(); +// if( d2 >= m_internal->m_innerRadius) +// { +// height1 = height1 + (m_internal->m_innerRadius - d1) * ( height2 - height1)/(d2 - d1); +// d1 = m_internal->m_innerRadius; +// --itr; +// break; +// } +// d1 = d2; +// height1 = height2; +// } + +// bool visible = true; +// SightMode mode = START; + + d1 = itr->dist(); + double curM = (height1 - centerHeight ) / d1; // bestimmende Steigung + +// sBeam.add( SightEntry( d1, START) ); + + while (++itr != losProfile.end()) + { + double d2 = itr->dist(); // *EarthRadius; + double height2 = itr->height(); + + if (std::isnan(height2)) // Undefined + { + return LineOfSight::LOS_UNDEFINED; + } + + double hObj = (height2 + objHat - centerHeight ); + double objM = hObj / d2; // Steigung bezogen auf Objekte( Objekte verdecken nicht !) + + LineOfSight newMode = ((objM >= curM)) ? LineOfSight::LOS_VISIBLE : LineOfSight::LOS_INVISIBLE; + + if (newMode == LineOfSight::LOS_INVISIBLE) + return LineOfSight::LOS_INVISIBLE; + + +// if (newMode != mode) +// { +// if ( (mode != LineOfSight::LOS_UNDEFINED) && (d2 != d1) ) +// { +// // Korrekturwert für Distanz berechnen, unter Berücksichtigung der Objekthoehe !! +// double localM = (height2 - height1) /(d2 - d1); +// // localM == curM kann nicht auftreten, da newMode != mode +// double dCorr = (hObj - (curM * d2)) / (localM - curM); + +// if (dCorr < 0.0) +// dCorr = 0.0; +// dCorr = d2 - dCorr; +// if (dCorr < d1) +// dCorr = d1; + +// sBeam.add( SightEntry( dCorr, mode) ); +// } +// mode = newMode; +// } + + double terrM = (height2 - centerHeight) / d2; // Steigung bezogen auf Gelaende + if (terrM >= curM) + { + curM = terrM; // neue bestimmende Steigung + } + +// height1 = height2; +// d1 = d2; + } // while + + return LineOfSight::LOS_VISIBLE; +} + +ElevationMapManager::LineOfSight ElevationMapManager::lineOfSightCalculation(const LtsUtils::QPoint3D &startPointGeo_absZ, + const LtsUtils::QPoint3D &endPointGeo_absZ) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", startPointGeo_absZ = " << startPointGeo_absZ << ", endPointGeo_absZ = " << endPointGeo_absZ; + + if (!isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, LtsElevationManager not valid"; + return LineOfSight::LOS_ERROR; + } + + // 1st: a) centerHat / startHat: + const double startHat = startPointGeo_absZ.z() - this->elevation(startPointGeo_absZ.x(), startPointGeo_absZ.y(), -10000); + if (startHat < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, startHat is negativ"; + return LineOfSight::LOS_ERROR; + } + // 1st: b) endHat / objHat: + const double endHat = endPointGeo_absZ.z() - this->elevation(endPointGeo_absZ.x(), endPointGeo_absZ.y(), -10000); + if (endHat < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, objHat is negativ" + << ", startPointGeo_absZ=" << startPointGeo_absZ + << ", endPointGeo_absZ=" << endPointGeo_absZ; + return LineOfSight::LOS_ERROR; + } + + SituationMap::ElevationMapManager::LineOfSight eleMapMgrResult = lineOfSightCalculation(startPointGeo_absZ.toPointF(), startHat, + endPointGeo_absZ.toPointF(), endHat); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", startPointGeo = " << startPointGeo_absZ << ", endPointGeo = " << endPointGeo_absZ + << " -> LineOfSight " << static_cast(eleMapMgrResult); + return eleMapMgrResult; +} + +ElevationMapManager::LineOfSight ElevationMapManager::lineOfSightCalculation(const QPointF &startPointGeo, double startHat, + const QPointF &endPointGeo, double endHat) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", startPointGeo = " << startPointGeo << ", startHat = " << startHat + << ", endPointGeo = " << endPointGeo << ", endHat = " << endHat ; + if (!isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, LtsElevationManager not valid"; + return LineOfSight::LOS_ERROR; + } + + if (startHat < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, startHat is negativ"; + return LineOfSight::LOS_ERROR; + } + if (endHat < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, endHat is negativ"; + return LineOfSight::LOS_ERROR; + } + + // 1st: create LOSVector: + SituationMap::LOSVector losProfile; + LOSProfile(startPointGeo, endPointGeo, losProfile); + if (losProfile.size() < 2) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, losProfile not valid" + << ", losProfile.size = " << losProfile.size(); + return LineOfSight::LOS_ERROR; + } + + // 2nd: lineOfSightCalculation + SituationMap::ElevationMapManager::LineOfSight eleMapMgrResult = this->lineOfSightCalculation(losProfile, startHat, endHat); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", startPointGeo = " << startPointGeo << ", startHat = " << startHat + << ", endPointGeo = " << endPointGeo << ", endPointHat = " << endHat + << " -> LineOfSight " << static_cast(eleMapMgrResult); + return eleMapMgrResult; +} + + + + +} // SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.h new file mode 100755 index 000000000..b77ddf77c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/elevation_map_manager.h @@ -0,0 +1,84 @@ +/** + * @RDE_HEADER@ + * Manage and combine several ElevationMap + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "elevation_map_helper.h" +#include "situationmapelevationmap_export.h" +#include + +#include + +namespace SituationMap +{ + +// forward: +class ElevationMap; + +//------------------------------------------------------------------------------------------------------------ +// ElevationMapManager +//------------------------------------------------------------------------------------------------------------ +class SITUATIONMAPELEVATIONMAP_EXPORT ElevationMapManager final : public QObject +{ + using BaseClass = QObject; + +public: + ElevationMapManager(); + ElevationMapManager(const ElevationMapManager &) = delete; + ElevationMapManager &operator = (const ElevationMapManager &) = delete; + ~ElevationMapManager(); + + int init(const QStringList &filePaths_rough_to_fine); + void cleanupCurrentContent(); + + bool waitForInitialization(); + bool isValid() const; + + + double elevation(double lon_rad, double lat_rad, bool *bIsCovered = nullptr) const; + double elevation(double lon_rad, double lat_rad, double defaultValue, bool *bIsCovered = nullptr) const; + + // united extent of seperate files: + QRectF simpleUnitedExtent() const; + + bool contains(const QPointF &point) const; + + // Line of Sight Profile: berücksichtigt Erdkrümmung und WGS84 Erdellipsoid + void LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile); + + /*! \name Line of Sight */ + //@{ + enum class LineOfSight { LOS_UNDEFINED, + LOS_ERROR, + LOS_VISIBLE, + LOS_INVISIBLE }; + + LineOfSight lineOfSightCalculation(const LOSVector &losProfile, + double centerHat, + double objHat) const; + + LineOfSight lineOfSightCalculation(const LtsUtils::QPoint3D &startPointGeo_absZ, + const LtsUtils::QPoint3D &endPointGeo_absZ); + + LineOfSight lineOfSightCalculation(const QPointF &startPointGeo, double startHat, + const QPointF &endPointGeo, double endHat); + + //@} + +protected: + + class Private; + + Private *d; + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; // ElevationMapManager + + +} // SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.cpp new file mode 100755 index 000000000..65a6a5805 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.cpp @@ -0,0 +1,229 @@ +/** + * @RDE_HEADER@ + * LineOfSightCalculation (L400) + * $Id$ + * $HeadURL$ + * $Author$ + * Created by N. Thoden, NLE, 06.04.2020 + */ + +#include "line_of_sight_calculation.h" +#include "elevation_map.h" + +#include + +// +// ESRI / ARCGIS +// if you link libEsriCommonQt, you propably have to add ArcGISQt5Hack AND: # make sure it's loaded *before* libEsriCommonQt.so +// this may effect all executables linking situationMapElevationMap. CHECK! +// + + +// Qt +#include +#include + + +// some helper functions +inline Q_CONSTEXPR double pi() { return M_PI; } +inline Q_CONSTEXPR double deg2rad() { return pi() / 180.; } +inline Q_CONSTEXPR double rad2deg() { return 1. / deg2rad(); } + + +namespace SituationMap +{ + +LineOfSightCalculation::LineOfSightCalculation(ElevationMap *elevationMap) + : m_logger(getLogger("LineOfSightCalculation")) + , m_elevationMap(elevationMap) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +LineOfSightCalculation::~LineOfSightCalculation() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool LineOfSightCalculation::calculate(ObserverTargetData &observerTarget, unsigned int sampleIntervallMeter) +{ + // same checks: + if (!m_elevationMap || !m_elevationMap->isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no elevationMap configured or not valid"; + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + return false; + } + + if (!observerTarget.isObserverAndTargetValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " observer and/or target not valid"; + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + return false; + } + + if (observerTarget.observer().toPointF() == observerTarget.target().toPointF()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " observer and target are identical regarding x and y"; + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + return false; + } + + bool success = true; + + QElapsedTimer timer; + if (m_logger.isInfoEnabled()) + timer.start(); + + // + // + // consider using ElevationMap::LOSProfile + // + // + + // determine how many iterations we need: + double distanceInMeter = distanceMeter(observerTarget.observer(), + observerTarget.target()); + //ERROR_LOG(m_logger) << LOGFUNCTION << " distanceMeter = " << distanceInMeter; + + int maxNumberOfIterations = distanceInMeter / sampleIntervallMeter; // check visibility every ~5m + // maxNumberOfIterations = 1000000; + + if (maxNumberOfIterations<=0) + maxNumberOfIterations = 5; + + + double distanceFromObserverToTargetAsFraction = 0; + int i = 0; + while (idirectLineElevation) -> obstructed/break + */ + + distanceFromObserverToTargetAsFraction = (double)i/maxNumberOfIterations; + + + const Point3D &pCandidate = movePoint(observerTarget.observer(), + observerTarget.target(), + distanceFromObserverToTargetAsFraction); + + // { + // static Point3D oldCandidate; + // double distanceInMeter = distanceMeter(pCandidate, oldCandidate); + // ERROR_LOG(m_logger) << LOGFUNCTION << " distanceMeter = " << distanceInMeter; + // oldCandidate = pCandidate; + // } + + + // z is the elevation of a *direct* line: + const double directLineElevation = pCandidate.z(); + const double elevationMapElevation = m_elevationMap->elevation(pCandidate.x(), pCandidate.y()); + + // Coord covered by elevationMap? + if (elevationMapElevation <= std::numeric_limits::min()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, coordinate not covered be elevationData. Finish this LineOfSightCalculation." + << " i=" << i + << ", pCandidate = " << pCandidate; + + // write back data + observerTarget.setObstacle(pCandidate); + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + + // finish the loop: + success = false; + break; + } + + // analyze / check visibility: + if (elevationMapElevation>directLineElevation) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " i=" << i + << " break - pCandidate = " << pCandidate + << " -> directLineElevation = " << directLineElevation + << " -> elevationMapElevation = " << elevationMapElevation; + + // write result + observerTarget.setObstacle(pCandidate); + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Obstructed); + + // finish the loop: + break; + } else { + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Visible); + } + + } // while + + + if (m_logger.isInfoEnabled()) + { + INFO_LOG(m_logger) << LOGFUNCTION << " END" + << ", success = " << success + << ", " << i << " of "<< maxNumberOfIterations << " iterations" + << ", distanceFromObserverToTargetAsFraction = " << distanceFromObserverToTargetAsFraction + << ", pCandidate=" << observerTarget.obstacle() + << ", lineOfSightVisibility=" << static_cast(observerTarget.lineOfSightVisibility()) + << ", took [ms] " << timer.elapsed(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", success = " << success; + return success; +} + +Point3D LineOfSightCalculation::movePoint(const Point3D &oberserver, const Point3D &target, double distanceFromObserverToTargetAsFraction) // 0.0 ... 1.0 +{ + // positions are lon/lat @ rad. + // for evaluation we do that very simple: + + // strange, but: using Point3D seems to be faster then using QPointF + const Point3D &dist = target - oberserver; + const Point3D &point = oberserver + (dist * distanceFromObserverToTargetAsFraction); + + return point; +} + +double LineOfSightCalculation::distanceMeter(const Point3D &oberserver, const Point3D &target) +{ + // Try to avoid the dependency to ESRI (keep ArcGISQt5Hack in mind) + // And good accuracy is not required here + // use LtsCoordTrans::distance + +// Esri::ArcGISRuntime::Point geom1(oberserver.x()*rad2deg(), oberserver.y()*rad2deg(), oberserver.z(), Esri::ArcGISRuntime::SpatialReference::wgs84()); +// Esri::ArcGISRuntime::Point geom2(target.x() *rad2deg(), target.y() *rad2deg(), target.z() , Esri::ArcGISRuntime::SpatialReference::wgs84()); + +// Esri::ArcGISRuntime::GeometryEngine *gEngine = Esri::ArcGISRuntime::GeometryEngine::instance(); +// Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistanceResult = gEngine->distanceGeodetic(geom1, geom2, +// Esri::ArcGISRuntime::LinearUnitId::Meters, +// Esri::ArcGISRuntime::AngularUnitId::Degrees, +// Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); +// double distance = geodeticDistanceResult.distance(); + + + double distance = LtsCoordTrans::distance(oberserver.toPointF(), target.toPointF()); + + return distance; +} + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.h new file mode 100755 index 000000000..75f83426e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/line_of_sight_calculation.h @@ -0,0 +1,92 @@ +/** + * @RDE_HEADER@ + * LineOfSightCalculation (L400) + * $Id$ + * $HeadURL$ + * $Author$ + * Created by N. Thoden, NLE, 06.04.2020 + */ + +#pragma once + +#include +#include + +#include +using Point3D = LtsUtils::QPoint3D; ///< x = lon ; y = lat , z = elevation/altitude in meters + + +namespace SituationMap +{ + +// forward +class ElevationMap; + +//---------------------------------------------------------------------------- +/*! + \brief Define visibility. + */ +enum class LineOfSightVisibility +{ + Undefined, /*!< no data at that location? */ + Visible, + Obstructed +}; +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +class SITUATIONMAPELEVATIONMAP_EXPORT ObserverTargetData +{ +public: + explicit ObserverTargetData(const Point3D &oberserver = Point3D(), // z-value: incl. observerHeight + const Point3D &target = Point3D()) // z-value: incl. targetHeight + : m_oberserver(oberserver) + , m_target(target) + {} + + const Point3D &observer() const { return m_oberserver; } + const Point3D &target() const { return m_target; } + + const Point3D &obstacle() const { return m_obstacle; } + void setObstacle(const Point3D &obstacle) { m_obstacle = obstacle; } + + LineOfSightVisibility lineOfSightVisibility() const { return m_visible; } + void setLineOfSightVisibility(const LineOfSightVisibility &visible) { m_visible = visible; } + + bool isObserverAndTargetValid() const { return (!m_oberserver.isNull()) && (!m_target.isNull()); } + +private: + Point3D m_oberserver; + Point3D m_target; + + Point3D m_obstacle; + LineOfSightVisibility m_visible = LineOfSightVisibility::Undefined; +}; +//---------------------------------------------------------------------------- + +class SITUATIONMAPELEVATIONMAP_EXPORT LineOfSightCalculation +{ +public: + explicit LineOfSightCalculation(ElevationMap *elevationMap); + ~LineOfSightCalculation(); + + bool calculate(ObserverTargetData &observerTarget, unsigned int sampleIntervallMeter = 5); + +protected: + static Point3D movePoint(const Point3D &oberserver, + const Point3D &target, + double distanceFromObserverToTargetAsFraction); + + static double distanceMeter(const Point3D &oberserver, + const Point3D &target); + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + ElevationMap *m_elevationMap = nullptr; + + ObserverTargetData m_observerTarget; +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/situationmapelevationmap_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/situationmapelevationmap_export.h new file mode 100644 index 000000000..2f350131e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ElevationMap/situationmapelevationmap_export.h @@ -0,0 +1,21 @@ +/** + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef SITUATIONMAPELEVATIONMAP_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define SITUATIONMAPELEVATIONMAP_EXPORT +# else +# if defined(MAKE_SITUATIONMAPELEVATIONMAP_LIB) +# define SITUATIONMAPELEVATIONMAP_EXPORT Q_DECL_EXPORT +# else +# define SITUATIONMAPELEVATIONMAP_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif \ No newline at end of file diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/CMakeLists.txt new file mode 100755 index 000000000..d7609782d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/CMakeLists.txt @@ -0,0 +1,9 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# ADD_SUBDIRECTORY(Viewshed3D) + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/CMakeLists.txt new file mode 100755 index 000000000..397d49bd4 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/CMakeLists.txt @@ -0,0 +1,43 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# Evaluation application for 3D viewshed + +FIND_PACKAGE(ArcGIS REQUIRED) + +INCLUDE_DIRECTORIES(${ARCGIS_INCLUDE_DIR}/arcgis) + +SET(EXE_SRCS + main.cpp + ViewshedLocation.cpp +) + +SET(EXE_HDRS + ViewshedLocation.h +) + +SOURCE_GROUP(EvalViewshed3DApp FILES ${EXE_SRCS} ${EXE_HDRS}) + +NGL_ADD_EXECUTABLE(WS NO_TRANSLATION EvalViewshed3DApp ${EXE_SRCS} ${EXE_HDRS}) + +NGL_TARGET_LINK_LIBRARIES( + EvalViewshed3DApp + ArcGIS::ArcGIS + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Network +) + + +IF(MSVC) + # Configure the template file + SET(VS_USER_FILE EvalViewshed3DApp.vcxproj.user) + configure_file( ../../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) +ENDIF(MSVC) + +NGL_INSTALL_TARGET(EvalViewshed3DApp) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/LocationViewshed.pro b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/LocationViewshed.pro new file mode 100755 index 000000000..374b1a7f8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/LocationViewshed.pro @@ -0,0 +1,58 @@ +#------------------------------------------------- +# Copyright 2015 Esri. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#------------------------------------------------- + +mac { + cache() +} + +CONFIG += c++11 esri_runtime_qt100_0_0 debug + +QT += core gui opengl xml network positioning sensors + +win32:CONFIG += \ + embed_manifest_exe + +TARGET = LocationViewshed +TEMPLATE = app + +SOURCES += \ + main.cpp \ + ViewshedLocation.cpp + +HEADERS += \ + ViewshedLocation.h + + +win32 { + INCLUDEPATH += "C:/Program Files (x86)/ArcGIS SDKs/Qt100.0/sdk/include" + debug:LIBPATH += "C:/Program Files (x86)/ArcGIS SDKs/Qt100.0/sdk/windows/x64/lib/Debug" + release:LIBPATH += "C:/Program Files (x86)/ArcGIS SDKs/Qt100.0/sdk/windows/x64/lib/Release" + debug:LIBS += -lEsriCommonQtd + release:LIBS += -lEsriCommonQt + debug:LIBS += -lEsriRuntimeQtd + release:LIBS += -lEsriRuntimeQt +} + +linux { + INCLUDEPATH += /usr/include/arcgis + LIBS += -lEsriCommonQt + LIBS += -lEsriRuntimeQt + LIBS += -lc++ +} + + +macx { + QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.11 +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.cpp new file mode 100755 index 000000000..8f0758190 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.cpp @@ -0,0 +1,274 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ +// [WriteFile Name=ViewshedLocation, Category=Analysis] +// [Legal] +// Copyright 2017 Esri. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// [Legal] + +#include "ViewshedLocation.h" + +#include "ArcGISTiledElevationSource.h" +#include "Scene.h" +#include "SceneGraphicsView.h" +#include "LocationViewshed.h" +#include "Camera.h" +#include "Point.h" +#include "AnalysisOverlay.h" + +#include "QColor" + + +using namespace Esri::ArcGISRuntime; + +ViewshedLocation::ViewshedLocation(QObject* parent /* = nullptr */): + QObject(parent) +{ +} + +void ViewshedLocation::init(Esri::ArcGISRuntime::SceneGraphicsView* sceneView) +{ + m_sceneView = sceneView; + + // Register classes for QML +// qmlRegisterType("Esri.Samples", 1, 0, "SceneView"); +// qmlRegisterType("Esri.Samples", 1, 0, "ViewshedLocationSample"); + + + Surface* surface = new Surface(this); + surface->elevationSources()->append( + new ArcGISTiledElevationSource( + QUrl(QStringLiteral("http://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")), + this)); + m_sceneView->arcGISScene()->setBaseSurface(surface); + + // set initial viewpoint + setInitialViewpoint(); + + // connect signals + connectSignals(); +} + + +void ViewshedLocation::setInitialViewpoint() +{ + // Set a viewpoint + double x = 6.86088; + double y = 45.3604; + double z = 3582.55; + Point point(x, y, z, SpatialReference(4326)); + double heading = 345; + double pitch = 70; + double roll = 0; + Camera camera(point, heading, pitch, roll); + m_sceneView->setViewpointCamera(camera); +} + +void ViewshedLocation::connectSignals() +{ + // on mouse click perform the location viewshed + connect(m_sceneView, &SceneGraphicsView::mouseClicked, this, [this](QMouseEvent& event) + { + if (!m_locationViewshed[0]) + createViewshed(event.x(), event.y()); + else + { + for (int i = 0; i < 3; ++i) + { + const Point pt = m_sceneView->screenToBaseSurface(event.x(), event.y()); + m_locationViewshed[i]->setLocation(pt); + } + } + }); + + connect(m_sceneView, &SceneGraphicsView::mousePressedAndHeld, this, [this](QMouseEvent& event) + { + if (!m_locationViewshed[0]) + createViewshed(event.x(), event.y()); + + m_calculating = true; + }); + + connect(m_sceneView, &SceneGraphicsView::mouseMoved, this, [this](QMouseEvent& event) + { + if (m_calculating) + { + for (int i = 0; i < 3; ++i) + { + const Point pt = m_sceneView->screenToBaseSurface(event.x(), event.y()); + m_locationViewshed[i]->setLocation(pt); + } + } + }); + + connect(m_sceneView, &SceneGraphicsView::mouseReleased, this, [this](QMouseEvent) + { + m_calculating = false; + }); +} + +void ViewshedLocation::createViewshed(double x, double y) +{ + + m_analysisOverlay = new AnalysisOverlay(this); + m_sceneView->analysisOverlays()->append(m_analysisOverlay); + + + const Point pt = m_sceneView->screenToBaseSurface(x, y); + + qCritical() << " pt="; + qCritical() << " pt=" << pt.x(); + qCritical() << " pt=" << pt.y(); + qCritical() << " pt=" << pt.z(); + + for (int i = 0; i < 3; ++i) + { + double heading = i * 120.0; + m_locationViewshed[i] = new LocationViewshed(pt + , heading + , 90.0 // pitch + , 120.0 // horizontalAngle + , 90.0 // verticalAngle + , 10.0 // minDistance + , 20000 + , this); + + + m_locationViewshed[i]->setVisible(m_viewshedVisible); + + // Add the Viewshed to the Analysis Overlay + m_analysisOverlay->analyses()->append(m_locationViewshed[i]); + + m_locationViewshed[i]->setFrustumOutlineVisible(true); + + // QColor frustumColor(0, 0, 0, 255); + // m_locationViewshed->setFrustumOutlineColor(frustumColor); + } + return; +} + +// Getters/Setters for each Q_PROPERTY +bool ViewshedLocation::isViewshedVisible() const +{ + return m_viewshedVisible; +} + +void ViewshedLocation::setViewshedVisible(bool viewshedVisible) +{ + for (int i = 0; i < 3; ++i) + { + if (m_locationViewshed[i]) + { + if (m_locationViewshed[i]->isVisible() == viewshedVisible) + { + continue; + } + + m_locationViewshed[i]->setVisible(viewshedVisible); + } + } + + if (m_viewshedVisible != viewshedVisible) + { + m_viewshedVisible = viewshedVisible; + + emit viewshedVisibleChanged(); + } +} + +bool ViewshedLocation::isFrustumOutlineVisible() const +{ + return m_locationViewshed[0] ? m_locationViewshed[0]->isFrustumOutlineVisible() : m_frustumVisible; +} + +void ViewshedLocation::setFrustumOutlineVisible(bool frustumVisible) +{ + for (int i = 0; i < 3; ++i) + { + if (m_locationViewshed[i]) + { + if (m_locationViewshed[i]->isFrustumOutlineVisible() == frustumVisible) + { + continue; + } + + m_frustumVisible = frustumVisible; + m_locationViewshed[i]->setFrustumOutlineVisible(frustumVisible); + } + } + + if (m_frustumVisible != frustumVisible) + { + m_frustumVisible = frustumVisible; + emit frustumVisibleChanged(); + } +} + +double ViewshedLocation::minDistance() const +{ + return m_locationViewshed[0] ? m_locationViewshed[0]->minDistance() : m_minDistance; +} + +void ViewshedLocation::setMinDistance(double minDistance) +{ + for (int i = 0; i < 3; ++i) + { + if (m_locationViewshed[i]) + { + if (m_locationViewshed[i]->minDistance() == minDistance) + { + continue; + } + m_locationViewshed[i]->setMinDistance(minDistance); + } + } + + if (m_minDistance != minDistance) + { + m_minDistance = minDistance; + emit minDistanceChanged(); + } +} + +double ViewshedLocation::maxDistance() const +{ + return m_locationViewshed[0] ? m_locationViewshed[0]->maxDistance() : m_maxDistance; +} + +void ViewshedLocation::setMaxDistance(double maxDistance) +{ + for (int i = 0; i < 3; ++i) + { + if (m_locationViewshed[i]) + { + if (m_locationViewshed[i]->maxDistance() == maxDistance) + { + continue; + } + m_locationViewshed[i]->setMaxDistance(maxDistance); + } + } + + if (m_maxDistance != maxDistance) + { + m_maxDistance = maxDistance; + emit maxDistanceChanged(); + } +} + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.h new file mode 100755 index 000000000..5cd4cb31a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/ViewshedLocation.h @@ -0,0 +1,99 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ +// [WriteFile Name=ViewshedLocation, Category=Analysis] +// [Legal] +// Copyright 2017 Esri. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// [Legal] + +#ifndef VIEWSHEDLOCATION_H +#define VIEWSHEDLOCATION_H + +#include +#include + + +namespace Esri +{ +namespace ArcGISRuntime +{ +class SceneGraphicsView; +class LocationViewshed; +class AnalysisOverlay; +} +} + + +class ViewshedLocation : public QObject +{ + Q_OBJECT + +public: + explicit ViewshedLocation(QObject* parent = nullptr); + ~ViewshedLocation() = default; + +// void componentComplete() Q_DECL_OVERRIDE; + void init(Esri::ArcGISRuntime::SceneGraphicsView* sceneView); + + Q_PROPERTY(bool viewshedVisible READ isViewshedVisible WRITE setViewshedVisible NOTIFY viewshedVisibleChanged) + Q_PROPERTY(bool frustumVisible READ isFrustumOutlineVisible WRITE setFrustumOutlineVisible NOTIFY frustumVisibleChanged) + Q_PROPERTY(double minDistance READ minDistance WRITE setMinDistance NOTIFY minDistanceChanged) + Q_PROPERTY(double maxDistance READ maxDistance WRITE setMaxDistance NOTIFY maxDistanceChanged) + + + bool isViewshedVisible() const; + void setViewshedVisible(bool viewshedVisible); + + bool isFrustumOutlineVisible() const; + void setFrustumOutlineVisible(bool frustumVisible); + + double minDistance() const; + void setMinDistance(double minDistance); + + double maxDistance() const; + void setMaxDistance(double maxDistance); + + +signals: + void viewshedVisibleChanged(); + void frustumVisibleChanged(); + void minDistanceChanged(); + void maxDistanceChanged(); + + +private: + void connectSignals(); + void setInitialViewpoint(); + void createViewshed(double x, double y); + + Esri::ArcGISRuntime::SceneGraphicsView *m_sceneView = nullptr; + Esri::ArcGISRuntime::LocationViewshed *m_locationViewshed[3] = { nullptr, nullptr, nullptr }; + Esri::ArcGISRuntime::AnalysisOverlay *m_analysisOverlay = nullptr; + + bool m_viewshedVisible = true; + bool m_frustumVisible = false; + double m_minDistance = 50; + double m_maxDistance = 1000; + double m_horizontalAngle = 45; + double m_veriticalAngle = 90; + double m_heading = 0; + double m_pitch = 90; + bool m_calculating = false; +}; + +#endif // VIEWSHEDLOCATION_H diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/main.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/main.cpp new file mode 100755 index 000000000..d4abac4af --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/EvalApps/Viewshed3D/main.cpp @@ -0,0 +1,244 @@ +/* +** +** @RDE_HEADER@ +** Showing artefacts when drawing 3D map. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Linux, Qt 5.9. ArcGISRuntimeQt 100.1 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 08.11.2017, JT : Initial +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ViewshedLocation.h" + + + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +class MapWindow : public QWidget +{ + + Esri::ArcGISRuntime::Basemap * m_basemap; + Esri::ArcGISRuntime::Scene * m_scene; + Esri::ArcGISRuntime::SceneGraphicsView * m_sceneGraphicsView; + Esri::ArcGISRuntime::GeoView * m_mapView; + Esri::ArcGISRuntime::GraphicsOverlay * m_graphicsOverlay; + + Qt::MouseButton m_pressedMouseButton; + QPoint m_mouseMoveRefPos; + QFuture m_pickingTask; + bool m_pendingRequest; + + public: + + MapWindow() : QWidget(), m_graphicsOverlay(nullptr), m_pickingTask(), m_pendingRequest(false) + { + m_pressedMouseButton = Qt::MouseButton::NoButton; + m_mouseMoveRefPos = QPoint(); + + m_basemap = new Esri::ArcGISRuntime::Basemap(this); + m_scene = new Esri::ArcGISRuntime::Scene(m_basemap,this); + + connect( m_basemap, &Esri::ArcGISRuntime::Map::errorOccurred , this, &MapWindow::onErrorBasemap ); + connect( m_scene , &Esri::ArcGISRuntime::Scene::errorOccurred, this, &MapWindow::onErrorScene ); + connect( m_scene , &Esri::ArcGISRuntime::Scene::doneLoading , this, &MapWindow::onDoneLoading ); + + m_sceneGraphicsView = nullptr; + try + { + m_sceneGraphicsView = new Esri::ArcGISRuntime::SceneGraphicsView(); // no parent, + m_sceneGraphicsView->setObjectName(QLatin1String("EsriSceneView")); + m_sceneGraphicsView->setFrameShape(QFrame::NoFrame); // frameless + } + catch (std::exception &stdEx) + { + const char *what = stdEx.what(); + if (what) + qCritical()<< "MapWindow init SceneGraphicsView: std Exception " << " what: '" << what << "'"; + else + qCritical() << "MapWindow init SceneGraphicsView: std Exception"; + + throw; // re-throw + } + catch (...) + { + qCritical() << "MapWindow init SceneGraphicsView: unknown Exception"; + throw; // re-throw + } + + if (!m_sceneGraphicsView) + return; + + m_sceneGraphicsView->setArcGISScene(m_scene); + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->addWidget(m_sceneGraphicsView); + + initMapLayer(QLatin1String("http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer")); + initMapLayer(QLatin1String("http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer")); + + Esri::ArcGISRuntime::Camera camera(28.4, 83.9, 10010.0, 10.0, 80.0, 300.0); + m_sceneGraphicsView->setViewpointCameraAndWait(camera);// set the viewpoint + + /* + QPushButton * aButton = new QPushButton(tr("Click me")); // toplevel widget + QGraphicsProxyWidget *proxy = m_sceneGraphicsView->QGraphicsView::scene()->addWidget(aButton); + proxy->setOpacity(0.99); + + connect( aButton, &QPushButton::clicked, this, []() {qCritical() << "A Button has been clicked!";} ); + */ + + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); + timer->setInterval(5000); + connect(timer, &QTimer::timeout, this, [this]() { + + qCritical() << "Create viewshed ..."; + ViewshedLocation * vLoc = new ViewshedLocation(this); + vLoc->init(m_sceneGraphicsView); + }); + timer->start(); + + + } // MapWindow() + + + ~MapWindow() = default; + + + void initMapLayer( const QString &mapServiceUrl ) + { + Esri::ArcGISRuntime::ArcGISTiledLayer *tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(QUrl(mapServiceUrl), this); + tiledLayer->setName(mapServiceUrl); + tiledLayer->setObjectName(tiledLayer->name()); + + m_basemap->baseLayers()->append(tiledLayer); + + } // void initMapLayer() + + private: + + void onDoneLoading(const Esri::ArcGISRuntime::Error &loadError) + { + + } // void onDoneLoading(const Esri::ArcGISRuntime::Error &loadError) + + + void onErrorBasemap(const Esri::ArcGISRuntime::Error &error) + { + qCritical() << " ---EsriError,Basemap --- occured: " + << " code=" << error.code() + << " domain=" << (int)error.domain() + << " extendedErrorType=" << (int)error.extendedErrorType() + << ", message='" << error.message() << "'"; + } // void onErrorBasemap(const Esri::ArcGISRuntime::Error &error) + + + void onErrorScene(const Esri::ArcGISRuntime::Error &error) + { + qCritical() << " ---EsriError, Scene --- occured: " + << " code=" << error.code() + << " domain=" << (int)error.domain() + << " extendedErrorType=" << (int)error.extendedErrorType() + << ", message='" << error.message() << "'"; + } // void onErrorScene(const Esri::ArcGISRuntime::Error &error) + + +}; // MapWindow + + +int main(int argc, char* argv[]) +{ + QApplication app(argc,argv); + + // ESRI : +#ifdef Q_OS_WIN + // Force usage of OpenGL ES through ANGLE on Windows + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); +#endif + // : ESRI + +#define PROXY +#ifdef PROXY + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::HttpProxy); + proxy.setHostName(QStringLiteral("proxy-rde")); + proxy.setPort(8080); + proxy.setUser(QStringLiteral("norbert.thoden@rheinmetall.com")); + proxy.setPassword(QStringLiteral("RDEWWW11")); + QNetworkProxy::setApplicationProxy(proxy); +#endif + + MapWindow * mapWin = new MapWindow(); + + int width = mapWin->frameGeometry().width(); + int height = mapWin->frameGeometry().height(); + + QScreen *screen = QGuiApplication::primaryScreen(); + int screenWidth = screen ? screen->size().width() : 1920; + int screenHeight = screen ? screen->size().height() : 1080; + + mapWin->setGeometry((screenWidth / 2) - (width / 2), (screenHeight / 2) - (height / 2), width, height); + + mapWin->show(); + + app.setQuitOnLastWindowClosed(true); + + int retval = app.exec(); + + delete mapWin; + + return retval; + +} // main diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/CMakeLists.txt new file mode 100755 index 000000000..6822589b3 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/CMakeLists.txt @@ -0,0 +1,46 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +FIND_PACKAGE(ArcGIS REQUIRED) + + +# LIBRARY +SET(FunctionDialog_SRCS + function_dialog.cpp + simple_esri_layer.cpp +) + +SET(FunctionDialog_HDRS + function_dialog_export.h + function_dialog.h + simple_esri_layer.h +) + +SET(FunctionDialog_ICONS + state_icon_b.xpm + state_icon_a.xpm + state_icon_c.xpm +) + +SOURCE_GROUP(FunctionDialog FILES ${FunctionDialog_SRCS} ${FunctionDialog_HDRS}) +NGL_ADD_LIBRARY(WS situationMapFunctionDialog SHARED ${FunctionDialog_SRCS} ${FunctionDialog_HDRS}) + +NGL_TARGET_LINK_LIBRARIES(situationMapFunctionDialog + PRIVATE + NGLLogger + LtsCoordTrans + LtsWidgets + advancedSymbologyGui + PUBLIC + ArcGIS::ArcGIS + situationMap + situationMapObjects + situationMapPortal + Qt6::Core + Qt6::Widgets +) + +NGL_INSTALL_TARGET(situationMapFunctionDialog) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.cpp new file mode 100755 index 000000000..bb4e3cdbd --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.cpp @@ -0,0 +1,8906 @@ +/** + * @RDE_HEADER@ + * FunctionDialog + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "function_dialog.h" +#include "simple_esri_layer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +// Portal +#include + +#include +#include +#include + +// RME LocalServer +#ifdef USE_LOCAL_SERVER +# include +#endif // USE_LOCAL_SERVER + + +// ESRI +#include +//#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include + +#include +#include +#include +#include + +// 3D +#include +#include + + + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +void appendQPointF(SituationMap::PositionCont &posCont, const QPointF &point) +{ + posCont.append(SituationMap::Point3D::fromQPointF(point)); +} + + +void drawEllipse(Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + const Esri::ArcGISRuntime::Point &mapPoint, + const double &semiAxis1Length, + const double &semiAxis2Length, + const double &maxSegmentLength, + const QColor &color) +{ + Esri::ArcGISRuntime::GeodesicEllipseParameters ePara; + ePara.setGeometryType(Esri::ArcGISRuntime::GeometryType::Polygon); + + // 1 + ePara.setMaxSegmentLength(maxSegmentLength); + + // 2 + Esri::ArcGISRuntime::Point normalizedPt = + Esri::ArcGISRuntime::GeometryEngine::normalizeCentralMeridian(mapPoint); + ePara.setCenter(normalizedPt); + + // 3, 4 + ePara.setSemiAxis1Length(semiAxis1Length); + ePara.setSemiAxis2Length(semiAxis2Length); + + // 5 + ePara.setAxisDirection(/*axisDirection*/90); + + // Unit??????? + + Esri::ArcGISRuntime::Geometry ellipseGeometry = Esri::ArcGISRuntime::GeometryEngine::ellipseGeodesic(ePara); + + if (ellipseGeometry.isEmpty()) + qWarning() << " There was an error computing the geodesic ellipse. Try a different segment length."; + else + { + Esri::ArcGISRuntime::Polygon polygon(ellipseGeometry); + + QColor fillColor = color; + fillColor.setAlpha(150); + + Esri::ArcGISRuntime::Graphic *ellipseGraphic = new Esri::ArcGISRuntime::Graphic(); + ellipseGraphic->setGeometry(polygon); + Esri::ArcGISRuntime::SimpleFillSymbol ellipseSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, fillColor); + ellipseGraphic->setSymbol(&ellipseSymbol); + graphicsLayer->graphics()->append(ellipseGraphic); + +// for (int count = 0; count < ellipse.pointCount(); count++) +// { +// EsriRuntimeQt::Graphic *pointGraphic = +// new EsriRuntimeQt::Graphic(ellipse.point(count), EsriRuntimeQt::SimpleMarkerSymbol(color, 3)); +// graphicsLayer->graphics()->append(pointGraphic); +// } + } + +} + + +namespace SituationMap +{ +static LtsCoordTrans::UtmCoordinate s_basePoint; + +static OperatorMapSettings & s_operatorMapSettings() +{ + static OperatorMapSettings operatorMapSettings; + return operatorMapSettings; +} + + +class FunctionDialog::Private +{ +public: + static QHash s_activeFunctionDialogMap; + static void cleanupActiveFunctionDialogMap() + { + s_activeFunctionDialogMap.clear(); + } + + Private() = default; + ~Private() = default; + + // ESRI data: + Esri::ArcGISRuntime::Map *m_esriMap = nullptr; + Esri::ArcGISRuntime::GeoView *m_mapGeoView = nullptr; + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayerDict = nullptr; + + // OWN data: + RenderingMode m_renderMode = RenderingMode::Dynamic; + SituationMap::Map *m_mapInterface = nullptr; + SituationMap::MapImpl *m_mapImpl = nullptr; + QLabel *m_lbMapPosition = nullptr; + QLabel *m_lbPixelSize = nullptr; + + QPushButton *m_pbDump = nullptr; + QPushButton *m_pbGrid = nullptr; + QPushButton *m_pbVisibility = nullptr; + QPushButton *m_pbVisibilityTechn = nullptr; + QPushButton *m_pbVisibilityTact = nullptr; + + QPushButton *m_pbSelected = nullptr; + + QPushButton *m_pbPicking = nullptr; + QList m_objectsWithBoundingBox; + + QPushButton *m_pbBoundingBox = nullptr; + + QPushButton *m_pbBB2 = nullptr; + + QPushButton *m_pbPickByArea = nullptr; + + QDial *m_dialMapRotation = nullptr; + float getMapRotation() + { + float rotationAngle = 1.0; + if (m_dialMapRotation) + rotationAngle = m_dialMapRotation->value(); + return rotationAngle; + } + + + QSlider *m_slSymbolSize = nullptr; + float getScale() + { + float scale = 1.0f; + if (m_slSymbolSize) + scale = m_slSymbolSize->value() / 100.0f; + return scale; + } + + QSlider *m_slSymbolOpacity = nullptr; + float getSymbolOpacity() + { + float opac = 1.0f; + if (m_slSymbolOpacity) + opac = m_slSymbolOpacity->value() / 100.0f; + return opac; + } + + + QSlider *m_slDrawOrder = nullptr; + int getDrawOrder() + { + int drawOrder = 1; + if (m_slDrawOrder) + drawOrder = m_slDrawOrder->value(); + return drawOrder; + } + + QPushButton *m_pbOverviewMap = nullptr; + QPushButton *m_pbBackupOms = nullptr; + QPushButton *m_pbRestoreOms = nullptr; + + QPushButton *m_pbRemove = nullptr; + QPushButton *m_pbRemoveAll = nullptr; + QPushButton *m_pbClose = nullptr; + + QTabWidget *m_tabwidget = nullptr; + + SimpleEsriLayer *m_simpleEsriLayer = nullptr; + int m_simpleEsriLayerIndex = -1; + + LayerControlWidget *m_layerControlWidget = nullptr; + int m_layerControlIndex = -1; + + + // Performance: + QLineEdit *m_layerPerf_leLayer = nullptr; + QLineEdit *m_layerPerf_leObject = nullptr; + QLineEdit *m_layerPerf_leRadius = nullptr; + QCheckBox *m_layerPerf_cbText = nullptr; + + + // Tools/Viewshed + QLineEdit *m_viewshedDistance = nullptr; + + QLineEdit *m_sightFanObserverHeight = nullptr; + QLineEdit *m_sightFanTargetHeight = nullptr; + + + bool m_visibility = true; + bool m_visibilityTechn = true; + bool m_visibilityTact = true; + + bool m_selected = false; + + bool m_pickable = true; + + QLineEdit *m_leSidc = nullptr; + + + QComboBox *m_cbMultiPointSidc = nullptr; + Esri::ArcGISRuntime::TextSymbol *m_textSymbolMultiPointSidc = nullptr; + Esri::ArcGISRuntime::Graphic *m_tsGraphicMultiPointSidc = nullptr; + + + QComboBox *m_cbRubSidc = nullptr; + + QPointF m_objectBasePoint = QPointF(0, 0); + + // Graphics3D + struct object3D_data + { + std::shared_ptr m_graphic; + Esri::ArcGISRuntime::Point m_position; + }; + QVector m_impactCircleSphere; + QVector m_impactCone; + QVector m_3dModel; + QVector m_impactConeViaUniqueValueRenderer; + + + + // + static void clearMapObjectPtr(RefPointer mapObj) + { + if (mapObj) + mapObj->removeMySelf(); + } + + RefPointer m_polygon; + RefPointer m_polyline; + RefPointer m_text; + + // + RefPointer m_participant; + + RefPointer m_areaFire; + RefPointer m_building; + RefPointer m_bAgent; + RefPointer m_cAgent; + RefPointer m_cube; + RefPointer m_dangerArea; + RefPointer m_functionalArea; + RefPointer m_geoImage; + RefPointer m_soundArea; + RefPointer m_hitLine; + RefPointer m_lineOfSight; + RefPointer m_mine; + RefPointer m_mineField; + RefPointer m_nuclearAgent; + RefPointer m_virtualSingleMineAgent; + RefPointer m_singleFire; + + RefPointer m_basePoint; + + const QList< RefPointer > getObjList() + { + QList > objList = + m_mapInterface->objectFactory().getAllObjects(); + return objList; + } + + +}; // class FunctionDialog::Private +QHash FunctionDialog::Private::s_activeFunctionDialogMap; +//----------------------------------------------------------------------------- + +FunctionDialog::FunctionDialog(Esri::ArcGISRuntime::Map *esriMap, + Esri::ArcGISRuntime::MapGraphicsView *esriView, + QWidget *parent) + : QWidget(parent) + , d(new Private) +{ + qDebug() << LOGFUNCTION << " BEGIN"; + this->setObjectName(QLatin1String("SituationMapFunctionDialog")); + d->m_esriMap = esriMap; + d->m_mapGeoView = esriView; + + qDebug() << LOGFUNCTION << " BEGIN"; + + init(); +} + +FunctionDialog::FunctionDialog(Map *map) + : QWidget(nullptr) + , d(new Private) +{ + qDebug() << LOGFUNCTION << " BEGIN"; + this->setObjectName(QLatin1String("SituationMapFunctionDialog")); + d->m_mapInterface = map; + d->m_mapImpl = dynamic_cast(d->m_mapInterface); + + if (d->m_mapImpl) + { + d->m_esriMap = d->m_mapImpl->getEsriMap(); + d->m_mapGeoView = d->m_mapImpl->getEsriGeoView(); + + // define + QLatin1String layerName("FunctionDialog"); + d->m_renderMode = RenderingMode::Dynamic; + + // prepare + LayerFactoryImpl &layerFactory = d->m_mapImpl->layerFactoryImpl(); + layerFactory.addOperationalLayer(layerName, layerName, d->m_renderMode); + + // create corresponding layer: + d->m_graphicsLayer = layerFactory.getGraphicsLayer(layerName); + d->m_graphicsLayerDict = layerFactory.getGraphicsLayerDict(layerName); + } + + init(); + + connect(map, &Map::beginDestroy, this, &FunctionDialog::onDestroyed); + + FunctionDialog::Private::s_activeFunctionDialogMap[d->m_mapInterface] = this; + + qAddPostRoutine(FunctionDialog::Private::cleanupActiveFunctionDialogMap); + +} + +FunctionDialog::~FunctionDialog() +{ + qDebug() << LOGFUNCTION << " END"; + FunctionDialog::Private::s_activeFunctionDialogMap.remove(d->m_mapInterface); + delete d; +} + + +void FunctionDialog::onDestroyed() +{ + qDebug() << LOGFUNCTION << " commit suicide"; + delete this; +} + + +void FunctionDialog::addButtonToMap(Map *map, bool isVisible) +{ + if (!map || !map->getQGraphicsView()) + { + qWarning() << LOGFUNCTION << " no map!!!"; + std::cerr << LOGFUNCTION << " no map!!!" << std::endl; + return; + } + + // create button: + QPushButton *pbFunctionDialog = new QPushButton(QLatin1String("FunctionDialog")); + pbFunctionDialog->setObjectName(QLatin1String("QPushButton-FunctionDialog")); + pbFunctionDialog->setAttribute(Qt::WA_AcceptTouchEvents); + // add button +// MapImpl *mapImpl = static_cast(); +// EsriRuntimeQt::MapGraphicsView *mapGraphicsView = mapImpl->getEsriMapGraphicsView(); + + // Bug iOSQz-000005 'QGraphicsProxyWidget will crash' + // Adding a widget will let the application crash + // Workaround: 'proxy->setOpacity(0.99);' + QGraphicsProxyWidget *proxy = map->getQGraphicsView()->scene()->addWidget(pbFunctionDialog); + proxy->setOpacity(0.99); + + // enable/disable button + pbFunctionDialog->setStyleSheet(QLatin1String("QPushButton {background-color: red;}")); + pbFunctionDialog->setEnabled(false); + + QPointer mapNotifier = map->createMapNotifier(); + if (mapNotifier) + { + connect(mapNotifier.data(), &MapNotifier::mapReadyAndSymbolDictionaryLoaded, map, [pbFunctionDialog] () + { + pbFunctionDialog->setStyleSheet(QLatin1String("QPushButton {background-color: gray;}")); + pbFunctionDialog->setEnabled(true); + }); + mapNotifier->run(); + } + + pbFunctionDialog->setVisible(isVisible); + + + // add action to button + // c++11 -> lambda :-) + connect(pbFunctionDialog, &QPushButton::clicked, map, [map]() + { + + // Keep multiple MapWidget in mind! + if (!FunctionDialog::Private::s_activeFunctionDialogMap.contains(map)) + FunctionDialog::Private::s_activeFunctionDialogMap[map] = new SituationMap::FunctionDialog(map); + + SituationMap::FunctionDialog *functionDialog = FunctionDialog::Private::s_activeFunctionDialogMap[map]; + +// // map->getQGraphicsView()->scene()->addWidget(functionDialog); // just for testing + + functionDialog->raise(); + functionDialog->show(); + functionDialog->move(100, 100); + }, Qt::QueuedConnection); + +} + +void FunctionDialog::init() +{ + setAttribute(Qt::WA_DeleteOnClose); + + s_basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(QLatin1String("32UMD000000000000")); // Bremen 32UMD000000000000 +// s_basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(QLatin1String("32TNT000000000000")); // +// s_basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(QLatin1String("32UPD000000000000")); // Salchau 32UPD000000000000 + qWarning() << " s_basePoint: " + << " x=" << s_basePoint.x + << " y=" << s_basePoint.y; + + qint64 myPid = QCoreApplication::applicationPid(); + QString symType(QLatin1String("noSymType")); + if (d->m_mapImpl) + symType = toString(d->m_mapImpl->symbologyType()); + + QString title = QStringLiteral("FunctionDialog (symType=%1) (Rendering=%2) (pid=%3)") + .arg(symType, toString(d->m_renderMode)) + .arg(myPid); + + setWindowTitle(title); + + QHBoxLayout *layout = new QHBoxLayout(this); + + QVBoxLayout *leftLayout = new QVBoxLayout(); + layout->addLayout(leftLayout); + + + QPushButton *pbToBremen = new QPushButton(QLatin1String("Center to Bremen"), this); + leftLayout->addWidget(pbToBremen); + connect(pbToBremen, &QPushButton::clicked, this, [this]() + { + if (d->m_mapImpl->isReady()) + d->m_mapImpl->setCenterPoint(QPointF(0.155, 0.925)); + }); + + + QComboBox *cbCenterTo = new QComboBox(this); + leftLayout->addWidget(cbCenterTo); + + QVariant data; + data.setValue(QPointF(0.155, 0.925)); + cbCenterTo->addItem(QStringLiteral("Bremen"), data); + + data.setValue(QPointF(0.158, 0.928)); + cbCenterTo->addItem(QStringLiteral("Schnoeggersburg"), data); + + data.setValue(QPointF(0.155, 0.925)); + cbCenterTo->addItem(QStringLiteral("AHTC"), data); + + connect(cbCenterTo, static_cast(&QComboBox::currentIndexChanged), this, [this, cbCenterTo](int) + { + if (!d->m_mapImpl->isReady()) + return; + QPointF p = cbCenterTo->currentData().toPointF(); + d->m_mapImpl->setCenterPoint(p); + }); + + + + d->m_lbMapPosition = new QLabel; + leftLayout->addWidget(d->m_lbMapPosition); + + d->m_lbPixelSize = new QLabel; + leftLayout->addWidget(d->m_lbPixelSize); + + + d->m_tabwidget = new QTabWidget(); + leftLayout->addWidget(d->m_tabwidget); + + // make connection to receive mouse position signals + + connect(d->m_mapInterface, &Map::mouseWindowPositionChanged, this, &FunctionDialog::onMouseWindowPositionChanged); + connect(d->m_mapInterface, &Map::mouseMapPositionChanged , this, &FunctionDialog::onMouseMapPositionChanged); + connect(d->m_mapInterface, &Map::mouseGeoPositionChanged , this, &FunctionDialog::onMouseGeoPositionChanged); + + connect(d->m_mapInterface, &Map::extentChanged, this, &FunctionDialog::onExtendChanged, Qt::QueuedConnection); + + //----------------------------------- + // Visibility + //----------------------------------- + QVBoxLayout *vBoxLayout = new QVBoxLayout(); + layout->addLayout(vBoxLayout); + + if (d->m_mapImpl) + { + d->m_pbGrid = new QPushButton(QLatin1String("Grid")); + if (d->m_mapImpl->grid().isVisible()) + d->m_pbGrid->setText(QLatin1String("Grid is ON ")); + else + d->m_pbGrid->setText(QLatin1String("Grid is OFF")); + vBoxLayout->addWidget(d->m_pbGrid); + connect(d->m_pbGrid, &QPushButton::clicked, this, &FunctionDialog::onToggleGrid); + } + + d->m_pbVisibility = new QPushButton(QLatin1String("Visibility")); + if (d->m_visibility) + d->m_pbVisibility->setText(QLatin1String("Visibility is ON ")); + else + d->m_pbVisibility->setText(QLatin1String("Visibility is OFF")); + vBoxLayout->addWidget(d->m_pbVisibility); + connect(d->m_pbVisibility, &QPushButton::clicked, this, &FunctionDialog::onToggleVisibility); + + d->m_pbVisibilityTechn = new QPushButton(QLatin1String("VisiTechn.")); + if (d->m_visibilityTechn) + d->m_pbVisibilityTechn->setText(QLatin1String("VisiTechn is ON ")); + else + d->m_pbVisibilityTechn->setText(QLatin1String("Visibility is OFF")); + vBoxLayout->addWidget(d->m_pbVisibilityTechn); + connect(d->m_pbVisibilityTechn, &QPushButton::clicked, this, &FunctionDialog::onToggleVisibilityTechn); + + d->m_pbVisibilityTact = new QPushButton(QLatin1String("VisiTact.")); + if (d->m_visibilityTact) + d->m_pbVisibilityTact->setText(QLatin1String("VisiTact is ON ")); + else + d->m_pbVisibilityTact->setText(QLatin1String("VisiTact is OFF")); + vBoxLayout->addWidget(d->m_pbVisibilityTact); + connect(d->m_pbVisibilityTact, &QPushButton::clicked, this, &FunctionDialog::onToggleVisibilityTact); + + + d->m_pbSelected = new QPushButton(QLatin1String("Selection")); + if (d->m_selected) + d->m_pbSelected->setText(QLatin1String("Selection is ON ")); + else + d->m_pbSelected->setText(QLatin1String("Selection is OFF")); + vBoxLayout->addWidget(d->m_pbSelected); + connect(d->m_pbSelected, &QPushButton::clicked, this, &FunctionDialog::onToggleSelection); + + d->m_pbPicking = new QPushButton(QLatin1String("Picking")); + if (d->m_pickable) + d->m_pbPicking->setText(QLatin1String("Picking is ON ")); + else + d->m_pbPicking->setText(QLatin1String("Picking is OFF")); + vBoxLayout->addWidget(d->m_pbPicking); + connect(d->m_pbPicking, &QPushButton::clicked, this, &FunctionDialog::onTogglePicking); + + + d->m_pbBoundingBox = new QPushButton(QLatin1String("BoundingBox is ON ")); + if (d->m_objectsWithBoundingBox.isEmpty()) + d->m_pbBoundingBox->setText(QLatin1String("BoundingBox is OFF")); + else + d->m_pbBoundingBox->setText(QLatin1String("BoundingBox is ON")); + vBoxLayout->addWidget(d->m_pbBoundingBox); + connect(d->m_pbBoundingBox, &QPushButton::clicked, this, &FunctionDialog::onToggleBoundingBox); + + + d->m_pbBB2 = new QPushButton(QLatin1String("BB2")); + vBoxLayout->addWidget(d->m_pbBB2); + connect(d->m_pbBB2, &QPushButton::clicked, this, &FunctionDialog::onToggleBB2); + + + d->m_pbPickByArea = new QPushButton(QLatin1String("pick by area")); + vBoxLayout->addWidget(d->m_pbPickByArea); + connect(d->m_pbPickByArea, &QPushButton::clicked, this, [this]() + { + d->m_mapInterface->pickMapObjectsArea(); + }); + + + // picking: + connect(d->m_mapInterface, &Map::mapObjectsPickedByArea, this, [](const SituationMap::Map::RefPointerMapObjectList & mapObjList) + { + qCritical() << " GUI is not available anymore, moved to AdapterDds."; + + QList uniqueMapIds; + uniqueMapIds.reserve(mapObjList.size()); + + for (RefPointer mapObj : mapObjList) + { + uniqueMapIds.append(mapObj->uniqueMapId()); + + qCritical() << " mapObjectsPickedByArea: " << mapObj->uniqueMapId() + << " , mapObj->debugInfo: " << mapObj->debugInfo(); + } + + }); + + + vBoxLayout->addWidget(new QLabel(QLatin1String("MapRotation:"))); + + QPushButton *pbMapRotation = new QPushButton(QLatin1String("MapRotation to 0")); + pbMapRotation->setToolTip(QLatin1String("reset rotation")); + vBoxLayout->addWidget(pbMapRotation); + + d->m_dialMapRotation = new QDial(); + d->m_dialMapRotation->setMinimum(-180); + d->m_dialMapRotation->setMaximum(180); + d->m_dialMapRotation->setValue(0); + +// d->m_dialMapRotation->setTickInterval(10); + d->m_dialMapRotation->setSingleStep(10); + + vBoxLayout->addWidget(d->m_dialMapRotation); + connect(d->m_dialMapRotation, &QSlider::valueChanged, this, &FunctionDialog::onMapRotationChanged); + + // enable 'reset' + connect(pbMapRotation, &QPushButton::pressed, this, [this]() + { + d->m_dialMapRotation->setValue(0); // reset + }); + + + + QLabel *lbSymScale = new QLabel(QLatin1String("SymSize:")); + vBoxLayout->addWidget(lbSymScale); + + d->m_slSymbolSize = new QSlider(Qt::Horizontal); + d->m_slSymbolSize->setMinimum(5); + d->m_slSymbolSize->setMaximum(500); + d->m_slSymbolSize->setValue(100); + + d->m_slSymbolSize->setTickInterval(50); + d->m_slSymbolSize->setSingleStep(1); + + vBoxLayout->addWidget(d->m_slSymbolSize); + connect(d->m_slSymbolSize, &QSlider::valueChanged, this, &FunctionDialog::onSymbolSizeChanged); + + + QLabel *lbSymOpacity = new QLabel(QLatin1String("SymOpacity:")); + vBoxLayout->addWidget(lbSymOpacity); + + d->m_slSymbolOpacity = new QSlider(Qt::Horizontal); + d->m_slSymbolOpacity->setMinimum(10); + d->m_slSymbolOpacity->setMaximum(90); + d->m_slSymbolOpacity->setValue(50); + + d->m_slSymbolOpacity->setTickInterval(10); + d->m_slSymbolOpacity->setSingleStep(10); + + vBoxLayout->addWidget(d->m_slSymbolOpacity); + connect(d->m_slSymbolOpacity, &QSlider::valueChanged, this, &FunctionDialog::onSymbolOpacityChanged); + + + QLabel *lbDrawOrder = new QLabel(QLatin1String("DrawOrder:")); + vBoxLayout->addWidget(lbDrawOrder); + + d->m_slDrawOrder = new QSlider(Qt::Horizontal); + d->m_slDrawOrder->setMinimum(-100); + d->m_slDrawOrder->setMaximum(100); + d->m_slDrawOrder->setValue(0); + + d->m_slDrawOrder->setTickInterval(1); + d->m_slDrawOrder->setSingleStep(1); + + vBoxLayout->addWidget(d->m_slDrawOrder); + connect(d->m_slDrawOrder, &QSlider::valueChanged, this, &FunctionDialog::onDrawOrderChanged); + + + //----------------------------------- + vBoxLayout->addStretch(); + + d->m_pbOverviewMap = new QPushButton(QLatin1String("OverviewMap on/off")); + vBoxLayout->addWidget(d->m_pbOverviewMap); + connect(d->m_pbOverviewMap, &QPushButton::clicked, this, &FunctionDialog::onOverviewMap); + + + //----------------------------------- + // Operator Map Settings + //----------------------------------- + d->m_pbBackupOms = new QPushButton(QLatin1String("backup Operator Map Settings")); + vBoxLayout->addWidget(d->m_pbBackupOms); + connect(d->m_pbBackupOms, &QPushButton::clicked, this, &FunctionDialog::onBackupOms); + + d->m_pbRestoreOms = new QPushButton(QLatin1String("restore Operator Map Settings")); + vBoxLayout->addWidget(d->m_pbRestoreOms); + connect(d->m_pbRestoreOms, &QPushButton::clicked, this, &FunctionDialog::onRestoreOms); + + + //----------------------------------- + // Remove + //----------------------------------- + d->m_pbRemove = new QPushButton(QLatin1String("Remove couple")); + vBoxLayout->addWidget(d->m_pbRemove); + connect(d->m_pbRemove, &QPushButton::clicked, this, &FunctionDialog::onRemove); + + d->m_pbRemoveAll = new QPushButton(QLatin1String("Remove ALL")); + vBoxLayout->addWidget(d->m_pbRemoveAll); + connect(d->m_pbRemoveAll, &QPushButton::clicked, this, &FunctionDialog::onRemoveAll); + + + //----------------------------------- + // Dump + //----------------------------------- + d->m_pbDump = new QPushButton(QLatin1String("Dump (ESRI)")); + vBoxLayout->addWidget(d->m_pbDump); + connect(d->m_pbDump, &QPushButton::clicked, this, &FunctionDialog::onDump); + + // Screenshot + QPushButton *pbScreenshot = new QPushButton(QLatin1String("Screenshot - grab()")); + vBoxLayout->addWidget(pbScreenshot); + connect(pbScreenshot, &QPushButton::clicked, this, []() + { + QString imageName = QDir::tempPath() + QLatin1String("/sitMapScreenshot.png"); + qWarning() << " save to '" << imageName << "'... ToDo."; + }); + + + // toPdf + QPushButton *pbToPdf = new QPushButton(QLatin1String("toPdf")); + vBoxLayout->addWidget(pbToPdf); + connect(pbToPdf, &QPushButton::clicked, this, &FunctionDialog::onToPdf); + + + + //----------------------------------- + // Quit + //----------------------------------- + d->m_pbClose = new QPushButton(QLatin1String("Close")); + vBoxLayout->addWidget(d->m_pbClose, 2); + // c++11 -> lambda :-) + connect(d->m_pbClose, &QPushButton::clicked, this, &FunctionDialog::close); + + + //----------------------------------- + // Tabs + //----------------------------------- + this->addSimpleLayerTab(); + this->addLogicalLayerTab(); + this->addLayerPerformanceTab(); + this->addGraphicsTab(); + this->addGraphics3DTab(); + this->addMessageTab(); + this->addMapObjectTab(); + this->addRubberbandTab(); + this->addRubberband3DTab(); + this->addToolsTab(); + this->addViewshedTab(); + this->addOverlayPlanTab(); + this->addIconRenderTab(); + this->addPortalTab(); + this->addMovePointLuxemburgTab(); + // this->addYourTab(); + + // c++11 -> lambda :-) + connect(d->m_tabwidget, &QTabWidget::currentChanged, this, [this]() + { + int curIdx = d->m_tabwidget->currentIndex(); + if (curIdx == d->m_simpleEsriLayerIndex) + d->m_simpleEsriLayer->updatePanel(); + else if (curIdx == d->m_layerControlIndex) + d->m_layerControlWidget->updatePanel(); + }); + + d->m_tabwidget->setCurrentIndex(7); + + + // Access symbolDictionary to create the dictionary at beginning + if (d->m_mapImpl) + d->m_mapImpl->symbolDictionary(); +} + + +void FunctionDialog::setGraphicsLayer(Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer) +{ + d->m_graphicsLayer = graphicsLayer; +} + + +void FunctionDialog::setMapPositionString(const QString &mapPosition) +{ + if (d->m_lbMapPosition) + d->m_lbMapPosition->setText(mapPosition); +} + +void FunctionDialog::setPixelSizeString(const QString &text) +{ + if (d->m_lbPixelSize) + d->m_lbPixelSize->setText(text); +} + +void FunctionDialog::addMapPositionString(const QString &mapPosition) +{ + if (d->m_lbMapPosition) + d->m_lbMapPosition->setText(d->m_lbMapPosition->text() + mapPosition); +} + +void FunctionDialog::addSimpleLayerTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + if (!d->m_simpleEsriLayer) + { + Esri::ArcGISRuntime::MapGraphicsView *mapGraphicsView = + dynamic_cast(d->m_mapGeoView); + if (mapGraphicsView) + d->m_simpleEsriLayer = new SimpleEsriLayer(this, mapGraphicsView); + } + mainLayout->addWidget(d->m_simpleEsriLayer); + mainLayout->addStretch(); + + d->m_simpleEsriLayerIndex = d->m_tabwidget->addTab(w, QLatin1String("SimpleLayer")); + + +} + +void FunctionDialog::addLogicalLayerTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + if (!d->m_layerControlWidget) + { + d->m_layerControlWidget = new LayerControlWidget(this, d->m_mapInterface, + true, // enableLayer + true, // enableBaseMap + QLatin1String("LC-FunctionDialog")); + } + mainLayout->addWidget(d->m_layerControlWidget); + mainLayout->addStretch(); + + d->m_layerControlIndex = d->m_tabwidget->addTab(w, QLatin1String("LogicalLayer")); +} + +void FunctionDialog::addLayerPerformanceTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + + d->m_tabwidget->addTab(w, QLatin1String("LayerPerf.")); + + QLabel *lb; + + // + lb = new QLabel(QLatin1String("# of Layer")); + layout->addWidget(lb, 0, 0); + + d->m_layerPerf_leLayer = new QLineEdit(QLatin1String("1")); + d->m_layerPerf_leLayer->setPlaceholderText(QLatin1String("# of Layer")); + d->m_layerPerf_leLayer->setToolTip(d->m_layerPerf_leLayer->placeholderText()); + layout->addWidget(d->m_layerPerf_leLayer, 1, 0); + + // + lb = new QLabel(QLatin1String("# of Objects (total)")); + layout->addWidget(lb, 0, 1); + + d->m_layerPerf_leObject = new QLineEdit(QLatin1String("1000")); + d->m_layerPerf_leObject->setPlaceholderText(QLatin1String("# of Objects")); + d->m_layerPerf_leObject->setToolTip(d->m_layerPerf_leObject->placeholderText()); + layout->addWidget(d->m_layerPerf_leObject, 1, 1); + + + // + lb = new QLabel(QLatin1String("radius")); + layout->addWidget(lb, 0, 2); + + d->m_layerPerf_leRadius = new QLineEdit(QLatin1String("100")); + d->m_layerPerf_leRadius->setPlaceholderText(QLatin1String("radius")); + d->m_layerPerf_leRadius->setToolTip(d->m_layerPerf_leRadius->placeholderText()); + layout->addWidget(d->m_layerPerf_leRadius, 1, 2); + + // + lb = new QLabel(QLatin1String("with Text")); + layout->addWidget(lb, 0, 3); + + d->m_layerPerf_cbText = new QCheckBox; + d->m_layerPerf_cbText->setToolTip(QLatin1String("with Text")); + layout->addWidget(d->m_layerPerf_cbText, 1, 3); + + + QPushButton *pbStart = new QPushButton(QLatin1String("generate Layer")); + layout->addWidget(pbStart); + connect(pbStart, &QPushButton::clicked, this, &FunctionDialog::onLayerPerfCreateLayer); + +} + + +void FunctionDialog::addGraphicsTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Graphics")); + + QPushButton *pbGraphics = new QPushButton(QLatin1String("generate Graphics")); + layout->addWidget(pbGraphics); + connect(pbGraphics, &QPushButton::clicked, this, &FunctionDialog::onGenerateGraphics); + + QPushButton *pbGenerateCube = new QPushButton(QLatin1String("generate/destroy Cube")); + layout->addWidget(pbGenerateCube); + connect(pbGenerateCube, &QPushButton::clicked, this, &FunctionDialog::onGenerateCube); + + QPushButton *pbGenerateCubeArray = new QPushButton(QLatin1String("generate/destroy CubeArray")); + layout->addWidget(pbGenerateCubeArray); + connect(pbGenerateCubeArray, &QPushButton::clicked, this, &FunctionDialog::onGenerateCubeArray); + + QPushButton *pbGenerateStackedCubes = new QPushButton(QLatin1String("generate stacked Cubes")); + layout->addWidget(pbGenerateStackedCubes); + connect(pbGenerateStackedCubes, &QPushButton::clicked, this, &FunctionDialog::onGenerateStackedCubes); + + QPushButton *pbGraphicsKristenStrandford = new QPushButton(QLatin1String("generate Graphics - KristenStrandford")); + layout->addWidget(pbGraphicsKristenStrandford); + connect(pbGraphicsKristenStrandford, &QPushButton::clicked, this, &FunctionDialog::onGenerateGraphicsKristenStrandford); +} + +void FunctionDialog::addGraphics3DTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Graphics 3D")); + + // + // Sphere (Kugel) - RENDERER + { + QPushButton *pbGraphics = new QPushButton(QLatin1String("generate 3D-Graphic Sphere RENDERER")); + layout->addWidget(pbGraphics); + connect(pbGraphics, &QPushButton::clicked, this, &FunctionDialog::onGenerate3DGraphicSphereRENDERER); + + QSlider *slOpcacity = new QSlider(Qt::Horizontal); + slOpcacity->setMinimum(0); + slOpcacity->setMaximum(100); + layout->addWidget(slOpcacity); + connect(slOpcacity, &QSlider::valueChanged, this, &FunctionDialog::onSphereOpacityChangedRENDERER); + + QSlider *slZValue = new QSlider(Qt::Horizontal); + slZValue->setMinimum(-150); + slZValue->setMaximum(150); + layout->addWidget(slZValue); + connect(slZValue, &QSlider::valueChanged, this, &FunctionDialog::onSphereZValueChanged); + } + + + // + // Sphere (Kugel) + { + QPushButton *pbGraphics = new QPushButton(QLatin1String("generate 3D-Graphic Sphere")); + layout->addWidget(pbGraphics); + connect(pbGraphics, &QPushButton::clicked, this, &FunctionDialog::onGenerate3DGraphicSphere); + + QSlider *slOpcacity = new QSlider(Qt::Horizontal); + slOpcacity->setMinimum(0); + slOpcacity->setMaximum(100); + layout->addWidget(slOpcacity); + connect(slOpcacity, &QSlider::valueChanged, this, &FunctionDialog::onSphereOpacityChanged); + + QSlider *slZValue = new QSlider(Qt::Horizontal); + slZValue->setMinimum(-150); + slZValue->setMaximum(150); + layout->addWidget(slZValue); + connect(slZValue, &QSlider::valueChanged, this, &FunctionDialog::onSphereZValueChanged); + } + + + // + // Cone (Kegel) + { + QPushButton *pbCone = new QPushButton(QLatin1String("generate 3D-Graphic Cone")); + layout->addWidget(pbCone); + connect(pbCone, &QPushButton::clicked, this, &FunctionDialog::onGenerate3DGraphicCone); + + QSlider *slOpcacity = new QSlider(Qt::Horizontal); + slOpcacity->setMinimum(0); + slOpcacity->setMaximum(100); + layout->addWidget(slOpcacity); + connect(slOpcacity, &QSlider::valueChanged, this, &FunctionDialog::onConeOpacityChanged); + + QSlider *slZValue = new QSlider(Qt::Horizontal); + slZValue->setMinimum(-150); + slZValue->setMaximum(150); + layout->addWidget(slZValue); + connect(slZValue, &QSlider::valueChanged, this, &FunctionDialog::onConeZValueChanged); + + QSlider *slHeading = new QSlider(Qt::Horizontal); + slHeading->setMinimum(-180); + slHeading->setMaximum(180); + layout->addWidget(slHeading); + connect(slHeading, &QSlider::valueChanged, this, &FunctionDialog::onConeHeadingChanged); + + QSlider *slRoll = new QSlider(Qt::Horizontal); + slRoll->setMinimum(-180); + slRoll->setMaximum(180); + layout->addWidget(slRoll); + connect(slRoll, &QSlider::valueChanged, this, &FunctionDialog::onConeRollChanged); + + QSlider *slPitch = new QSlider(Qt::Horizontal); + slPitch->setMinimum(-180); + slPitch->setMaximum(180); + layout->addWidget(slPitch); + connect(slPitch, &QSlider::valueChanged, this, &FunctionDialog::onConePitchChanged); + } + + // + // 3D Model + { + QPushButton *pbGraphics = new QPushButton(QLatin1String("place 3D model (symbol and graphic)")); + layout->addWidget(pbGraphics); + connect(pbGraphics, &QPushButton::clicked, this, &FunctionDialog::on3dModel); + +// QSlider *slOpcacity = new QSlider(Qt::Horizontal); +// slOpcacity->setMinimum(0); +// slOpcacity->setMaximum(100); +// layout->addWidget(slOpcacity); +// connect(slOpcacity, &QSlider::valueChanged, this, &FunctionDialog::onSphereOpacityChangedRENDERER); + + QSlider *slZValue = new QSlider(Qt::Horizontal); + slZValue->setMinimum(-150); + slZValue->setMaximum(150); + layout->addWidget(slZValue); + connect(slZValue, &QSlider::valueChanged, this, &FunctionDialog::on3dModelZValueChanged); + } + + // + // Cones via UniqueValueRenderer + { + QPushButton *pbGraphics = new QPushButton(QStringLiteral("Cones via UniqueValueRenderer")); + layout->addWidget(pbGraphics); + connect(pbGraphics, &QPushButton::clicked, this, &FunctionDialog::onGenerate3DGraphicConeUniqueValueRenderer); + +// QSlider *slOpcacity = new QSlider(Qt::Horizontal); +// slOpcacity->setMinimum(0); +// slOpcacity->setMaximum(100); +// layout->addWidget(slOpcacity); +// connect(slOpcacity, &QSlider::valueChanged, this, &FunctionDialog::onSphereOpacityChangedRENDERER); + +// QSlider *slZValue = new QSlider(Qt::Horizontal); +// slZValue->setMinimum(-150); +// slZValue->setMaximum(150); +// layout->addWidget(slZValue); +// connect(slZValue, &QSlider::valueChanged, this, &FunctionDialog::on3dModelZValueChanged); + } + + +} + + +QSet listOfPointSidcs_Internal() +{ + QSet sidcs; + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10011500002102000000"); // mine_impl + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002108000000"); // single_fire_impl + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002707050000"); // 25270705 -> Protection Areas : Minefield : Dummy + + + // SymbolSet 01 == air + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10030100001101000000"); // differs in APP6(D) + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10030100001201000000"); + + // SymbolSet 10 == Land Unit + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031000001100000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031000001101000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031000001102000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031000001103000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031000001104000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031000001105000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031000001106000000"); + + // SymbolSet 15 == Land Equipment + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031500001110000000"); // baseSymbolCode = 15 111000 Missle Launcher + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031500001100000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031500001101000000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031500001101010000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031500001101020000"); + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031500001101030000"); + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10011000001213030000"); // Movement and Maneuver : Reconnaissance 10 121303 (FULL_FRAME, but no real MainIcon) + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10031500001202000000"); // Vehicle : Tank 15 120200 + + + // Guez-IEDs + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10011500002104000000"); // LAND MINE - IED + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10013500001202000000"); // Weapon - IED + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10014000001103000000"); // IED SUICIDE BOMBER (OHNE ZickZack) + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10014000001103010000"); // IED SUICIDE BOMBER (MIT ZickZack) + // ^ + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10014000001103040000"); // IED suicide bomber + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10014000001101110000"); // BOOBY TRAP (replaced by 'IED suicide bomber') + + return sidcs; +} + +QSet listOfLineSidcs_Internal() +{ + QSet sidcs; + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10032500001406030000"); // hit_line_impl + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500001402000000"); // 25140200_6 == "Maneuver Lines : Line of Contact" (no key8-Entry) + +#ifdef SYM_PROTECTION_AREAS_BECOMES_LINE + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002711000000"); // Control Measure: Protection Areas : + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002713000000"); // Control Measure: Protection Areas : Assult Crossing (former GeometryType::area, now GeometryType::Line) + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002714000000"); // Control Measure: Protection Areas : Bridge +#endif // SYM_PROTECTION_AREAS_BECOMES_LINE + + return sidcs; +} + +QSet listOfAreaSidcs_Internal() +{ + QSet sidcs; + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002408010000"); // area_fire_impl + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002717000000"); // biological_agent_impl + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10042500002701000000"); // building_outline_impl + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002718000000"); // chemical_agent_impl + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500001511000000"); // danger_area_impl +// sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D,"10012500002707010000"); // MineField + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002707060000"); // mine_field_impl + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002719000000"); // nuclear_agent_impl + +#ifndef SYM_PROTECTION_AREAS_BECOMES_LINE + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002711000000"); // Control Measure: Protection Areas : + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002713000000"); // Control Measure: Protection Areas : Assult Crossing (former GeometryType::area, now GeometryType::Line) + sidcs += AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6D, "10012500002714000000"); // Control Measure: Protection Areas : Bridge +#endif // SYM_PROTECTION_AREAS_BECOMES_LINE + + return sidcs; +} + +QSet listOfPlanEditorSidcs_Internal() +{ + QSet sidcs; + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25342300"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25140500"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25152100"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25141400"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25141700"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25290500"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25270503"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25341900"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25270504"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25230100"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25271100"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25341500"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25241202"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25241602"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25340800"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25270502"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25342201"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25271204"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25240103"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25240802"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25270501"); + sidcs += AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, "25271600"); + + return sidcs; +} + + + +void FunctionDialog::addMessageTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Message")); + + //----------------------------------- + QPushButton *pbAddCivilSymbols = new QPushButton(QLatin1String("add civil symbols")); + layout->addWidget(pbAddCivilSymbols); + connect(pbAddCivilSymbols, &QPushButton::clicked, this, &FunctionDialog::onAddCivilSymbols); + + //----------------------------------- + QPushButton *pbMessage = new QPushButton(QLatin1String("generate Message")); + layout->addWidget(pbMessage); + connect(pbMessage, &QPushButton::clicked, this, &FunctionDialog::onMessage); + + //----------------------------------- + QPushButton *pbSelectMessage = new QPushButton(QLatin1String("select Message")); + layout->addWidget(pbSelectMessage); + connect(pbSelectMessage, &QPushButton::clicked, this, &FunctionDialog::onMessageSelect); + + //----------------------------------- + QPushButton *pbSymbolOverview = new QPushButton(QLatin1String("overviewESRI - wait!!!")); + layout->addWidget(pbSymbolOverview); + + // c++11 -> lambda :-) + connect(pbSymbolOverview, &QPushButton::clicked, this, []() + { + AdvancedSymbology::Gui::BaseSymbolLookupDialog *bslDlg = + AdvancedSymbology::Gui::BaseSymbolLookupDialog::showInstance(); + + QSet sidcs; + sidcs += listOfPointSidcs_Internal(); + sidcs += listOfLineSidcs_Internal(); + sidcs += listOfAreaSidcs_Internal(); + sidcs += listOfPlanEditorSidcs_Internal(); + + QSet baseSidcs; + baseSidcs.reserve(sidcs.size()); + for(const auto &item : std::as_const(sidcs)) + { + QString res = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(item); + if (res.isEmpty()) + baseSidcs.insert(item); + else + baseSidcs.insert(res); + } // for + bslDlg->setValidBaseSymbolCodes(baseSidcs); + + bslDlg->show(); + }); + + //----------------------------------- + QPushButton *pbAddSymbol = new QPushButton(QLatin1String("add symbol from File")); + layout->addWidget(pbAddSymbol); + connect(pbAddSymbol, &QPushButton::clicked, this, &FunctionDialog::onAddSymbol); + + + //----------------------------------- + QGridLayout *someParticipantLayout = new QGridLayout(); + someParticipantLayout->setObjectName(QLatin1String("SomeParticipantsLayout")); + layout->addLayout(someParticipantLayout, layout->rowCount(), 0); + + QPushButton *pbSomeParticipants = new QPushButton(QLatin1String("Aggregation-Participants")); + someParticipantLayout->addWidget(pbSomeParticipants, 0, 0); + connect(pbSomeParticipants, &QPushButton::clicked, this, &FunctionDialog::onSomeParticipants); + + QSlider *slAggregationModifier = new QSlider(Qt::Horizontal); + + slAggregationModifier->setTickInterval(10); + slAggregationModifier->setSingleStep(10); + + someParticipantLayout->addWidget(slAggregationModifier, 0, 1); + + connect(slAggregationModifier, &QSlider::valueChanged, this, [this, slAggregationModifier]() + { + const int counter = 6; + int scaleMin = 80000; + int scaleMax = 500; + + int interval = (scaleMin - scaleMax) / counter; + + + LayerFactory &layerFac = d->m_mapInterface->layerFactory(); + int newVal = 0; + for (int i = 1; i <= counter; ++i) + { + const QString &layername = QStringLiteral("Participants_%1").arg(i); + LayerParameter layerPara = layerFac.layerParameter(layername); + + // double minScale = 0; ///< big value (e.g. 10000000) limiting zoomOut 1:10000000 -> 0.0000001 ; '0' without function + // double maxScale = 0; ///< small value (e.g. 1000) limiting zoomIn 1:1000 -> 0.001 ; '0' without function + + layerPara.maxScale = newVal; + + newVal = scaleMax + interval * i; + double userVal = slAggregationModifier->value() / 100.0; + newVal += interval * userVal; + newVal = std::min(newVal, scaleMin); + newVal = std::max(newVal, scaleMax); + + layerPara.minScale = newVal; + + if (i == counter) + layerPara.minScale = 0; + + qWarning() << LOGFUNCTION << " layer: " << layername << " -> " + << " userVal=" << userVal + << " max=" << layerPara.maxScale + << " min=" << layerPara.minScale; + + layerFac.setLayerParameter(layername, layerPara); + + + } // for + + }); + + slAggregationModifier->setMinimum(-200); + slAggregationModifier->setMaximum(200); + slAggregationModifier->setValue(0); + + + //----------------------------------- + // createParticipantButton + //----------------------------------- + QGridLayout *participantLayout = new QGridLayout(); + participantLayout->setObjectName(QLatin1String("participantLayout")); + layout->addLayout(participantLayout, layout->rowCount(), 0); + + d->m_leSidc = new QLineEdit(); + + switch (d->m_mapInterface->symbologyType()) + { + case AdvancedSymbology::SymbologyType::None: + d->m_leSidc->setText(AdvancedSymbology::SidcCodec::encodeCivilEts("StelleBerRaum")); + d->m_leSidc->setToolTip(QStringLiteral("

symbols added \?\?

")); + d->m_leSidc->setToolTipDuration(1000); + break; + case AdvancedSymbology::SymbologyType::APP6B: + case AdvancedSymbology::SymbologyType::MIL2525C: + d->m_leSidc->setText( AdvancedSymbology::SidcCodec::encodeComplete( d->m_mapInterface->symbologyType(), "SFGPUCD---D-") ); + break; + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + d->m_leSidc->setText(AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::MIL2525D, "10011501001202000000")); // 10015000001202000000 -> 15 120200 LandEquip, Tank + d->m_leSidc->setText(AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::MIL2525D, "10011001001110000000")); // 10031000001110000000 -> 10 111000 LandUnits, CommandAndControl Signal + d->m_leSidc->setText(AdvancedSymbology::SidcCodec::encodeCivilEts("Pers_FW_BerFue")); +// d->m_leSidc->setText(AdvancedSymbology::SidcCodec::encodeCivilEts("ETS_PersFueAllg")); + break; + } + + d->m_leSidc->setObjectName(QLatin1String("SIDC")); + QFont f = d->m_leSidc->font(); + f.setPixelSize(20); + d->m_leSidc->setFont(f); + participantLayout->addWidget(d->m_leSidc, 0, 0); + connect(d->m_leSidc, &QLineEdit::textChanged, this, [this]() + { + if (d->m_leSidc->text().length() > 20) + d->m_leSidc->setStyleSheet(QLatin1String("QLineEdit#SIDC { background-color: red; color: #000; }")); + else + d->m_leSidc->setStyleSheet(QLatin1String("QLineEdit#SIDC { background-color: white; color: #000; }")); + }); + + QPushButton *pbCreatePart = new QPushButton(QLatin1String("createParticipant!! \n symbols added?")); + pbCreatePart->setToolTip(d->m_leSidc->toolTip()); + participantLayout->addWidget(pbCreatePart, 0, 1); + + connect(pbCreatePart, &QPushButton::clicked, this, &FunctionDialog::onParticipantCreate); +} /* FunctionDialog::addMessageTab */ + + +void FunctionDialog::addMapObjectTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Map Objects (Factory)")); + + //----------------------------------- + // createPolygonButton + //----------------------------------- + QPushButton *pbPolygon = new QPushButton(QLatin1String("create Polygon")); + layout->addWidget(pbPolygon); + connect(pbPolygon, &QPushButton::clicked, + this, &FunctionDialog::onPolygonCreate); + + + //----------------------------------- + // createPolylineButton + //----------------------------------- + QPushButton *pbPolyline = new QPushButton(QLatin1String("create Polyline")); + layout->addWidget(pbPolyline); + connect(pbPolyline, &QPushButton::clicked, + this, &FunctionDialog::onPolylineCreate); + + //----------------------------------- + // createTextButton + //----------------------------------- + QPushButton *pbText = new QPushButton(QLatin1String("create Text")); + layout->addWidget(pbText); + connect(pbText, &QPushButton::clicked, + this, &FunctionDialog::onTextCreate); + + + //----------------------------------- + // createMapObjectIndicatorButton + //----------------------------------- + QPushButton *pbMapObjectIndicator = new QPushButton(QLatin1String("create MapObjectIndicator")); + layout->addWidget(pbMapObjectIndicator); + connect(pbMapObjectIndicator, &QPushButton::clicked, + this, &FunctionDialog::onMapObjectIndicatorCreate); + + + //----------------------------------- + // createBuildingOutlineButton + //----------------------------------- + QPushButton *pbCreateBuilding = new QPushButton(QLatin1String("create Building")); + layout->addWidget(pbCreateBuilding); + connect(pbCreateBuilding, &QPushButton::clicked, + this, &FunctionDialog::onBuildingCreate); + + //----------------------------------- + // createBAgentButton + //----------------------------------- + QPushButton *pbCreateBAgent = new QPushButton(QLatin1String("create B-Agent")); + layout->addWidget(pbCreateBAgent); + connect(pbCreateBAgent, &QPushButton::clicked, + this, &FunctionDialog::onBiologicalAgentCreate); + + //----------------------------------- + // createCAgentButton + //----------------------------------- + QPushButton *pbCreateCAgent = new QPushButton(QLatin1String("create C-Agent")); + layout->addWidget(pbCreateCAgent); + connect(pbCreateCAgent, &QPushButton::clicked, + this, &FunctionDialog::onChemicalAgentCreate); + + + //----------------------------------- + // createDangerAreaButton + //----------------------------------- + QPushButton *createDangerAreaButton = new QPushButton(QLatin1String("create DangerArea")); + layout->addWidget(createDangerAreaButton); + connect(createDangerAreaButton, &QPushButton::clicked, + this, &FunctionDialog::onDangerAreaCreate); + + //----------------------------------- + // createDangerAreaButton + //----------------------------------- + QPushButton *createFunctionalAreaButton = new QPushButton(QLatin1String("create FunctionalArea")); + layout->addWidget(createFunctionalAreaButton); + connect(createFunctionalAreaButton, &QPushButton::clicked, + this, &FunctionDialog::onFunctionalAreaCreate); + + //----------------------------------- + // createAreaFireButton + //----------------------------------- + QPushButton *createAreaFireButton = new QPushButton(QLatin1String("create AreaFire")); + layout->addWidget(createAreaFireButton); + connect(createAreaFireButton, &QPushButton::clicked, + this, &FunctionDialog::onAreaFireCreate); + + //----------------------------------- + // createSingleFireButton + //----------------------------------- + QPushButton *createSingleFireButton = new QPushButton(QLatin1String("create SingleFire")); + layout->addWidget(createSingleFireButton); + connect(createSingleFireButton, &QPushButton::clicked, + this, &FunctionDialog::onSingleFireCreate); + + //----------------------------------- + // createChargeEffectButton + //----------------------------------- + QPushButton *createChargeEffectButton = new QPushButton(QLatin1String("create Charge Effect")); + layout->addWidget(createChargeEffectButton); + connect(createChargeEffectButton, &QPushButton::clicked, + this, &FunctionDialog::onChargeEffectCreate); + + //----------------------------------- + // createMineButton + //----------------------------------- + QPushButton *createMineButton = new QPushButton(QLatin1String("create Mine")); + layout->addWidget(createMineButton); + connect(createMineButton, &QPushButton::clicked, + this, &FunctionDialog::onMineCreate); + + //----------------------------------- + // createMineButton + //----------------------------------- + QPushButton *createMineFieldButton = new QPushButton(QLatin1String("create MineField")); + layout->addWidget(createMineFieldButton); + connect(createMineFieldButton, &QPushButton::clicked, + this, &FunctionDialog::onMineFieldCreate); + + //----------------------------------- + // createNuclearButton + //----------------------------------- + QPushButton *createNuclearButton = new QPushButton(QLatin1String("create Nuclear")); + layout->addWidget(createNuclearButton); + connect(createNuclearButton, &QPushButton::clicked, + this, &FunctionDialog::onNuclearCreate); + + //----------------------------------- + // createVirtualSingleMineButton + //----------------------------------- + QPushButton *createVirtualSingleMineButton = new QPushButton(QLatin1String("create Virtual Single Mine")); + layout->addWidget(createVirtualSingleMineButton); + connect(createVirtualSingleMineButton, &QPushButton::clicked, + this, &FunctionDialog::onBoobyTrapCreate); + + + //----------------------------------- + // createHitLineButton + //----------------------------------- + QHBoxLayout *hBox = new QHBoxLayout(); + layout->addLayout(hBox, 99, 0); + + QPushButton *createHitLineButton = new QPushButton(QLatin1String("create HitLine")); + hBox->addWidget(createHitLineButton); + connect(createHitLineButton, &QPushButton::clicked, + this, &FunctionDialog::onHitLineCreate); + + QPushButton *hitLineRotate = new QPushButton(QLatin1String("rotate HitLine")); + hBox->addWidget(hitLineRotate); + connect(hitLineRotate, &QPushButton::clicked, this, [this]() + { + if (d->m_hitLine) + { + static int i = 0; + const QPointF &start = d->m_hitLine->startPoint().toPointF(); + QPointF end = d->m_hitLine->endPoint().toPointF(); + MathUtils::rotatePoint(end, start, 10); + + Point3D start3D = Point3D::fromQPointF(start); + Point3D end3D = Point3D::fromQPointF(end); + end3D.rz() += i*10; + + i++; + qCritical() << " Hitline: z = " << end3D.z(); + + d->m_hitLine->setStartAndEndPoint(start3D, + end3D); + d->m_hitLine->updateView(); + } + }); + + //----------------------------------- + // createLineOfSightLine + //----------------------------------- + QPushButton *createLosLineButton = new QPushButton(QLatin1String("create LineOfSightLinge")); + layout->addWidget(createLosLineButton); + connect(createLosLineButton, &QPushButton::clicked, + this, &FunctionDialog::onLineOfSightLineCreate); + + + //----------------------------------- + // createGeoImage + //----------------------------------- + QPushButton *createGeoImageButton = new QPushButton(QLatin1String("create GeoImage \n(handled on signal 'mapObjectsClicked'?)")); + layout->addWidget(createGeoImageButton); + connect(createGeoImageButton, &QPushButton::clicked, + this, &FunctionDialog::onGeoImageCreate); + + // similar to the application: + connect(d->m_mapInterface, &SituationMap::Map::mapObjectsClicked, + this, &FunctionDialog::onMapObjectClicked); + + + //----------------------------------- + // createSoundArea + //----------------------------------- + QPushButton *createSoundAreaButton = new QPushButton(QLatin1String("create SoundArea")); + layout->addWidget(createSoundAreaButton); + connect(createSoundAreaButton, &QPushButton::clicked, + this, &FunctionDialog::onSoundAreaCreate); + + //----------------------------------- + // createMapObjectIndicatorButton + //----------------------------------- + QPushButton *pbCube = new QPushButton(QLatin1String("create cube")); + layout->addWidget(pbCube); + connect(pbCube, &QPushButton::clicked, + this, &FunctionDialog::onCubeCreate); + + //----------------------------------- + // ??? + //----------------------------------- + + + //----------------------------------- + // createShowBasePoint + //----------------------------------- + QPushButton *createShowBasePointButton = new QPushButton(QLatin1String("ShowBasePoint")); + layout->addWidget(createShowBasePointButton); + connect(createShowBasePointButton, &QPushButton::clicked, + this, &FunctionDialog::onShowBasePoint); + + + //----------------------------------- + // createPolizeiButton + //----------------------------------- + QPushButton *createPolizeiButton = new QPushButton(QLatin1String("create Polizei")); + layout->addWidget(createPolizeiButton); + connect(createPolizeiButton, &QPushButton::clicked, + this, &FunctionDialog::onShowPolizei); + +} + + +void FunctionDialog::addRubberband3DTab() +{ + QWidget *w = new QWidget(); + QGridLayout *layout = new QGridLayout(w); + + d->m_tabwidget->addTab(w, QLatin1String("Rubberband3D")); + + + QPushButton *pbPointFtRubberband = new QPushButton(QLatin1String("Point Feature Rubberband"), w); + pbPointFtRubberband->setToolTip(QString()); + + layout->addWidget(pbPointFtRubberband, 0, 0); + + connect(pbPointFtRubberband, &QPushButton::clicked, this, [this]() + { + RB::RubberbandPointFeature3D *rubberband = d->m_mapInterface->rubberbandFactory().create3D(); + + if (nullptr != rubberband) + { + rubberband->setAutoDelete(true); + + QObject::connect(rubberband, &RB::RubberbandBase::apply, this, &FunctionDialog::onRubberband3DApply); + QObject::connect(rubberband, &RB::RubberbandBase::abort, this, &FunctionDialog::onRubberband3DAbort); + } + else + qWarning() << "Creating rubberband failed."; + + }); + + QPushButton *pbMultiPointFtRubberband = new QPushButton(QLatin1String("Multi Point Feature Rubberband - Polyline"), w); + pbMultiPointFtRubberband->setToolTip(QStringLiteral("Create 3D polyline rubberband")); + + layout->addWidget(pbMultiPointFtRubberband, 1, 0); + + connect(pbMultiPointFtRubberband, &QPushButton::clicked, this, [this]() + { + RB::RubberbandMultiPointFeature3D *rubberband = d->m_mapInterface->rubberbandFactory().create3D(); + + if (nullptr != rubberband) + { + rubberband->setAutoDelete(true); + + QObject::connect(rubberband, &RB::RubberbandBase::apply, this, &FunctionDialog::onRubberband3DApply); + QObject::connect(rubberband, &RB::RubberbandBase::abort, this, &FunctionDialog::onRubberband3DAbort); + } + else qWarning() << "Creating rubberband failed."; + + }); + + QPushButton *pbMultiPointFtClosedRubberband = new QPushButton(QLatin1String("Multi Point Feature Rubberband - Polygon"), w); + pbMultiPointFtClosedRubberband->setToolTip(QStringLiteral("Create 3D polygon rubberband")); + + layout->addWidget(pbMultiPointFtClosedRubberband, 2, 0); + + connect(pbMultiPointFtClosedRubberband, &QPushButton::clicked, this, [this]() + { + RB::RubberbandMultiPointFeature3D *rubberband = d->m_mapInterface->rubberbandFactory().create3D(); + + if (nullptr != rubberband) + { + rubberband->setAutoDelete(true); + rubberband->enableClosed(true); + + QObject::connect(rubberband, &RB::RubberbandBase::apply, this, &FunctionDialog::onRubberband3DApply); + QObject::connect(rubberband, &RB::RubberbandBase::abort, this, &FunctionDialog::onRubberband3DAbort); + } + else qWarning() << "Creating rubberband failed."; + + }); + + QPushButton *pbMultiPointFtCircleRubberband = new QPushButton(QLatin1String("Multi Point Feature Rubberband - Circle"), w); + pbMultiPointFtCircleRubberband->setToolTip(QStringLiteral("Create 3D circle rubberband")); + + layout->addWidget(pbMultiPointFtCircleRubberband, 3, 0); + + connect(pbMultiPointFtCircleRubberband, &QPushButton::clicked, this, [this]() + { + RB::RubberbandMultiPointFeature3D *rubberband = d->m_mapInterface->rubberbandFactory().create3D(); + + if (nullptr != rubberband) + { + rubberband->setAutoDelete(true); + rubberband->enableCircular(true); + + QObject::connect(rubberband, &RB::RubberbandBase::apply, this, &FunctionDialog::onRubberband3DApply); + QObject::connect(rubberband, &RB::RubberbandBase::abort, this, &FunctionDialog::onRubberband3DAbort); + } + else qWarning() << "Creating rubberband failed."; + + }); +} // void FunctionDialog::addRubberband3DTab() + + +void FunctionDialog::onRubberband3DApply(RB::RubberbandBase *) +{ + qDebug() << LOGFUNCTION; +} // void FunctionDialog::onRubberband3DApply(RB::RubberbandBase *) + + +void FunctionDialog::onRubberband3DAbort(RB::RubberbandBase *) +{ + qDebug() << LOGFUNCTION; +} // void FunctionDialog::onRubberband3DAbort(RB::RubberbandBase *) + + +void inline getSectorStartEnd(double angle, double rotation, double &aStartRad, double &aEndRad) +{ + const double rotationMath = SituationMap::MathUtils::getAngleDegree2Math(rotation);//rotation < 90. ? std::fabs(rotation - 90) : std::fabs(360 - rotation + 90) ; // 12uhr ist 0° | rotation Math counter clockwise + // angle sectorStart + { + double val = rotationMath - angle / 2.; + aStartRad = val > 0 ? val : val + 360; // return the positive angle of the value + aStartRad = (aStartRad > 360. ? std::fmod(aStartRad, 360) : aStartRad) * LtsCoordTrans::deg2rad(); // convert to rad + } + // angle sectorEnd + { + aEndRad = rotationMath + angle / 2.; + aEndRad = (aEndRad > 360. ? std::fmod(aEndRad, 360) : aEndRad) * LtsCoordTrans::deg2rad(); + } +} + +void FunctionDialog::addRubberbandTab() +{ + QWidget *w = new QWidget(); + QGridLayout *layout = new QGridLayout(w); + + d->m_tabwidget->addTab(w, QLatin1String("Rubberband")); + + //----------------------------------- + // create symbol type edit line + //----------------------------------- + QGridLayout *rubberbandLayout = new QGridLayout(); + rubberbandLayout->setObjectName(QLatin1String("rubberbandLayout")); + layout->addLayout(rubberbandLayout, layout->rowCount(), 0); + + d->m_cbRubSidc = new QComboBox(w); + d->m_cbRubSidc -> setObjectName(QLatin1String("SIDC Rubberband")); + + AdvancedSymbology::SymbolDictionary &dict = d->m_mapImpl->symbolDictionary(); + + + switch (d->m_mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::MIL2525D: + { + + // following works, but uses lambda. + // Therefore switch to SymbolResultWatcherComboxBox... + // QString sidc = QLatin1String("10051000001213030000"); // 01011000001213030000 -> LandUnits, Observer, Motorized + // AdvancedSymbology::SymbolResultWatcher *watcher = new AdvancedSymbology::SymbolResultWatcher(d->m_cbRubSidc); + // connect(watcher, &AdvancedSymbology::SymbolResultWatcher::resultReady, [this, watcher]() + // { + // qWarning() << " lamdbadYYYYYYY" << watcher; + // QSharedPointer symResult = watcher->symbolResult(); + // if (! symResult) + // return; // should NEVER happen! (Which object should have triggered the signal???) + // int idx = d->m_cbRubSidc->findData( QVariant::fromValue(watcher) ); + // const SymbolImage &symImage = symResult->symbolImage(); + // d->m_cbRubSidc->setItemIcon(idx, QPixmap::fromImage(symImage.image()) ); + // }); + // d->m_cbRubSidc->addItem(sidc, QVariant::fromValue(watcher) ); + // watcher->setResult(dict.findSymbol(sidc)); + + + QStringList sidcs; + sidcs += AdvancedSymbology::SidcCodec::encodeCivilEts("Pers_FW_BerFue"); // ETS + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10051000001213030000"); // 01011000001213030000 -> LandUnits, Observer, Motorized + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500001202000000"); // 01015000001202000000 -> 15 120200 LandEquip, Tank + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500001110000000"); // 10031500001110000000 -> 15 111000 Missle Launcher + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10032500002701000000"); // 25270100 + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10032500002718000000"); // 25271800 + + // For Hanna : + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10011500001101000000"); // 15 110100 Soldier (rifle) + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10011500001102000000"); // Soldier MG + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10011500001202000000"); // Tank + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10011500131202000000"); // Tank BT + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10011500001408000000"); // Utility Vehicle CROSS-COUNTRY + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10011000131211000000"); // Infantry + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10011000131215000000"); // Sniper + + + + // : For Hanna + + + // FLOT + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10012500001401010000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10012500001401020000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10012500001401030000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10012500001401040000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10032500001401010000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10032500001401020000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10032500001401030000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10032500001401040000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10042500001401010000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10042500001401020000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10042500001401030000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10042500001401040000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10062500001401010000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10062500001401020000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10042500001401030000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10042500001401040000"); // 10032500001401030000 -> 24 140103 Maneuver Lines Direction of Attack + + for (const auto &sidc : std::as_const(sidcs)) + { + QSharedPointer srwCb(new AdvancedSymbology::SymbolResultWatcherComboxBox(d->m_cbRubSidc)); + d->m_cbRubSidc->addItem(sidc, QVariant::fromValue(srwCb)); + //AdvancedSymbology::SymbolResultWatcherComboxBox *srwCb; + //d->m_cbRubSidc->addItem(sidc, QVariant::fromValue(srwCb = new AdvancedSymbology::SymbolResultWatcherComboxBox(d->m_cbRubSidc))); + srwCb->setSymbolResult(dict.fetchSymbol(sidc)); + } + } + break; + + case AdvancedSymbology::SymbologyType::MIL2525C: + { + // Check Quartz + // Point Feature +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("SFGPUCD---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("SUGPEWSM--"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFGPGPWA--"), 64)), sidc); + + d->m_cbRubSidc->insertSeparator(d->m_cbRubSidc->maxCount()); + + // Line Feature +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFGPOLAA--"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFMPOGL---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFMPOWCT--"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFMPOWU---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFMPOADC--"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFMPOEB---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFGPSLA---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFGPOLKGM-"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFMPOFG---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GHGPOLAGM-"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GHMPBDI---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GHMPBCR---"), 64)), sidc); + + d->m_cbRubSidc->insertSeparator(d->m_cbRubSidc->maxCount()); + + // Area Feature +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFGPPM----"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GHMPOFA---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GHMPOGF---"), 64)), sidc); + } break; + + case AdvancedSymbology::SymbologyType::APP6B: + { + // Point Feature +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("SFGPUCD---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("SUGPEWSM--"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFOPID----"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("SNGPUCRH--"), 64)), sidc); + + d->m_cbRubSidc->insertSeparator(d->m_cbRubSidc->maxCount()); + + // Line Feature +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFCPMOLAM-"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFCPMOLDS-"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFCPMOLDM-"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GHCPMOLI--"), 64)), sidc); + + d->m_cbRubSidc->insertSeparator(d->m_cbRubSidc->maxCount()); + + // Area Feature +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFCPMGASF-"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFCPMDM---"), 64)), sidc); +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("GFCPMDN---"), 64)), sidc); + + } break; + + case AdvancedSymbology::SymbologyType::APP6D: + // empty + break; + + + case AdvancedSymbology::SymbologyType::None: + // try civil (ETS_xxx +// d->m_cbRubSidc -> addItem(QPixmap::fromImage(dict.symbolImageBySidc(sidc = QLatin1String("ETS_StelleBerRaum"), 64)), sidc); + break; + + } // switch (d->m_map->symbologyType()) + + QFont f = d->m_cbRubSidc->font(); + f.setPixelSize(16); + d->m_cbRubSidc->setFont(f); + d->m_cbRubSidc->setIconSize(QSize(32, 32)); + d->m_cbRubSidc->setEditable(true); + +// connect(d->m_cbRubSidc, &QComboBox::editTextChanged, [=](const QString & text) +// { +// QVariant decoration = this->d->m_cbRubSidc->currentData( Qt::DecorationRole ); + +// AdvancedSymbology::SymbolDictionary & dict = this->d->m_mapImpl->symbolDictionary(); +// if (dict.contains(text)) +// { +// const QPixmap & icon = QPixmap::fromImage(dict.symbolImageBySidc(text,64)); +// // why isn't there write excess to currentData?? +// } +//// else; // why isn't there write excess to currentData?? + +// } ); + + rubberbandLayout->addWidget(d->m_cbRubSidc, 0, 0, 2, 2); + connect(d->m_cbRubSidc, &QComboBox::currentTextChanged, this, [this]() + { + if (d->m_cbRubSidc->currentText().length() > 15) + d->m_cbRubSidc->setStyleSheet(QLatin1String("QLineEdit#SIDC { background-color: red; color: #000; }")); + else + d->m_cbRubSidc->setStyleSheet(QLatin1String("QLineEdit#SIDC { background-color: white; color: #000; }")); + }); /* connect */ + + + QPushButton *pbCreatePointSymbol = new QPushButton(QLatin1String("Create Point Symbol"), w); + pbCreatePointSymbol->setToolTip(d->m_leSidc->toolTip()); + + rubberbandLayout->addWidget(pbCreatePointSymbol, 0, 2); + + connect(pbCreatePointSymbol, &QPushButton::clicked, + this , &FunctionDialog::onParticipantGetPosition); + + QPushButton *pbCreateRubberband = new QPushButton(QLatin1String("Create Rubberband"), w); + pbCreateRubberband->setToolTip(d->m_leSidc->toolTip()); + + rubberbandLayout->addWidget(pbCreateRubberband, 1, 2); + + connect(pbCreateRubberband, &QPushButton::clicked, this, [this]() + { + RB::Rubberband *rubberband = d->m_mapInterface->rubberbandFactory().createSymbol(d->m_cbRubSidc->currentText()); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband failed, may be wrong sidc (" << d->m_cbRubSidc->currentText() << ")"; + else + { + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + + unsigned int rubberbandTests = 2; + + QPushButton *pbTestPointClusterRubber = new QPushButton(QLatin1String("Test PointCluster Rubberband"), w); + rubberbandLayout->addWidget(pbTestPointClusterRubber, rubberbandTests++, 0); + + connect(pbTestPointClusterRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandPointCluster *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband point cluster failed"; + else + { + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + //-------------------------------------- + QPushButton *pbTestMultiPointRubber = new QPushButton(QLatin1String("Multipoint RB"), w); + rubberbandLayout->addWidget(pbTestMultiPointRubber, rubberbandTests++, 0); + + connect(pbTestMultiPointRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandMultiPointFeature *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband multi points feature failed"; + else + { + rubberband -> enableClosed(false); + rubberband -> minimumPointCount(3); + rubberband -> maximumPointCount(6); + rubberband -> maximumLength(20000); // will not be checked for closed lines + rubberband -> maximumSegmentLength(5000); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + //-------------------------------------- + QPushButton *pbTestMultiEllipseRubber = new QPushButton(QLatin1String("MultiEllipse RB (BoobyTrap) from Scratch"), w); + rubberbandLayout->addWidget(pbTestMultiEllipseRubber, rubberbandTests++, 0); + connect(pbTestMultiEllipseRubber, &QPushButton::clicked, this, [this]() + { + + // FIRST RubberbandMultiEllipseFeature + RB::RubberbandMultiEllipseFeature *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband multi CIRCLE feature failed"; + else + { + rubberband->minimumRadiusCount(2); + rubberband->maximumRadiusCount(2); + + using RWC = RB::RubberbandMultiEllipseFeature::RadiusWithContraints; + RWC radiusWithContraints1(0, 750, 1250); + RWC radiusWithContraints2(0, 1750, 2250); + rubberband->prepareFixedNumberOfRadiiWithConstraints({radiusWithContraints1, radiusWithContraints2}); + +#define CIRCLE_CONTRAINT +#ifdef CIRCLE_CONTRAINT + rubberband->constrainQuadratic(true); +#endif + //rubberband -> maximumLength(20000); // will not be checked for closed lines + //rubberband -> maximumSegmentLength(5000); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + //-------------------------------------- + QPushButton *pbTestMultiEllipseRubberPreDefined = new QPushButton(QLatin1String("MultiEllipse RB (BoobyTrap) predefined Start"), w); + rubberbandLayout->addWidget(pbTestMultiEllipseRubberPreDefined, rubberbandTests++, 0); + connect(pbTestMultiEllipseRubberPreDefined, &QPushButton::clicked, this, [this]() + { + RB::RubberbandMultiEllipseFeature *rubberbandForPreperation = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberbandForPreperation == nullptr) + qWarning() << "Creating rubberband multi CIRCLE feature failed"; + else + { + // start with GUEZ-typical BoobyTrap: + + QPointF centerOfMapView = d->m_mapInterface->centerPoint(); + Q_UNUSED(centerOfMapView); + + //const QVector radii( { {1000, 1000}, {1500, 1500} } ); +// rubberband->setCenterAndRadii(centerOfMapView, RB::CoordinateSystem::Geo, radii); +// rubberbandForPreperation->prepareFixedNumberOfRadii(radii); + + + RB::RubberbandMultiEllipseFeature::RadiusWithContraints radiusWithContraints1(1000, 1000, 1000); + RB::RubberbandMultiEllipseFeature::RadiusWithContraints radiusWithContraints2(3000, 2750, 3250); + + rubberbandForPreperation->prepareFixedNumberOfRadiiWithConstraints({radiusWithContraints1, radiusWithContraints2}); + + + + + rubberbandForPreperation->constrainQuadratic(true); + + rubberbandForPreperation->enableSector(true); + double aStartRad=0, aEndRad=0; + double angle = 90; + double rot = 0; + getSectorStartEnd(angle, rot, aStartRad, aEndRad); + + rubberbandForPreperation->setSectorStart(std::sin(aStartRad), std::cos(aStartRad)); + rubberbandForPreperation->setSectorEnd(std::sin(aEndRad), std::cos(aEndRad)); + rubberbandForPreperation->setSectorAngleFixed(true); + + QObject::connect(rubberbandForPreperation, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberbandForPreperation, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + + + QPushButton *pbTestMultiPointFillRubber = new QPushButton(QLatin1String("Filled Multipoint RB"), w); + rubberbandLayout->addWidget(pbTestMultiPointFillRubber, rubberbandTests++, 0); + + connect(pbTestMultiPointFillRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandMultiPointFeature *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating filled rubberband multi points feature failed"; + else + { + rubberband -> enableClosed(true); + rubberband -> setFillColor(QColor(QLatin1String("teal"))); + rubberband -> maximumLength(20000); // will not be checked for closed lines + rubberband -> maximumSegmentLength(5000); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + QPushButton *pbTestSmoothLineRubber = new QPushButton(QLatin1String("Smooth Line RB"), w); + rubberbandLayout->addWidget(pbTestSmoothLineRubber, rubberbandTests++, 0); + + connect(pbTestSmoothLineRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandSmoothLine *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband smooth line failed"; + else + { + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + + QPushButton *pbTestSmoothFillRubber = new QPushButton(QLatin1String("Filled Smooth Line RB"), w); + rubberbandLayout->addWidget(pbTestSmoothFillRubber, rubberbandTests++, 0); + + connect(pbTestSmoothFillRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandSmoothLine *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating filled rubberband smooth line failed"; + else + { + rubberband->enableClosed(true); + rubberband->setFillColor(QColor(QLatin1String("olive"))); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + QPushButton *pbTestCorridorRubber = new QPushButton(QLatin1String("Corridor RB(Boundary)"), w); + rubberbandLayout->addWidget(pbTestCorridorRubber, rubberbandTests++, 0); + + connect(pbTestCorridorRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandCorridor *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband corridors failed"; + else + { + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + pbTestCorridorRubber = new QPushButton(QLatin1String("Corridor RB (Centerl., W = 50000)"), w); + rubberbandLayout->addWidget(pbTestCorridorRubber, rubberbandTests++, 0); + + connect(pbTestCorridorRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandCorridor *rubberband = d->m_mapInterface->rubberbandFactory().create(RB::RubberbandCorridor::InputMode::InputCenterLine, 50000); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband corridors failed"; + else + { + rubberband->setCorridorFillColor(QColor(QLatin1String("orange"))); + + rubberband -> minimumPointCount(3); + rubberband -> maximumPointCount(6); + rubberband -> minimumCorridorWidth(400); + rubberband -> maximumCorridorWidth(1000); + rubberband -> maximumLength(7000); + rubberband -> maximumSegmentLength(2000); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + QPushButton *pbTestSquareRubber = new QPushButton(QLatin1String("Square RB"), w); + rubberbandLayout->addWidget(pbTestSquareRubber, rubberbandTests++, 0); + + connect(pbTestSquareRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandRectangle *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband rectangle failed"; + else + { + rubberband->constrainQuadratic(true); + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + QPushButton *pbTestRectangleRubber = new QPushButton(QLatin1String("Rectangle RB"), w); + rubberbandLayout->addWidget(pbTestRectangleRubber, rubberbandTests++, 0); + + connect(pbTestRectangleRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandRectangle *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband rectangle failed"; + else + { + rubberband->minWidth(5000); + rubberband->maxWidth(2000000); + rubberband->minHeight(5000); + rubberband->maxHeight(1000000); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + QPushButton *pbTestRectangleOrthoRubber = new QPushButton(QLatin1String("Rectangle Ortho RB"), w); + rubberbandLayout->addWidget(pbTestRectangleOrthoRubber, rubberbandTests++, 0); + + connect(pbTestRectangleOrthoRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandRectangle *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband rectangle failed"; + else + { + rubberband->constrainOrthogonal(true); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + + QPushButton *pbTestRectangleParameter = new QPushButton(QLatin1String("Rectangle Parameter"), w); + rubberbandLayout->addWidget(pbTestRectangleParameter, rubberbandTests++, 0); + + connect(pbTestRectangleParameter, &QPushButton::clicked, this, [this]() + { + RB::RubberbandRectangle *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband rectangle failed"; + else + { + SituationMap::CoordTranslationFunctor *cf = d->m_mapInterface->coordTranslationFunctor(); + + + QPointF bottomLeft = d->m_mapInterface->centerPoint(); + if (cf) bottomLeft = cf->geographicToMap(bottomLeft); + rubberband->buildRectangleRelativeToBottomLeftCorner(bottomLeft, 3000, 1500, QPoint(-1, 0), SituationMap::RB::CoordinateSystem::Map); + + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + }); + + QPushButton *pbTestEllipseRubber = new QPushButton(QLatin1String("Ellipse RB"), w); + rubberbandLayout->addWidget(pbTestEllipseRubber, rubberbandTests++, 0); + + connect(pbTestEllipseRubber, &QPushButton::clicked, this, [this]() + { + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(true); + + RB::RubberbandEllipse *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(false); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband ellipse failed"; + else + { + if (rubberband->rubberbandIsAlive()) + { + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + else + { + qWarning() << "Creating rubberband ellipse is not alive"; + rubberband->deleteMySelf(); + } + } + }); + + QPushButton *pbTestArrowRubber = new QPushButton(QLatin1String("Arrow RB"), w); + rubberbandLayout->addWidget(pbTestArrowRubber, rubberbandTests++, 0); + + connect(pbTestArrowRubber, &QPushButton::clicked, this, [this]() + { + + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(true); + + RB::RubberbandArrow *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(false); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband arrow failed"; + else + { + if (rubberband->rubberbandIsAlive()) + { + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + else + { + qWarning() << "Creating rubberband arrow is not alive"; + rubberband->deleteMySelf(); + } + } + }); + + QPushButton *pbTestFreeHdRubber = new QPushButton(QLatin1String("Freehand RB"), w); + rubberbandLayout->addWidget(pbTestFreeHdRubber, rubberbandTests++, 0); + + connect(pbTestFreeHdRubber, &QPushButton::clicked, this, [this]() + { + RB::RubberbandFreeHand *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband freehand failed"; + else + { + rubberband->setFillColor(QColor(0, 0, 0, 0)); + if (rubberband->rubberbandIsAlive()) + { + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + else + { + qWarning() << "Creating rubberband freehand is not alive"; + rubberband->deleteMySelf(); + } + } + }); + + rubberbandTests = 2; + QPushButton *pbTestRubberDefineArrow = new QPushButton(QLatin1String("Define Arrow"), w); + rubberbandLayout->addWidget(pbTestRubberDefineArrow, rubberbandTests++, 1); + + connect(pbTestRubberDefineArrow, &QPushButton::clicked, this, &FunctionDialog::onRubberbandDefineArrow); + + QPushButton *pbTestRubberDefineEllipse = new QPushButton(QLatin1String("Define Ellipse"), w); + rubberbandLayout->addWidget(pbTestRubberDefineEllipse, rubberbandTests++, 1); + + connect(pbTestRubberDefineEllipse, &QPushButton::clicked, this, &FunctionDialog::onRubberbandDefineEllipse); + + QPushButton *pbTestRubberCoordinates = new QPushButton(QLatin1String("RB Coordinates"), w); + rubberbandLayout->addWidget(pbTestRubberCoordinates, rubberbandTests++, 1); + + connect(pbTestRubberCoordinates, &QPushButton::clicked, this, &FunctionDialog::onRubberbandCheckCoordinates); + + QPushButton *pbTestRectCoordinates = new QPushButton(QLatin1String("Rectangle Coord."), w); + rubberbandLayout->addWidget(pbTestRectCoordinates, rubberbandTests++, 1); + + connect(pbTestRectCoordinates, &QPushButton::clicked, this, &FunctionDialog::onRectCheckCoordinates); + + QPushButton *pbTestRubberStateSelect = new QPushButton(QLatin1String("State Select RB"), w); + rubberbandLayout->addWidget(pbTestRubberStateSelect, rubberbandTests++, 1); + + connect(pbTestRubberStateSelect, &QPushButton::clicked, this, &FunctionDialog::onRubberbandStateSelect); + + QPushButton *pbTestRubberDefineBoobyTrap = new QPushButton(QLatin1String("BoobyTrap Sector RB"), w); + rubberbandLayout->addWidget(pbTestRubberDefineBoobyTrap, rubberbandTests++, 1); + + connect(pbTestRubberDefineBoobyTrap, &QPushButton::clicked, this, &FunctionDialog::onRubberbandBoobyTrapSector); + + + + rubberbandTests = 2; + QPushButton *pbTestTowInOneRubberband = new QPushButton(QLatin1String("TwoInOne"), w); + rubberbandLayout->addWidget(pbTestTowInOneRubberband, rubberbandTests++, 3); + connect(pbTestTowInOneRubberband, &QPushButton::clicked, this, &FunctionDialog::onRubberbandTwoInOne); + +} /* FunctionDialog::addRubberbandTab */ + +void FunctionDialog::onRubberbandApply(RB::RubberbandBase *rubberband) +{ + qCritical() << LOGFUNCTION << " rubberband = " << rubberband; + + RB::RubberbandPointFeature *rubberbandPoint = dynamic_cast(rubberband); + RB::RubberbandPointCluster *rubberbandClust = dynamic_cast(rubberband); + RB::RubberbandRectangle *rubberbandRect = dynamic_cast(rubberband); + RB::RubberbandEllipse *rubberbandElli = dynamic_cast(rubberband); + RB::RubberbandMultiPointFeature *rubberbandMFeat = dynamic_cast(rubberband); + RB::RubberbandMultiEllipseFeature *rubberbandMEllipseFeat = dynamic_cast(rubberband); + RB::RubberbandCorridor *rubberbandCorr = dynamic_cast(rubberband); + RB::RubberbandSmoothLine *rubberbandSmooth = dynamic_cast(rubberband); + RB::RubberbandArrow *rubberbandArrow = dynamic_cast(rubberband); + RB::RubberbandFreeHand *rubberbandFreehand = dynamic_cast(rubberband); + + if (rubberbandPoint) qCritical() << LOGFUNCTION << "point feature rubberband"; + if (rubberbandClust) qCritical() << LOGFUNCTION << "point cluster rubberband"; + if (rubberbandRect) qCritical() << LOGFUNCTION << "rectangle rubberband"; + if (rubberbandElli) qCritical() << LOGFUNCTION << "ellipse rubberband"; + if (rubberbandFreehand) qCritical() << LOGFUNCTION << "freehand rubberband"; + if (rubberbandMEllipseFeat) qCritical() << LOGFUNCTION << "multiCircle rubberband"; + + if (rubberbandMFeat) + { + if (rubberbandCorr) qDebug() << LOGFUNCTION << "corridor rubberband"; + else if (rubberbandSmooth) qDebug() << LOGFUNCTION << "smooth line rubberband"; + else qDebug() << LOGFUNCTION << "multi point feature rubberband"; + } + + qCritical() << LOGFUNCTION << " Count of Coordinates " << rubberband->coordinateCount(); + + if (rubberbandMEllipseFeat) + { + QPointF centerCoord = rubberbandMEllipseFeat->centerCoordinate(RB::CoordinateSystem::Map); + const QVector &radii = rubberbandMEllipseFeat->radii(); + + qCritical() << LOGFUNCTION << " FIRST centerCoord " << centerCoord << ", radii = " << radii; + qCritical() << LOGFUNCTION << " FIRST Sector sectorStart " << rubberbandMEllipseFeat->sectorStart(); + qCritical() << LOGFUNCTION << " FIRST Sector sectorEnd " << rubberbandMEllipseFeat->sectorEnd(); + + qCritical() << LOGFUNCTION << " FIRST Sector sectorStartDegMath " << rubberbandMEllipseFeat->sectorStartDegMath(); + qCritical() << LOGFUNCTION << " FIRST Sector sectorEndDegMath " << rubberbandMEllipseFeat->sectorEndDegMath(); + + + + RB::RubberbandMultiEllipseFeature *rubberbandReCreate = d->m_mapInterface->rubberbandFactory().create(); + + if (!rubberbandReCreate) + qWarning() << "Creating rubberband multi CIRCLE feature failed"; + else + { + rubberbandReCreate->setAutoDelete(true); +#ifdef CIRCLE_CONTRAINT + rubberbandReCreate->constrainQuadratic(true); +#endif + QVector radiiWithConstraints; + radiiWithConstraints.reserve(2); + for (const QPointF &item: std::as_const(radii)) + { + RB::RubberbandMultiEllipseFeature::RadiusWithContraints radiusWC; + radiusWC.m_radius = item.x(); + radiusWC.m_minRadius = radiusWC.m_radius-250; + radiusWC.m_maxRadius = radiusWC.m_radius+250; + + radiiWithConstraints.append(radiusWC); + } + rubberbandReCreate->setCenterAndRadii2(centerCoord, RB::CoordinateSystem::Map, + radiiWithConstraints); + + + QList rotation = {0, 45, 90, -45, 180, 270, 315, 360}; + double angle = 90; // oeffnungsWinkel + double rot = rotation.at(0); // clockwise + double aStartRad=0, aEndRad=0; + getSectorStartEnd(angle, rot, aStartRad, aEndRad); +// qCritical()<< "[FORWARD] rotation: " << rot +// << ", angle start: "<< aStartRad << "[rad] (" << (aStartRad*LtsCoordTrans::rad2deg()) << "[deg])" +// << ", angle end: "<< aEndRad << "[rad] (" << (aEndRad*LtsCoordTrans::rad2deg()) << "[deg])"; + + rubberbandReCreate->setSectorStart(std::sin(aStartRad), std::cos(aStartRad)); + rubberbandReCreate->setSectorEnd(std::sin(aEndRad), std::cos(aEndRad)); + + rubberbandReCreate->setSectorAngleFixed(true); + + QObject::connect(rubberbandReCreate, &RB::Rubberband::apply, this, [](RB::RubberbandBase *rubberband) + { + RB::RubberbandMultiEllipseFeature *rubberbandMCircleFeat = dynamic_cast(rubberband); + if (!rubberbandMCircleFeat) + return; + QPointF centerCoord = rubberbandMCircleFeat->centerCoordinate(RB::CoordinateSystem::Map); + const QVector &radii = rubberbandMCircleFeat->radii(); + + qCritical() << LOGFUNCTION << " SECOND centerCoord " << centerCoord << ", radii = " << radii; + qCritical() << LOGFUNCTION << " SECOND Sector sectorStart " << rubberbandMCircleFeat->sectorStart(); + qCritical() << LOGFUNCTION << " SECOND Sector sectorEnd " << rubberbandMCircleFeat->sectorEnd(); + + }); + +//// QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); +//// QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + + } + + + QVector vertexList = rubberband->coordinateVector(); + bool constrainQuad = false; + bool constrainOrtho = false; + if (rubberbandRect) + { + constrainQuad = rubberbandRect->constrainQuadratic(); + constrainOrtho = rubberbandRect->constrainOrthogonal(); + QPointF geoPoint = rubberband->singleCoordinate(0, RB::CoordinateSystem::Geo); + qDebug() << LOGFUNCTION << "Rectangle center : geo coordinate " << geoPoint; + qDebug() << LOGFUNCTION << "Vertexlist sim coordinates " << vertexList; + d->m_mapInterface->geoCoordinateToViewPortWindowPoint(geoPoint); + } + else + { + for (unsigned int i = 0; i < rubberband->coordinateCount(); ++i) + { + QPointF geoPoint = rubberband->singleCoordinate(i, RB::CoordinateSystem::Geo); + qDebug() << LOGFUNCTION << " " << i << " geo coordinate " << geoPoint; + } + } + + if (rubberbandFreehand) + { + rubberband->deleteMySelf(); + return; + } + + if (rubberbandCorr) + { + QVector bufferVertexList; + + if (rubberbandCorr->bufferPolygon(bufferVertexList, RB::CoordinateSystem::Map)) + { + static SituationMap::RefPointer bufferPolygon; + if (bufferPolygon.valid()) bufferPolygon->removeMySelf(); + + bufferPolygon = d->m_mapInterface->objectFactory().create(QStringLiteral("FunctionDialog"), QStringLiteral("BufferPolygon")); + + if (bufferPolygon.valid()) + { + + bufferPolygon->setIdentifier(QLatin1String("BufferPolygon")); + bufferPolygon->setVisibility(true); + + QColor magenta(Qt::magenta); + QColor magentaAlpha(Qt::magenta); + + magentaAlpha.setAlpha(100); + + bufferPolygon->setLineStyle(LineStyle(magenta, 2, LinePattern::Solid)); + bufferPolygon->setFillStyle(FillStyle(magentaAlpha, FillPattern::Solid)); + + SituationMap::PositionCont posCont; + posCont.reserve(bufferVertexList.size()); + for (const auto &bufferPoint : std::as_const(bufferVertexList)) + { + posCont << Point3D::fromQPointF(bufferPoint); + qDebug() << fixed << qSetRealNumberPrecision(2) << LOGFUNCTION << "buffer coordinate : " << bufferPoint ; + } + + bufferPolygon->setPositionsTranslated(posCont); // use map coordinates to print the coordinate values + bufferPolygon->updateView(); + + + QTimer::singleShot(10000, this, [this]() + { + if (bufferPolygon.valid()) bufferPolygon->removeMySelf(); + bufferPolygon = nullptr; + + this->update(); + }); + } + else qDebug() << LOGFUNCTION << " creating polygon to visualize buffer polygon failed"; + } + else qWarning() << LOGFUNCTION << "RubberbandCorridor::bufferPolygon failed"; + } + + static QString rubberTypeLabel; + + auto slotFunc = [ = ](RB::RubberbandBase * rubberband) + { + qDebug() << LOGFUNCTION << "end of " << rubberTypeLabel << " rubberband"; + + qDebug() << LOGFUNCTION << "Vertexlist sim coordinates " << rubberband->coordinateVector(); + + for (unsigned int i = 0; i < rubberband->coordinateCount(); ++i) + { + QPointF simPoint = rubberband->singleCoordinate(i, RB::CoordinateSystem::Geo); + qDebug() << LOGFUNCTION << " " << i << " sim coordinate " << simPoint; + } + rubberband->deleteMySelf(); + }; + + if (rubberbandRect) + { + if (rubberbandElli) + { + rubberTypeLabel = QLatin1String("ellpise"); + if (rubberbandElli->hasSector()) + { + double rotation=0; + double openAngle = 0; + getRotationAndAngle(rubberbandElli->sectorStart(),rubberbandElli->sectorEnd(), rotation, openAngle); + } + + rubberbandRect = d->m_mapInterface->rubberbandFactory().create(); + } + else + { + rubberTypeLabel = QLatin1String("rectangle"); + rubberbandRect = d->m_mapInterface->rubberbandFactory().create(); + } + if (rubberbandRect == nullptr) + qWarning() << "Creating rubberband " << rubberTypeLabel << " failed"; + else + { + rubberbandRect -> setCoordinateVector(vertexList); + rubberbandRect -> constrainQuadratic(constrainQuad); + rubberbandRect -> constrainOrthogonal(constrainOrtho); + rubberbandRect -> setDrawColor(QColor(QLatin1String("blue"))); + + connect(rubberbandRect, &RB::Rubberband::apply, slotFunc); + connect(rubberbandRect, &RB::Rubberband::abort, slotFunc); + } + } + + bool mRbands = d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(); + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(true); + { + if (rubberbandMFeat) + { + RB::RubberbandMultiPointFeature *rbandMPF = nullptr; + if (rubberbandCorr) + { + RB::RubberbandCorridor *rbandCorr = d->m_mapInterface->rubberbandFactory().create(rubberbandCorr->isEnabledInputBoundary() ? RB::RubberbandCorridor::InputMode::InputBoundary + : RB::RubberbandCorridor::InputMode::InputCenterLine); + if (rbandCorr != nullptr) + { + if (rubberbandCorr->isEnabledInputBoundary()) + { + rbandCorr->setCoordinateVector(vertexList); +// rbandCorr->setDrawColor(QColor(QLatin1String("blue"))); +// rbandCorr->setCorridorFillColor(QColor(QLatin1String("aqua"))); + } + else + { + rbandCorr->setCoordinateVectorWidth(vertexList, rubberbandCorr->corridorWidth()); +// rbandCorr->setDrawColor(QColor(QLatin1String("blue"))); +// rbandCorr->setCorridorFillColor(QColor(QLatin1String("red"))); + } + + rubberTypeLabel = QLatin1String("corridor"); + + connect(rbandCorr, &RB::Rubberband::apply, slotFunc); + connect(rbandCorr, &RB::Rubberband::abort, slotFunc); + } + else qWarning() << "Creating rubberband corridor failed"; + } + else if (rubberbandSmooth) + { + RB::RubberbandSmoothLine *rbandSmooth = d->m_mapInterface->rubberbandFactory().create(); + + if (rbandSmooth != nullptr) + { + rbandMPF = rbandSmooth; + rubberTypeLabel = QLatin1String("smooth line"); + } + else qWarning() << "Creating rubberband smooth line failed"; + } + else if (rubberbandArrow) + { + RB::RubberbandArrow *rbandArrow = d->m_mapInterface->rubberbandFactory().create(); + + if (rbandArrow != nullptr) + { + rbandArrow->setCoordinateVectorArrow(vertexList, rubberbandArrow->arrowHalfWidth(), rubberbandArrow->arrowLength()); + rbandArrow->setDrawColor(QColor(QLatin1String("purple"))); + + rubberTypeLabel = QLatin1String("arrow"); + + connect(rbandArrow, &RB::Rubberband::apply, slotFunc); + connect(rbandArrow, &RB::Rubberband::abort, slotFunc); + } + else qWarning() << "Creating rubberband arrow failed"; + } + else + { + rbandMPF = d->m_mapInterface->rubberbandFactory().create(); + + rubberTypeLabel = QLatin1String("multi point feature"); + if (rbandMPF == nullptr) + qWarning() << "Creating rubberband multi point feature failed"; + } + + if (rbandMPF != nullptr) + { + rbandMPF -> setCoordinateVector(vertexList); + rbandMPF -> enableClosed(rubberbandMFeat->isClosedEnabled()); + rbandMPF -> setDrawColor(QColor(QLatin1String("maroon"))); + rbandMPF -> setFillColor(QColor(QLatin1String("lime"))); + rbandMPF -> setPreviewSidc(rubberbandMFeat->previewSidc()); + + connect(rbandMPF, &RB::Rubberband::apply, slotFunc); + connect(rbandMPF, &RB::Rubberband::abort, slotFunc); + } + } + if (rubberbandClust) + { + RB::RubberbandPointCluster *rbandClust = d->m_mapInterface->rubberbandFactory().create(); + + if (rbandClust == nullptr) + qWarning() << "Creating rubberband point cluster failed"; + else + { + rbandClust -> setCoordinateVector(vertexList); + rbandClust -> setPreviewSidc(rubberbandClust->previewSidc()); + + rubberTypeLabel = QLatin1String("point cluster"); + + connect(rbandClust, &RB::Rubberband::apply, slotFunc); + connect(rbandClust, &RB::Rubberband::abort, slotFunc); + } + } + } + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(mRbands); + + rubberband->deleteMySelf(); +} // FunctionDialog::onRubberbandApply + + +void FunctionDialog::onRubberbandAbort(RB::RubberbandBase *rubberband) +{ + qDebug() << LOGFUNCTION; + rubberband->deleteMySelf(); +} // FunctionDialog::onRubberbandAbort + + +void FunctionDialog::onRubberbandDefineEllipse() +{ + static QVector vertexList; + + if (vertexList.isEmpty()) + { + vertexList << s_basePoint.geographic(d->m_objectBasePoint + QPointF(36042.4, 38162.5)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(302120, -46643.1)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(244876, -224735)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-21201.4, -139929)); + } + + SituationMap::RB::RubberbandEllipse *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << LOGFUNCTION << "Creating rubberband failed"; + else + { + rubberband->setCoordinateVector(vertexList); + + auto slotFunc = [ = ](RB::RubberbandBase * rubberband) + { + rubberband->deleteMySelf(); + }; + + connect(rubberband, &RB::Rubberband::apply, slotFunc); + connect(rubberband, &RB::Rubberband::abort, slotFunc); + } +} + +void FunctionDialog::getRotationAndAngle(const QPointF §Start, const QPointF §End, double &rotationDegOut, double &openAngleDegOut) +{ + double startAngle = SituationMap::MathUtils::getMathAngle(sectStart) * LtsCoordTrans::rad2deg(); + double endAngle = SituationMap::MathUtils::getMathAngle(sectEnd) * LtsCoordTrans::rad2deg(); + // get start angle value in deg + { + startAngle = startAngle > 0 ? startAngle : (360 - startAngle); + startAngle = startAngle < 360 ? startAngle : std::fmod(startAngle,360.); + } + + // get end angle value in deg + { + endAngle = endAngle > 0 ? endAngle : (360 - endAngle); + } + + // get angle value in deg + { + if ( startAngle > endAngle) + openAngleDegOut = 360 + (endAngle - startAngle); + else + openAngleDegOut = endAngle - startAngle; + } + + // get rotation angle north (in deg) orientated 0° North(12h) + { + rotationDegOut = SituationMap::MathUtils::getAngleDegree2North((startAngle + (openAngleDegOut/2.))); + } + + //qCritical()<<" sDeg:"<m_mapInterface->rubberbandFactory().allowMultipleRubberbands(true); + + RB::RubberbandEllipse *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(false); + + if (rubberband == nullptr) + qWarning() << "Creating rubberband BoobyTrap failed"; + else + { + if (rubberband->rubberbandIsAlive()) + { + rubberband->constrainQuadratic(true); + rubberband->enableSector(true); + + QList rotation = QList()<<0<<45<<90<<-45<<180<<270<<315<<360; + double angle = 90; + double rot = rotation.at(0); + double aStartRad=0, aEndRad=0; + getSectorStartEnd(angle, rot, aStartRad, aEndRad); +// qCritical()<<"[FORWARD] rotation: "<setSectorStart(std::sin(aStartRad), std::cos(aStartRad)); + rubberband->setSectorEnd(std::sin(aEndRad), std::cos(aEndRad)); + + QObject::connect(rubberband, &RB::Rubberband::apply, this, &FunctionDialog::onRubberbandApply); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + else + { + qWarning() << "Creating rubberband BoobyTrap is not alive"; + rubberband->deleteMySelf(); + } + } + + +} /* FunctionDialog::onRubberbandDefineEllipse */ + + +void FunctionDialog::onRubberbandDefineArrow() +{ + static QVector vertexList; + + if (vertexList.isEmpty()) + { + vertexList << s_basePoint.geographic(d->m_objectBasePoint + QPointF(36042.4, 38162.5)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(302120, -46643.1)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(244876, -224735)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-21201.4, -139929)); + } + + SituationMap::RB::RubberbandArrow *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + if (rubberband == nullptr) + qWarning() << LOGFUNCTION << "Creating rubberband failed"; + else + { + rubberband->setCoordinateVector(vertexList); + + auto slotFunc = [ = ](RB::RubberbandBase * rubberband) + { + rubberband->deleteMySelf(); + }; + + connect(rubberband, &RB::Rubberband::apply, slotFunc); + connect(rubberband, &RB::Rubberband::abort, slotFunc); + } +} /* FunctionDialog::onRubberbandDefineEllipse */ + + +void FunctionDialog::onRubberbandCheckCoordinates() +{ + static QVector vertexList; + + if (vertexList.isEmpty()) + { + vertexList << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-338163, 168551)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(154770, 168551)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(154770, -71025)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-338163, -71025)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(36042.4, 38162.5)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(302120, -46643.1)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(244876, -224735)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-21201.4, -139929)); + } + + if (d->m_mapImpl->coordTranslationFunctor()) + { + bool ok = true; + for (const QPointF &p : std::as_const(vertexList)) + { + const QPointF p1(d->m_mapImpl->coordTranslationFunctor()->geographicToMap(p)); + const QPointF p2(d->m_mapImpl->coordTranslationFunctor()->mapToGeographic(p1)); + if ((fabs(p2.x() - p.x()) > 1) || (fabs(p2.y() - p.y()) > 1)) + { + ok = false; +#ifdef LTS_DEBUG + assert(false); +#endif + } + } + if (!ok) + qWarning() << LOGFUNCTION << "CoordTranslationFunctor is to inaccurate."; + } + + + auto slotFunc = [ = ](RB::RubberbandBase * rubberband) + { + bool ok = true; + + if (((unsigned int)vertexList.size()) == rubberband->coordinateCount()) + { + for (unsigned int i = 0; i < rubberband->coordinateCount(); ++i) + { + QPointF simPoint = rubberband->singleCoordinate(i); + QPointF p = vertexList[i]; + + if ((fabs(simPoint.x() - p.x()) > d->m_mapImpl->pixelSizeMeter()) || (fabs(simPoint.y() - p.y()) > d->m_mapImpl->pixelSizeMeter())) + { + ok = false; +#ifdef LTS_DEBUG + assert(false); +#endif + } + } + } + else qWarning() << LOGFUNCTION << "Rubberband coordinates are modified by user, skip coordinate test."; + rubberband->deleteMySelf(); + + if (!ok) + qWarning() << LOGFUNCTION << "Rubberband coordinates may be changed without editing."; + }; + + RB::RubberbandMultiPointFeature *rband = d->m_mapInterface->rubberbandFactory().create(); + + if (rband == nullptr) + qWarning() << "Creating rubberband multi point failed"; + else + { + rband->setCoordinateVector(vertexList); + + connect(rband, &RB::Rubberband::apply, slotFunc); + connect(rband, &RB::Rubberband::abort, slotFunc); + } +} /* FunctionDialog::onRubberbandCheckCoordinates */ + + + + +#include "state_icon_a.xpm" +#include "state_icon_b.xpm" +#include "state_icon_c.xpm" + +void FunctionDialog::onRubberbandStateSelect() +{ + static QVector vertexList; + + if (vertexList.isEmpty()) + { + vertexList << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-338163, 168551)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(154770, 168551)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(154770, -71025)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-338163, -71025)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(36042.4, 38162.5)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(302120, -46643.1)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(244876, -224735)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-21201.4, -139929)); + } + + RB::RubberbandStateSelect *rband = d->m_mapInterface->rubberbandFactory().create(); + + if (rband) + { + rband -> setHandleFillColor(QColor(0, 0, 0, 0)); + rband -> setHandleSelectionColor(QColor(255, 0, 0, 100)); + + QVector stateVector; + QVector itemVector; + + stateVector << RB::RubberbandStateSelect::State('A', QLatin1String("Test State A"), QPixmap(stateIconA_xpm)) + << RB::RubberbandStateSelect::State('B', QLatin1String("Test State B"), QPixmap(stateIconB_xpm)) + << RB::RubberbandStateSelect::State('C', QLatin1String("Test State C"), QPixmap(stateIconC_xpm)); + + quint32 id = 815; + itemVector.reserve(vertexList.size()); + for (const QPointF &p : std::as_const(vertexList)) + { + itemVector << RB::RubberbandStateSelect::StateItem(id, QStringLiteral("Item %1").arg(id), p, 'A'); + ++id; + } + + if (rband->setStateItems(stateVector, itemVector)) + { + auto slotFunc = [ = ](RB::RubberbandBase * rubberband) + { + rubberband->deleteMySelf(); + }; + connect(rband, &RB::Rubberband::apply, slotFunc); + connect(rband, &RB::Rubberband::abort, slotFunc); + } + else + { + rband->deleteMySelf(); + qWarning() << "Setting item states to rubberband state select failed."; + } + } + else + qWarning() << "Creating rubberband state select"; + +} /* FunctionDialog::onRubberbandStateSelect */ + + +void FunctionDialog::onRubberbandTwoInOne() +{ + qCritical() << " onRubberbandTwoInOne..."; + + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(false); + + static QVector s_vertexList; + s_vertexList.clear(); + + + const auto onApply2 = [](RB::RubberbandBase *rubberband) -> void + { + qCritical() << " onRubberbandTwoInOne onApply 2 "; + + RB::RubberbandEllipse *rubberbandElli = dynamic_cast(rubberband); + if (!rubberband) + { + qCritical() << " onRubberbandTwoInOne dynamic_cast failed"; + return; + } + + if (!rubberbandElli) + { + qCritical() << " onRubberbandTwoInOne no Rubberband!"; + return; + } + + QVector coordinateVector = rubberbandElli->coordinateVector(); + qCritical() << " onRubberbandTwoInOne coordinateVector = " << coordinateVector; + + QVector data = rubberbandElli->rectangleVerticies(); + const LtsCoordTrans::UtmCoordinate p0(LtsCoordTrans::UtmCoordinate::fromGeographic(data.at(0))); + const LtsCoordTrans::UtmCoordinate p1(LtsCoordTrans::UtmCoordinate::fromGeographic(data.at(2), p0.zone)); + const double radius = fabs((p0.x - p1.x) / 2); + qCritical() << " onRubberbandTwoInOne radius 2 = " << radius; + + }; // onApply2 + + + + const auto onApply1 = [this, onApply2](RB::RubberbandBase *rubberband) -> void + { + qCritical() << " onRubberbandTwoInOne onApply 1 "; + + RB::RubberbandMultiEllipseFeature *rubberbandElli = dynamic_cast(rubberband); + if (!rubberband) + { + qCritical() << " onRubberbandTwoInOne dynamic_cast failed"; + return; + } + + QPointF centerCoord = rubberbandElli->centerCoordinate(RB::CoordinateSystem::Map); + const QVector &radii = rubberbandElli->radii(); + + qCritical() << LOGFUNCTION << " FRIST centerCoord " << centerCoord + << " - radii = " << radii; + + double radiusMax = radii.last().x(); + + // 'save' for following rubberband: + s_vertexList = rubberbandElli->coordinateVector(); + + + // + // create SECOND RB + + // + QTimer::singleShot(250, this, [this, onApply2, radiusMax] + { + RB::RubberbandEllipse *rubberband2 = d->m_mapInterface->rubberbandFactory().create(); + if (rubberband2->rubberbandIsAlive()) + { + QObject::connect(rubberband2, &RB::Rubberband::apply, onApply2); + QObject::connect(rubberband2, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + else + { + qWarning() << "Creating rubberband 2 ellipse is not alive"; + rubberband2->deleteMySelf(); + } + + rubberband2->setAutoDelete(true); + + rubberband2->buildFromRectangleParamter(s_vertexList.first(), + radiusMax * 2, radiusMax * 2, + QPointF(1, 0)); +// rubberband2->setCoordinateVector(s_vertexList); + + rubberband2->minWidth(2*radiusMax); + rubberband2->maxWidth(2*radiusMax); + rubberband2->minHeight(2*radiusMax); + rubberband2->maxHeight(2*radiusMax); + + rubberband2->enableSector(true); + rubberband2->setSectorAngleFixed(true); + rubberband2->setSectorStart(0.75,0.75); + rubberband2->setSectorEnd(0.75,-0.75); + rubberband2->constrainQuadratic(true); +// rubberband2->constrainOrthogonal(true); + + rubberband2->setLineHandleWidth(2); + + }); + }; // onApply1 + + // + // 'main' + // + RB::RubberbandMultiEllipseFeature *rubberband = d->m_mapInterface->rubberbandFactory().create(); + + d->m_mapInterface->rubberbandFactory().allowMultipleRubberbands(true); + + if (!rubberband) + qWarning() << "Creating rubberband ellipse failed"; + else + { + rubberband->setAutoDelete(true); + rubberband->minimumRadiusCount(2); + rubberband->maximumRadiusCount(2); + rubberband->constrainQuadratic(true); + + if (rubberband->rubberbandIsAlive()) + { + QObject::connect(rubberband, &RB::Rubberband::apply, onApply1); + QObject::connect(rubberband, &RB::Rubberband::abort, this, &FunctionDialog::onRubberbandAbort); + } + else + { + qWarning() << "Creating rubberband 1 ellipse is not alive"; + rubberband->deleteMySelf(); + } + } + + +} + + +void FunctionDialog::onRectCheckCoordinates() +{ + static QVector vertexList; + + if (vertexList.isEmpty()) + { + vertexList << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-338163, 168551)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(154770, 168551)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(154770, -71025)) + << s_basePoint.geographic(d->m_objectBasePoint + QPointF(-338163, -71025)); + } + + auto slotFunc = [ = ](RB::RubberbandBase * rubberband) + { + bool ok = true; + RB::RubberbandRectangle *rband = dynamic_cast(rubberband); + + if (rband == nullptr) return; + + QVector rectangleCoords = rband->rectangleVerticies(); + for (int i = 0; i < rectangleCoords.size(); ++i) + { + const QPointF &simPoint = rectangleCoords[i]; + const QPointF &p = vertexList[i]; + + if ((fabs(simPoint.x() - p.x()) > d->m_mapImpl->pixelSizeMeter()) || (fabs(simPoint.y() - p.y()) > d->m_mapImpl->pixelSizeMeter())) + ok = false; + } + + if (!rband->rectangleIsValid()) + qWarning() << LOGFUNCTION << "Rectangle rubberband has been initialized with invalid coordinates"; + else + { + if (!ok) qWarning() << LOGFUNCTION << "Rubberband coordinates changed without editing."; + } + + rubberband->deleteMySelf(); + }; // slotFunc + + RB::RubberbandRectangle *rband = d->m_mapInterface->rubberbandFactory().create(); + + if (rband == nullptr) + qWarning() << "Creating rubberband rectangle failed"; + else + { + rband->setCoordinateVector(vertexList); + + connect(rband, &RB::Rubberband::apply, slotFunc); + connect(rband, &RB::Rubberband::abort, slotFunc); + } +} /* FunctionDialog::onRectCheckCoordinates */ + + + + +void FunctionDialog::addToolsTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Tools - ObjIndicator, measure, Location")); + + //----------------------------------- + // createObjectIndicator + //----------------------------------- + QPushButton *pbObjectIndicator = new QPushButton(QLatin1String("Object Indicator")); + layout->addWidget(pbObjectIndicator); + connect(pbObjectIndicator, &QPushButton::clicked, + this, &FunctionDialog::onObjectIndicator); + + + //----------------------------------- + // createMeasurementButton + //----------------------------------- + QPushButton *pbCreateMeasurement = new QPushButton(QLatin1String("measure length")); + layout->addWidget(pbCreateMeasurement); + connect(pbCreateMeasurement, &QPushButton::clicked, + this, &FunctionDialog::onMeasureLength); + + + //----------------------------------- + // LocationDisplay + //----------------------------------- + QGridLayout *locationDisplayLayout = new QGridLayout(); + locationDisplayLayout->setObjectName(QLatin1String("LocationDisplayLayout")); + layout->addLayout(locationDisplayLayout, layout->rowCount(), 0); + + //----------------------------------- + // startLocationDisplay + //----------------------------------- + QPushButton *pbstartLocationDisplay = new QPushButton(QLatin1String("start LocationDisplay")); + locationDisplayLayout->addWidget(pbstartLocationDisplay, 0, 0); + connect(pbstartLocationDisplay, &QPushButton::clicked, + this, &FunctionDialog::onStartLocationDisplay); + //----------------------------------- + // stopLocationDisplay + //----------------------------------- + QPushButton *pbstopLocationDisplay = new QPushButton(QLatin1String("stop LocationDisplay")); + locationDisplayLayout->addWidget(pbstopLocationDisplay, 0, 1); + connect(pbstopLocationDisplay, &QPushButton::clicked, + this, &FunctionDialog::onStopLocationDisplay); +} + +void FunctionDialog::addViewshedTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Tool Viewshed")); + + //----------------------------------- + // createViewshedButtons + //----------------------------------- + QPushButton *pbLoadFile = new QPushButton(QLatin1String(" pbLoadFile")); + layout->addWidget(pbLoadFile); + connect(pbLoadFile, &QPushButton::clicked, + this, &FunctionDialog::onLoadFile); + +#ifdef USE_LOCAL_SERVER + { + QPushButton *pbStartLocalService = new QPushButton(QLatin1String(" Start LocalService")); + layout->addWidget(pbStartLocalService); + connect(pbStartLocalService, &QPushButton::clicked, + this, &FunctionDialog::onStartLocalService); + pbStartLocalService->setStyleSheet(QLatin1String("QPushButton {background-color: gray;}")); + + QPushButton *pbStopLocalService = new QPushButton(QLatin1String(" Stop LocalService")); + layout->addWidget(pbStopLocalService); + connect(pbStopLocalService, &QPushButton::clicked, + this, &FunctionDialog::onStopLocalService); + + QPushButton *pbStartViewshedSvc = new QPushButton(QLatin1String(" Start ViewshedSvc")); + layout->addWidget(pbStartViewshedSvc); + connect(pbStartViewshedSvc, &QPushButton::clicked, + this, &FunctionDialog::onStartViewshedSvc); + pbStartViewshedSvc->setStyleSheet(QLatin1String("QPushButton {background-color: gray;}")); + + QPushButton *pbStopViewshedSvc = new QPushButton(QLatin1String(" Stop ViewshedSvc")); + layout->addWidget(pbStopViewshedSvc); + connect(pbStopViewshedSvc, &QPushButton::clicked, + this, &FunctionDialog::onStopViewshedSvc); + } + + + if (!d->m_viewshedDistance) + { + d->m_viewshedDistance = new QLineEdit(this); + layout->addWidget(d->m_viewshedDistance); + d->m_viewshedDistance->setText(QLatin1String("5123")); + } + + QPushButton *pbCreateViewshedIF = new QPushButton(QLatin1String("create viewshed interactive - at Interface")); + layout->addWidget(pbCreateViewshedIF); + connect(pbCreateViewshedIF, &QPushButton::clicked, + this, &FunctionDialog::onViewshedInteractive); + + QPushButton *pbCreateViewshedEsri = new QPushButton(QLatin1String("create viewshed - ESRI")); + layout->addWidget(pbCreateViewshedEsri); + connect(pbCreateViewshedEsri, &QPushButton::clicked, + this, &FunctionDialog::onViewshed); +#endif // USE_LOCAL_SERVER + + QPushButton *pbCreateViewshed = new QPushButton(QLatin1String("create viewshed - SightFan")); + layout->addWidget(pbCreateViewshed); + connect(pbCreateViewshed, &QPushButton::clicked, + this, &FunctionDialog::onViewshedSightFan); + + QFormLayout * sightFanLayout = new QFormLayout(); + + mainLayout->addLayout(sightFanLayout); + if (!d->m_sightFanObserverHeight) + { + d->m_sightFanObserverHeight = new QLineEdit(this); + d->m_sightFanTargetHeight = new QLineEdit(this); + + d->m_sightFanObserverHeight->setValidator(new QDoubleValidator(d->m_sightFanObserverHeight)); + d->m_sightFanTargetHeight->setValidator(new QDoubleValidator(d->m_sightFanTargetHeight)); + + d->m_sightFanObserverHeight->setText(QStringLiteral("2.0")); + d->m_sightFanTargetHeight->setText(QStringLiteral("2.0")); + + QPushButton *pbCreateSightFan = new QPushButton(QStringLiteral("Create Sight Fan"), this); + + connect(pbCreateSightFan, &QPushButton::clicked, this, &FunctionDialog::onSightFan ); + + sightFanLayout->addRow(QStringLiteral("Target Height"), d->m_sightFanObserverHeight); + sightFanLayout->addRow(QStringLiteral("Observer Height"), d->m_sightFanTargetHeight); + sightFanLayout->addWidget(pbCreateSightFan); + + } +} + +void SituationMap::FunctionDialog::addOverlayPlanTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Overlay/Plan")); + + + const auto slOverlays = { + QLatin1String("myplan"), + QLatin1String("myplan2") + }; + + int row = 0; + int key = 4711; +// layout->addWidget(new QLabel(QLatin1String("onOff")), row, 0); +// layout->addWidget(new QLabel(QLatin1String("load plan")), row, 1); +// layout->addWidget(new QLabel(QLatin1String("remove plan")), row, 2); + for (const auto &overlayName : slOverlays) + { + ++key; + ++row; + int col = 0; + QCheckBox *cbPlan = new QCheckBox(overlayName); + cbPlan->setProperty("key", key); + layout->addWidget(cbPlan, row, col); + connect(cbPlan, &QPushButton::clicked, + this, &FunctionDialog::onOverlayVisiChanged); + + ++col; + QPushButton *pbLoadPlan = new QPushButton(QLatin1String("load ") + overlayName); + pbLoadPlan->setProperty("name", overlayName); + pbLoadPlan->setProperty("key", key); + layout->addWidget(pbLoadPlan, row, 1); + connect(pbLoadPlan, &QPushButton::clicked, + this, &FunctionDialog::onLoadPlan); + + ++col; + QPushButton *pbRemovePlan = new QPushButton(QLatin1String("remove ") + overlayName); + pbRemovePlan->setProperty("key", key); + layout->addWidget(pbRemovePlan, row, 2); + connect(pbRemovePlan, &QPushButton::clicked, + this, &FunctionDialog::onRemovePlan); + } // Q_FOREACH + + // Remove all + ++row; + QPushButton *pbRemoveAll = new QPushButton(QLatin1String("Remove all overlays")); + layout->addWidget(pbRemoveAll, row, 0); + connect(pbRemoveAll, &QPushButton::clicked, + this, &FunctionDialog::onRemoveAllPlans); +} + +void FunctionDialog::addIconRenderTab() +{ + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Icon Demo")); + + //----------------------------------- + // create button + //----------------------------------- + LtsWidgets::LtsDragIcon *dummyIcon = new LtsWidgets::LtsDragIcon(this); + layout->addWidget(dummyIcon); + + dummyIcon->setText(QLatin1String("dummyIcon")); + + QImage dummyImage(100, 100, QImage::Format_ARGB32); + { + dummyImage.fill(Qt::transparent); + QPainter painter(&dummyImage); + painter.setBrush(QBrush(Qt::gray)); + painter.setPen(Qt::NoPen); + painter.drawEllipse(dummyImage.rect()); + painter.setPen(QPen(Qt::red)); + painter.drawText(dummyImage.rect(), Qt::AlignCenter, QLatin1String("dummyIcon")); + painter.end(); + } + + dummyIcon->setIcon(QIcon(QPixmap::fromImage(dummyImage))); + + // show template-based approach : + // (is executed by FunctionDialog::addIconRenderTab() ) + { +// AdvancedSymbology::SymbolResultWatcherQWidgetBase *dummyIconSibling = +// new AdvancedSymbology::SymbolResultWatcherQWidget(dummyIcon); + QSharedPointer dummyIconSibling (new AdvancedSymbology::SymbolResultWatcherQWidget(dummyIcon)); + + AdvancedSymbology::SymbolDictionary &dict = d->m_mapImpl->symbolDictionary(); + const QString sidc(AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10051000001213030000")); + + // option a + // dummyIconSibling->setSymbolResult(dict.findPreview(sidc)); + + // option b + AdvancedSymbology::SymbolDataToRender symDataToRender(sidc); + symDataToRender.injury = AdvancedSymbology::Injury::TotalKill; + symDataToRender.injuryColor = Qt::blue; + dummyIconSibling->setSymbolResult(dict.findImage(symDataToRender)); + + // option c + // dummyIconSibling->setSymbolResult(dict.findBaseSymbol(sidc)); + } + // : show template-based approach +} + +void FunctionDialog::addPortalTab() +{ + qDebug() << LOGFUNCTION ; + + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("Portal")); + + //----------------------------------- + // create button + //----------------------------------- + QPushButton *pbLoadPortal = new QPushButton(QLatin1String("load Portal")); + layout->addWidget(pbLoadPortal); + connect(pbLoadPortal, &QPushButton::clicked, this, &FunctionDialog::onPortal); + +} + +void FunctionDialog::addMovePointLuxemburgTab() +{ + qDebug() << LOGFUNCTION ; + + QWidget *w = new QWidget(); + + QVBoxLayout *mainLayout = new QVBoxLayout(w); + QGridLayout *layout = new QGridLayout(); + mainLayout->addLayout(layout); + mainLayout->addStretch(); + + d->m_tabwidget->addTab(w, QLatin1String("MovePoint")); + + //----------------------------------- + // create button + //----------------------------------- + QPushButton *pbMovePoint = new QPushButton(QLatin1String("MovePoint")); + layout->addWidget(pbMovePoint); + connect(pbMovePoint, &QPushButton::clicked, this, &FunctionDialog::onMovePoint); +} + +void FunctionDialog::onPortal() +{ + Portal *portal = new Portal(this); + + bool init = portal->init(); + qCritical() << " portal.init = " << init; + + // bool fetchData = portal->fetchData(); + // qCritical() << " portal.fetchData = " << fetchData; +} + +void FunctionDialog::onMovePoint() +{ + const int incrementX = 1000; + qCritical() << " move marker from Center to left (arcording UTM), incrementX [m]:" << incrementX; + + const QPointF centerGeo = d->m_mapImpl->centerPoint(); + const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerGeo); + + qCritical() << "center " << centerGeo + << " # utm " << centerUtm.x << " ; " << centerUtm.y<< " # utm " << centerUtm.mgrs(); + + + PositionCont posContIncrement; + posContIncrement.reserve(10); + + for (int i=0; i< 10; ++i) + { + LtsCoordTrans::UtmCoordinate movedCoord = centerUtm; + movedCoord.x -= i*incrementX; + + qCritical() << qSetRealNumberPrecision(8) << " # UTM " << movedCoord.x << " ; " << movedCoord.y << " # utm " << movedCoord.mgrs(); + + { + LtsCoordTrans::UtmCoordinate reverse = LtsCoordTrans::UtmCoordinate::fromGeographic(movedCoord.geographic()); + qCritical() << qSetRealNumberPrecision(8) << " # rev " << reverse.x << " ; " << reverse.y << " # utm " << reverse.mgrs(); + + // diff zw. movedCoord & reverse: + QPointF diff = movedCoord.geographic() - reverse.geographic(); + qCritical() << qSetRealNumberPrecision(8) << " diff " << diff; + } + + appendQPointF(posContIncrement, movedCoord.geographic()); + } + + qCritical() << " reCreate Coord every step **********+"; + + // reCreate Coord every step + PositionCont posContRelative; + posContRelative.reserve(10); + + LtsCoordTrans::UtmCoordinate incrementedCoord = centerUtm; + + QPointF geo = incrementedCoord.geographic(); + + for (int i=0; i< 10; ++i) + { + LtsCoordTrans::UtmCoordinate coord = LtsCoordTrans::UtmCoordinate::fromGeographic(geo); + + qCritical() << qSetRealNumberPrecision(8) << " # UTM " << coord.x << " ; " << coord.y << " # utm " << coord.mgrs(); + appendQPointF(posContRelative, geo); + + coord.x -= incrementX; + geo = coord.geographic(); + } + + { + RefPointer polyline; + + polyline = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("PolyLINE_Lux1Increment")); + if (!polyline) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + polyline->setIdentifier(QLatin1String("Polyline-Luxemburg")); + LineStyle lineStyle(Qt::red, 5, LinePattern::Solid); + polyline->setLineStyle(lineStyle); + polyline->setPositions(posContIncrement); + bool success = polyline->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } + + { + RefPointer polyline; + + polyline = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("PolyLINE_Lux2Relative")); + if (!polyline) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + polyline->setIdentifier(QLatin1String("Polyline-Luxemburg")); + LineStyle lineStyle(Qt::blue, 2, LinePattern::Dash); + polyline->setLineStyle(lineStyle); + polyline->setPositions(posContRelative); + bool success = polyline->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } +} + + +void FunctionDialog::onPolygonCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_polygon)); + + d->m_polygon = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("PolyGON_Id")); + + if (!d->m_polygon) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_polygon->setIdentifier(QLatin1String("Polygon-NotOutLined")); + + d->m_polygon->setVisibility(d->m_visibility); + d->m_polygon->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_polygon->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_polygon->setSelected(d->m_selected); + + TextStyle textStyle(Qt::magenta); + LineStyle lineStyle(Qt::blue, 2, LinePattern::Solid); + FillStyle fillStyle(Qt::red, FillPattern::ForwardDiagonal); + + d->m_polygon->setTextStyle(textStyle); + d->m_polygon->setLineStyle(lineStyle); + d->m_polygon->setFillStyle(fillStyle); + + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(90000, 150000); + + int extent = 100000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-extent / 2, extent / 2))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(0, extent))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_polygon->setPositions(posCont); + + bool success = d->m_polygon->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_polygon->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " Polygon success = " << success << std::endl; + std::cout << " Polygon dump: " << (*d->m_polygon) << std::endl; + + +// QTimer* timer = new QTimer(this); +// connect(timer, &QTimer::timeout, this, &FunctionDialog::onDangerAreaUpdate); +// timer->setSingleShot(true); +// timer->start(2000); + +} + +void FunctionDialog::onPolylineCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_polyline)); + + d->m_polyline = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("PolyLINE_Id")); + + if (!d->m_polyline) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_polyline->setIdentifier(QLatin1String("Polyline-NotOutLined")); + + d->m_polyline->setVisibility(d->m_visibility); + d->m_polyline->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_polyline->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_polyline->setSelected(d->m_selected); + + TextStyle textStyle(Qt::darkGreen); + LineStyle lineStyle(Qt::red, 5, LinePattern::Solid); + FillStyle fillStyle(Qt::blue, FillPattern::DiagonalCross); + + d->m_polyline->setTextStyle(textStyle); + d->m_polyline->setLineStyle(lineStyle); + + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(100000, 150000); + + int extent = 100000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(0, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent / 2, extent / 2))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(0, 0))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_polyline->setPositions(posCont); + + bool success = d->m_polyline->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_polyline->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " Polyline success = " << success << std::endl; + std::cout << " Polyline dump: " << (*d->m_polyline) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onPolylineUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onPolylineUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_polyline) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_polyline->setArrowHeadStart(true); + d->m_polyline->setArrowHeadEnd(true); + + bool success = d->m_polyline->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onTextCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_text)); + + d->m_text = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("TextId")); + + if (!d->m_text) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_text->setIdentifier(QLatin1String("Text-NotOutLined\nNewLineForPatrick")); + + d->m_text->setVisibility(d->m_visibility); +// d->m_polyline->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); +// d->m_polyline->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_text->setSelected(d->m_selected); + + TextStyle textStyle(Qt::darkRed); + + d->m_text->setTextStyle(textStyle); + + + // set additional values: + QPointF origin = d->m_objectBasePoint + QPointF(90000, 120000); + + std::cout << " origin=" << origin << std::endl; + + origin = s_basePoint.geographic(origin); + d->m_text->setOrigin(Point3D::fromQPointF(origin)); + + bool success = d->m_text->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_text->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " Text success = " << success << std::endl; + std::cout << " Text dump: " << (*d->m_text) << std::endl; + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onTextUpdate); + timer->setSingleShot(true); + timer->start(2000); + +} + +void FunctionDialog::onTextUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_text) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_text->rTextStyle().setFontFamily(QLatin1String("Palatino")); // which values are valid????????? + d->m_text->rTextStyle().setHorizontalAlignment(HorizontalAlignment::Left); + std::cout << " Text dump: " << (*d->m_text) << std::endl; + + bool success = d->m_text->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onMapObjectIndicatorCreate() +{ + qDebug() << LOGFUNCTION ; + + static SituationMap::UniqueMapId uniqueId = SituationMap::INVALID_MAP_ID; + + if (uniqueId == SituationMap::INVALID_MAP_ID) + { + qDebug() << LOGFUNCTION << " create......"; + + // create and set debugInfo for debugging: + RefPointer moIndi + = d->m_mapInterface->objectFactory(). + create(QLatin1String("Participants"), QLatin1String("MapObjectIndicatorId")); + + if (!moIndi) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + uniqueId = moIndi->uniqueMapId(); + + moIndi->setIdentifier(QLatin1String("MapObjectIndicator")); + + moIndi->setVisibility(true); // was maybe invisible... + moIndi->setMapObjectToPointTo(1); + + bool success = moIndi->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = moIndi->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + } + else + { + qDebug() << LOGFUNCTION << " remove...... (uId=" << uniqueId << ")"; + + RefPointer moIndi = + d->m_mapInterface->objectFactory().get(uniqueId); + if (moIndi.valid()) + { + qDebug() << LOGFUNCTION << " removeMySelf: " << uniqueId; + moIndi->removeMySelf(); + } + } + +} + + +void FunctionDialog::onLayerPerfCreateLayer() +{ + qDebug() << LOGFUNCTION ; + + int layerCount = d->m_layerPerf_leLayer->text().toInt(); + int objCount = d->m_layerPerf_leObject->text().toInt(); + int objPerLayer = objCount / layerCount; + int radius = d->m_layerPerf_leRadius->text().toInt(); + bool withText = d->m_layerPerf_cbText->isChecked(); + + qDebug() << LOGFUNCTION << " LayerCount =" << layerCount; + qDebug() << LOGFUNCTION << " objCount =" << objCount; + qDebug() << LOGFUNCTION << " -> ObjPerLayer =" << objPerLayer; + qDebug() << LOGFUNCTION << " radius =" << radius; + qDebug() << LOGFUNCTION << " withText =" << withText; + + + QStringList layerNames; + LayerFactory &layerFac = d->m_mapImpl->layerFactory(); + + // + // create operational layer: + // + layerNames.reserve(layerCount - 1); + for (int i = 1; i <= layerCount; ++i) + layerNames << QStringLiteral("LayerPerformance_%1").arg(i); + + for (const auto &layerName : std::as_const(layerNames)) + layerFac.addOperationalLayer(layerName, layerName); + + // + // force creation of techn. Layer: + // create on object per layer: + // + + qDebug() << LOGFUNCTION << " Create ..."; + + // B-Agent -> GraphicsLayer only + for (int i = 0; i < layerCount; ++i) + { + QString layername = layerNames[i]; + + for (int j = 0; j < objPerLayer; ++j) + { + QString debugIdStr = QStringLiteral("DebugId_L%1_Obj%2").arg(i).arg(j); + + if (j % 100 == 0) + std::cerr << LOGFUNCTION + << " Layer = " << i + << " Obj = " << j << std::endl; + + + +#define BAGENT +#ifdef BAGENT + RefPointer bAgent + = d->m_mapInterface->objectFactory(). + create(layername, debugIdStr); + + if (!bAgent) + { + qDebug() << LOGFUNCTION << " no object"; + abort(); + // return; + } +// QPointF center = d->m_objectBasePoint + QPointF(10 * j, 10 * i); + QPointF center = d->m_mapImpl->centerPoint(); + + bAgent->setOrigin(Point3D::fromQPointF(center)); + bAgent->setRadius(radius); + + bAgent->setVisibility(d->m_visibility); + bAgent->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + bAgent->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + // if ( ( j%10 == 0) || ( (i==(layerCount-1)) && (j==(objPerLayer-1)) ) ) + { + if (withText) + bAgent->setIdentifier(debugIdStr); + } + + bAgent->updateView(); +#else + RefPointer part + = d->m_mapInterface->objectFactory(). + create(layername, debugIdStr); + + if (!part) + { + qDebug() << LOGFUNCTION << " no object"; + abort(); + return; + } + QPointF center = d->m_objectBasePoint + QPointF(1000 * j, 1000 * i); + + SituationMap::SymbolData symData; + symData.m_sidc = "SFGPUCD---"; + part->setSymbol(symData); + + part->setOrigin(center); + +// if ( ( j%10 == 0) || ( (i==(layerCount-1)) && (j==(objPerLayer-1)) ) ) + { + if (withText) + part->setIdentifier(debugIdStr); + } + + part->updateView(); + +#endif + + + } + } // for + + std::cerr << LOGFUNCTION << " Create ... DONE" << std::endl; + + std::cerr << LOGFUNCTION << " LayerCount =" << layerCount << std::endl; + std::cerr << LOGFUNCTION << " objCount =" << objCount << std::endl; + std::cerr << LOGFUNCTION << " -> ObjPerLayer =" << objPerLayer << std::endl; + std::cerr << LOGFUNCTION << " radius =" << radius << std::endl; + std::cerr << LOGFUNCTION << " withText =" << withText << std::endl; + + + +} /* FunctionDialog::onLayerPerfCreateLayer */ + + + +void FunctionDialog::onGenerateGraphics() +{ + + // + // + // Pay Attention to the current RenderingMode!!!!!!!!!!!!!!!!!! + // + // + static bool done = false; + if (done) + { + qWarning() << "already done"; + return; + } + else + done = true; + + + if (d->m_graphicsLayer) + { + qWarning() << "generate graphics"; + + // SurfacePlacement::Draped is default on V100.0 + //d->m_graphicsLayer->setSceneProperties(Esri::ArcGISRuntime::LayerSceneProperties(Esri::ArcGISRuntime::SurfacePlacement::Draped)); + + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + +#define TEXT_WKT +#ifdef TEXT_WKT + { + double i = 1000000; // Meter + + QString wkText = QLatin1String("GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137,298.257223563]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]]"); + /* + "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",\ + GEOGCS[\"GCS_WGS_1984\",\ + DATUM[\"D_WGS_1984\",\ + SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],\ + PRIMEM[\"Greenwich\",0.0],\ + UNIT[\"Degree\",0.0174532925199433]],\ + PROJECTION[\"Mercator_Auxiliary_Sphere\"],\ + PARAMETER[\"False_Easting\",0.0],\ + PARAMETER[\"False_Northing\",0.0],\ + PARAMETER[\"Central_Meridian\",0.0],\ + PARAMETER[\"Standard_Parallel_1\",0.0],\ + PARAMETER[\"Auxiliary_Sphere_Type\",0.0],\ + UNIT[\"Meter\",1.0]]\""); + + */ + + const Esri::ArcGISRuntime::SpatialReference wgs84(wkText); + + +// \brief Projects the given point from EPSG:4326 (WGS-84) to the output spatial reference. +// \param[in] x The x coordinate of the point. +// \param[in] y The y coordinate of the point. +// \param[in] sr The spatial reference to which to project the point. +// \return The projected point. + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + const Esri::ArcGISRuntime::Point &bremenPoint = mapCenter; +// bremenPoint = Esri::ArcGISRuntime::GeometryEngine::project( Esri::ArcGISRuntime::Point(8.897803, 53.054957, Esri::ArcGISRuntime::SpatialReference(4326)), sr); + + // myWKT : + QLatin1String myWkt( + "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",\ + GEOGCS[\"GCS_WGS_1984\",\ + DATUM[\"D_WGS_1984\",\ + SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],\ + PRIMEM[\"Greenwich\",0.0],\ + UNIT[\"Degree\",0.0174532925199433]],\ + PROJECTION[\"Mercator_Auxiliary_Sphere\"],\ + PARAMETER[\"False_Easting\",1000000.0],\ + PARAMETER[\"False_Northing\",1000000.0],\ + PARAMETER[\"Central_Meridian\",0.0],\ + PARAMETER[\"Standard_Parallel_1\",0.0],\ + PARAMETER[\"Auxiliary_Sphere_Type\",0.0],\ + UNIT[\"Meter\",1.0]]\""); + + Esri::ArcGISRuntime::Point myPoint(i, i, Esri::ArcGISRuntime::SpatialReference(myWkt)); +// bremenPoint = EsriRuntimeQt::GeometryEngine::project(myPoint, sr); + // : myWKT + + // Esri::ArcGISRuntime::Point(lon, lat) + Esri::ArcGISRuntime::Point marseille(5.371113, 43.283954, 0, sr); + Esri::ArcGISRuntime::Point nullPoint(0, 0, 0, sr); + +// nullPoint = marseille; + + + Esri::ArcGISRuntime::SimpleMarkerSymbol redCircle(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle, Qt::red, 20); + Esri::ArcGISRuntime::Graphic *graphic1 = new Esri::ArcGISRuntime::Graphic(nullPoint, &redCircle); + d->m_graphicsLayer->graphics()->append(graphic1); + + Esri::ArcGISRuntime::SimpleMarkerSymbol blueCircle(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle, Qt::blue, 10); + Esri::ArcGISRuntime::Graphic *myGraphic = new Esri::ArcGISRuntime::Graphic(bremenPoint, &blueCircle); + d->m_graphicsLayer->graphics()->append(myGraphic); + + Esri::ArcGISRuntime::PolylineBuilder builder(sr); + builder.addPoint(nullPoint); + builder.addPoint(Esri::ArcGISRuntime::Point(bremenPoint.x(), bremenPoint.y(), 100, bremenPoint.spatialReference())); + + Esri::ArcGISRuntime::SimpleLineSymbol lineSym(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, QColor("green"), 5); + + Esri::ArcGISRuntime::Graphic *graphicPolyline = new Esri::ArcGISRuntime::Graphic(builder.toGeometry(), &lineSym); + d->m_graphicsLayer->graphics()->append(graphicPolyline); + + } +#else // WKT + { + //----------------------------------- + // LINE + //----------------------------------- + EsriRuntimeQt::Point point11(0, 0, sr); + EsriRuntimeQt::Point point12(i, i, sr); + + EsriRuntimeQt::SimpleMarkerSymbol blueCircle(Qt::blue, 8, EsriRuntimeQt::SimpleMarkerSymbolStyle::Circle); + EsriRuntimeQt::Graphic *graphic1 = new EsriRuntimeQt::Graphic(point12, blueCircle); + d->m_graphicsLayer->addGraphic(graphic1); + + EsriRuntimeQt::LIST_OF_POINTS lop; + lop.append(point11); + lop.append(point12); + EsriRuntimeQt::Polyline polyline(QList() << lop); + + EsriRuntimeQt::SimpleLineSymbol lineSym; + + EsriRuntimeQt::Graphic *graphicPolyline = new EsriRuntimeQt::Graphic(polyline, lineSym); + d->m_graphicsLayer->addGraphic(graphicPolyline); + + //----------------------------------- + // CIRCLE + //----------------------------------- + int radius = sqrt(2.0 * pow(i, 2)); // Meter + drawEllipse(d->m_graphicsLayer, point11, radius, radius, 5000.0, QColor(230, 139, 200)); + + + // MOVE (GeometryEngine::geodesicMovePoints) + QList plIN, plOUT; + plIN << point11; + int wkid = sr.wkid(); + EsriRuntimeQt::Unit *unit = EsriRuntimeQt::Unit::create(wkid); + qWarning() << "geodesicMovePoints" + << " wkid=" << wkid + // << " unit = " << unit->name() + ; + + plOUT = EsriRuntimeQt::GeometryEngine::geodesicMovePoints(plIN, + sqrt(2.0 * pow(i, 2)), + 45.0, + // nullptr + unit + ); + EsriRuntimeQt::SimpleMarkerSymbol greenDot(Qt::green, 20, EsriRuntimeQt::SimpleMarkerSymbolStyle::Triangle); + greenDot.setAngle(10); + EsriRuntimeQt::Graphic *graphicMoved = new EsriRuntimeQt::Graphic(plOUT.at(0), greenDot); + d->m_graphicsLayer->addGraphic(graphicMoved); + + //----------------------------------- + // CIRCLE II + //----------------------------------- + { + int radius = 10400; // Meter + drawEllipse(d->m_graphicsLayer, point12, radius, radius, radius / 20, Qt::yellow); + } + + + //----------------------------------- + // IMAGE + //----------------------------------- + int widthPixel = 100; + QImage image(widthPixel, widthPixel, QImage::Format_ARGB32); + image.fill(qRgba(0, 0, 0, 0)); + + QPainter p; + QPen pen(Qt::yellow); + pen.setWidth(5); + + p.begin(&image); + p.setPen(pen); + p.drawLine(QPointF(0, 0), QPointF(100, 100)); + p.end(); + + bool success = image.save(QLatin1String("/tmp/dummy.png")); + qWarning() << " success = " << success; + + EsriRuntimeQt::PictureMarkerSymbol pms(image); + EsriRuntimeQt::Point origin(20000, 20000, d->m_graphicsLayer->spatialReference()); + EsriRuntimeQt::Graphic *graphic = new EsriRuntimeQt::Graphic(origin, pms); + + qint64 gid = d->m_graphicsLayer->addGraphic(graphic); + qWarning() << " gid = " << gid; + + + + //----------------------------------- + // TEXT + //----------------------------------- + EsriRuntimeQt::TextSymbol textSymbol; + + textSymbol.setText(QLatin1String("Mein Langer Text")); + + textSymbol.setTextColor(QColor(255, 0, 0)); + textSymbol.setSize(20.0); + textSymbol.setTextOutlineWidth(1.0); + textSymbol.setTextOutlineColor(QColor(255, 0, 0)); + textSymbol.setHorizontalAlignment(EsriRuntimeQt::HorizontalAlignment::Left); + textSymbol.setVerticalAlignment(EsriRuntimeQt::VerticalAlignment::Middle); + + // 2. Graphic + EsriRuntimeQt::Geometry originGeometry(point11); + EsriRuntimeQt::Graphic *textGraphic = new EsriRuntimeQt::Graphic(originGeometry, + textSymbol); + // 3. add graphic + qint64 gidText = d->m_graphicsLayer->addGraphic(textGraphic); + qWarning() << " gidText = " << gidText; + } +#endif // WKT + } + else + { + qWarning() << "no m_graphicsLayer"; + QMessageBox msgBox; + msgBox.setText(QLatin1String("no m_graphicsLayer")); + msgBox.exec(); + } +} + +Esri::ArcGISRuntime::Graphic *FunctionDialog::createCube(const PositionCont &mapPosCont, + int baseHeight, + double objHeight, + const QString &partyNameValue) +{ + if (!d->m_graphicsLayer) + { + qCritical() << " no m_graphicsLayer"; + return nullptr; + } + + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + Esri::ArcGISRuntime::PolygonBuilder builder(sr, this); + + for (const auto &coord : mapPosCont) + { + Esri::ArcGISRuntime::Point pnt (coord.x(), coord.y(), baseHeight); + builder.addPoint(pnt); + qCritical() << " coord=" << pnt; + } + + Esri::ArcGISRuntime::Polygon poly(builder.toGeometry()); + + const int maxSegmentLength = 100; + Esri::ArcGISRuntime::Geometry geometry = Esri::ArcGISRuntime::GeometryEngine::densifyGeodetic(poly, + maxSegmentLength, + Esri::ArcGISRuntime::LinearUnit::meters(), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + + Esri::ArcGISRuntime::Graphic *graphic = new Esri::ArcGISRuntime::Graphic(this); + graphic->setGeometry(geometry); + QVariantMap att = graphic->attributes()->attributesMap(); + att[QLatin1String("height")] = QString::number(objHeight); + qCritical() << " height=" << att[QLatin1String("height")]; + if (!partyNameValue.isEmpty()) + att[QLatin1String("PartyName")] = partyNameValue; + graphic->attributes()->setAttributesMap(att); + + return graphic; +} + + +void FunctionDialog::onGenerateCube() +{ + static Esri::ArcGISRuntime::Graphic *s_graphic = nullptr; + if (s_graphic) + { + qWarning() << "removing..."; + if (d->m_graphicsLayer && d->m_graphicsLayer->graphics()) + d->m_graphicsLayer->graphics()->removeOne(s_graphic); + s_graphic->deleteLater(); + s_graphic = nullptr; + return; + } + + d->m_graphicsLayer->setSceneProperties(Esri::ArcGISRuntime::LayerSceneProperties(Esri::ArcGISRuntime::SurfacePlacement::Relative)); + + // Renderer + QColor col = Qt::red; + col.setAlphaF(0.5); + Esri::ArcGISRuntime::SimpleFillSymbol *sls = + new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, col, this); + + qCritical() << " USE RENDERER"; + Esri::ArcGISRuntime::SimpleRenderer *renderer = new Esri::ArcGISRuntime::SimpleRenderer(sls, this); + Esri::ArcGISRuntime::RendererSceneProperties props = renderer->sceneProperties(); + props.setExtrusionMode(Esri::ArcGISRuntime::ExtrusionMode::BaseHeight); + props.setExtrusionExpression(QLatin1String("[height]")); + renderer->setSceneProperties(props); + + d->m_graphicsLayer->setRenderer(renderer); + +// Esri::ArcGISRuntime::GraphicsOverlay *gOverlay = d->m_mapImpl->getGraphicsLayer3DExtrusion(QLatin1String("AES")); +// qCritical() << " USE RENDERER gOverlay " << gOverlay; +// gOverlay->setRenderer(renderer); + + qCritical() << " USE RENDERER" << (int) props.extrusionMode(); + + // Geometry + int sizeX = 1500; + int sizeY = 50; + + SituationMap::PositionCont posCont; + + LtsCoordTrans::UtmCoordinate local_basePoint; + + // local_basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(QLatin1String("32UNE000000000000")); // Bremen 32UMD000000000000 + local_basePoint = s_basePoint; + + QPointF offset = d->m_objectBasePoint + QPointF(91000, 77000); + appendQPointF(posCont, local_basePoint.geographic(offset + QPointF(-sizeX, -sizeY))); // ul + appendQPointF(posCont, local_basePoint.geographic(offset + QPointF(-sizeX, sizeY))); + appendQPointF(posCont, local_basePoint.geographic(offset + QPointF( sizeX, sizeY))); + appendQPointF(posCont, local_basePoint.geographic(offset + QPointF( sizeX, -sizeY))); + + SituationMap::PositionsTranslated posTrans; + posTrans.setCoordTransFunctor(d->m_mapImpl->coordTranslationFunctor()); + posTrans.setPositions(posCont); + SituationMap::PositionCont posContTrans = posTrans.positionsTranslated(); + + s_graphic = createCube(posContTrans, 10, 50); + + d->m_graphicsLayer->graphics()->append(s_graphic); +} + +void FunctionDialog::onGenerateCubeArray() +{ + if (!d->m_graphicsLayer) + { + qCritical() << " no m_graphicsLayer"; + return; + } + if (d->m_mapImpl->mode() != Mode::Mode_3D) + return; + + d->m_graphicsLayer->setSceneProperties(Esri::ArcGISRuntime::LayerSceneProperties(Esri::ArcGISRuntime::SurfacePlacement::Relative)); + + // Renderer + { + QColor col = Qt::red; + col.setAlphaF(0.25); + Esri::ArcGISRuntime::SimpleFillSymbol *sls = + new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, col, this); + + qCritical() << " USE RENDERER"; + Esri::ArcGISRuntime::SimpleRenderer *renderer = new Esri::ArcGISRuntime::SimpleRenderer(sls, this); + Esri::ArcGISRuntime::RendererSceneProperties props = renderer->sceneProperties(); + props.setExtrusionMode(Esri::ArcGISRuntime::ExtrusionMode::BaseHeight); + props.setExtrusionExpression(QLatin1String("[height]")); + renderer->setSceneProperties(props); + + d->m_graphicsLayer->setRenderer(renderer); + } + + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + + Esri::ArcGISRuntime::Graphic *clipGraphic = nullptr; + { + const double clipOffset = 0.0085; + Esri::ArcGISRuntime::PolygonBuilder builder(sr, this); + builder.addPoint(mapCenter.x(), mapCenter.y()); + builder.addPoint(mapCenter.x()-clipOffset, mapCenter.y() + clipOffset); + builder.addPoint(mapCenter.x()+clipOffset, mapCenter.y() + clipOffset); + clipGraphic = new Esri::ArcGISRuntime::Graphic(builder.toGeometry(), this); +// Esri::ArcGISRuntime::SimpleLineSymbol *sls = +// new Esri::ArcGISRuntime::SimpleLineSymbol(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, Qt::yellow, 5, this); +// clipGraphic->setSymbol(sls); + + + QVariantMap att = clipGraphic->attributes()->attributesMap(); + att[QLatin1String("height")] = QString::number(2); + clipGraphic->attributes()->setAttributesMap(att); + + + d->m_graphicsLayer->graphics()->append(clipGraphic); + } + + + QElapsedTimer elapsedTimer; + elapsedTimer.start(); + + + const int maxRow = 10; + const int maxColumn = 10; + const double offsetSize = 0.001; + + Esri::ArcGISRuntime::PartCollection partCollection(sr); + + for (int row=0; rowattributes()->attributesMap(); +// att[QLatin1String("height")] = QString::number(10); +// graphic->attributes()->setAttributesMap(att); + +// d->m_graphicsLayer->graphics()->append(graphic); + + Esri::ArcGISRuntime::Geometry clippedGeometry = Esri::ArcGISRuntime::GeometryEngine::intersection(polylineBuilder.toPolygon(), + clipGraphic->geometry()); +// qCritical() << " cg" << clippedGeometry.toJson(); + + if (!clippedGeometry.isEmpty()) + { + Esri::ArcGISRuntime::Polygon poly(clippedGeometry); + const Esri::ArcGISRuntime::ImmutablePointCollection pc = poly.parts().part(0).points(); + + Esri::ArcGISRuntime::Part part(sr); + for (const Esri::ArcGISRuntime::Point &pnt : pc) + part.addPoint(pnt); + + // part.addPoint(bottomLeft.x() + 0, bottomLeft.y() + 0); + // part.addPoint(bottomLeft.x() + offsetSize, bottomLeft.y() + 0); + // part.addPoint(bottomLeft.x() + offsetSize, bottomLeft.y() + offsetSize); + // part.addPoint(bottomLeft.x() + 0, bottomLeft.y() + offsetSize); + + partCollection.addPart(&part); + } + + } // for + } // for + + Esri::ArcGISRuntime::Graphic *multiPartGraphic = new Esri::ArcGISRuntime::Graphic(this); + + Esri::ArcGISRuntime::PolygonBuilder builder(sr, this); + builder.setParts(&partCollection); + + const Esri::ArcGISRuntime::Polygon &multipartPolygon = builder.toPolygon(); + multiPartGraphic->setGeometry(multipartPolygon); + + int msec = elapsedTimer.elapsed(); + qCritical() << " XYZ took [msec]: " << msec; + + + QVariantMap att = multiPartGraphic->attributes()->attributesMap(); + att[QLatin1String("height")] = QString::number(10); + multiPartGraphic->attributes()->setAttributesMap(att); + d->m_graphicsLayer->graphics()->append(multiPartGraphic); + +// Esri::ArcGISRuntime::SimpleLineSymbol *sls = +// new Esri::ArcGISRuntime::SimpleLineSymbol(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, Qt::green, 5, this); +// graphic->setSymbol(sls); +} + +void FunctionDialog::onGenerateStackedCubes() +{ + if (!d->m_graphicsLayer) + { + qCritical() << " no m_graphicsLayer"; + return; + } + + d->m_graphicsLayer->setSceneProperties(Esri::ArcGISRuntime::LayerSceneProperties(Esri::ArcGISRuntime::SurfacePlacement::DrapedFlat)); + + // RenderingMode::Dynamic: ok, for V100.0 ; V100.1 + // RenderingMode::Static: no Height in V100.1 + + // Renderer + qCritical() << " USE UniqueValueRenderer"; + Esri::ArcGISRuntime::SimpleFillSymbol *sls; + + const QVector partyList = { + QLatin1String("red"), + QLatin1String("blue"), + QLatin1String("green") + }; + QList uvList; + + for (const auto &party : partyList) + { + QColor col(party); + // col.setAlphaF(0.3); // ---> set opacity of complete layer!!!???!!! + sls = new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, col, this); + + Esri::ArcGISRuntime::UniqueValue *uv = + new Esri::ArcGISRuntime::UniqueValue(party, + QStringLiteral("Party '%1'").arg(party), + QVariantList() << party, sls, this); + uvList.append(uv); + } // Q_FOREACH + + sls = new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, Qt::white, this); + + Esri::ArcGISRuntime::UniqueValueRenderer* uniqueRenderer = + new Esri::ArcGISRuntime::UniqueValueRenderer(QLatin1String("UnknownParty"), + sls, // default + QStringList() << QLatin1String("PartyName"), + uvList, this); + + Esri::ArcGISRuntime::RendererSceneProperties props = uniqueRenderer->sceneProperties(); + props.setExtrusionMode(Esri::ArcGISRuntime::ExtrusionMode::BaseHeight); + props.setExtrusionExpression(QLatin1String("[height]")); + uniqueRenderer->setSceneProperties(props); + d->m_graphicsLayer->setRenderer(uniqueRenderer); + + + // Geometry + const auto centerPoints = { + d->m_objectBasePoint + QPointF(92400, 78600), + d->m_objectBasePoint + QPointF(92450, 78600), + d->m_objectBasePoint + QPointF(92450, 78675), + d->m_objectBasePoint + QPointF(93069, 78331) + }; + + SituationMap::PositionCont posCont; + for (const QPointF ¢erPoint : centerPoints) + { + double sizeX = 10; + double sizeY = sizeX; + + for (int i=0; i<3; ++i) + { + sizeX -= 0.01; + sizeY -= 0.01; + + posCont.clear(); + posCont.reserve(4); + appendQPointF(posCont, s_basePoint.geographic(centerPoint + QPointF(-sizeX, -sizeY))); // ul + appendQPointF(posCont, s_basePoint.geographic(centerPoint + QPointF(-sizeX, sizeY))); + appendQPointF(posCont, s_basePoint.geographic(centerPoint + QPointF( sizeX, sizeY))); + appendQPointF(posCont, s_basePoint.geographic(centerPoint + QPointF( sizeX, -sizeY))); + + SituationMap::PositionsTranslated posTrans; + posTrans.setCoordTransFunctor(d->m_mapImpl->coordTranslationFunctor()); + posTrans.setPositions(posCont); + SituationMap::PositionCont posContTrans = posTrans.positionsTranslated(); + const QString &partyNameValue = partyList[i]; + Esri::ArcGISRuntime::Graphic *graphic = createCube(posContTrans, 1000, 1+i, partyNameValue); + + d->m_graphicsLayer->graphics()->append(graphic); + } // for + } // Q_FOREACH +} + +void FunctionDialog::onGenerateGraphicsKristenStrandford() +{ + static bool done = false; + if (done) + { + qWarning() << "already done"; + return; + } + else + done = true; + + if (d->m_graphicsLayer) + { + qWarning() << "onGenerateGraphicsKristenStrandford"; + + // SurfacePlacement::Draped is default on V100.0 + d->m_graphicsLayer->setSceneProperties(Esri::ArcGISRuntime::LayerSceneProperties(Esri::ArcGISRuntime::SurfacePlacement::Absolute)); + + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + Esri::ArcGISRuntime::Point myLocation = Esri::ArcGISRuntime::Point(mapCenter.x(), mapCenter.y(), 1000, sr); + Esri::ArcGISRuntime::Point skyLocation = Esri::ArcGISRuntime::Point(mapCenter.x(), mapCenter.y(), 99999, sr); + + qCritical() << " myLocation=" << myLocation.toJson() << " z=" << myLocation.z();; + qCritical() << " skyLocation=" << skyLocation.toJson() << " z=" << skyLocation.z(); + + Esri::ArcGISRuntime::PolygonBuilder polylineBuilder(sr, this); + polylineBuilder.addPoint(myLocation); + polylineBuilder.addPoint(skyLocation); + + Esri::ArcGISRuntime::SimpleLineSymbol *sls = + new Esri::ArcGISRuntime::SimpleLineSymbol(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, Qt::red, 5, this); + + Esri::ArcGISRuntime::Graphic *lineGraphic = new Esri::ArcGISRuntime::Graphic(polylineBuilder.toGeometry(), this); + +//#define USE_RENDERER +#ifdef USE_RENDERER + qCritical() << " USE RENDERER"; + d->m_graphicsLayer->setRenderer(new Esri::ArcGISRuntime::SimpleRenderer(sls, this)); +#else + qCritical() << " USE no RENDERER"; + lineGraphic->setSymbol(sls); +#endif // USE_RENDERER + + d->m_graphicsLayer->graphics()->append(lineGraphic); + } +} + +void FunctionDialog::onGenerateUniqueVaueRendererMountingPoints() +{ + qCritical() << " onGenerateUniqueVaueRendererMountingPoints"; + + static QString s_prefix(QStringLiteral("MyXXXXXXXXXXXXXXXXXXXX_VeryLongPrefix_")); + + qCritical() << " USE UniqueValueRenderer"; + Esri::ArcGISRuntime::SimpleMarkerSymbol *sms; + + const QVector partyList = { + QStringLiteral("red"), + QStringLiteral("blue"), + QStringLiteral("blue"), + QStringLiteral("green") + }; + QList uvList; + + for (const auto &party : partyList) + { + QColor col(party); + sms = new Esri::ArcGISRuntime::SimpleMarkerSymbol(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Diamond, col, 20, this); + +// QString value = party; + QString value = s_prefix+party; + + qCritical() << " XXXXXX value '" << value << "'"; + + Esri::ArcGISRuntime::UniqueValue *uv = + new Esri::ArcGISRuntime::UniqueValue(party, + QStringLiteral("Party '%1'").arg(party), + QVariantList() << value, sms, this); + uvList.append(uv); + } // Q_FOREACH + + // default: + sms = new Esri::ArcGISRuntime::SimpleMarkerSymbol(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::X, Qt::white, 15, this); + + Esri::ArcGISRuntime::UniqueValueRenderer* uniqueRenderer = + new Esri::ArcGISRuntime::UniqueValueRenderer(QStringLiteral("UnknownParty"), + sms, // default + QStringList() << QStringLiteral("MountingPointFieldName"), + uvList, this); + uniqueRenderer->setObjectName(QStringLiteral("UniqueValueRenderer_onGenerateUniqueVaueRendererMountingPoints")); + d->m_graphicsLayer->setRenderer(uniqueRenderer); + + const auto centerPoints = { + d->m_objectBasePoint + QPointF(91400, 78600), + d->m_objectBasePoint + QPointF(92450, 78600), + d->m_objectBasePoint + QPointF(92450, 78675), + d->m_objectBasePoint + QPointF(93069, 78331) + }; + + for (const QPointF &pppp : centerPoints) + { + QPointF originSim = d->m_objectBasePoint + pppp; + QPointF originGeo = s_basePoint.geographic(originSim); + + SituationMap::PositionCont posCont; + posCont.append(Point3D::fromQPointF(originGeo)); + + SituationMap::PositionsTranslated posTrans; + posTrans.setCoordTransFunctor(d->m_mapImpl->coordTranslationFunctor()); + posTrans.setPositions(posCont); + SituationMap::PositionCont posContTrans = posTrans.positionsTranslated(); + + Point3D p3D = posContTrans.first(); + + Esri::ArcGISRuntime::Point newPnt(p3D.x(), + p3D.y(), + p3D.z()); + + Esri::ArcGISRuntime::Graphic *graphic = new Esri::ArcGISRuntime::Graphic(); + graphic->setGeometry(newPnt); + d->m_graphicsLayer->graphics()->append(graphic); + + QVariantMap rawAttributes = graphic->attributes()->attributesMap(); + rawAttributes[QStringLiteral("MountingPointFieldName")] = QString(s_prefix+QStringLiteral("blue")); + graphic->attributes()->setAttributesMap(rawAttributes); + + qCritical() << " XXXXX set rawAttributes " << rawAttributes; + + } + return; +} + +void FunctionDialog::onGenerate3DGraphicSphereRENDERER() +{ + int instances = 1000; + qCritical() << " onGenerate3DGraphicSphereRENDERER, instances=" << instances; + + // setup Renderer + // Renderer + QColor col = Qt::green; + col.setAlphaF(0.5); + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::sphere(col, 25, Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Center); + Q_UNUSED(sym); + + // symbol + static QString resourcePath = SituationMap::getResourcePath() + QDir::separator() + QStringLiteral("3Dmodels") + QDir::separator(); + QUrl url = QUrl(resourcePath + QString(QStringLiteral("SPD/buildings/Bruecke_20m/Bruecke_20m.dae"))); // modelled horizontal + +// QUrl url(QStringLiteral("/tmp/Halbkugel.fbx")); + + const float scale = 4.0; + Esri::ArcGISRuntime::ModelSceneSymbol *modelSceneSymbol = + new Esri::ArcGISRuntime::ModelSceneSymbol(url, scale); // url, scale + + modelSceneSymbol->setHeading(45); + modelSceneSymbol->setRoll(115); + modelSceneSymbol->setPitch(75); + + modelSceneSymbol->setColor(Qt::red); + + + +// Esri::ArcGISRuntime::Symbol* symbol = sym; + Esri::ArcGISRuntime::Symbol* symbol = modelSceneSymbol; + + qCritical() << " USE RENDERER"; + Esri::ArcGISRuntime::SimpleRenderer *renderer = new Esri::ArcGISRuntime::SimpleRenderer(symbol, this); + d->m_graphicsLayer->setRenderer(renderer); + + //----------------------------------------------------- + //----------------------------------------------------- + //----------------------------------------------------- + + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + MEMORY_LOG() << " START"; + + if (!d->m_impactCircleSphere.isEmpty()) + { + qCritical() << " onGenerate3DGraphicSphereRENDERER DESTROY " << instances; + for (Private::object3D_data &data : d->m_impactCircleSphere) + d->m_graphicsLayer->graphics()->removeOne(data.m_graphic.get()); + d->m_impactCircleSphere.clear(); + } + else + { + if (d->m_mapImpl->mode() != Mode::Mode_3D) + return; + + int i = 0; + d->m_impactCircleSphere.resize(instances); + for (Private::object3D_data &data : d->m_impactCircleSphere) + { + data.m_position = Esri::ArcGISRuntime::Point(mapCenter.x(), mapCenter.y()+ (0.001 * i)); + ++i; + + data.m_graphic = std::shared_ptr(new Esri::ArcGISRuntime::Graphic(data.m_position)); +// static int myGraphicCnt = 0; connect(data.m_graphic.get(), &QObject::destroyed, [] () { qCritical() << " myGraphic destroyed cnt=" << ++myGraphicCnt; }); + + d->m_graphicsLayer->graphics()->append(data.m_graphic.get()); + } // for + } + MEMORY_LOG() << " END"; + +} + +void FunctionDialog::onSphereOpacityChangedRENDERER(int opacityValue) +{ + // update Renderer: + Esri::ArcGISRuntime::Renderer* myRenderer = d->m_graphicsLayer->renderer(); + if (!myRenderer) + return; + + Esri::ArcGISRuntime::SimpleRenderer *simpleRenderer = dynamic_cast(myRenderer); + if (!simpleRenderer) + return; + + // Symbol + Esri::ArcGISRuntime::Symbol* mySymbol = simpleRenderer->symbol(); + if (!mySymbol) + return; + + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = dynamic_cast(mySymbol); + if (!sym) + return; + + QColor col = Qt::green; + col.setAlphaF(opacityValue/100.0); + sym->setColor(col); + +// Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = +// Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::sphere(col, 10, Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Center); + +// simpleRenderer->setSymbol(sym); +} + +void FunctionDialog::onGenerate3DGraphicSphere() +{ + int instances = 1000; + qCritical() << " onGenerate3DGraphicSphere, instances=" << instances; + + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + MEMORY_LOG() << " START"; + + if (!d->m_impactCircleSphere.isEmpty()) + { + qCritical() << " onGenerate3DGraphicSphere DESTROY " << instances; + for (Private::object3D_data &data : d->m_impactCircleSphere) + d->m_graphicsLayer->graphics()->removeOne(data.m_graphic.get()); + d->m_impactCircleSphere.clear(); + return; + } + else + { + d->m_impactCircleSphere.resize(instances); + + int i = 0; + for (Private::object3D_data &data : d->m_impactCircleSphere) + { + data.m_position = Esri::ArcGISRuntime::Point(mapCenter.x(), mapCenter.y()+ (0.00001 * i)); + ++i; + + if (d->m_mapImpl->mode() == Mode::Mode_2D) + { + data.m_graphic = std::shared_ptr(new Esri::ArcGISRuntime::Graphic(data.m_position)); + Esri::ArcGISRuntime::SimpleMarkerSymbol redCircle(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle, Qt::red, 10); + data.m_graphic->setSymbol(&redCircle); + d->m_graphicsLayer->graphics()->append(data.m_graphic.get()); + } + else if (d->m_mapImpl->mode() == Mode::Mode_3D) + { + // SimpleMarkerSceneSymbol(SimpleMarkerSceneSymbolStyle style, const QColor& color, double height, double width, double depth, SceneSymbolAnchorPosition anchorPosition, QObject* parent = nullptr); + + QColor col = Qt::red; + col.setAlphaF(0.5); + + data.m_graphic = std::shared_ptr(new Esri::ArcGISRuntime::Graphic(data.m_position)); +// static int myGraphicCnt = 0; connect(data.m_graphic.get(), &QObject::destroyed, [] () { qCritical() << " myGraphic destroyed cnt=" << ++myGraphicCnt; }); + + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::sphere(col, 25, Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Center, data.m_graphic.get()); +// static int mySymbolCnt = 0; connect(sym, &QObject::destroyed, [] () { qCritical() << " sphere destroyed cnt=" << ++mySymbolCnt; }); + + data.m_graphic->setSymbol(sym); + + d->m_graphicsLayer->graphics()->append(data.m_graphic.get()); + } + } // for + } + MEMORY_LOG() << " END"; +} + +void FunctionDialog::onSphereOpacityChanged(int opacityValue) +{ + for (Private::object3D_data &data : d->m_impactCircleSphere) + { + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = dynamic_cast(data.m_graphic->symbol()); + if (!sym) + return; + + QColor col = sym->color(); + col.setAlphaF(opacityValue/100.0); + sym->setColor(col); + + data.m_graphic->setSymbol(sym); + } // for +} + +void FunctionDialog::onSphereZValueChanged(int ZValue) +{ + qCritical() << " onSphereZValueChanged -> " << ZValue; + + for (Private::object3D_data &data : d->m_impactCircleSphere) + { + Esri::ArcGISRuntime::Point newPnt(data.m_position.x(), + data.m_position.y(), + data.m_position.z()+ZValue); + data.m_graphic->setGeometry(newPnt); + } // for +} + +// +// 3DGraphic Cone +// +void FunctionDialog::onGenerate3DGraphicCone() +{ + int instances = 1000; + qCritical() << " onGenerate3DGraphicCone, instances=" << instances; + + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + MEMORY_LOG() << " START"; + + if (!d->m_impactCone.isEmpty()) + { + qCritical() << " onGenerate3DGraphicCone DESTROY " << instances; + for (Private::object3D_data &data : d->m_impactCone) + d->m_graphicsLayer->graphics()->removeOne(data.m_graphic.get()); + d->m_impactCone.clear(); + } + else + { + d->m_impactCone.resize(instances); + + int i = 0; + for (Private::object3D_data &data : d->m_impactCone) + { + data.m_position = Esri::ArcGISRuntime::Point(mapCenter.x(), mapCenter.y()+ (0.001 * i)); + ++i; + + if (d->m_mapImpl->mode() == Mode::Mode_2D) + { + Esri::ArcGISRuntime::SimpleMarkerSymbol redCircle(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle, Qt::red, 10); + Esri::ArcGISRuntime::Graphic *myGraphic = new Esri::ArcGISRuntime::Graphic(data.m_position, &redCircle); + d->m_graphicsLayer->graphics()->append(myGraphic); + } + else if (d->m_mapImpl->mode() == Mode::Mode_3D) + { + // SimpleMarkerSceneSymbol(SimpleMarkerSceneSymbolStyle style, const QColor& color, double height, double width, double depth, SceneSymbolAnchorPosition anchorPosition, QObject* parent = nullptr); + + QColor col = Qt::red; + col.setAlphaF(0.5); + + if (!data.m_graphic) + { + data.m_graphic = std::shared_ptr(new Esri::ArcGISRuntime::Graphic(data.m_position)); + + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::cone(col, 2, 1.0, data.m_graphic.get()); // color, diameter, height + + data.m_graphic->setSymbol(sym); + + d->m_graphicsLayer->graphics()->append(data.m_graphic.get()); + } + // update values: + data.m_graphic->setGeometry(data.m_position); + } + } // for + } + + MEMORY_LOG() << " END"; +} + +void FunctionDialog::onConeOpacityChanged(int opacityValue) +{ + for (Private::object3D_data &data : d->m_impactCone) + { + if (!data.m_graphic) + return; + + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = dynamic_cast(data.m_graphic->symbol()); + if (!sym) + return; + + QColor col = sym->color(); + col.setAlphaF(opacityValue/100.0); + sym->setColor(col); + + data.m_graphic->setSymbol(sym); + } // for +} + +void FunctionDialog::onConeZValueChanged(int ZValue) +{ + qCritical() << " onConeZValueChanged -> " << ZValue; + + for (Private::object3D_data &data : d->m_impactCone) + { + if (!data.m_graphic) + return; + + Esri::ArcGISRuntime::Point newPnt(data.m_position.x(), + data.m_position.y(), + data.m_position.z()+ZValue); + data.m_graphic->setGeometry(newPnt); + } +} + +void FunctionDialog::onConeHeadingChanged(float angle) +{ + qCritical() << " onConeHeadingChanged -> " << angle; + + for (Private::object3D_data &data : d->m_impactCone) + { + if (!data.m_graphic) + return; + + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = dynamic_cast(data.m_graphic->symbol()); + if (!sym) + return; + + sym->setHeading(angle); + data.m_graphic->setSymbol(sym); + } // for +} + +void FunctionDialog::onConeRollChanged(float angle) +{ + qCritical() << " onConeRollChanged -> " << angle; + + for (Private::object3D_data &data : d->m_impactCone) + { + if (!data.m_graphic) + return; + + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = dynamic_cast(data.m_graphic->symbol()); + if (!sym) + return; + + sym->setRoll(angle); + data.m_graphic->setSymbol(sym); + } // for +} + +void FunctionDialog::onConePitchChanged(float angle) +{ + qCritical() << " onConePitchChanged -> " << angle; + + for (Private::object3D_data &data : d->m_impactCone) + { + + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *sym = dynamic_cast(data.m_graphic->symbol()); + if (!sym) + return; + + sym->setPitch(angle); + data.m_graphic->setSymbol(sym); + } // for +} + +void FunctionDialog::on3dModel() +{ + int instances = 100; + + // Graphic: + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + MEMORY_LOG() << " START"; + + // symbol + static QString resourcePath = SituationMap::getResourcePath() + QDir::separator() + QStringLiteral("3Dmodels") + QDir::separator(); + QUrl url = QUrl(resourcePath + QString(QStringLiteral("SPD/buildings/Bruecke_20m/Bruecke_20m.dae"))); // modelled horizontal + + const float scale = 2.0; + Esri::ArcGISRuntime::ModelSceneSymbol *modelSceneSymbol = + new Esri::ArcGISRuntime::ModelSceneSymbol(url, scale); // url, scale + + + if (!d->m_3dModel.isEmpty()) + { + qCritical() << " on3dModel DESTROY " << instances; + for (Private::object3D_data &data : d->m_3dModel) + d->m_graphicsLayer->graphics()->removeOne(data.m_graphic.get()); + d->m_3dModel.clear(); + } + else + { + d->m_3dModel.resize(instances); + + int i = 0; + for (Private::object3D_data &data : d->m_3dModel) + { + if (d->m_mapImpl->mode() != Mode::Mode_3D) + return; + + data.m_position = Esri::ArcGISRuntime::Point(mapCenter.x(), mapCenter.y()+ (0.001 * i)); + ++i; + + if (!data.m_graphic) + { + data.m_graphic = std::shared_ptr(new Esri::ArcGISRuntime::Graphic(data.m_position)); + data.m_graphic->setSymbol(modelSceneSymbol); + d->m_graphicsLayer->graphics()->append(data.m_graphic.get()); + } + // update values: + data.m_graphic->setGeometry(data.m_position); + } // for + } + MEMORY_LOG() << " END"; +} + +void FunctionDialog::on3dModelZValueChanged(int ZValue) +{ + qCritical() << " on3dModelZValueChanged -> " << ZValue; + + for (Private::object3D_data &data : d->m_3dModel) + { + if (!data.m_graphic) + return; + + Esri::ArcGISRuntime::Point newPnt(data.m_position.x(), + data.m_position.y(), + data.m_position.z()+ZValue); + data.m_graphic->setGeometry(newPnt); + } +} + +void FunctionDialog::onGenerate3DGraphicConeUniqueValueRenderer() +{ + qCritical() << " onGenerate3DGraphicConeUniqueValueRenderer"; + + MEMORY_LOG() << " START"; + + const QString layernameAES(QStringLiteral("AES")); + + LayerFactoryImpl &layerFactory = d->m_mapImpl->layerFactoryImpl(); + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = layerFactory.getGraphicsLayer(layernameAES); + + if (!d->m_impactConeViaUniqueValueRenderer.isEmpty()) + { + qCritical() << " onGenerate3DGraphicConeUniqueValueRenderer DESTROY " << d->m_impactConeViaUniqueValueRenderer.size(); + for (Private::object3D_data &data : d->m_impactConeViaUniqueValueRenderer) + graphicsLayer->graphics()->removeOne(data.m_graphic.get()); + d->m_impactConeViaUniqueValueRenderer.clear(); + MEMORY_LOG() << " START"; + return; + } + + QVector typeVec; + typeVec << LayerFactory::UniqueValueDataAesCentral::TYPE::CONE; + typeVec << LayerFactory::UniqueValueDataAesCentral::TYPE::SPHERE; + + QVector colVec; + colVec << Qt::darkMagenta << Qt::darkGreen; + + QVector radiuss; + radiuss << 10 << 1 << 2.5 << 5 << 10 << 20; + + colVec.first().setAlpha(128); + + // init + // Assume that typically the layer does not have a renderer: + + + if (graphicsLayer->renderer()) + { + qCritical() << " onGenerate3DGraphicConeUniqueValueRenderer, there is already a renderer assigned: " << graphicsLayer->renderer()->objectName(); + } else { + QVector uvCont; + + for (const LayerFactory::UniqueValueDataAesCentral::TYPE &type : std::as_const(typeVec)) + { + for (const QColor &col : std::as_const(colVec)) + { + for (float radius : std::as_const(radiuss)) + { + LayerFactory::UniqueValueDataAesCentral uvd; + QString singleVal; + + // V100.8: order seems to be relevant. Keep alphabetical; + singleVal = QString(QStringLiteral("Impact_Color_%1")).arg(col.name()); + uvd.values.append(singleVal); + singleVal = QString(QStringLiteral("Impact_Radius_%1")).arg(radius); + uvd.values.append(singleVal); + singleVal = QString(QStringLiteral("Impact_Type_%1")).arg(LayerFactory::toString(type)); + uvd.values.append(singleVal); + + uvd.type = type; + uvd.col = col; + uvd.radius = radius; + uvd.height = uvd.radius; + + uvCont.append(uvd); + } // for + } // for + } // for + + + QStringList fieldnames; + // V100.8: order seems to be relevant. keep alphabetical + fieldnames.append(QString(QStringLiteral("IMPACT_COLOR"))); + fieldnames.append(QString(QStringLiteral("IMPACT_RADIUS"))); + fieldnames.append(QString(QStringLiteral("IMPACT_TYPE"))); + + bool success = + d->m_mapInterface->layerFactory().assignUniqueValueRendererAesCentral(layernameAES, + fieldnames, + uvCont); + qCritical() << " onGenerate3DGraphicConeUniqueValueRenderer, success=" << success; + } + + // create graphics: + { + qCritical() << " onGenerate3DGraphicConeUniqueValueRenderer, setup Instances..."; + + // Graphic: + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGeoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Point &mapCenter = static_cast(vp.targetGeometry()); + + d->m_impactConeViaUniqueValueRenderer.clear(); + + + int n = 0; + while (++n < 500) + radiuss.append(20); + + int k = 0; + for (const QColor &col : std::as_const(colVec)) + { + int i = 0; + for (float radius : std::as_const(radiuss)) + { + Private::object3D_data data; + data.m_position = Esri::ArcGISRuntime::Point(mapCenter.x() + (0.001 * k), mapCenter.y() + (0.001 * i)); + data.m_graphic = std::shared_ptr(new Esri::ArcGISRuntime::Graphic(data.m_position)); + + QVariantMap attribMap; + QString attribValue; + + attribValue = QString(QStringLiteral("Impact_Color_%1")).arg(col.name()); + attribMap.insert(QStringLiteral("IMPACT_COLOR"), attribValue); + + attribValue = QString(QStringLiteral("Impact_Radius_%1")).arg(radius); + attribMap.insert(QStringLiteral("IMPACT_RADIUS"), attribValue); + + LayerFactory::UniqueValueDataAesCentral::TYPE type{LayerFactory::UniqueValueDataAesCentral::TYPE::CONE}; + if (i%2) + type = LayerFactory::UniqueValueDataAesCentral::TYPE::SPHERE; + + attribValue = QString(QStringLiteral("Impact_Type_%1")).arg(LayerFactory::toString(type)); + attribMap.insert(QStringLiteral("IMPACT_TYPE"), attribValue); + + // SceneProperties: HEADING, PITCH, ROLL + attribMap.insert(QStringLiteral("HEADING"), ((10*i)%360) ); + attribMap.insert(QStringLiteral("PITCH"), ((30*i)%360)); + attribMap.insert(QStringLiteral("ROLL"), ((30*i)%360)); + + // qCritical() << " attribMap " << attribMap; + data.m_graphic->attributes()->setAttributesMap(attribMap); + + graphicsLayer->graphics()->append(data.m_graphic.get()); + d->m_impactConeViaUniqueValueRenderer.append(data); + + ++i; + } // for + + ++k; + } // for + + qCritical() << " onGenerate3DGraphicConeUniqueValueRenderer, setup Instances..." << d->m_impactConeViaUniqueValueRenderer.size(); + + } + + MEMORY_LOG() << " END"; +} + +void FunctionDialog::onAddCivilSymbols() +{ + AdvancedSymbology::SymbolDictionary &dict = d->m_mapImpl->symbolDictionary(); + int files = dict.addEtsSymbols(QLatin1String(":/advancedSymbology/CivilSymbols")); + qWarning() << LOGFUNCTION << " dict.addCivilSymbols() = " << files; + + AdvancedSymbology::SymbolProperties symProps; + dict.insertXmlTestSymbol(symProps); + qWarning() << LOGFUNCTION << " dict.insertXmlTestSymbol (sidc=" << symProps.sidc() << ")"; +} + + + +void FunctionDialog::onMessage() +{ + /* + QString messageId = QLatin1String("mySpecialId"); + + if (d->m_messageGroupLayer) + { + EsriRuntimeQt::SpatialReference sr = d->m_esriMap->spatialReference(); + + //create and process message, adding it to the map + EsriRuntimeQt::Message message; + message.setProperty(QLatin1String("_id"), messageId); + + message.setProperty(QLatin1String("_type"), QLatin1String("position_report")); + message.setProperty(QLatin1String("_action"), QLatin1String("update")); + + EsriRuntimeQt::Point mapPoint(0, 0, sr); + QString ctrlPoints = QString::number(mapPoint.x(), 'f') + QLatin1Char(',') + QString::number(mapPoint.y(), 'f'); + message.setProperty(QLatin1String("_control_points"), ctrlPoints); + + // 123456789012345 + // message.setProperty(QLatin1String("sic"), "SFGPUSAC-------"); + message.setProperty(QLatin1String("sic"), QLatin1String("GFGPOPP-------X")); + + message.setProperty(QLatin1String("_wkid"), QString::number(d->m_esriMap->spatialReference().wkid())); + message.setProperty(QLatin1String("uniquedesignation"), QLatin1String("MySymbolName")); + + qDebug() << "message = " << message.toString(); + + if (d->m_messageGroupLayer->messageProcessor()->processMessage(message)) + { + qDebug() << "message processed OK" ; + qDebug() << "message = '" << message.toString() << "'"; + messageId = message.messageId(); + } + else + qWarning() << "message processed NOT OK" ; + + QList layerList = d->m_messageGroupLayer->getLayers(); + + for ( auto layer, layerList) + qDebug() << "MessageGroupLayer consists of layer " << static_cast(layer->type()); + + } + else + { + qWarning() << "no messageGroupLayer"; + QMessageBox msgBox; + msgBox.setText(QLatin1String("no messageGroupLayer")); + msgBox.exec(); + } + */ +} + +void FunctionDialog::onMessageSelect() +{ + /* + QString messageId = QLatin1String("mySpecialId"); + + if (d->m_messageGroupLayer) + { + static bool select = true; + + EsriRuntimeQt::Message message; + + // arcGIS 10.2.6: createSelectMessage generates a wrong message + // -> wrong : .. {_action,highlight}... + // -> correct: .. {_action,select}... + // message = EsriRuntimeQt::MessageHelper::createSelectMessage(EsriRuntimeQt::SymbolDictionaryType::Mil2525C, + // messageId, + // QLatin1String("position_report"), + // true); + message.setMessageId(messageId); + message.setProperty(QLatin1String("_type"), QLatin1String("position_report")); + if (select) + message.setProperty(QLatin1String("_action"), QLatin1String("select")); + else + message.setProperty(QLatin1String("_action"), QLatin1String("un-select")); + + // toggle + select = ! select; + + qDebug() << "message = " << message.toString(); + + if (d->m_messageGroupLayer->messageProcessor()->processMessage(message)) + qDebug() << "createSelectMessage processed OK" ; + else + qWarning() << "createSelectMessage processed NOT OK" ; + + + } + else + { + qWarning() << "no messageGroupLayer or messageId.isEmpty"; + QMessageBox msgBox; + msgBox.setText(QLatin1String("no messageGroupLayer")); + msgBox.exec(); + } + */ +} + +void FunctionDialog::onAddSymbol() +{ + QString fileNameAndPath = QFileDialog::getOpenFileName(this, tr("Add Symbol"), + QLatin1String("/tmp"), + tr("Images (*.png)")); + + QImage symImg(fileNameAndPath); + + QFileInfo fi(fileNameAndPath); + + const QString &baseName = fi.baseName(); + + AdvancedSymbology::SymbolProperties symProps; + symProps.setName(baseName); + symProps.setKey(baseName); + + if ( d->m_mapImpl->symbolDictionary().setEtsSymbol(4711, baseName, symProps, symImg) ) + qDebug() << " insertExtraSymbol ok"; + else + qCritical() << " insertExtraSymbol failed"; + +} + +void FunctionDialog::onSomeParticipants() +{ + + // + // place participants + // + + QStringList sidcs; + // 1=Team/Crew, 2=Squad, 3=Section, 4=Platoon, 5=Company, 6=Battalion + // 12345678|| + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500111202000000"); // 01015000001202000000 -> 15 120200 LandEquip, Tank + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500121202000000"); // 01015000001202000000 -> 15 120200 LandEquip, Tank + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500131202000000"); // 01015000001202000000 -> 15 120200 LandEquip, Tank + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500141202000000"); // 01015000001202000000 -> 15 120200 LandEquip, Tank + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500151202000000"); // 01015000001202000000 -> 15 120200 LandEquip, Tank + sidcs += AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500161202000000"); // 01015000001202000000 -> 15 120200 LandEquip, Tank + + int counter = 0; + for (const QString &sidc : std::as_const(sidcs)) + { + ++counter; + const QString &layername = QStringLiteral("Participants_%1").arg(counter); + + // set additional values: + SituationMap::SymbolData symData; + symData.sidc = sidc; + + // create and set debugInfo for debugging: + const QString &debugInfo = layername; + RefPointer participant = + d->m_mapInterface->objectFactory(). + create(layername, debugInfo); + + if (!participant) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + symData.color = Qt::red; // Will be overwritten via partyId. + symData.size = 2; + + participant->setSymbol(symData); + participant->setTextModifier(Participant::TextModifier::T_UniqueDesignation, layername.right(1)); + + // QPointF(92750, 80525) -> Rennplatz - Bremen + + const QPointF &origin = s_basePoint.geographic(QPointF(90000 + counter * 100, 80000 + counter * 100)); + participant->setOrigin(Point3D::fromQPointF(origin)); + + participant->updateView(); + } + +} + +void FunctionDialog::onParticipantGetPosition() +{ + qWarning() << LOGFUNCTION << "XXXXXXXXXX"; + + if (d->m_participant) + { + d->m_participant->removeMySelf(); + d->m_participant.reset(); + } + +// if (d->m_mapGeoView) + { +// AdvancedSymbology::SymbolDictionary &dict = d->m_mapImpl->symbolDictionary(); + QString sidc(AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525C, "SFGPUCD---MR")); + + if ((d->m_cbRubSidc) +// && (d->m_cbRubSidc->currentText().length() <= 15) // civil symbols: + ) + sidc = d->m_cbRubSidc->currentText(); + +// if (!dict.contains(sidc)) +// { +// qWarning() << "sidc not found"; +// return; +// } + +// const QImage &symbolImage = dict.symbolImageBySidc(sidc, 32); +// if (symbolImage.isNull()) +// { +// qWarning() << "symbolImageBySidc failed"; +// return; +// } + + RB::RubberbandPointFeature *rubberband = d->m_mapInterface->rubberbandFactory().create(); + if (!rubberband) + { + qWarning() << "Creating rubberband to get participant position failed"; + return; + } + rubberband->maximumPointCount(1); // rubberband will finish after first mouse click + // rubberband->connectPreviewPixmap(QPixmap::fromImage(symbolImage)); + + rubberband->setUserData(new QString(sidc)); + rubberband->setPreviewSidc(sidc); + + connect(rubberband, &RB::RubberbandPointFeature::apply, this, &FunctionDialog::onParticipantGetPositionApplied); + connect(rubberband, &RB::RubberbandPointFeature::abort, rubberband, &RB::Rubberband::deleteMySelf); + } +} /* FunctionDialog::onParticipantGetPosition */ + + +void FunctionDialog::onParticipantGetPositionApplied(RB::RubberbandBase *rubberband) +{ + qDebug() << LOGFUNCTION; + + if (! rubberband) + { + qWarning() << LOGFUNCTION << " no rubberband anymore!!!"; + return; + } + + const QPointF &rubberCoord = rubberband->singleCoordinate(); + + + QString *p_sidc = reinterpret_cast(rubberband->userData()); + if (! p_sidc) + { + qWarning() << LOGFUNCTION << " no sidc anymore!!!"; + return; + } + + if (! this->createParticipant(Point3D::fromQPointF(rubberCoord), + QLatin1String("ParticipantRubberbandTab")) + ) + qWarning() << LOGFUNCTION << " createParticipant (ParticipantRubberbandTab) failed"; + + + SymbolData symData; + symData.sidc = *p_sidc; + d->m_participant->setSymbol(symData); + bool success = d->m_participant->updateView(); + qWarning() << LOGFUNCTION << " success (setMovementTrack) =" << success; + + // cleanup + delete p_sidc; + + rubberband->setUserData(nullptr); + rubberband -> deleteMySelf(); +} + + +bool FunctionDialog::createParticipant(const Point3D &origin, + const QString &debugInfo) +{ + qDebug() << LOGFUNCTION << " debugInfo='" << debugInfo << "'"; + + if (d->m_participant) + { + d->m_participant->removeMySelf(); + d->m_participant.reset(); + } + + // create and set debugInfo for debugging: + d->m_participant = d->m_mapInterface->objectFactory(). + create(QLatin1String("Participants"), debugInfo); + + if (!d->m_participant) + { + qDebug() << LOGFUNCTION << " no object"; + return false; + } + + d->m_participant->setOrigin(origin); + + return true; +} + + +void FunctionDialog::onParticipantCreate() +{ + qDebug() << LOGFUNCTION ; + + if (d->m_participant) + { + d->m_participant->removeMySelf(); + d->m_participant.reset(); + } + +// QPointF origin = d->m_objectBasePoint + QPointF( -150000, -150000 ); + QPointF originSim = d->m_objectBasePoint + QPointF(88000, 76000); + QPointF originGeo = s_basePoint.geographic(originSim); + + QString sidc; + + if (d->m_leSidc) + { + QString text = d->m_leSidc->text(); + // if (text.length() <= 15) // do not check regarding civil symbols + sidc = text; + } + + qWarning() << LOGFUNCTION << " sidc=" << sidc; + + + + bool success = this->createParticipant(Point3D::fromQPointF(originGeo), + QLatin1String("ParticipantMessageTab")); + + d->m_participant->setTextModifier(Participant::TextModifier::EF_ReinforcedReduced, QLatin1String("EF_ReinforcedReduced")); + d->m_participant->setTextModifier(Participant::TextModifier::M_HigherFormation, QLatin1String("M_HigherFormation")); + d->m_participant->setTextModifier(Participant::TextModifier::T_UniqueDesignation, QLatin1String("T_UniqueDesignation")); + d->m_participant->setTextModifier(Participant::TextModifier::W_DateTimeGroup, QLatin1String("W_DateTimeGroup")); + + d->m_participant->setVisibility(d->m_visibility); +// d->m_participant->setVisibility(SituationMap::ObjectAppearance::Technical, false); // no sense + + + SymbolData symData; + symData.sidc = sidc; + symData.color = Qt::cyan; // Will be overwritten via partyId. +// symData.size = 0.5; + + d->m_participant->setSymbol(symData); + d->m_participant->setPartyId(4); + d->m_participant->setScale(d->getScale()); + +// d->m_participant->setInjury(Injury::MobilityKill, QColor(Qt::red) ); // QColor() to use the correct signature! +// d->m_participant->setInjury(Injury::TotalKill, 3); + +// d->m_participant->setManipulation(true); +// d->m_participant->setBiteError(true); +// d->m_participant->setUseLightColor(true); +// d->m_participant->setNbcProtection(true); + +// d->m_participant->setMarked(true); + +// d->m_participant->setRanges(QVector() << 8000 << 10000); + + + if (! success) + { + qWarning() << LOGFUNCTION << " createParticipant (ParticipantMessageTab) failed"; + return; + } + + // MovementTrack: + d->m_participant->setMovementTrackEnabled(true); +// d->m_participant->setMovementTrackVisibility(true); // visible = true = default + + PositionCont intialMovementTrack; + Point3D ppp; + + ppp = Point3D::fromQPointF(s_basePoint.geographic(originSim + QPointF(-500, -500))); + ppp.setZ(100); + qCritical() << " ppp=" << ppp.x() << " ; " << ppp.y() << " ; "<< ppp.z(); + intialMovementTrack << ppp; + + ppp = Point3D::fromQPointF(s_basePoint.geographic(originSim + QPointF(-500, 500))); + ppp.setZ(50); + qCritical() << " ppp=" << ppp.x() << " ; " << ppp.y() << " ; "<< ppp.z(); + intialMovementTrack << ppp; + + + d->m_participant->setMovementTrack(intialMovementTrack); + + + + // DirectionIndicator: + { + Participant::DirectionIndicatorData directionIndicatorData ; + directionIndicatorData.color = Qt::black; + + directionIndicatorData.directionDegree = 22; + directionIndicatorData.lineStyle = Qt::SolidLine; + directionIndicatorData.lineWidth = 1; + d->m_participant->setDirectionIndicator(0, directionIndicatorData); + + directionIndicatorData.directionDegree = 45; + directionIndicatorData.lineStyle = Qt::DashLine; + directionIndicatorData.lineWidth = 2; + d->m_participant->setDirectionIndicator(1, directionIndicatorData); + + directionIndicatorData.directionDegree = 90; + directionIndicatorData.lineStyle = Qt::DashDotDotLine; + directionIndicatorData.lineWidth = 3; + d->m_participant->setDirectionIndicator(2, directionIndicatorData); + } + + + // Designation: + { + Participant::DesignationData designationData; + designationData.spotLocation.rx() = 0.155; //spotLocation.lon(); + designationData.spotLocation.ry() = 0.926; //spotLocation.lat(); + designationData.code = 123; + + d->m_participant->setDesignations({designationData}); + } + + + success = d->m_participant->updateView(); + + qWarning() << LOGFUNCTION << " success =" << success; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onParticipantUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onParticipantUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_participant) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + // move + QPointF origin = s_basePoint.geographic(QPointF(93064, 78331)); + d->m_participant->setOrigin(Point3D(origin.x(), origin.y(), 100.2345)); + + d->m_participant->setRanges(QVector() << 50 << 150 << 350 << 500 << 1000 << 1800); + d->m_participant->setRangesVisibility(true); + + + d->m_participant->setInjury(Injury::TotalKill, QColor(Qt::blue)); + + d->m_participant->setAdditionalSymbol(SituationMap::AdditionalSymbolType::NbcProtection, true); + d->m_participant->setAdditionalSymbol(SituationMap::AdditionalSymbolType::Manipulation, true); + d->m_participant->setAdditionalSymbol(SituationMap::AdditionalSymbolType::NoPosition, true); + d->m_participant->setAdditionalSymbol(SituationMap::AdditionalSymbolType::BiteError, true); + + d->m_participant->setTextModifier(Participant::TextModifier::EF_ReinforcedReduced, QLatin1String("+ ")); + + SymbolData symData = d->m_participant->symbol(); + symData.color = Qt::magenta; // Will be overwritten via partyId (if partyId != -1) + symData.size = 1.0; + d->m_participant->setSymbol(symData); + + d->m_participant->setPartyId(4); + + d->m_participant->setMarked(true); + + // Designation: + { + Participant::DesignationData designationData; + designationData.spotLocation.rx() = 0; //spotLocation.lon(); + designationData.spotLocation.ry() = 0; //spotLocation.lat(); + designationData.code = 123; + d->m_participant->setDesignations({designationData}); + } + + + bool success = d->m_participant->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_participant->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + +// //----------------------------------- +// // second participant near to the first one: +// //----------------------------------- +// Participant* secondPart = d->m_mapImpl->createParticipant(); +// +// if (!secondPart) +// { +// qDebug() << LOGFUNCTION << " no object"; +// return; +// } +// +// // Set objId for debugging: +// secondPart->setObjId("secondParticipant"); +// +// secondPart->setSymbol(d->m_participant->symbol()); +// secondPart->setOrigin( d->m_participant->origin() + QPointF(1000, 1000) ); +// +// // update view +// success = secondPart->updateView() && success; +// +// if (success) +// qDebug() << LOGFUNCTION << " secondParticipant updateView OK" ; +// else +// qDebug() << LOGFUNCTION << " secondParticipant updateView NOT OK" ; + + +} + +void FunctionDialog::onBuildingCreate() +{ + qDebug() << LOGFUNCTION ; + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_building)); + + d->m_building = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("BuildingOutlineId2")); + + if (!d->m_building) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_building->setIdentifier(QLatin1String("Building")); + + d->m_building->setVisibility(d->m_visibility); + d->m_building->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_building->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_building->setSelected(d->m_selected); + + d->m_building->rTextStyle().setTextColor(Qt::magenta); + d->m_building->rTextStyle().setSize(30); +// d->m_building->rTextStyle().setAngle(-90); // -90 -> north + d->m_building->rTextStyle().setAntiAliased(true); + + + d->m_building->rTextStyle().setItalic(true); + d->m_building->rTextStyle().setHorizontalAlignment(HorizontalAlignment::Justify); + d->m_building->rTextStyle().setVerticalAlignment(VerticalAlignment::BaseLine); + + d->m_building->rFillStyle().setFillColor(QColor(Qt::darkGray)); + + // set additional values: + SituationMap::PositionCont posCont; +// QPointF offset = d->m_objectBasePoint + QPointF(50000, 50000); + +// int extent = 30000; +// posCont << offset - QPointF(0, 0); +// posCont << offset - QPointF(extent, 0); +// posCont << offset - QPointF(extent, extent); +// posCont << offset - QPointF(0, extent); + + if (true) + { + // Bt56 + appendQPointF(posCont, s_basePoint.geographic(QPointF(93064 , 78331))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93069.9, 78337.8))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93076.8, 78331.8))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93070.9, 78325))); + } + else + { + // Salchau + appendQPointF(posCont, s_basePoint.geographic(QPointF(75000.0 , 13000))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(75000.0 , 13100))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(75100.0 , 13100))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(75100.0 , 13000))); + } + + std::cout << " posCont=" << posCont << std::endl; + + d->m_building->setPositions(posCont); + + + d->m_building->setMarker(posCont[0]); + + + // update view + bool success = d->m_building->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_building->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " building dump: " << (*d->m_building) << std::endl; + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onBuildingUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onBuildingUpdate() +{ + + if (!d->m_building) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + qDebug() << LOGFUNCTION << " update Values.." ; + + d->m_building->setIdentifier(d->m_building->identifier() + QLatin1String("Update")); + d->m_building->rTextStyle().setAngle(-45); // -45 -> north/east + + d->m_building->setMarker(Point3D()); + + + // Bt56 + SituationMap::PositionCont posCont; + appendQPointF(posCont, s_basePoint.geographic(QPointF(93064 , 78331))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93069.9, 78337.8))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93076.8, 78331.8))); + + appendQPointF(posCont, s_basePoint.geographic(QPointF(93100.9, 78300))); + + appendQPointF(posCont, s_basePoint.geographic(QPointF(93070.9, 78325))); + + d->m_building->setPositions(posCont); + + d->m_building->rLineStyle().setLineColor(Qt::red); + d->m_building->rLineStyle().setLineWidth(6); + d->m_building->rLineStyle().setLinePattern(LinePattern::DashDotDot); + + d->m_building->rFillStyle().setFillColor(Qt::yellow); + d->m_building->rFillStyle().setFillPattern(FillPattern::DiagonalCross); + + bool success = d->m_building->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_building->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onBiologicalAgentCreate() +{ + qDebug() << LOGFUNCTION ; + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_bAgent)); + + d->m_bAgent = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("B-AgentId")); + + if (!d->m_bAgent) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_bAgent->setIdentifier(QLatin1String("My BiologicalAgent\nNext Line-OutLined")); + + d->m_bAgent->setVisibility(d->m_visibility); + d->m_bAgent->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_bAgent->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_bAgent->setSelected(d->m_selected); + + d->m_bAgent->rTextStyle().setTextColor(Qt::magenta); + d->m_bAgent->rTextStyle().setSize(30); + d->m_bAgent->rTextStyle().setBold(true); + d->m_bAgent->rTextStyle().setHorizontalAlignment(HorizontalAlignment::Left); + d->m_bAgent->rTextStyle().setHorizontalAlignment(HorizontalAlignment::Justify); + d->m_bAgent->rTextStyle().setVerticalAlignment(VerticalAlignment::Top); + + + LineStyle lineStyle(Qt::cyan, 5, LinePattern::Solid); + FillStyle fillStyle(Qt::blue, FillPattern::Solid); + + d->m_bAgent->setLineStyle(lineStyle); + d->m_bAgent->setFillStyle(fillStyle); + + d->m_bAgent->setPartyId(3); // Will overwrite line/fillcolor + d->m_bAgent->setAesState(AesEffectState::Unknown); // != Unknown will overwrite fillPattern + + +// QPointF center = d->m_objectBasePoint + QPointF(100000, 100000); + QPointF center = d->m_mapImpl->centerPoint(); + qWarning() << LOGFUNCTION << " xxxxxxxxxx center=" << center; + +// center = s_basePoint.geographic(center); + d->m_bAgent->setOrigin(Point3D::fromQPointF(center)); + + d->m_bAgent->setRadius(10000); + + + // Extrusion + d->m_bAgent->setExtrusionHeight(5); + + // if setExtrusionColorName is not set, the partyId is used as fallback + // see -> AbstractMapObjectImpl::updateGraphicalObjectExtrusion() + // + + // d->m_bAgent->setExtrusionColorName( QColor(Qt::green).name() ); + + + bool success = d->m_bAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_bAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " B-Agent dump: " << (*d->m_bAgent) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onBiologicalAgentUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onBiologicalAgentUpdate() +{ + if (!d->m_bAgent) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + if (d->m_mapImpl->mode() == Mode::Mode_2D) + d->m_bAgent->setIdentifier(d->m_bAgent->identifier() + QLatin1String("Upd")); + + // modify geometry: + // d->m_bAgent->setOrigin(d->m_bAgent->origin() + Point3D(500, 500, .0)); + d->m_bAgent->setRadius(d->m_bAgent->radius() + 50); + + LineStyle lineStyle(Qt::red, 6, LinePattern::DashDotDot); + FillStyle fillStyle(Qt::magenta, FillPattern::DiagonalCross); + d->m_bAgent->setLineStyle(lineStyle); + d->m_bAgent->setFillStyle(fillStyle); + + d->m_bAgent->setPartyId(4); // Will overwrite line/fillcolor + d->m_bAgent->setAesState(AesEffectState::Prepared); // != Unknown will overwrite fillPattern + + + SymbolData symData = d->m_bAgent->symbol(); + + switch (d->m_mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + symData.sidc = QLatin1String("GUMPNB----"); // only to test modify of sidc + break; + case AdvancedSymbology::SymbologyType::APP6B: + symData.sidc = QLatin1String("GUCPBWC---"); // only to test modify of sidc + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + + d->m_bAgent->setSymbol(symData); + + bool success = d->m_bAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_bAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onChemicalAgentCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_cAgent)); + + d->m_cAgent = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("C-AgentId")); + + if (!d->m_cAgent) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_cAgent->setIdentifier(QLatin1String("My ChemicalAgent-OutLined")); + + d->m_cAgent->setVisibility(d->m_visibility); + d->m_cAgent->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_cAgent->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_cAgent->setSelected(d->m_selected); + + LineStyle lineStyle(Qt::cyan, 5, LinePattern::Solid); + FillStyle fillStyle(Qt::blue, FillPattern::Null); + d->m_cAgent->setLineStyle(lineStyle); + d->m_cAgent->setFillStyle(fillStyle); + + d->m_cAgent->setPartyId(4); // Will overwrite line/fillcolor + d->m_cAgent->setAesState(AesEffectState::Unknown); // != Unknown will overwrite fillPattern + + QPointF center = d->m_objectBasePoint + QPointF(90000, 100100); + + center = s_basePoint.geographic(center); + d->m_cAgent->setOrigin(Point3D::fromQPointF(center)); + d->m_cAgent->setRadius(1000); + + bool success = d->m_cAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_cAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " C-Agent dump: " << (*d->m_cAgent) << std::endl; +} + +void FunctionDialog::onCubeCreate() +{ + qDebug() << LOGFUNCTION ; + + d->clearMapObjectPtr(RefPointer(d->m_cube)); + + const QString &layernameForCubes = d->m_mapInterface->layerFactory().layernameForCubes(); + + d->m_cube = d->m_mapInterface->objectFactory(). + create(layernameForCubes, QLatin1String("CubeID")); + + if (!d->m_cube) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(92500, 79000); + + int extent = 1000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_cube->setPositions(posCont); + + d->m_cube->setHeight(123456); + d->m_cube->setPartyId(1); + + d->m_cube->setDrawOrder(234); + + bool success = d->m_cube->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_cube->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + + +void FunctionDialog::onDangerAreaCreate() +{ + qDebug() << LOGFUNCTION ; + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_dangerArea)); + + d->m_dangerArea = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("DangerAreaId")); + + if (!d->m_dangerArea) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_dangerArea->setIdentifier(QLatin1String("DangerArea-NotOutLined")); + + d->m_dangerArea->setVisibility(d->m_visibility); + d->m_dangerArea->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_dangerArea->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_dangerArea->setSelected(d->m_selected); + + TextStyle textStyle(Qt::yellow); + textStyle.setTextOutlineColor(Qt::white); + textStyle.setTextOutlineWidth(0.5); + LineStyle lineStyle(Qt::yellow, 5, LinePattern::Solid); + FillStyle fillStyle(Qt::blue, FillPattern::DiagonalCross); + + d->m_dangerArea->setTextStyle(textStyle); + d->m_dangerArea->setLineStyle(lineStyle); + d->m_dangerArea->setFillStyle(fillStyle); + + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(150000, 150000); + + int extent = 100000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent / 2, extent / 2))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_dangerArea->setPositions(posCont); + + bool success = d->m_dangerArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_dangerArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " DangerArea success = " << success << std::endl; + std::cout << " DangerArea dump: " << (*d->m_dangerArea) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onDangerAreaUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onDangerAreaUpdate() +{ + if (!d->m_dangerArea) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_dangerArea->setIdentifier(d->m_dangerArea->identifier() + QLatin1String("Update")); + + d->m_dangerArea->rLineStyle().setLineWidth(4); + d->m_dangerArea->rFillStyle().setFillPattern(FillPattern::Null); + + d->m_dangerArea->setPartyId(2); // Will overwrite line/fillcolor + + // move + QPointF offset(d->m_dangerArea->positions().value(0).toPointF() + QPointF(10000, 10000)); + + qDebug() << LOGFUNCTION << " update Values.." ; + + SituationMap::PositionCont posCont; + + int extent = 150000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent * 2, extent))); +// appendQPointF(posCont, s_basePoint.geographic( offset + QPointF(extent/2, extent/2)) ; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + d->m_dangerArea->setPositions(posCont); + + bool success = d->m_dangerArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_dangerArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onFunctionalAreaCreate() +{ + qDebug() << LOGFUNCTION ; + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_functionalArea)); + + d->m_functionalArea = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("FunctionalAreaId")); + + if (!d->m_functionalArea) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_functionalArea->setIdentifier(QLatin1String("FunctionalArea-OutLined")); + + d->m_functionalArea->setVisibility(d->m_visibility); + d->m_functionalArea->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_functionalArea->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_functionalArea->setSelected(d->m_selected); + + LineStyle lineStyle(Qt::red, 5, LinePattern::Solid); + FillStyle fillStyle(Qt::blue, FillPattern::ForwardDiagonal); + + d->m_functionalArea->setLineStyle(lineStyle); + d->m_functionalArea->setFillStyle(fillStyle); + + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = QPointF(93550, 78550); + + int extent = 1000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent / 2, extent / 2))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_functionalArea->setPositions(posCont); + + + // pointSymbol : + QPointF psOrg = s_basePoint.geographic(offset + QPointF(extent / 2, extent / 2)); + d->m_functionalArea->setPointSymbolOrigin(Point3D::fromQPointF(psOrg)); + + // !!! use only POINT symbols !!! + // DoDo: find adequate sidc + d->m_functionalArea->setPointSymbolSidc(AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10012500002108000000")); // Maritime Points : Impact Point + d->m_functionalArea->setPointSymbolSidc(AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500001102030000")); // Weapon/Weapon System + d->m_functionalArea->setPointSymbolSidc(AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D, "10031500001202000000")); // Vehicle : Tank +// d->m_functionalArea->setPointSymbolSidc(AdvancedSymbology::SidcCodec::encodeCivil("ETS_Pers_FW_BerFue")); // Color Replacement not working yet + + d->m_functionalArea->setPartyId(2); + // : pointSymbol + + bool success = d->m_functionalArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_functionalArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " FunctionalArea dump: " << (*d->m_functionalArea) << std::endl; + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onFunctionalAreaUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onFunctionalAreaUpdate() +{ + if (!d->m_functionalArea) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_functionalArea->rLineStyle().setLineWidth(2); + d->m_functionalArea->rFillStyle().setFillPattern(FillPattern::Null); + + d->m_functionalArea->setIdentifier(d->m_functionalArea->identifier() + QLatin1String("Update")); + + // move + QPointF offset = QPointF(93550, 78550) + QPointF(100, 100); + + qDebug() << LOGFUNCTION << " update Values.."; + + SituationMap::PositionCont posCont; + + int extent = 1000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent * 2, extent))); +// appendQPointF(posCont, s_basePoint.geographic( offset + QPointF(extent/2, extent/2) )); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + d->m_functionalArea->setPositions(posCont); + + // pointSymbol : + QPointF pSymOrg = s_basePoint.geographic(offset + QPointF(extent / 2, extent / 2)); + d->m_functionalArea->setPointSymbolOrigin(Point3D::fromQPointF(pSymOrg)); + d->m_functionalArea->setPartyId(3); + // : pointSymbol + + bool success = d->m_functionalArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_functionalArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onGeoImageCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_geoImage)); + + d->m_geoImage = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("GeoImageId")); + + if (!d->m_geoImage) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_geoImage->setIdentifier(QLatin1String("GeoImageIdent")); // maybe empty!? + + { + // d->m_geoImage->setProxyImage(); // no proxyImage -> use defaultMarker + QImage mainImage(100, 100, QImage::Format_ARGB32); + mainImage.fill(qRgba(0, 0, 0, 0)); + + QPainter painter(&mainImage); + painter.setBrush(QBrush(Qt::gray)); + painter.setPen(Qt::NoPen); + painter.drawEllipse(mainImage.rect()); + + painter.setPen(QPen(Qt::red)); + painter.drawText(mainImage.rect(), Qt::AlignCenter, QLatin1String("GeoImg")); + + painter.end(); + d->m_geoImage->setMainImage(mainImage); + d->m_geoImage->setDescription(QLatin1String("desc")); + +// d->m_geoImage->setProxyImage(mainImage, QSize(20, 20)); + + } + + + d->m_geoImage->setVisibility(d->m_visibility); + d->m_geoImage->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_geoImage->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_geoImage->setSelected(d->m_selected); + + QPointF origin = s_basePoint.geographic(d->m_objectBasePoint + QPointF(90500, 80500)); + d->m_geoImage->setOrigin(Point3D::fromQPointF(origin)); + + bool success = d->m_geoImage->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_geoImage->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " GeoImage dump: " << (*d->m_geoImage) << std::endl; +} + +void FunctionDialog::onMapObjectClicked(const SituationMap::Map::RefPointerMapObjectList &mapObjList, + const QPointF &viewportPosition) +{ + qDebug() << LOGFUNCTION ; + + // Keep in mind: It´s likely that the application has also such a slot. + + for (auto mapObj : mapObjList) + { + qDebug() << LOGFUNCTION << " id=" << mapObj->uniqueMapId() + << "object-debugInfo=" << mapObj->debugInfo() + << "object-identifier=" << mapObj->identifier(); + + // GeoImage: + SituationMap::RefPointer geoImage(mapObj); + if (geoImage.valid()) + { + qDebug() << LOGFUNCTION << " geoImage found, id=" << mapObj->uniqueMapId() + << "object-debugInfo=" << mapObj->debugInfo() + << "object-identifier=" << mapObj->identifier(); + + QWidget *geoImageWidget = geoImage->createWidget(); + + if (!geoImageWidget) + { + qDebug() << LOGFUNCTION << " geoImage found, id=" << mapObj->uniqueMapId() + << "object-debugInfo=" << mapObj->debugInfo() + << "object-identifier=" << mapObj->identifier() + << " no mainImage provided!?"; + return; + } + + // Bug iOSQz-000005 'QGraphicsProxyWidget will crash' + // Adding a widget will let the application crash + // Workaround: 'proxy->setOpacity(0.99);' + QGraphicsProxyWidget *proxyWidget = d->m_mapInterface->getQGraphicsView()->scene()->addWidget(geoImageWidget); + proxyWidget->setOpacity(0.99); + proxyWidget->setPos(viewportPosition + QPointF(10, 10)); + + return; // to do it for just one GeoImage + } + } // for +} + +void FunctionDialog::onSoundAreaCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_soundArea)); + + d->m_soundArea = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("SoundAreaId")); + + if (!d->m_soundArea) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_soundArea->setIdentifier(QLatin1String("SoundAreaIdent")); // maybe empty!? + + SituationMap::PositionCont posCont; + QPointF offset = QPointF(93000, 78000); + + int extent = 1123; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent / 2, extent / 2))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_soundArea->setPositions(posCont); + + { + QImage img(100, 100, QImage::Format_ARGB32); + img.fill(qRgba(0, 0, 0, 0)); + + QPainter painter(&img); + painter.setBrush(QBrush(Qt::red)); + painter.setPen(Qt::NoPen); + painter.drawEllipse(img.rect()); + painter.setPen(QPen(Qt::black)); + painter.drawText(img.rect(), Qt::AlignCenter, QLatin1String("Img")); + painter.end(); + + d->m_soundArea->setMarkerData(MarkerData(img, QSize(40, 40))); // image in high resolution and small (default) size + d->m_soundArea->setMarkerOrigin(posCont[3]); + } + + + d->m_soundArea->setVisibility(d->m_visibility); + d->m_soundArea->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_soundArea->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_soundArea->setSelected(d->m_selected); + + + bool success = d->m_soundArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_soundArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + + std::cout << " GeoImage dump: " << (*d->m_soundArea) << std::endl; + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onSoundAreaUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onSoundAreaUpdate() +{ + if (!d->m_soundArea) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + { + // changed pos + SituationMap::PositionCont posCont = d->m_soundArea->positions(); + d->m_soundArea->setMarkerOrigin(posCont[2]); + } + + { + // changed img to marker + d->m_soundArea->setMarkerData(MarkerData(MarkerSymbolType::Cross, 20, Qt::cyan)); + } + + bool success = d->m_soundArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + + +void FunctionDialog::onHitLineCreate() +{ + qDebug() << LOGFUNCTION ; + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_hitLine)); + + d->m_hitLine = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("HitLineId")); + + if (!d->m_hitLine) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_hitLine->setIdentifier(QLatin1String("HitLine-OutLined")); + + d->m_hitLine->setVisibility(d->m_visibility); + d->m_hitLine->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_hitLine->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_hitLine->setSelected(d->m_selected); + + d->m_hitLine->rLineStyle().setLineColor(Qt::blue); + d->m_hitLine->rLineStyle().setLineWidth(3); + + + // set additional values: + QPointF offset = QPointF(80000, 80000); + + Point3D start = Point3D::fromQPointF(s_basePoint.geographic(offset + QPointF())); + Point3D end = Point3D::fromQPointF(s_basePoint.geographic(offset + QPointF(1230, 1560))); + + d->m_hitLine->setStartAndEndPoint(start, end); + + bool success = d->m_hitLine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_hitLine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " HitLine dump: " << (*d->m_hitLine) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onHitLineUpdate); + timer->setSingleShot(true); +// timer->start(2000); +} + +void FunctionDialog::onHitLineUpdate() +{ + if (!d->m_hitLine) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_hitLine->setIdentifier(d->m_hitLine->identifier() + QLatin1String("Update")); + + d->m_hitLine->setPartyId(4); // Will overwrite line/fillcolor + + // set additional values: + Point3D start = d->m_hitLine->startPoint(); + Point3D end = d->m_hitLine->endPoint(); + + // modify: + end.rx() += 0.001; + + d->m_hitLine->setStartAndEndPoint(start, end); + + bool success = d->m_hitLine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_hitLine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onLineOfSightLineCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_lineOfSight)); + + d->m_lineOfSight = d->m_mapInterface->objectFactory(). + create(QLatin1String("MountingPoints"), QLatin1String("LineOfSight")); // SurcfacePlacementMode () + + d->m_lineOfSight->setPoints(Point3D(0.155, 0.926, 100) // observer + , Point3D(0.156, 0.926, 50) // obstacle + , Point3D(0.157, 0.926, 0) // target +// , Qt::cyan, // visibleColor +// , Qt::magenta + ); // obstructedColor + + d->m_lineOfSight->setVisibility(d->m_visibility); + d->m_lineOfSight->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_lineOfSight->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_lineOfSight->setSelected(d->m_selected); + + + bool success = d->m_lineOfSight->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_lineOfSight->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " LineOfSight dump: " << (*d->m_lineOfSight) << std::endl; +} + + +void FunctionDialog::onMineCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_mine)); + + d->m_mine = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("MineId")); + + if (!d->m_mine) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_mine->setIdentifier(QLatin1String("Mine-OutLined")); + + d->m_mine->setVisibility(d->m_visibility); + d->m_mine->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_mine->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_mine->setSelected(d->m_selected); + + d->m_mine->setPartyId(3); // Will overwrite line/fillcolor + + + // set additional values: + QPointF origin = s_basePoint.geographic(d->m_objectBasePoint + QPointF(91500, 81500)); + d->m_mine->setOrigin(Point3D::fromQPointF(origin)); + + bool success = d->m_mine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_mine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " Mine dump: " << (*d->m_mine) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onMineUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onMineUpdate() +{ + if (!d->m_mine) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_mine->setState(true); + + bool success = d->m_mine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_mine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onMineFieldCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_mineField)); + + d->m_mineField = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("MineFieldId")); + + if (!d->m_mineField) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_mineField->setIdentifier(QLatin1String("MineField-OutLined")); + + d->m_mineField->setVisibility(d->m_visibility); + d->m_mineField->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_mineField->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_mineField->setSelected(d->m_selected); + + d->m_mineField->setAesState(AesEffectState::Unknown); // != Unknown will overwrite fillPattern + + d->m_mineField->rLineStyle().setLineColor(Qt::green); + d->m_mineField->rLineStyle().setLineWidth(2); + d->m_mineField->rFillStyle().setFillColor(Qt::blue); + d->m_mineField->rFillStyle().setFillPattern(FillPattern::DiagonalCross); + +// d->m_mineField->setPartyId(5); // Will overwrite line/fillcolor + + + + // set paths: + SituationMap::PositionCont posContSeg1; + QPointF offsetSeg1 = d->m_objectBasePoint + QPointF(150000, 150000); + + int extent = 40000; + appendQPointF(posContSeg1, s_basePoint.geographic(offsetSeg1 + QPointF( 0, 0))); + appendQPointF(posContSeg1, s_basePoint.geographic(offsetSeg1 + QPointF(extent, 0))); + appendQPointF(posContSeg1, s_basePoint.geographic(offsetSeg1 + QPointF(extent, 2 * extent))); + appendQPointF(posContSeg1, s_basePoint.geographic(offsetSeg1 + QPointF( 0, 2 * extent))); + d->m_mineField->setSegment(1, posContSeg1); + + PositionCont posContSeg2; + QPointF offsetSeg2 = d->m_objectBasePoint + QPointF(200000, 150000); + appendQPointF(posContSeg2, s_basePoint.geographic(offsetSeg2 + QPointF( 0, 0))); + appendQPointF(posContSeg2, s_basePoint.geographic(offsetSeg2 + QPointF(2 * extent, 0))); + appendQPointF(posContSeg2, s_basePoint.geographic(offsetSeg2 + QPointF(2 * extent, extent))); + appendQPointF(posContSeg2, s_basePoint.geographic(offsetSeg2 + QPointF( 0, extent))); + d->m_mineField->setSegment(2, posContSeg2); + +// PositionCont posContSeg3; +// QPointF offsetSeg3 = d->m_objectBasePoint + QPointF(300000, 150000); +// posContSeg3 << offsetSeg3 + QPointF(0, 0); +// posContSeg3 << offsetSeg3 + QPointF(2 * extent, 0); +// posContSeg3 << offsetSeg3 + QPointF(2 * extent, extent); +// posContSeg3 << offsetSeg3 + QPointF(0, extent); +// d->m_mineField->setSegment(3, posContSeg3); + PositionCont posContSeg3; + QPointF offsetSeg3 = d->m_objectBasePoint + QPointF(300000, 150000); + appendQPointF(posContSeg3, s_basePoint.geographic(offsetSeg3 + QPointF( 0, 0))); + appendQPointF(posContSeg3, s_basePoint.geographic(offsetSeg3 + QPointF(2 * extent, 0))); + appendQPointF(posContSeg3, s_basePoint.geographic(offsetSeg3 + QPointF(2 * extent, extent))); + appendQPointF(posContSeg3, s_basePoint.geographic(offsetSeg3 + QPointF( 0, extent))); + d->m_mineField->setSegment(3, posContSeg3); + + +// PositionCont posContComplete; +// QPolygonF polyComplete; +// QPolygonF polySeg1; +// Q_FOREACH(const QPointF &item, posContSeg1) +// polySeg1.append(item); + +// QPolygonF polySeg2; +// Q_FOREACH(const QPointF &item, posContSeg2) +// polySeg2.append(item); +// polyComplete = polySeg1.united(polySeg2); + +// qWarning() << "polyComplete.size()=" << polyComplete.size(); +// qWarning() << "polyComplete=" << polyComplete; + +// Q_FOREACH(const QPointF &item, polyComplete) +// posContComplete.append(item); + +// posContComplete +// << posContSeg1[0] +// << posContSeg1[3] +// << posContSeg1[2] +// << posContSeg3[2] +// << posContSeg3[1]; + + + d->m_mineField->setPositions(posContSeg1); + + PositionCont minePosCont; + appendQPointF(minePosCont, s_basePoint.geographic(offsetSeg1)); + appendQPointF(minePosCont, s_basePoint.geographic(offsetSeg1 + QPointF(1000, 100))); + appendQPointF(minePosCont, s_basePoint.geographic(offsetSeg1 + QPointF(12000, 12000))); + appendQPointF(minePosCont, s_basePoint.geographic(offsetSeg1 + QPointF(12000, 13000))); + + d->m_mineField->setPositionsMine(MineField::MineState::Activated, minePosCont); + + PositionCont minePosContDeActive; + appendQPointF(minePosContDeActive, s_basePoint.geographic(offsetSeg1 + QPointF(0, 100))); + d->m_mineField->setPositionsMine(MineField::MineState::DeActivated, minePosContDeActive); + + bool success = d->m_mineField->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_mineField->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " MineField dump: " << (*d->m_mineField) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onMineFieldUpdate); + timer->setSingleShot(true); + timer->start(5000); +} + +void FunctionDialog::onMineFieldUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_mineField) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_mineField->setDrawOrder(10); + + d->m_mineField->setMineVisibility(MineField::MineState::Activated, false); + + d->m_mineField->updateView(); + + // remove some mines: + PositionCont empty; + d->m_mineField->setPositionsMine(MineField::MineState::DeActivated, empty); + + // remove segment: + d->m_mineField->removeSegment(2); + + d->m_mineField->setAesState(AesEffectState::Created); // != Unknown will overwrite fillPattern + + d->m_mineField->setPartyId(5); // Will overwrite line/fillcolor + + + bool success = d->m_mineField->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_mineField->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onNuclearCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_nuclearAgent)); + + d->m_nuclearAgent = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("NuclearAgentId")); + + if (!d->m_nuclearAgent) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_nuclearAgent->setIdentifier(QLatin1String("NuclearAgent-OutLined")); + + d->m_nuclearAgent->setVisibility(d->m_visibility); + d->m_nuclearAgent->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_nuclearAgent->setVisibility(SituationMap::ObjectAppearance::Tactical, false); + + d->m_nuclearAgent->setSelected(d->m_selected); + + d->m_nuclearAgent->setPartyId(4); // Will overwrite line/fillcolor + + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(90000, 80000); + + int extent = 1000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, 1 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-2 * extent, 3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(2 * extent, 3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(1 * extent, 1 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(1 * extent, 0))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_nuclearAgent->setPositions(posCont); + + d->m_nuclearAgent->setAesState(AesEffectState::Paused); + + + SituationMap::PositionCont subLine1; + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(-1 * extent, 1 * extent))); + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(+1 * extent, 1 * extent))); + + d->m_nuclearAgent->setSubsidiaryLine(0, subLine1); + + SituationMap::PositionCont subLine2; + appendQPointF(subLine2, s_basePoint.geographic(offset + QPointF(-1.7 * extent, 2 * extent))); + appendQPointF(subLine2, s_basePoint.geographic(offset + QPointF(+1.7 * extent, 2 * extent))); + + d->m_nuclearAgent-> + setSubsidiaryLine(1, subLine2); + + + bool success = d->m_nuclearAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_nuclearAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " NuclearAgent dump: " << (*d->m_nuclearAgent) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onNuclearUpdate); + timer->setSingleShot(true); +// timer->start(2000); +} + +void FunctionDialog::onNuclearUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_nuclearAgent) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + // move + QPointF offset(d->m_nuclearAgent->positions().value(0).toPointF() + QPointF(10000, 10000)); + + qDebug() << LOGFUNCTION << " update values.." ; + + SituationMap::PositionCont posCont; + + int extent = 10000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, 1 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-2 * extent, 4 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(+2 * extent, 4 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(+1 * extent, 1 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(+1 * extent, 0))); + + d->m_nuclearAgent->setPositions(posCont); + + d->m_nuclearAgent->removeSubsidiaryLines(); + + d->m_nuclearAgent->setAesState(AesEffectState::Activated); + + d->m_nuclearAgent->setPartyId(5); // Will overwrite line/fillcolor + + + bool success = d->m_nuclearAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_nuclearAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + +} + +void FunctionDialog::onChargeEffectCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_singleFire)); + + d->m_singleFire = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("SingleFireId")); + + if (!d->m_singleFire) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_singleFire->setIdentifier(QLatin1String("ChargeEffect-OutLined")); + + d->m_singleFire->setVisibility(d->m_visibility); + d->m_singleFire->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_singleFire->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_singleFire->setSelected(d->m_selected); + + d->m_singleFire->setPartyId(3); // Will overwrite line/fillcolor + + + // set additional values: + + QPointF offset = QPointF(84000, 77000); + Point3D origin = Point3D::fromQPointF(s_basePoint.geographic(offset + QPointF())); + qWarning() << LOGFUNCTION <<" origin "<< origin.toPointF(); + //QPointF origin = s_basePoint.geographic(d->m_objectBasePoint + QPointF(155000, 155000)); + + d->m_singleFire->setOrigin(/*Point3D::fromQPointF*/(origin)); + d->m_singleFire->setRadius(300); + + d->m_singleFire->setImpactOrigin(d->m_singleFire->origin()); + + d->m_singleFire->setAesState(AesEffectState::Created); + + bool success = d->m_singleFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_singleFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " SingleFire dump: " << (*d->m_singleFire) << std::endl; + + + // QTimer *timer = new QTimer(this); + // connect(timer, &QTimer::timeout, this, &FunctionDialog::onChargeEffectUpdate); + // timer->setSingleShot(true); + // timer->start(2000); +} + +void FunctionDialog::onChargeEffectUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_singleFire) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + // move + //QPointF offset = QPointF(80000, 80000); + //Point3D origin = Point3D::fromQPointF(s_basePoint.geographic(offset + QPointF())); + //QPointF origin = s_basePoint.geographic(d->m_singleFire->origin().toPointF() + QPointF(50, 50)); + + //d->m_singleFire->setOrigin(origin); + d->m_singleFire->setRadius(500); + d->m_singleFire->setImpactOrigin(d->m_singleFire->origin()); + + d->m_singleFire->setPartyId(4); // Will overwrite line/fillcolor + d->m_singleFire->setAesState(AesEffectState::Activated); // created, Prepared, Activated + + bool success = d->m_singleFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + +} + +void FunctionDialog::onBoobyTrapCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_virtualSingleMineAgent)); + + d->m_virtualSingleMineAgent = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("NuclearAgentId")); + + if (!d->m_virtualSingleMineAgent) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_virtualSingleMineAgent->setIdentifier(QLatin1String("virtualSingleMine-OutLined")); + + d->m_virtualSingleMineAgent->setVisibility(d->m_visibility); + d->m_virtualSingleMineAgent->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_virtualSingleMineAgent->setVisibility(SituationMap::ObjectAppearance::Tactical, false); + + d->m_virtualSingleMineAgent->setSelected(d->m_selected); + + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(90000, 80000); + + int extent = 1000; + + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, 3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(3 * extent, 3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(3 * extent, -3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, -3 * extent))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_virtualSingleMineAgent->setPositions(posCont); + + d->m_virtualSingleMineAgent->setAesState(AesEffectState::Paused); + + const SituationMap::PartyIdToColorFunctor::PartyData pData1(QStringLiteral("P1"), + AdvancedSymbology::Role::HostileFaker, + Qt::blue, Qt::blue); + d->m_mapInterface->partyIdToColorFunctor().setParty(1, pData1); + + SituationMap::PositionCont subLine1; + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(1 * extent, -1 * extent))); + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(1 * extent, -3 * extent))); + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(+5 * extent, -3 * extent))); + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(+5 * extent, -1 * extent))); + + + NuclearAgent::OptionalData optData; + optData.closed = true; + optData.lineStyle = QSharedPointer(new LineStyle(Qt::red, 5, LinePattern::DashDot)); + optData.fillStyle = QSharedPointer(new FillStyle(Qt::red, FillPattern::DiagonalCross)); + d->m_virtualSingleMineAgent->setSubsidiaryLine(0, subLine1, optData); + +// SituationMap::PositionCont subLine2; +// appendQPointF(subLine2, s_basePoint.geographic(offset + QPointF(-1.7 * extent, 2 * extent))); +// appendQPointF(subLine2, s_basePoint.geographic(offset + QPointF(+1.7 * extent, 2 * extent))); +// d->m_virtualSingleMineAgent->setSubsidiaryLine(1, subLine2, false); + + + bool success = d->m_virtualSingleMineAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_virtualSingleMineAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " Virtual Single Mine dump: " << (*d->m_virtualSingleMineAgent) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onBoobyTrapUpdate); + timer->setSingleShot(true); + timer->start(2000); + +} + +void FunctionDialog::onBoobyTrapUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_virtualSingleMineAgent) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + // move + //QPointF offset(d->m_virtualSingleMineAgent->positions().value(0).toPointF() + QPointF(1000, 1000)); + + QPointF offset = d->m_objectBasePoint + QPointF(90000, 80000); + qDebug() << LOGFUNCTION << " update values.." ; + + SituationMap::PositionCont posCont; + + int extent = 1000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, 3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(3 * extent, 3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(3 * extent, -3 * extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(-1 * extent, -3 * extent))); + + + d->m_virtualSingleMineAgent->setPositions(posCont); + + d->m_virtualSingleMineAgent->removeSubsidiaryLines(); + + d->m_virtualSingleMineAgent->setAesState(AesEffectState::Activated); + + //d->m_virtualSingleMineAgent->setPartyId(5); // Will overwrite line/fillcolor + + const SituationMap::PartyIdToColorFunctor::PartyData pData1(QStringLiteral("P1"), + AdvancedSymbology::Role::HostileFaker, + Qt::blue, Qt::blue); + d->m_mapInterface->partyIdToColorFunctor().setParty(3, pData1); + + SituationMap::PositionCont subLine1; + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(1 * extent, -1 * extent))); + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(1 * extent, -3 * extent))); + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(+5 * extent, -3 * extent))); + appendQPointF(subLine1, s_basePoint.geographic(offset + QPointF(+5 * extent, -1 * extent))); + + + NuclearAgent::OptionalData optData; + optData.closed = true; + d->m_virtualSingleMineAgent->setSubsidiaryLine(0, subLine1, optData); + + bool success = d->m_virtualSingleMineAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_virtualSingleMineAgent->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + + +void FunctionDialog::onSingleFireCreate() +{ + qDebug() << LOGFUNCTION ; + + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_singleFire)); + + d->m_singleFire = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("SingleFireId")); + + if (!d->m_singleFire) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_singleFire->setIdentifier(QLatin1String("SingleFire-OutLined")); + + d->m_singleFire->setVisibility(d->m_visibility); + d->m_singleFire->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_singleFire->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_singleFire->setSelected(d->m_selected); + + d->m_singleFire->setPartyId(3); // Will overwrite line/fillcolor + + + // set additional values: + QPointF origin = s_basePoint.geographic(d->m_objectBasePoint + QPointF(155000, 155000)); + + d->m_singleFire->setOrigin(Point3D::fromQPointF(origin)); + d->m_singleFire->setRadius(100); + + d->m_singleFire->setImpactOrigin(d->m_singleFire->origin()); + + d->m_singleFire->setAesState(AesEffectState::Paused); + + bool success = d->m_singleFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_singleFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " SingleFire dump: " << (*d->m_singleFire) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onSingleFireUpdate); + timer->setSingleShot(true); + timer->start(2000); +} + +void FunctionDialog::onSingleFireUpdate() +{ + qDebug() << LOGFUNCTION ; + + if (!d->m_singleFire) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + // move + QPointF origin = s_basePoint.geographic(d->m_singleFire->origin().toPointF() + QPointF(50, 50)); + + d->m_singleFire->setOrigin(Point3D::fromQPointF(origin)); + d->m_singleFire->setRadius(250); + d->m_singleFire->setImpactOrigin(d->m_singleFire->origin()); + +// d->m_singleFire->setPartyId(4); // Will overwrite line/fillcolor + d->m_singleFire->setAesState(AesEffectState::Activated); // created, Prepared, Activated + + bool success = d->m_singleFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_singleFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; +} + +void FunctionDialog::onShowBasePoint() +{ + qDebug() << LOGFUNCTION ; + + if (d->m_basePoint) + { + qDebug() << LOGFUNCTION << " already exists"; + return; + } + + // create and set debugInfo for debugging: + d->m_basePoint = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("BasePointPolygon")); + + if (!d->m_basePoint) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + + d->m_basePoint->setIdentifier(QLatin1String("BasePoint\n SimCoord = 0,0")); + + d->m_basePoint->rLineStyle().setLineColor(Qt::red); + d->m_basePoint->rLineStyle().setLineWidth(8); + + PositionCont posCont; + appendQPointF(posCont, QPointF(0, 0)); + appendQPointF(posCont, d->m_mapImpl->centerPoint()); + + d->m_basePoint->setPositions(posCont); + + bool success = d->m_basePoint->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + + std::cout << " m_basePoint dump: " << (*d->m_basePoint) << std::endl; +} + +void FunctionDialog::onShowPolizei() +{ + onRemoveAll(); + + QString layername = d->m_mapInterface->layerFactory().basemapLayernamePrefix(); + layername += QLatin1String("0"); + + LayerParameter lp = d->m_mapInterface->layerFactory().layerParameter(layername); + lp.opacity = 20; + d->m_mapInterface->layerFactory().setLayerParameter(layername, lp); + + + // Bld + { + RefPointer building; + + building = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("BuildingOutlineId2")); + + if (!building) { qDebug() << LOGFUNCTION << " no object"; return; } + + building->setIdentifier(QLatin1String("Building")); + + building->setVisibility(true); + building->setVisibility(SituationMap::ObjectAppearance::Technical, true); + building->setVisibility(SituationMap::ObjectAppearance::Tactical, true); + + + building->rTextStyle().setTextColor(Qt::magenta); + building->rTextStyle().setSize(30); + building->rTextStyle().setAntiAliased(true); + building->rTextStyle().setHorizontalAlignment(HorizontalAlignment::Left); + building->rTextStyle().setVerticalAlignment(VerticalAlignment::BaseLine); + building->rTextStyle().setTextOutlineWidth(0); + + + // set additional values: + SituationMap::PositionCont posCont; + appendQPointF(posCont, s_basePoint.geographic(QPointF(93076.8, 78331.8))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93070.9, 78325.0))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93064.0, 78331.0))); + appendQPointF(posCont, s_basePoint.geographic(QPointF(93069.9, 78337.8))); + + building->setPositions(posCont); + + bool success = building->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + } + + + static bool single = true; + single = !single; + + const QColor participantColor(41, 76, 56); + + QPointF shooterOrigin = s_basePoint.geographic(QPointF(93072, 78344)); + QPointF targetOrigin = s_basePoint.geographic(QPointF(92927, 78333)); + + // part0 - shooter + { + RefPointer participant = d->m_mapInterface->objectFactory(). + create(QLatin1String("Participants"), QLatin1String("ParticipantShooter")); + + participant->setOrigin(Point3D::fromQPointF(shooterOrigin)); + + participant->setTextModifier(Participant::TextModifier::M_HigherFormation, QLatin1String("Amok")); + participant->setTextModifier(Participant::TextModifier::T_UniqueDesignation, QLatin1String("")); + + SymbolData symData; + symData.sidc = QStringLiteral("ETS_Zivil_Person"); + symData.color = Qt::cyan; // Will be overwritten via partyId. + + participant->setSymbol(symData); + participant->setPartyId(3); + participant->forcePartyColor(Qt::gray); + participant->setScale(1.0); + + bool success = participant->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } + + // part1 - target + { + RefPointer participant = d->m_mapInterface->objectFactory(). + create(QLatin1String("Participants"), QLatin1String("ParticipantPolizei111")); + + participant->setOrigin(Point3D::fromQPointF(targetOrigin)); + + participant->setTextModifier(Participant::TextModifier::M_HigherFormation, QLatin1String("Schupo")); + participant->setTextModifier(Participant::TextModifier::T_UniqueDesignation, QLatin1String("FuStw.1")); + + SymbolData symData; + if (single) + symData.sidc = QStringLiteral("ETS_Polizei_PKW"); + else + symData.sidc = QStringLiteral("ETS_Polizei_FueKraft"); + symData.color = Qt::cyan; // Will be overwritten via partyId. + + participant->setSymbol(symData); + participant->setPartyId(3); + participant->forcePartyColor(participantColor); + if (single) + participant->setScale(1.0); + else + participant->setScale(2.0); + + bool success = participant->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } + + if (single) + { + // part2 + { + RefPointer participant = d->m_mapInterface->objectFactory(). + create(QLatin1String("Participants"), QLatin1String("ParticipantPolizei222")); + + QPointF origin = s_basePoint.geographic(QPointF(92936, 78368)); + participant->setOrigin(Point3D::fromQPointF(origin)); + + participant->setTextModifier(Participant::TextModifier::M_HigherFormation, QLatin1String("Schupo")); + participant->setTextModifier(Participant::TextModifier::T_UniqueDesignation, QLatin1String("Wm.1")); + + SymbolData symData; + symData.sidc = QStringLiteral("ETS_Polizei_Person"); + symData.color = Qt::cyan; // Will be overwritten via partyId. + symData.size = 1.0; + + participant->setSymbol(symData); + participant->setPartyId(3); + participant->forcePartyColor(participantColor); + participant->setScale(1.0); + + bool success = participant->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } + + // part3 + { + RefPointer participant = d->m_mapInterface->objectFactory(). + create(QLatin1String("Participants"), QLatin1String("ParticipantPolizei222")); + + QPointF origin = s_basePoint.geographic(QPointF(92944, 78393)); + participant->setOrigin(Point3D::fromQPointF(origin)); + + participant->setTextModifier(Participant::TextModifier::M_HigherFormation, QLatin1String("Schupo")); + participant->setTextModifier(Participant::TextModifier::T_UniqueDesignation, QLatin1String("Wm.2")); + + SymbolData symData; + symData.sidc = QStringLiteral("ETS_Polizei_Person"); + symData.color = Qt::cyan; // Will be overwritten via partyId. + symData.size = 1.0; + + participant->setSymbol(symData); + participant->setPartyId(3); + participant->forcePartyColor(participantColor); + participant->setScale(1.0); + + bool success = participant->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } + } + + // hitline + { + RefPointer hitLine; + hitLine = d->m_mapInterface->objectFactory(). + create(QLatin1String("Participants"), QLatin1String("HitLineId")); + + if (!hitLine) { qDebug() << LOGFUNCTION << " no object"; return; } + + hitLine->setDrawOrder(11); + hitLine->setVisibility(SituationMap::ObjectAppearance::Technical, true); + hitLine->setVisibility(SituationMap::ObjectAppearance::Tactical, false); + + hitLine->setIdentifier(QLatin1String("M16")); + + hitLine->rLineStyle().setLineColor(Qt::green); + hitLine->rLineStyle().setLineWidth(3); + + hitLine->setStartAndEndPoint(Point3D::fromQPointF(shooterOrigin), + Point3D::fromQPointF(targetOrigin)); + + bool success = hitLine->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } + + // Area + { + RefPointer functionalArea = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("FunctionalAreaId")); + + if (!functionalArea) { qDebug() << LOGFUNCTION << " no object"; return; } + + functionalArea->setIdentifier(QLatin1String("Medical")); + functionalArea->rTextStyle().setTextColor(Qt::red); + functionalArea->rTextStyle().setTextOutlineWidth(0); + + functionalArea->setVisibility(d->m_visibility); + functionalArea->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + functionalArea->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + functionalArea->setLineStyle(LineStyle(Qt::red, 2, LinePattern::Solid)); + functionalArea->setFillStyle(FillStyle(Qt::red, FillPattern::ForwardDiagonal)); + + SituationMap::PositionCont posCont; + QPointF offset(92850, 78250); + + int extent = 25; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(0, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(0, 0))); + + functionalArea->setPositions(posCont); + + bool success = functionalArea->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + } + +} + +void FunctionDialog::onObjectIndicator() +{ + qCritical() << LOGFUNCTION << " START"; + + d->m_mapInterface->objectIndicator().setMapObjectToPointTo(0); +} + +void FunctionDialog::onMeasureLength() +{ + qDebug() << LOGFUNCTION ; + d->m_mapInterface->measurement().measureLength(); +} + +void FunctionDialog::onStartLocationDisplay() +{ + qCritical() << LOGFUNCTION ; + QPointF center = d->m_mapInterface->centerPoint(); + d->m_mapInterface->locationDisplay().setCurrentLocation(Point3D::fromQPointF(center)); + d->m_mapInterface->locationDisplay().start(); +} + +void FunctionDialog::onStopLocationDisplay() +{ + d->m_mapInterface->locationDisplay().stop(); +} + +void FunctionDialog::onLoadFile() +{ + qCritical() << LOGFUNCTION << " AAAAAAAAAA"; + + { + QString filePath(QLatin1String("/tmp/Observer1Reference.tif")); + + Esri::ArcGISRuntime::Raster *isr = new Esri::ArcGISRuntime::Raster(filePath, this); + if (!isr) + { + qCritical() << LOGFUNCTION << " isr failed"; + return; + } + + Esri::ArcGISRuntime::RasterLayer *rl = new Esri::ArcGISRuntime::RasterLayer(isr, this); + if (!rl) + { + qCritical() << LOGFUNCTION << " rl failed"; + return; + } + + + connect(rl, &Esri::ArcGISRuntime::RasterLayer::errorOccurred, this, [] (const Esri::ArcGISRuntime::Error &error) { + qCritical() << LOGFUNCTION << " EsriError occured: " << toString(error); + }); + + qCritical() << LOGFUNCTION << " getEsriMap()->operationalLayers()->size" << d->m_mapImpl->getEsriMap()->operationalLayers()->size(); + d->m_mapImpl->getEsriMap()->operationalLayers()->append(rl); + qCritical() << LOGFUNCTION << " getEsriMap()->operationalLayers()->size" << d->m_mapImpl->getEsriMap()->operationalLayers()->size(); + + + connect(rl, &Esri::ArcGISRuntime::RasterLayer::loadStatusChanged, this, [] (Esri::ArcGISRuntime::LoadStatus loadStatus) { + qCritical() << LOGFUNCTION << " loadStatus=" << (int)loadStatus; + }); + + + rl->load(); + } +} + +#ifdef USE_LOCAL_SERVER +void FunctionDialog::onStartLocalService() +{ + QPushButton *pb = qobject_cast(sender()); + if (!pb) + return; + + + + SituationMap::LocalServer *localServer = SituationMap::LocalServer::instance(); + + connect(localServer, &LocalServer::localServerStatusChanged, pb, [localServer, pb] () { + if (localServer->isLocalServerStarted()) + pb->setStyleSheet(QLatin1String("QPushButton {background-color: green;}")); + else if (localServer->isLocalServerStopped()) + pb->setStyleSheet(QLatin1String("QPushButton {background-color: gray;}")); + }); + + connect(localServer, &LocalServer::localGeoprocessingServiceStarted, this, [this] (const QString &serviceName, const LocalServiceData &svcData) { + qCritical() << LOGFUNCTION + << " :-) service started: '" << serviceName << "'" + << " url: '" << svcData.svcUrl().toString() << "'" + << " sync: " << svcData.isSync(); + d->m_mapImpl->viewshed().setViewshedUrl(svcData.svcUrl().toString()); + }); + + + // starts LocalServer (and pending GPSsvc, which are none yet.) + localServer->start(); +} + +void FunctionDialog::onStopLocalService() +{ + SituationMap::LocalServer *localServer = SituationMap::LocalServer::instance(); + localServer->stop(); +} + +void FunctionDialog::onStartViewshedSvc() +{ + QPushButton *pb = qobject_cast(sender()); + if (!pb) + return; + + SituationMap::LocalServer *localServer = SituationMap::LocalServer::instance(); + const QString viewshedSvcName(QLatin1String("Viewshed")); + localServer->setLocalGeoprocessingService(viewshedSvcName, + QLatin1String("/shd/CTC/TOOLS/arcgis/EsriJebu/LegatusBremen201803151510.gpk"), + QLatin1String("Viewshed2Feature"), + true); + localServer->startLocalGeoprocessingService(viewshedSvcName); + + connect(localServer, &LocalServer::localGeoprocessingServiceStatusChanged, this, [localServer, pb] (const QString &serviceName) { + if (localServer->isLocalGeoprocessingServiceStarted(serviceName)) + pb->setStyleSheet(QLatin1String("QPushButton {background-color: green;}")); + else if (localServer->isLocalGeoprocessingServiceStopped(serviceName)) + pb->setStyleSheet(QLatin1String("QPushButton {background-color: gray;}")); + }); +} + +void FunctionDialog::onStopViewshedSvc() +{ + SituationMap::LocalServer *localServer = SituationMap::LocalServer::instance(); + const QString viewshedSvcName(QLatin1String("Viewshed")); + localServer->stopLocalGeoprocessingService(viewshedSvcName); +} + + +void FunctionDialog::onViewshedInteractive() +{ + d->m_mapInterface->viewshed().create(); +} + +void FunctionDialog::onViewshed() +{ + static long viewshedId = -1; + + if (viewshedId >= 0) + d->m_mapInterface->viewshed().remove(viewshedId); + + // GP-based: + QPointF viewshedPoint = d->m_mapInterface->centerPoint(); + int distance = 523; //meter + + + if (d->m_viewshedDistance) + { + bool ok = false; + int localDis = d->m_viewshedDistance->text().toInt(&ok); + if (ok) + distance= localDis; + } + + ViewshedParameter viewshedParameter; + viewshedParameter.viewshedOrigin_latLon = viewshedPoint; + viewshedParameter.outerRadius = distance; + viewshedParameter.observerHeight = 15; + + viewshedId = d->m_mapInterface->viewshed().create(viewshedParameter); + qDebug() << LOGFUNCTION +// << " viewshedParameter =" << viewshedParameter; + << " center =" << viewshedPoint + << " distance =" << distance; +} + +#endif // USE_LOCAL_SERVER + +void FunctionDialog::onSightFan() +{ + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapInterface); + + SituationMap::RB::RubberbandEllipse *rubberband = d->m_mapInterface->rubberbandFactory().create(this); + if (rubberband) + { + rubberband->setAutoDelete(true); + rubberband->constrainQuadratic(true); + rubberband->constrainOrthogonal(true); + rubberband->enableSector(true); + + static SituationMap::RefPointer sightFan; + static QString toolLayerName; + + if (toolLayerName.isEmpty()) + { + toolLayerName = d->m_mapImpl->layerFactory().toolLayer(); + } + + if (nullptr == sightFan.get()) + { + connect( d->m_sightFanObserverHeight, &QLineEdit::textEdited, this, [this](const QString&) + { + if (nullptr == sightFan.get()) return; + sightFan->setCenterHat(d->m_sightFanObserverHeight->text().toDouble()); + sightFan->show(); + }); + + connect(d->m_sightFanTargetHeight, &QLineEdit::textEdited, this, [this](const QString&) + { + if (nullptr == sightFan.get()) return; + sightFan->setObjectHat(d->m_sightFanTargetHeight->text().toDouble()); + sightFan->show(); + }); + } + + + std::function updateSightFan = [this](SituationMap::RB::RubberbandBase* rbBase) -> bool + { + SituationMap::RB::RubberbandEllipse *rubberElli = dynamic_cast(rbBase); + if (rubberElli) + { + QPointF center; + double diameter; + double dummy; + QPointF direction; + double observerHeight = d->m_sightFanObserverHeight->text().toDouble(); + double targetHeight = d->m_sightFanTargetHeight->text().toDouble(); + + rubberElli->rectangleParameter(center, diameter, dummy, direction); + + if (diameter > 0.0) + { + double startAngle = 0.0; + double dAngle = 2.0 / 180.0*MathConst::pi; + unsigned int noBeams = 180; + double radius = diameter * 0.5; + + if (rubberElli->hasSector()) + { + startAngle = SituationMap::MathUtils::getMathAngle(rubberElli->sectorStart()); + double endAngle = SituationMap::MathUtils::getMathAngle(rubberElli->sectorEnd()); + + noBeams = ceil(((startAngle > endAngle) ? (endAngle - startAngle + MathConst::pi_two) :(endAngle - startAngle)) / dAngle); + } + + if (nullptr == sightFan.get()) + { + SituationMap::ObjectFactory &objFac = d->m_mapInterface->objectFactory(); + + sightFan = objFac.create(toolLayerName); + if (sightFan) + { + sightFan->initFan(center, observerHeight, targetHeight, radius, radius / 1000.0, startAngle, dAngle, noBeams, 1.0); + } + } + else + { + sightFan->setInnerRadius(radius / 1000.0); + sightFan->setRadius(radius); + sightFan->setStartAngle(startAngle); + sightFan->setNoOfFans(noBeams); + sightFan->move(center); + } + } + } + return true; + }; // std::function + + rubberband->installExternalUpdate(updateSightFan); + + QObject::connect(rubberband, &SituationMap::RB::Rubberband::apply, this, [this](SituationMap::RB::RubberbandBase *rubberband) + { + if (nullptr != sightFan.get()) + { + sightFan->removeMySelf(); + sightFan = nullptr; + } + + SituationMap::RB::RubberbandEllipse *rubberElli = dynamic_cast(rubberband); + if (rubberElli) + { + SituationMap::ObjectFactory &objFac = d->m_mapInterface->objectFactory(); + + sightFan = objFac.create(toolLayerName); + + if (sightFan) + { + QPointF center = rubberElli->centerPoint(); + double diameter; + double dummy; + QPointF direction; + double startAngle = 0.0; + double dAngle = 2.0 / 180.0*MathConst::pi; + unsigned int noBeams = 180; + + double observerHeight = d->m_sightFanObserverHeight->text().toDouble(); + double targetHeight = d->m_sightFanTargetHeight->text().toDouble(); + + rubberElli->rectangleParameter(center, diameter, dummy, direction); + + if (rubberElli->hasSector()) + { + startAngle = SituationMap::MathUtils::getMathAngle(rubberElli->sectorStart()); + double endAngle = SituationMap::MathUtils::getMathAngle(rubberElli->sectorEnd()); + + noBeams = floor((endAngle - startAngle) / dAngle); + } + + double radius = diameter * 0.5; + + sightFan->initFan(center, observerHeight, targetHeight, radius, radius / 1000.0, startAngle, dAngle, noBeams, 1.0); + + SituationMap::RB::RubberbandPointFeature *moveRB = d->m_mapInterface->rubberbandFactory().create(this); + if (moveRB) + { + moveRB->setAutoDelete(true); + moveRB->enableModificationState(); + moveRB->setCoordinateVector( QVector() << sightFan->center() ); + + std::function moveSightFan = [](SituationMap::RB::RubberbandBase* rbBase) -> bool + { + SituationMap::RB::RubberbandPointFeature *rubberPFeat = dynamic_cast(rbBase); + if (rubberPFeat && sightFan) + { + const QPointF & loc = rubberPFeat->singleCoordinate(); + sightFan->move(loc); + } + return true; + }; + + moveRB->installExternalUpdate(moveSightFan); + } + } + } + }); + + QObject::connect(rubberband, &SituationMap::RB::Rubberband::abort, this, [this](SituationMap::RB::RubberbandBase *rubberband) + { + if (nullptr != sightFan.get()) + { + sightFan->removeMySelf(); + sightFan = nullptr; + } + + onRubberbandAbort(rubberband); + }); + } +} // void FunctionDialog::onSightFan() + + +void FunctionDialog::onViewshedSightFan() +{ + static long sightFanId = -1; + + if (sightFanId >= 0) + { + d->m_mapInterface->viewshed().remove(sightFanId); + } + + Viewshed &viewshed = d->m_mapInterface->viewshed(); + sightFanId = viewshed.create(100.0, 1.8, 1.8, 2.5, 1.0); + +} // void FunctionDialog::onViewshedSightFan() + + +void FunctionDialog::onLoadPlan() +{ + qDebug() << LOGFUNCTION ; + + OverlayHandler &overlayHandler = d->m_mapInterface->overlayHandler(); + + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + QString name = pb->property("name").toString(); + + if (name.isEmpty()) + { + qWarning() << LOGFUNCTION << " no name"; + return; + } + + QFile file(QStringLiteral("/tmp/") + name); + if (!file.open((QIODevice::ReadOnly | QIODevice::Text))) + { + qWarning() << LOGFUNCTION << " file open failed"; + return; + } + const QByteArray overlayContent = file.readAll(); + + qWarning() << LOGFUNCTION << " overlayContent.size()='" << overlayContent.size() << "'"; + qWarning() << LOGFUNCTION << " overlayContent='" << overlayContent << "'"; + + int key = pb->property("key").toInt(); + overlayHandler.setOverlay(key, QStringLiteral("OverlayDisplayname"), overlayContent); + overlayHandler.setMinScale(key, 500000); // limiting zoomOut + overlayHandler.setMaxScale(key, 20000); // limiting zoomIn + +} + +void FunctionDialog::onOverlayVisiChanged(bool checked) +{ + qDebug() << LOGFUNCTION; + + QCheckBox *cb = qobject_cast(sender()); + if (! cb) + { + qWarning() << LOGFUNCTION << " no object"; + return; + } + + int key = cb->property("key").toInt(); + OverlayHandler &overlayHandler = d->m_mapInterface->overlayHandler(); + overlayHandler.setOverlayVisibility(key, checked); +} + +void FunctionDialog::onRemovePlan() +{ + qDebug() << LOGFUNCTION; + + OverlayHandler &overlayHandler = d->m_mapInterface->overlayHandler(); + + QString filename; + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + filename = pb->text(); + + if (filename.isEmpty()) + { + qWarning() << LOGFUNCTION << " no filename"; + return; + } + + int key = pb->property("key").toInt(); + overlayHandler.removeOverlay(key); +} + +void FunctionDialog::onRemoveAllPlans() +{ + qDebug() << LOGFUNCTION; + + OverlayHandler &overlayHandler = d->m_mapInterface->overlayHandler(); + + qWarning() << LOGFUNCTION << " overlayCount =" << overlayHandler.overlayCount(); + int nummberRemoved = overlayHandler.removeAllOverlays(); + qWarning() << LOGFUNCTION << " removed =" << nummberRemoved; + qWarning() << LOGFUNCTION << " overlayCount =" << overlayHandler.overlayCount(); +} + +void FunctionDialog::onToPdf() +{ + qDebug() << LOGFUNCTION; + + QString fileName = QDir::tempPath() + QLatin1String("/sitMap.pdf"); + + d->m_mapInterface->toPdf(fileName, + QLatin1String("ScreenshotTitle"), + QLatin1String("Uebung: Test"), + QLatin1String("Benutzer: Norbert"), + QLatin1String("2016-01-01 - 10:11:12")); + + qWarning() << LOGFUNCTION << " pdf written to '" << fileName << "'"; + +} + + +void FunctionDialog::onAreaFireCreate() +{ + qDebug() << LOGFUNCTION ; + // create and set debugInfo for debugging: + d->clearMapObjectPtr(RefPointer(d->m_areaFire)); + + d->m_areaFire = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), QLatin1String("AreaFireId")); + + if (!d->m_areaFire) + { + qDebug() << LOGFUNCTION << " no object"; + return; + } + + d->m_areaFire->setIdentifier(QLatin1String("AreaFire-OutLined")); + + d->m_areaFire->setVisibility(d->m_visibility); + d->m_areaFire->setVisibility(SituationMap::ObjectAppearance::Technical, d->m_visibilityTechn); + d->m_areaFire->setVisibility(SituationMap::ObjectAppearance::Tactical, d->m_visibilityTact); + + d->m_areaFire->setSelected(d->m_selected); + + LineStyle lineStyle(Qt::red, 5, LinePattern::Solid); + FillStyle fillStyle(Qt::blue, FillPattern::Null); + + d->m_areaFire->setLineStyle(lineStyle); + d->m_areaFire->setFillStyle(fillStyle); + + d->m_areaFire->setPartyId(3); // Will overwrite line/fillcolor + d->m_areaFire->setAesState(AesEffectState::Unknown); // != Unknown will overwrite fillPattern + + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(170000, 170000); + + int extent = 100000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, extent))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent / 2, extent / 2))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + std::cout << " posCont=" << posCont.size() << std::endl; + + d->m_areaFire->setPositions(posCont); + + + posCont.removeAt(3); + d->m_areaFire->setImpacts(posCont); + + bool success = d->m_areaFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_areaFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + std::cout << " AreaFire dump: " << (*d->m_areaFire) << std::endl; + + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FunctionDialog::onAreaFireUpdate); + timer->setSingleShot(true); + timer->start(2000); + +} + +void FunctionDialog::onAreaFireUpdate() +{ + if (!d->m_areaFire) + { + qDebug() << LOGFUNCTION << " no object anymore"; + return; + } + + d->m_areaFire->setIdentifier(d->m_areaFire->identifier() + QLatin1String("Update")); + + d->m_areaFire->setPartyId(4); // Will overwrite line/fillcolor + d->m_areaFire->setAesState(AesEffectState::Activated); // != Unknown will overwrite fillPattern + + + SituationMap::PositionCont posCont; + QPointF offset = d->m_objectBasePoint + QPointF(170000, 170000); + + int extent = 100000; + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, 0))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF(extent, extent * 2))); + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, extent))); + + appendQPointF(posCont, s_basePoint.geographic(offset + QPointF( 0, 0))); + + d->m_areaFire->setPositions(posCont); + + posCont.removeAt(1); + posCont.removeAt(3); + d->m_areaFire->setImpacts(posCont); + + d->m_areaFire->setDrawOrder(10); + + bool success = d->m_areaFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + + success = d->m_areaFire->updateView(); + qWarning() << LOGFUNCTION << " success=" << success; + +} + + +void FunctionDialog::onDump() +{ + const QString &dump = d->m_mapImpl->toDumpString(); + std::cout << dump << std::endl; + + LtsCoordTrans::dumpConversionCounter(); + +// std::stringstream myLines; +// myLines.str(dump.toStdString()); + +// std::string line; +// { +// while(std::getline(myLines,line,'\n')){ +// qCritical() << QString::fromStdString(line); +// } +// } +} + +void FunctionDialog::onOverviewMap() +{ + OverviewMapHandler &omHdl = d->m_mapInterface->overviewMapHandler(); + + if (omHdl.builtInOverviewMapWidget()) + omHdl.removeBuiltInOverviewMap(); + else + omHdl.createBuiltInOverviewMap(); +} + +void FunctionDialog::onBackupOms() +{ + qDebug() << LOGFUNCTION << " START"; + s_operatorMapSettings() = d->m_mapInterface->operatorMapSettings(); + + // json + QFile saveFile(QStringLiteral("/tmp/oms.json")); + if (!saveFile.open(QIODevice::WriteOnly)) { + qWarning("Couldn't open oms file."); + return; + } + + saveFile.write(s_operatorMapSettings().toJson()); + qCritical() << "wrote " << QFileInfo(saveFile).absoluteFilePath(); + qCritical() << "oms.toJson()=" << s_operatorMapSettings().toJson(); +} + +void FunctionDialog::onRestoreOms() +{ + qDebug() << LOGFUNCTION << " START"; + +// bool success = d->m_mapInterface->restoreOperatorMapSettings(s_operatorMapSettings); +// qDebug() << LOGFUNCTION << " success=" << success; + + + // json + QFile loadFile(QStringLiteral("/tmp/oms.json")); + if (!loadFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open oms file."); + return; + } + + const QByteArray &ba = loadFile.readAll(); + SituationMap::OperatorMapSettings oms; + if (oms.fromJson(ba)) + qWarning("fromJson ok"); + else + qWarning("fromJson failed"); + + + bool success = d->m_mapInterface->restoreOperatorMapSettings(oms); + qDebug() << LOGFUNCTION << " success=" << success; + +} + +void FunctionDialog::onRemove() +{ + qDebug() << LOGFUNCTION << " START XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + + if (d->m_polygon) + { + qDebug() << LOGFUNCTION << " " << d->m_polygon->debugInfo(); + d->m_polygon->removeMySelf(); + d->m_polygon.reset(); + } + if (d->m_polyline) + { + qDebug() << LOGFUNCTION << " " << d->m_polyline->debugInfo(); + d->m_polyline->removeMySelf(); + d->m_polyline.reset(); + } + if (d->m_text) + { + qDebug() << LOGFUNCTION << " " << d->m_text->debugInfo(); + d->m_text->removeMySelf(); + d->m_text.reset(); + } +// if (d->m_mapObjectIndicator) +// { +// qDebug() << LOGFUNCTION << " " << d->m_mapObjectIndicator->debugInfo(); +// d->m_mapObjectIndicator->removeMySelf(); +// d->m_mapObjectIndicator.reset(); +// } + + + if (d->m_participant) + { + qDebug() << LOGFUNCTION << " " << d->m_participant->debugInfo(); + d->m_participant->removeMySelf(); + d->m_participant.reset(); + } + + if (d->m_building) + { + qDebug() << LOGFUNCTION << " " << d->m_building->debugInfo(); + d->m_building->removeMySelf(); + d->m_building.reset(); + } + + if (d->m_bAgent) + { + qDebug() << LOGFUNCTION << " " << d->m_bAgent->debugInfo(); + d->m_bAgent->removeMySelf(); + d->m_bAgent.reset(); + } + + if (d->m_cAgent) + { + qDebug() << LOGFUNCTION << " " << d->m_cAgent->debugInfo(); + d->m_cAgent->removeMySelf(); + d->m_cAgent.reset(); + } + + if (d->m_dangerArea) + { + qDebug() << LOGFUNCTION << " " << d->m_dangerArea->debugInfo(); + d->m_dangerArea->removeMySelf(); + d->m_dangerArea.reset(); + } + + if (d->m_functionalArea) + { + qDebug() << LOGFUNCTION << " " << d->m_functionalArea->debugInfo(); + d->m_functionalArea->removeMySelf(); + d->m_functionalArea.reset(); + } + + if (d->m_geoImage) + { + qDebug() << LOGFUNCTION << " " << d->m_geoImage->debugInfo(); + d->m_geoImage->removeMySelf(); + d->m_geoImage.reset(); + } + + if (d->m_areaFire) + { + qDebug() << LOGFUNCTION << " " << d->m_areaFire->debugInfo(); + d->m_areaFire->removeMySelf(); + d->m_areaFire.reset(); + } + + if (d->m_soundArea) + { + qDebug() << LOGFUNCTION << " " << d->m_soundArea->debugInfo(); + d->m_soundArea->removeMySelf(); + d->m_soundArea.reset(); + } + + if (d->m_hitLine) + { + qDebug() << LOGFUNCTION << " " << d->m_hitLine->debugInfo(); + d->m_hitLine->removeMySelf(); + d->m_hitLine.reset(); + } + + if (d->m_mine) + { + qDebug() << LOGFUNCTION << " " << d->m_mine->debugInfo(); + d->m_mine->removeMySelf(); + d->m_mine.reset(); + } + + if (d->m_mineField) + { + qDebug() << LOGFUNCTION << " " << d->m_mineField->debugInfo(); + d->m_mineField->removeMySelf(); + d->m_mineField.reset(); + } + + if (d->m_nuclearAgent) + { + qDebug() << LOGFUNCTION << " " << d->m_nuclearAgent->debugInfo(); + d->m_nuclearAgent->removeMySelf(); + d->m_nuclearAgent.reset(); + } + + if (d->m_singleFire) + { + qDebug() << LOGFUNCTION << " " << d->m_singleFire->debugInfo(); + d->m_singleFire->removeMySelf(); + d->m_singleFire.reset(); + } + + if (d->m_cube) + { + qDebug() << LOGFUNCTION << " " << d->m_cube->debugInfo(); + d->m_cube->removeMySelf(); + d->m_cube.reset(); + } + + qDebug() << LOGFUNCTION << " END XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; +} + +void FunctionDialog::onRemoveAll() +{ + qDebug() << LOGFUNCTION << " START XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + + onRemove(); + + qDebug() << LOGFUNCTION << " clear ObjectFactory"; + d->m_mapImpl->objectFactory().clearObjects(); + + qDebug() << LOGFUNCTION << " END XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; +} + +void FunctionDialog::onToggleGrid() +{ + bool gridOnOff = d->m_mapImpl->grid().isVisible(); + d->m_mapImpl->grid().setVisible(!gridOnOff); + + if (d->m_mapImpl->grid().isVisible()) + d->m_pbGrid->setText(QLatin1String("Grid is ON ")); + else + d->m_pbGrid->setText(QLatin1String("Grid is OFF")); +} + +void FunctionDialog::onToggleVisibility() +{ + d->m_visibility = !d->m_visibility; + qDebug() << LOGFUNCTION << " visibility -> " << d->m_visibility; + + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + if (d->m_visibility) + pb->setText(QLatin1String("Visibility is ON ")); + else + pb->setText(QLatin1String("Visibility is OFF")); + + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + mObj->setVisibility(d->m_visibility); + mObj->updateView(); + } + } + +} + +void FunctionDialog::onToggleVisibilityTechn() +{ + d->m_visibilityTechn = !d->m_visibilityTechn; + qDebug() << LOGFUNCTION << " m_visibilityTechn -> " << d->m_visibilityTechn; + + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + if (d->m_visibilityTechn) + pb->setText(QLatin1String("VisiTechn is ON ")); + else + pb->setText(QLatin1String("VisiTechn is OFF")); + + ObjectAppearance oa = SituationMap::ObjectAppearance::Technical; + bool localVisi = d->m_visibilityTechn; + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + mObj->setVisibility(oa, localVisi); + mObj->updateView(); + } + } + +} + +void FunctionDialog::onToggleVisibilityTact() +{ + d->m_visibilityTact = !d->m_visibilityTact; + qDebug() << LOGFUNCTION << " m_visibilityTact -> " << d->m_visibilityTact; + + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + if (d->m_visibilityTact) + pb->setText(QLatin1String("VisiTact is ON ")); + else + pb->setText(QLatin1String("VisiTact is OFF")); + + ObjectAppearance oa = SituationMap::ObjectAppearance::Tactical; + bool localVisi = d->m_visibilityTact; + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + mObj->setVisibility(oa, localVisi); + mObj->updateView(); + } + } + +} + +void FunctionDialog::onToggleSelection() +{ + d->m_selected = !d->m_selected; + qDebug() << LOGFUNCTION << " m_selected -> " << d->m_selected; + + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + if (d->m_selected) + pb->setText(QLatin1String("Selection is ON ")); + else + pb->setText(QLatin1String("Selection is OFF")); + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + mObj->setSelected(d->m_selected); + mObj->updateView(); + } + } + +} + +void FunctionDialog::onTogglePicking() +{ + d->m_pickable = !d->m_pickable; + qDebug() << LOGFUNCTION << " m_pickable -> " << d->m_pickable; + + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + if (d->m_pickable) + pb->setText(QLatin1String("Picking is ON ")); + else + pb->setText(QLatin1String("Picking is OFF")); + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + mObj->setPickable(d->m_pickable); + mObj->updateView(); + } + } + +} + +void FunctionDialog::onToggleBoundingBox() +{ + qDebug() << LOGFUNCTION << " m_objectsWithBoundingBox=" << d->m_objectsWithBoundingBox; + + QPushButton *pb = qobject_cast(sender()); + if (! pb) + return; + + if (d->m_objectsWithBoundingBox.isEmpty()) + pb->setText(QLatin1String("BoundingBox is ON ")); + else + pb->setText(QLatin1String("BoundingBox is OFF")); + + // create or destroy? + if (d->m_objectsWithBoundingBox.isEmpty()) + { + // create: + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + SituationMap::BoundingBox bbox; + mObj->boundingBox(bbox); + + if (!bbox.isValid()) + { + qWarning() << LOGFUNCTION << " bbox invalid, debugInfo=" << mObj->debugInfo() + << " bbox=" << bbox.toString(); + continue; + } + + qDebug() << LOGFUNCTION + << " debugInfo =" << mObj->debugInfo() + << " bbox =" << bbox.toString(); + + RefPointer moPoly + = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), + QStringLiteral("BoundingBoxPrimitiveOf(%1)").arg(mObj->debugInfo())); + + d->m_objectsWithBoundingBox.append(moPoly->uniqueMapId()); + + switch (bbox.extentType()) + { + case SituationMap::BoundingBox::ExtentType::Sim: + moPoly->setPositions(bbox.positions()); + break; + case SituationMap::BoundingBox::ExtentType::Pixel: + { + const QPointF pCenter = d->m_mapImpl->geoCoordinateToViewPortWindowPoint(bbox.center().toPointF()); // ToDo Check + SituationMap::BoundingBox pixelBox(SituationMap::BoundingBox::ExtentType::Pixel, + Point3D::fromQPointF(pCenter), bbox.width(), bbox.height()); + + const QList points({d->m_mapImpl->viewPortWindowPointToGeoCoordinate(pixelBox.topLeft().toPointF()), // ToDo Check + d->m_mapImpl->viewPortWindowPointToGeoCoordinate(pixelBox.topRight().toPointF()), // ToDo Check + d->m_mapImpl->viewPortWindowPointToGeoCoordinate(pixelBox.bottomRight().toPointF()), // ToDo Check + d->m_mapImpl->viewPortWindowPointToGeoCoordinate(pixelBox.bottomLeft().toPointF()) // ToDo Check + }); + SituationMap::PositionCont posContForCurrentScale = convert2D3D(points); + + moPoly->setPositions(posContForCurrentScale); + } + break; + case SituationMap::BoundingBox::ExtentType::Undefined: + { + qWarning() << LOGFUNCTION << " bbox has no ExtentType, bbox=" << bbox.toString(); + } + + } //switch + + moPoly->rLineStyle().setLineColor(Qt::magenta); + moPoly->updateView(); + } + } + + } + else + { + // destroy: + + qDebug() << LOGFUNCTION << " m_objectsWithBoundingBox=" << d->m_objectsWithBoundingBox; + for (UniqueMapId id : std::as_const(d->m_objectsWithBoundingBox)) + { + RefPointer moPoly + = d->m_mapInterface->objectFactory().get(id); + moPoly->removeMySelf(); + } + d->m_objectsWithBoundingBox.clear(); + } + + +} + +void FunctionDialog::onToggleBB2() +{ + SituationMap::PositionCont posCont; + for (RefPointer mObj : d->getObjList()) + { + if (!mObj) + continue; + qCritical() << LOGFUNCTION << " debugInfo" << mObj->debugInfo(); + + const QVector resultVec = MathUtils::GeometryEngine::buffer(mObj.get(), 100); + + + posCont.clear(); + posCont.reserve(resultVec.size()); + for (const Point3D &p : resultVec) + posCont.append(d->m_mapImpl->coordTranslationFunctor()->mapToGeographic(p)); + + RefPointer bb2Poly + = d->m_mapInterface->objectFactory(). + create(QLatin1String("AES"), + QStringLiteral("BB2PrimitiveOf(%1)").arg(mObj->debugInfo())); + + bb2Poly->setPositions(posCont); + + bb2Poly->rLineStyle().setLineColor(Qt::magenta); + bb2Poly->updateView(); + } +} + +void FunctionDialog::onMapRotationChanged() +{ + float mapRotationDegree = d->getMapRotation(); + + double currrentRotation = d->m_mapImpl->rotation(); + + qDebug() << LOGFUNCTION + << " change from " << currrentRotation + << " to new " << mapRotationDegree; + + d->m_mapImpl->setRotation(mapRotationDegree); +} + +void FunctionDialog::onSymbolSizeChanged() +{ + float newScale = d->getScale(); + qDebug() << LOGFUNCTION << " newScale " << newScale << " START"; + + QSlider *sl = qobject_cast(sender()); + if (! sl) + return; + + sl->setToolTip(QStringLiteral("%1%").arg(newScale * 100)); + + + d->m_mapInterface->setSymbolScale(newScale); + +// Q_FOREACH (RefPointer mObj, d->getObjList()) +// { +// if (mObj) +// { +// qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); +// mObj->setScale(newScale); +// mObj->updateView(); +// } +// } + + qDebug() << LOGFUNCTION << " newScale " << newScale << " END"; + +} + +void FunctionDialog::onSymbolOpacityChanged() +{ + float newOpacity = d->getSymbolOpacity(); + qDebug() << LOGFUNCTION << " newOpacity " << newOpacity << " START"; + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + + Participant *part = dynamic_cast(mObj.get()); + + if (part) + { + part->setUserSymbolOpacity(newOpacity); + part->updateView(); + } + } + } + +} + +void FunctionDialog::onDrawOrderChanged() +{ + int drawOrder = d->getDrawOrder(); + qDebug() << LOGFUNCTION << " newDrawOrder " << drawOrder << " START"; + + std::cerr << LOGFUNCTION << " drawOrder = " << drawOrder << std::endl; + + const auto objList = d->getObjList(); + for (RefPointer mObj : objList) + { + if (mObj) + { + qDebug() << LOGFUNCTION << " " << mObj->debugInfo(); + mObj->setDrawOrder(drawOrder); + mObj->updateView(); + } + } + + +} + + +void FunctionDialog::onMouseWindowPositionChanged(int x, int y) +{ + QString strWindow(QLatin1String("Screen: X = ")); + strWindow.append(QString::number(x)); + strWindow.append(QLatin1String(", Y = ")); + strWindow.append(QString::number(y)); + + setMapPositionString(strWindow); + +} /* FunctionDialog::onMouseWindowPositionChanged */ + + +void FunctionDialog::onMouseMapPositionChanged(const Point3D &mapPos, bool isValid) +{ + QString strMap(QLatin1String("\nMap Coord: X = ")); + + strMap.append(QString::number(mapPos.x(), 'f', -1)); + strMap.append(QLatin1String(", Y = ")); + strMap.append(QString::number(mapPos.y(), 'f', -1)); + strMap.append(QLatin1String(", z = ")); + strMap.append(QString::number(mapPos.z(), 'f', -1)); + if (isValid) + strMap.append(QLatin1String(" (valid)")); + else + strMap.append(QLatin1String(" (invalid)")); + + QString strLatLon(QLatin1String("\nLon(x) - Lat(y) Coord: ")); + +// Esri::ArcGISRuntime::Point normalPt = +// Esri::ArcGISRuntime::GeometryEngine::normalizeCentralMeridian(Esri::ArcGISRuntime::Point(mapPos.x(), mapPos.y(), d->m_mapImpl->getEsriSpatialReference())); + + const CoordTranslationFunctor *ctFunctor = d->m_mapImpl->coordTranslationFunctor(); + if (ctFunctor) + { + Point3D geoPos = ctFunctor->mapToGeographic(mapPos); + strLatLon.append(QString::number(geoPos.x(), 'f', 10)); + strLatLon.append(QLatin1String(" - ")); + strLatLon.append(QString::number(geoPos.y(), 'f', 10)); + strLatLon.append(QLatin1String(" - ")); + strLatLon.append(QString::number(geoPos.z(), 'f', -1)); + } + + QString strUTM(QLatin1String("\nUTM Mgrs Coord: ")); +// strUTM.append(EsriRuntimeQt::CoordinateConversion::PointToMgrs(normalPt +// , EsriRuntimeQt::MgrsConversionMode::Automatic +// , 5 +// , true +// , true)); + strUTM.append(d->m_mapImpl->mapToMgrsString(mapPos.toPointF())); + addMapPositionString(strMap); + addMapPositionString(strLatLon); + addMapPositionString(strUTM); + + +} /* FunctionDialog::onMouseMapPositionChanged */ + + +void FunctionDialog::onMouseGeoPositionChanged(const Point3D &geoPos, bool isValid) +{ + QString strSim(QLatin1String("\nGeo Coord: X = ")); + + strSim.append(QString::number(geoPos.x(), 'f', -1)); + strSim.append(QLatin1String(", Y = ")); + strSim.append(QString::number(geoPos.y(), 'f', -1)); + strSim.append(QLatin1String(", z = ")); + strSim.append(QString::number(geoPos.z(), 'f', -1)); + if (isValid) + strSim.append(QLatin1String(" (valid)")); + else + strSim.append(QLatin1String(" (invalid)")); + + addMapPositionString(strSim); + + onExtendChanged(); +} + +void FunctionDialog::onExtendChanged() +{ + double pixelSize = d->m_mapInterface->pixelSizeMeter(); + double scale = d->m_mapInterface->scale(); + setPixelSizeString(QStringLiteral("PixelSize=%1 - Scale=%2").arg(pixelSize).arg(scale)); +} + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.h new file mode 100755 index 000000000..f6de06288 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog.h @@ -0,0 +1,289 @@ +/** + * @RDE_HEADER@ + * FunctionDialog + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "function_dialog_export.h" +#include +#include +#include + + +// ESRI +#include +#include +#include + +// #define USE_LOCAL_SERVER + + +class QStandardItemModel; + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +namespace RB +{ +class RubberbandBase; +} + +class Map; + +// TODO: nur fuer Tests exportiert +class FUNCTIONDIALOG_EXPORT FunctionDialog : public QWidget +{ + Q_OBJECT + +public: + explicit FunctionDialog(Esri::ArcGISRuntime::Map *esriMap, + Esri::ArcGISRuntime::MapGraphicsView *esriView, + QWidget *parent); + explicit FunctionDialog(SituationMap::Map *map); + virtual ~FunctionDialog(); + + static void addButtonToMap(SituationMap::Map *map, bool isVisible = true); + + + void init(); + + void setGraphicsLayer(Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer); + + void setMapPositionString(const QString &mapPosition); + void setPixelSizeString(const QString &text); + void addMapPositionString(const QString &mapPosition); + + FunctionDialog(const FunctionDialog &other) = delete; // No copy + FunctionDialog &operator=(const FunctionDialog &other) = delete; // No copy + + + +protected: + void addSimpleLayerTab(); + void addLogicalLayerTab(); + void addLayerPerformanceTab(); + void addGraphicsTab(); + void addGraphics3DTab(); + void addMessageTab(); + void addMapObjectTab(); + void addRubberbandTab(); + void addRubberband3DTab(); + void addToolsTab(); + void addViewshedTab(); + void addOverlayPlanTab(); + void addIconRenderTab(); + void addPortalTab(); + void addMovePointLuxemburgTab(); + + + void getRotationAndAngle(const QPointF §Start, const QPointF §End, double &rotationDegOut, double &openAngleDegOut); + // void addYourTab(); + + + Esri::ArcGISRuntime::Graphic *createCube(const SituationMap::PositionCont &mapPosCont, + int baseHeight, + double objHeight, + const QString &partyNameValue = QString()); + + +public Q_SLOTS: + void onDestroyed(); + + void onLayerPerfCreateLayer(); + + + void onGenerateGraphics(); + void onGenerateCube(); + void onGenerateCubeArray(); + void onGenerateStackedCubes(); + void onGenerateGraphicsKristenStrandford(); + void onGenerateUniqueVaueRendererMountingPoints(); + + void onGenerate3DGraphicSphereRENDERER(); + void onSphereOpacityChangedRENDERER(int opacityValue); + + void onGenerate3DGraphicSphere(); + void onSphereOpacityChanged(int opacityValue); + void onSphereZValueChanged(int ZValue); + + void onGenerate3DGraphicCone(); + void onConeOpacityChanged(int opacityValue); + void onConeZValueChanged(int ZValue); + void onConeHeadingChanged(float angle); + void onConeRollChanged(float angle); + void onConePitchChanged(float angle); + + void on3dModel(); + void on3dModelZValueChanged(int ZValue); + + void onGenerate3DGraphicConeUniqueValueRenderer(); + + + + void onAddCivilSymbols(); + void onMessage(); + void onMessageSelect(); + void onAddSymbol(); + void onSomeParticipants(); + + void onRubberbandApply(RB::RubberbandBase *); + void onRubberbandAbort(RB::RubberbandBase *); + void onRubberband3DApply(RB::RubberbandBase *); + void onRubberband3DAbort(RB::RubberbandBase *); + void onRubberbandDefineArrow(); + void onRubberbandDefineEllipse(); + void onRubberbandBoobyTrapSector(); + void onRubberbandCheckCoordinates(); + void onRubberbandStateSelect(); + void onRubberbandTwoInOne(); + void onRectCheckCoordinates(); + + void onPolygonCreate(); + void onPolylineCreate(); + void onPolylineUpdate(); + void onTextCreate(); + void onTextUpdate(); + void onMapObjectIndicatorCreate(); + + + void onParticipantGetPosition(); + void onParticipantGetPositionApplied(RB::RubberbandBase *rubberband = nullptr); + bool createParticipant(const Point3D &origin, + const QString &debugInfo); + void onParticipantCreate(); + void onParticipantUpdate(); + + void onAreaFireCreate(); + void onAreaFireUpdate(); + + void onBuildingCreate(); + void onBuildingUpdate(); + + void onBiologicalAgentCreate(); + void onBiologicalAgentUpdate(); + + void onChemicalAgentCreate(); + + void onCubeCreate(); + + void onDangerAreaCreate(); + void onDangerAreaUpdate(); + + void onFunctionalAreaCreate(); + void onFunctionalAreaUpdate(); + + void onGeoImageCreate(); + void onMapObjectClicked(const SituationMap::Map::RefPointerMapObjectList &mapObjList, + const QPointF &viewportPosition); + + void onSoundAreaCreate(); + void onSoundAreaUpdate(); + + void onHitLineCreate(); + void onHitLineUpdate(); + + void onLineOfSightLineCreate(); + + + void onMineCreate(); + void onMineUpdate(); + + void onMineFieldCreate(); + void onMineFieldUpdate(); + + void onNuclearCreate(); + void onNuclearUpdate(); + + void onChargeEffectCreate(); + void onChargeEffectUpdate(); + + void onBoobyTrapCreate(); + void onBoobyTrapUpdate(); + + void onSingleFireCreate(); + void onSingleFireUpdate(); + + void onShowBasePoint(); + + void onShowPolizei(); + + void onObjectIndicator(); + + void onMeasureLength(); + + void onStartLocationDisplay(); + void onStopLocationDisplay(); + + void onLoadFile(); + + void onPortal(); + + void onMovePoint(); + +#ifdef USE_LOCAL_SERVER + void onStartLocalService(); + void onStopLocalService(); + void onStartViewshedSvc(); + void onStopViewshedSvc(); + + void onViewshedInteractive(); + void onViewshed(); +#endif // USE_LOCAL_SERVER + + void onSightFan(); + void onViewshedSightFan(); + + void onLoadPlan(); + void onOverlayVisiChanged(bool checked); + void onRemovePlan(); + void onRemoveAllPlans(); + + void onToPdf(); + + void onDump(); + + void onOverviewMap(); + + void onBackupOms(); + void onRestoreOms(); + + void onRemove(); + void onRemoveAll(); + void onToggleGrid(); + void onToggleVisibility(); + void onToggleVisibilityTechn(); + void onToggleVisibilityTact(); + void onToggleSelection(); + void onTogglePicking(); + void onToggleBoundingBox(); + void onToggleBB2(); + void onMapRotationChanged(); + void onSymbolSizeChanged(); + void onSymbolOpacityChanged(); + void onDrawOrderChanged(); + + + //void onBuildingPERFORMANCE(); + + void onMouseWindowPositionChanged(int x, int y); + void onMouseMapPositionChanged(const Point3D &mapPos, bool isValid); + void onMouseGeoPositionChanged(const Point3D &geoPos, bool isValid); + + void onExtendChanged(); + +private: + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog_export.h new file mode 100755 index 000000000..c4d4f2535 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/function_dialog_export.h @@ -0,0 +1,21 @@ +/** + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef FUNCTIONDIALOG_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define FUNCTIONDIALOG_EXPORT +# else +# if defined(MAKE_SITUATIONMAPFUNCTIONDIALOG_LIB) +# define FUNCTIONDIALOG_EXPORT Q_DECL_EXPORT +# else +# define FUNCTIONDIALOG_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.cpp new file mode 100755 index 000000000..29ecf4ce1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.cpp @@ -0,0 +1,378 @@ +/** + * @RDE_HEADER@ + * FunctionDialog + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "simple_esri_layer.h" + +#include + +#include +#include +#include +#include +#include + + +// for overview +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace SituationMap +{ + +Esri::ArcGISRuntime::Layer *getEsriBasemap_Layer(Esri::ArcGISRuntime::Map *map, + const QString &layername) +{ + if (!(map && + map->basemap() && + map->basemap()->baseLayers()) + ) + return nullptr; + + Esri::ArcGISRuntime::Layer *layerFound = nullptr; + + const Esri::ArcGISRuntime::LayerListModel *model = map->basemap()->baseLayers(); + for (int i = 0; i < model->size(); ++i) + { + const Esri::ArcGISRuntime::Layer *curLayer = model->at(i); + const QString &curName = curLayer->name(); + if (QString::compare(layername, curName) == 0) + { + layerFound = model->at(i); + break; + } + } // for + return layerFound; +} + + +Esri::ArcGISRuntime::ArcGISTiledLayer *getEsriBasemap_ArcGISTiledLayer(Esri::ArcGISRuntime::Map *map, + const QString &layername) +{ + Esri::ArcGISRuntime::Layer *layerFound = getEsriBasemap_Layer(map, layername); + if (! layerFound) + return nullptr; + + if (layerFound->layerType() == Esri::ArcGISRuntime::LayerType::ArcGISTiledLayer) + { + Esri::ArcGISRuntime::ArcGISTiledLayer *layer2 = + static_cast(layerFound); + return layer2; + } + return nullptr; +} + + + +class SimpleEsriLayer::Private +{ + +public: + Private() + { + } + + ~Private() + { + } + + // ESRI data: + Esri::ArcGISRuntime::Map *m_map = nullptr; + Esri::ArcGISRuntime::MapGraphicsView *m_mapView = nullptr; + + // OWN data: + QGridLayout *m_layout = nullptr; + + +}; // class SimpleEsriLayer::Private +//----------------------------------------------------------------------------- + + + +SimpleEsriLayer::SimpleEsriLayer(QWidget *parent, Esri::ArcGISRuntime::MapGraphicsView *mapView) + : QWidget(parent) + , d(new Private) +{ + d->m_mapView = mapView; + if (d->m_mapView) + d->m_map = d->m_mapView->map(); + + d->m_layout = new QGridLayout(this); + + updatePanel(); +} + +SimpleEsriLayer::~SimpleEsriLayer() +{ + delete d; +} + +bool SimpleEsriLayer::updatePanel() +{ + + for (int row = 0; row < d->m_layout->rowCount(); ++row) + { + for (int col = 0; col < d->m_layout->columnCount(); ++col) + { + QLayoutItem *item = d->m_layout->itemAtPosition(row, col); + if (item) + { + QWidget *w = item->widget(); + delete w; + } + } + } + + delete d->m_layout; + d->m_layout = new QGridLayout(this); + + + if (d->m_map) + { + // Header + int row = 0; + int col = 0; + for (const char *name : { "OnOff", "Opac", "Brightness", "Contrast", "Gamma" }) + d->m_layout->addWidget(new QLabel(QString::fromLatin1(name)), row, col++); + + + row = d->m_layout->rowCount(); + + // GraphicalOverlay + Esri::ArcGISRuntime::GraphicsOverlayListModel *graphicsOverlayListModel = d->m_mapView->graphicsOverlays(); + if (graphicsOverlayListModel) + { + int graphicsOverlaySize = graphicsOverlayListModel->size(); +// qDebug() << LOGFUNCTION << " graphicsOverlayListModel -> size=" << graphicsOverlaySize; + + row += graphicsOverlaySize; + + for (int i = 0; i < graphicsOverlaySize; ++i) + { + --row; + col = 0; +// qDebug() << LOGFUNCTION << " graphicsOverlayListModel handle layer i='" << i; + + Esri::ArcGISRuntime::GraphicsOverlay *gOverlay = graphicsOverlayListModel->at(i); + if (! gOverlay) + { + qWarning() << LOGFUNCTION << " gOverlay is nil"; + return false; + } + + const QString &layername = gOverlay->objectName(); + + QCheckBox *cbOnOff = new QCheckBox(layername); + cbOnOff->setChecked(gOverlay->isVisible()); + + connect(cbOnOff, &QCheckBox::stateChanged, this, &SimpleEsriLayer::layerOnOff); + d->m_layout->addWidget(cbOnOff, row, col++); + + SimpleEsriLayerSlider *sl_opac = new SimpleEsriLayerSlider(layername, 0, 100); + sl_opac->setValue(gOverlay->opacity() * 100); + connect(sl_opac, &SimpleEsriLayerSlider::valueChanged, this, &SimpleEsriLayer::opacity); + d->m_layout->addWidget(sl_opac, row, col++); + + } // for + } // if (graphicsOverlayListModel) + + + + // BasemapLayer + row = d->m_layout->rowCount(); + + Esri::ArcGISRuntime::LayerListModel *baseLayerModel = d->m_map->basemap()->baseLayers(); + if (baseLayerModel) + { + int baseLayerModelSize = baseLayerModel->size(); + qWarning() << LOGFUNCTION << " baseLayerModel -> size=" << baseLayerModelSize; + + row += baseLayerModelSize; + + for (int i = 0; i < baseLayerModelSize; ++i) + { + col = 0; + --row; + qWarning() << LOGFUNCTION << " baseLayerModel handle layer i=" << i; + Esri::ArcGISRuntime::Layer *layer = baseLayerModel->at(i); + if (! layer) + { + qWarning() << LOGFUNCTION << " layer is nil"; + return false; + } + + const QString &layername = layer->name(); + + QCheckBox *cbOnOff = new QCheckBox(layername); + cbOnOff->setChecked(layer->isVisible()); + + // QString tooltip = layer->url(); + // if (!l->serviceDescription().isEmpty()) + // tooltip += QLatin1String(" ; ") % l->serviceDescription(); + // if (!tooltip.isEmpty()) + // cbOnOff->setToolTip(tooltip); + + connect(cbOnOff, &QCheckBox::stateChanged, this, &SimpleEsriLayer::layerOnOff); + d->m_layout->addWidget(cbOnOff, row, col++); + + SimpleEsriLayerSlider *sl_opac = new SimpleEsriLayerSlider(layername, 0, 100); + sl_opac->setValue(layer->opacity() * 100); + connect(sl_opac, &SimpleEsriLayerSlider::valueChanged, this, &SimpleEsriLayer::opacity); + d->m_layout->addWidget(sl_opac, row, col++); + + + if (layer->layerType() == Esri::ArcGISRuntime::LayerType::ArcGISTiledLayer + // || layer->type() == EsriRuntimeQt::LayerType::ArcGISTiledMapService + ) + { + + Esri::ArcGISRuntime::ArcGISTiledLayer *realLayer = + dynamic_cast(layer); + if (realLayer) + { + float brightness = realLayer->brightness(); + float contrast = realLayer->contrast(); + float gamma = realLayer->gamma(); + + qWarning() << " brightness=" << brightness; // QuartzBeta1 -> not supported + qWarning() << " contrast=" << contrast; // QuartzBeta1 -> not supported + qWarning() << " gamma=" << gamma; // QuartzBeta1 -> not supported + + SimpleEsriLayerSlider *sl_brightness = new SimpleEsriLayerSlider(layername, -100, 100); + sl_brightness->setValue(brightness * 100); + connect(sl_brightness, &SimpleEsriLayerSlider::valueChanged, this, &SimpleEsriLayer::brightness); + d->m_layout->addWidget(sl_brightness, row, col++); + + SimpleEsriLayerSlider *sl_contrast = new SimpleEsriLayerSlider(layername, -100, 100); + sl_contrast->setValue(contrast * 100); + connect(sl_contrast, &SimpleEsriLayerSlider::valueChanged, this, &SimpleEsriLayer::contrast); + d->m_layout->addWidget(sl_contrast, row, col++); + + SimpleEsriLayerSlider *sl_gamma = new SimpleEsriLayerSlider(layername, -100, 100); + sl_gamma->setValue(gamma * 100); + connect(sl_gamma, &SimpleEsriLayerSlider::valueChanged, this, &SimpleEsriLayer::gamma); + d->m_layout->addWidget(sl_gamma, row, col++); + } // ir (realLayer) + + } // if (layer->type() == Es + } // for + } // if (baseLayerModel) + + + QPushButton *pbMaximizeExtent = new QPushButton(QLatin1String("maximize scale of 1st basemap")); + d->m_layout->addWidget(pbMaximizeExtent, 9999, 0); + connect(pbMaximizeExtent, &QPushButton::clicked, this, [this]() + { + if (d->m_map) + { + d->m_map->setMinScale(500000000); // 5e+08 + d->m_map->setMaxScale(100); + } + }); + + } + else + qWarning() << LOGFUNCTION << " no map !!! "; + + return true; +} + + +void SimpleEsriLayer::layerOnOff() +{ + QCheckBox *cb = qobject_cast(sender()); + if (!cb) + return; + + QString layername = cb->text(); + qDebug() << LOGFUNCTION << " layername=" << layername; + + Esri::ArcGISRuntime::Layer *l = getEsriBasemap_Layer(d->m_map, layername); + if (l) + l->setVisible(cb->isChecked()); + else + qDebug() << LOGFUNCTION << " no layer!!!, layername=" << layername; +} + +void SimpleEsriLayer::opacity(int) +{ + SimpleEsriLayerSlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + double value = sl->value() / 100.0; + + Esri::ArcGISRuntime::Layer *l = getEsriBasemap_Layer(d->m_map, layername); + if (l) + { + qDebug() << LOGFUNCTION << " layername=" << layername << " set to val=" << value; + l->setOpacity(value); + } + else + qWarning() << LOGFUNCTION << " no layer!!!, layername=" << layername; +} + +void SimpleEsriLayer::brightness(int) +{ + SimpleEsriLayerSlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + Esri::ArcGISRuntime::ArcGISTiledLayer *l = getEsriBasemap_ArcGISTiledLayer(d->m_map, layername); + + double value = sl->value() / 100.0; +// double value = sl->value(); + + qDebug() << LOGFUNCTION << " layername=" << layername << " set to val=" << value; + + l->setBrightness(value); +} + +void SimpleEsriLayer::contrast(int) +{ + SimpleEsriLayerSlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + Esri::ArcGISRuntime::ArcGISTiledLayer *l = getEsriBasemap_ArcGISTiledLayer(d->m_map, layername); + + double value = sl->value() / 100.0; +// double value = sl->value(); + + qDebug() << LOGFUNCTION << " layername=" << layername << " set to val=" << value; + + l->setContrast(value); +} + +void SimpleEsriLayer::gamma(int) +{ + SimpleEsriLayerSlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + Esri::ArcGISRuntime::ArcGISTiledLayer *l = getEsriBasemap_ArcGISTiledLayer(d->m_map, layername); + + double value = sl->value() / 100.0; +// double value = sl->value(); + + qDebug() << LOGFUNCTION << " layername=" << layername << " set to val=" << value; + + l->setGamma(value); +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.h new file mode 100755 index 000000000..82ec611cb --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/simple_esri_layer.h @@ -0,0 +1,79 @@ +/** + * @RDE_HEADER@ + * FunctionDialog + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include +#include + + +// ESRI +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class SimpleEsriLayerSlider : public QSlider +{ + Q_OBJECT + +public: + SimpleEsriLayerSlider(const QString &layername, int min, int max) + : QSlider(), m_layername(layername) + { + this->setOrientation(Qt::Horizontal); + setMinimum(min); + setMaximum(max); + } + +public: + QString m_layername; +}; + + +class SimpleEsriLayer : public QWidget +{ + Q_OBJECT + +public: + + SimpleEsriLayer(QWidget *parent, Esri::ArcGISRuntime::MapGraphicsView *mapView); + virtual ~SimpleEsriLayer(); + + SimpleEsriLayer(const SimpleEsriLayer &other) = delete; // No copy + SimpleEsriLayer &operator=(const SimpleEsriLayer &other) = delete; // No copy + + bool updatePanel(); +private slots: + void layerOnOff(); + void opacity(int); + void brightness(int); + void contrast(int); + void gamma(int); + +private : + + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; + + +} // namespace SituationMap + +//---------------------------------------------------------------------------- +// Q_DECLARE_METATYPE(SituationMap::GeometryType); + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_a.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_a.xpm new file mode 100755 index 000000000..f56427fa1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_a.xpm @@ -0,0 +1,156 @@ +/* XPM */ +static const char * stateIconA_xpm[] = { +"24 24 129 2", +" c None", +". c #4CE146", +"+ c #9E8E3A", +"@ c #4CE045", +"# c #BDAB46", +"$ c #A3912F", +"% c #4BDF44", +"& c #32942E", +"* c #B8C14A", +"= c #CABF35", +"- c #4ADE43", +"; c #F2DA55", +"> c #A7973C", +", c #CDC74E", +"' c #E1D132", +") c #347D25", +"! c #48DD42", +"~ c #E7CF4C", +"{ c #F7D519", +"] c #F8DB3E", +"^ c #9D8E38", +"/ c #E3CC50", +"( c #F6D629", +"_ c #56821F", +": c #37AA32", +"< c #47DB41", +"[ c #DAC54B", +"} c #F6D51F", +"| c #F5D210", +"1 c #F5D211", +"2 c #F5DA3F", +"3 c #819D38", +"4 c #E8D14F", +"5 c #F6D522", +"6 c #A48D0C", +"7 c #46DA40", +"8 c #C3B445", +"9 c #F4D425", +"0 c #F3D00E", +"a c #C9BF13", +"b c #F3D00F", +"c c #F2D840", +"d c #8FAA3D", +"e c #E7D54D", +"f c #F4D31B", +"g c #D4B50C", +"h c #45D93E", +"i c #AC9B3C", +"j c #F3D52C", +"k c #EBCC0D", +"l c #7D9516", +"m c #CCAE0A", +"n c #F1CE0C", +"o c #F1CE0E", +"p c #F4D738", +"q c #F1D015", +"r c #DFC70E", +"s c #43D73D", +"t c #8E7F31", +"u c #F0D434", +"v c #DBC50D", +"w c #588019", +"x c #D6B709", +"y c #EFCC0A", +"z c #EDCB0A", +"A c #5B8F1F", +"B c #42D63B", +"C c #ECD23C", +"D c #C9BA0D", +"E c #3A7F1F", +"F c #DEBD07", +"G c #EDCA08", +"H c #A39309", +"I c #2E972A", +"J c #40D43A", +"K c #E7CF41", +"L c #C2A60A", +"M c #27701E", +"N c #33A92E", +"O c #C1A405", +"P c #38BB32", +"Q c #DCC543", +"R c #9D8709", +"S c #37B931", +"T c #3FD338", +"U c #24711E", +"V c #C2B33F", +"W c #3DD137", +"X c #C8B54A", +"Y c #83A134", +"Z c #3D6517", +"` c #3CD036", +" . c #E4CA37", +".. c #EDD035", +"+. c #29731E", +"@. c #F6D726", +"#. c #F9D821", +"$. c #3ACF34", +"%. c #39CD33", +"&. c #8F8132", +"*. c #F6D414", +"=. c #F6D311", +"-. c #AD9B39", +";. c #38CC32", +">. c #C9B337", +",. c #D9BF33", +"'. c #37CB30", +"). c #E9CB29", +"!. c #F1CD0C", +"~. c #EFD022", +"{. c #36CA2F", +"]. c #EFCD12", +"^. c #EECB09", +"/. c #EECC0C", +"(. c #8C7E2F", +"_. c #35C92E", +":. c #B29F34", +"<. c #EBC806", +"[. c #C4AD31", +"}. c #D7BD29", +"|. c #E8C503", +"1. c #E4C722", +"2. c #E8C714", +"3. c #E7C402", +"4. c #E8C508", +"5. c #97872E", +"6. c #E5C201", +"7. c #E5C200", +"8. c #B8A330", +" ", +" . . . . . . . . + ", +" @ @ @ @ # $ ", +" % & * = ", +" - ; > , ' ) ", +" ! ~ { ] ^ / ( _ : ", +" < [ } | 1 2 3 4 5 6 < ", +" 7 8 9 0 a 0 b c d e f g 7 ", +" h i j k l m n o p q r h ", +" s t u v w x y y z A s ", +" B C D E F G H I B ", +" J K L M N O J ", +" P Q R S T ", +" U V W # X W W ", +" Y Z ` ... ` ` ", +" +. @.#. $. $. ", +" %. &.*.=.-. %. ", +" ;. >.0 0 ,. ;. ", +" '. ).!.!.~. '. ", +" {. ].^.^./.(. {. ", +" _. :.<.<.<.<.[. _. ", +" }.|.|.|.|.1. ", +" 2.3.3.3.3.4. ", +" 5.6.7.7.7.7.7.8. "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_b.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_b.xpm new file mode 100755 index 000000000..52c1f0db9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_b.xpm @@ -0,0 +1,216 @@ +/* XPM */ +static const char * stateIconB_xpm[] = { +"24 24 189 2", +" c None", +". c #4A8448", +"+ c #69BC66", +"@ c #75D272", +"# c #7ADA76", +"$ c #77D673", +"% c #6CC269", +"& c #508E4D", +"* c #61AE5E", +"= c #7EE57A", +"- c #73E76E", +"; c #64E55F", +"> c #5BE355", +", c #57E351", +"' c #59E353", +") c #62E55D", +"! c #71E76C", +"~ c #7EE779", +"{ c #68BB65", +"] c #7BE076", +"^ c #69E563", +"/ c #50E149", +"( c #46CE40", +"_ c #34982E", +": c #44C93E", +"< c #4EE047", +"[ c #65E45F", +"} c #7CE377", +"| c #4A8447", +"1 c #51934E", +"2 c #75E56F", +"3 c #51E04B", +"4 c #48D541", +"5 c #46D140", +"6 c #4FE048", +"7 c #71E56B", +"8 c #59A056", +"9 c #6EE368", +"0 c #4BDE44", +"a c #42C73C", +"b c #40BF3A", +"c c #6AE364", +"d c #4C894A", +"e c #6EDF69", +"f c #49DD43", +"g c #40C63B", +"h c #3FC13A", +"i c #6CE068", +"j c #66BD62", +"k c #4BDC45", +"l c #44D03E", +"m c #5EAB5B", +"n c #75D571", +"o c #42CD3D", +"p c #65C062", +"q c #5CDE57", +"r c #46DA40", +"s c #74D670", +"t c #64E05F", +"u c #52DC4C", +"v c #44CF3E", +"w c #5BDD56", +"x c #5AA956", +"y c #46D93F", +"z c #3EC438", +"A c #6DD168", +"B c #4FDB49", +"C c #3CBC36", +"D c #6DD268", +"E c #58A954", +"F c #57C352", +"G c #43D73D", +"H c #389234", +"I c #569F53", +"J c #50DA4A", +"K c #3ABA35", +"L c #3ABB35", +"M c #4FDA49", +"N c #5BA758", +"O c #58C553", +"P c #50D04B", +"Q c #42D63B", +"R c #5CCA57", +"S c #5BCE56", +"T c #50D049", +"U c #46D240", +"V c #40D43A", +"W c #4CD146", +"X c #3ECE38", +"Y c #3DCB38", +"Z c #4AD545", +"` c #41CF3A", +" . c #3FD338", +".. c #42CF3B", +"+. c #43D03C", +"@. c #45CF3E", +"#. c #40D139", +"$. c #41CE3A", +"%. c #3EC738", +"&. c #3DD137", +"*. c #3ABC35", +"=. c #41D23B", +"-. c #BDAB46", +";. c #C8B54A", +">. c #42D23C", +",. c #3ABF35", +"'. c #3BB136", +"). c #3CD036", +"!. c #53A74F", +"~. c #2F812B", +"{. c #64CF60", +"]. c #E4CA37", +"^. c #EDD035", +"/. c #66CF62", +"(. c #30852C", +"_. c #51A44D", +":. c #3DB637", +"<. c #338530", +"[. c #3ACF34", +"}. c #55CF51", +"|. c #33B72E", +"1. c #42CF3D", +"2. c #F6D726", +"3. c #F9D821", +"4. c #3A6E37", +"5. c #44D13E", +"6. c #35BD2F", +"7. c #55CC51", +"8. c #348930", +"9. c #38CA32", +"0. c #3FCF39", +"a. c #57A654", +"b. c #278723", +"c. c #8F8132", +"d. c #F6D414", +"e. c #F6D311", +"f. c #AD9B39", +"g. c #2EA72A", +"h. c #519A4E", +"i. c #42CF3C", +"j. c #2C9928", +"k. c #38CC32", +"l. c #56D351", +"m. c #C9B337", +"n. c #F3D00E", +"o. c #D9BF33", +"p. c #5AD256", +"q. c #2E9F2A", +"r. c #34C02D", +"s. c #38CB31", +"t. c #62D25D", +"u. c #E9CB29", +"v. c #F1CD0C", +"w. c #EFD022", +"x. c #64CD5F", +"y. c #3ACC33", +"z. c #34BF2D", +"A. c #35C62E", +"B. c #3BCB34", +"C. c #67D261", +"D. c #EFCD12", +"E. c #EECB09", +"F. c #EECC0C", +"G. c #8C7E2F", +"H. c #6ACF65", +"I. c #3FCC39", +"J. c #34C42D", +"K. c #39CA32", +"L. c #5BC656", +"M. c #B29F34", +"N. c #EBC806", +"O. c #C4AD31", +"P. c #63CD5E", +"Q. c #3ECB37", +"R. c #34C52D", +"S. c #30B82A", +"T. c #2B8327", +"U. c #D7BD29", +"V. c #E8C503", +"W. c #E4C722", +"X. c #2D9428", +"Y. c #2FB72A", +"Z. c #E8C714", +"`. c #E7C402", +" + c #E8C508", +".+ c #97872E", +"++ c #E5C201", +"@+ c #E5C200", +"#+ c #B8A330", +" . + @ # # $ % & ", +" * = - ; > , , ' ) ! ~ { ", +" ] ^ / ( _ : < [ } | ", +" 1 2 3 4 5 6 7 8 ", +" 9 0 a b 0 c d ", +" e f g h f i ", +" j k l m n = = n m o k p ", +" q r s t u v v u t s r w ", +"x y z A B C C B D z y E ", +"F G H I J K L M N H G O ", +"P Q R Q Q S Q T ", +"U V W X Y Z V U ", +"` . ..+. @.#. .$.", +"%.&. *.=. -.;. >.,. &.%.", +"'.).!. ~.).{. ].^. /.).(. _.).:.", +"<.[.}. |.1. 2.3. 4.5.6. 7.[.8.", +" 9.0.a. b. c.d.e.f. g. h.i.9. ", +" j.k.l. m.n.n.o. p.k.q. ", +" r.s.t. u.v.v.w. x.y.z. ", +" A.B.C. D.E.E.F.G. H.I.A. ", +" J.K.L. M.N.N.N.N.O. P.Q.R. ", +" S.T. U.V.V.V.V.W. X.Y. ", +" Z.`.`.`.`. + ", +" .+++@+@+@+@+@+#+ "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_c.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_c.xpm new file mode 100755 index 000000000..edbc31ba5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/state_icon_c.xpm @@ -0,0 +1,110 @@ +/* XPM */ +static const char * stateIconC_xpm[] = { +"24 24 83 1", +" c None", +". c #7FBD7C", +"+ c #8BCF88", +"@ c #84C582", +"# c #588456", +"$ c #63BE5F", +"% c #57B953", +"& c #73BF70", +"* c #56B752", +"= c #499B45", +"- c #699F66", +"; c #59B755", +"> c #54B550", +", c #52B14E", +"' c #6ABC67", +") c #52B34E", +"! c #377334", +"~ c #6FAF6C", +"{ c #50B24C", +"] c #4FB14B", +"^ c #48A144", +"/ c #4C734A", +"( c #5DB559", +"_ c #4DAE49", +": c #4CAD48", +"< c #6BB569", +"[ c #4AAC46", +"} c #377F35", +"| c #5E915C", +"1 c #50AD4C", +"2 c #48A944", +"3 c #44A040", +"4 c #61B05E", +"5 c #45A641", +"6 c #527F50", +"7 c #7BBF78", +"8 c #78BA75", +"9 c #639C60", +"0 c #46A542", +"a c #42A33E", +"b c #368633", +"c c #389035", +"d c #3FA13B", +"e c #43A33F", +"f c #619B5E", +"g c #56AC53", +"h c #3D9D39", +"i c #3B9C37", +"j c #3C9E38", +"k c #54A951", +"l c #64A761", +"m c #3D9E39", +"n c #2A6B26", +"o c #3A9B35", +"p c #3B9B36", +"q c #62A55F", +"r c #4B7749", +"s c #48A243", +"t c #33892F", +"u c #328B2F", +"v c #379833", +"w c #459F41", +"x c #4F7E4D", +"y c #5CA659", +"z c #369632", +"A c #349530", +"B c #349630", +"C c #59A955", +"D c #3A9936", +"E c #276C25", +"F c #256C22", +"G c #32932E", +"H c #2F892B", +"I c #2C8728", +"J c #2F912B", +"K c #2F902B", +"L c #2D8E29", +"M c #2D8F29", +"N c #257322", +"O c #226E1F", +"P c #2B8D27", +"Q c #298826", +"R c #288424", +" ", +" ", +" .+++@ ", +" #$%%% ", +" &***= ", +" -;>>, ", +" ')))! ", +" ~{]]^ ", +" /(__: ", +" <[[[} ", +" |1223 ", +" 4555 ", +" 67778 90aab ", +" cddef gddh ", +" ijjk lmjj ", +" noopq rsoot ", +" uvvwxyvvz ", +" ABBCDBBE ", +" FGGGGGH ", +" IJJJK ", +" LMMN ", +" OPQ ", +" R ", +" "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/symbole2.csv b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/symbole2.csv new file mode 100755 index 000000000..8f85c38d7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/FunctionDialog/symbole2.csv @@ -0,0 +1,445 @@ +"id";"name";"sym_klassifiz_id";"name";"symbol_id_code";"id";"name";"def_symbol_id" +"611";"ADMINISTRATIVE FINANCE";"331";"FINANCE";"S*G*USAF--*****";"(null)";"(null)";"(null)" +"609";"ADMINISTRATIVE JUDGE ADVOCATE GENERAL (JAG)";"325";"JUDGEADVOCATEGENERAL(JAG)";"S*G*USAJ--*****";"(null)";"(null)";"(null)" +"606";"ADMINISTRATIVE LABOUR";"355";"LABOUR";"S*G*USAL--*****";"(null)";"(null)";"(null)" +"607";"ADMINISTRATIVE MORAL, WELFARE, RECREATION (MWR)";"358";"MORAL,WELFARE,RECREATION(MWR)";"S*G*USAW--*****";"(null)";"(null)";"(null)" +"613";"ADMINISTRATIVE MORTUARY/GRAVE REGISTRY";"337";"MORTUARY/GRAVESREGISTRY";"S*G*USAM--*****";"(null)";"(null)";"(null)" +"612";"ADMINISTRATIVE PERSONNEL SERVICES";"334";"PERSONNELSERVICES";"S*G*USAS--*****";"(null)";"(null)";"(null)" +"610";"ADMINISTRATIVE POSTAL";"328";"POSTAL";"S*G*USAO--*****";"(null)";"(null)";"(null)" +"615";"ADMINISTRATIVE PUPLIC AFFAIRS";"343";"PUBLICAFFAIRS";"S*G*USAP--*****";"(null)";"(null)";"(null)" +"608";"ADMINISTRATIVE QUARTERMASTER (";"361";"QUARTERMASTER(SUPPLY)";"S*G*USAQ--*****";"(null)";"(null)";"(null)" +"614";"ADMINISTRATIVE RELIGIOUS/CHAPLAIN";"340";"RELIGIOUS/CHAPLAIN";"S*G*USAR--*****";"(null)";"(null)";"(null)" +"605";"ADMINISTRATIVE REPLACEMENT HOLDING UNIT (RHV)";"352";"REPLACEMENTHOLDINGUNIT(RHU)";"S*G*USAX--*****";"(null)";"(null)";"(null)" +"364";"AIR DEFENCE";"79";"AIRDEFENCE";"S*G*UCD---*****";"(null)";"(null)";"(null)" +"664";"AIR DEFENCE (AD) MISSILE LAUNC";"471";"AIRDEFENCE(AD)MISSILELAUNCH";"S*G*EWMA--*****";"(null)";"(null)";"(null)" +"379";"AIR DEFENCE COMPOSITE";"90";"COMPOSITE";"S*G*UCDC--*****";"(null)";"(null)";"(null)" +"367";"Air Defence COY";"79";"AIRDEFENCE";"S*G*UCD---*****";"(null)";"(null)";"(null)" +"683";"AIR DEFENCE GUN";"527";"AIRDEFENCEGUN";"S*G*EWA---*****";"(null)";"(null)";"(null)" +"686";"AIR DEFENCE GUN HEAVY";"530";"AIRDEFENCEGUNHEAVY";"S*G*EWAH--*****";"(null)";"(null)";"(null)" +"684";"AIR DEFENCE GUN LIGHT";"528";"AIRDEFENCEGUNLIGHT";"S*G*EWAL--*****";"(null)";"(null)";"(null)" +"685";"AIR DEFENCE GUN MEDIUM";"529";"AIRDEFENCEGUNMEDIUM";"S*G*EWAM--*****";"44";"Soldier Air Defence";"685" +"378";"AIR DEFENCE GUN UNIT";"89";"GUNUNIT";"S*G*UCDG--*****";"(null)";"(null)";"(null)" +"377";"AIR DEFENCE H/MAD";"88";"H/MAD";"S*G*UCDH--*****";"(null)";"(null)";"(null)" +"372";"AIR DEFENCE MISSILE";"83";"AIRDEFENCEMISSILE";"S*G*UCDM--*****";"(null)";"(null)";"(null)" +"376";"AIR DEFENCE MISSILE HEAVY";"87";"AIRDEFENCEMISSILEHEAVY";"S*G*UCDMH-*****";"(null)";"(null)";"(null)" +"373";"AIR DEFENCE MISSILE LIGHT";"84";"AIRDEFENCEMISSILELIGHT";"S*G*UCDML-*****";"229";"Legatus Wiesel";"373" +"375";"AIR DEFENCE MISSILE MEDIUM";"86";"AIRDEFENCEMISSILEMEDIUM";"S*G*UCDMM-*****";"(null)";"(null)";"(null)" +"374";"AIR DEFENCE MISSILE MOTORIZED";"85";"AIRDEFENCEMISSILEMOTORIZED";"S*G*UCDMLA*****";"(null)";"(null)";"(null)" +"369";"AIR DEFENCE SHORT RANGE";"80";"SHORTRANGE";"S*G*UCDS--*****";"(null)";"(null)";"(null)" +"371";"AIR DEFENCE SHORT RANGE GUN";"82";"GUN";"S*G*UCDS--*****";"(null)";"(null)";"(null)" +"370";"AIR DEFENCE SHORT RANGE MISSILE";"81";"MISSILE";"S*G*UCDS--*****";"(null)";"(null)";"(null)" +"380";"AIR DEFENCE TARGETING UNIT";"91";"TARGETINGUNIT";"S*G*UCDT--*****";"(null)";"(null)";"(null)" +"381";"AIR DEFENCE THEATRE MISSILE DEFENCE UNIT";"92";"THEATREMISSILEDEFENCEUNIT";"S*G*UCDO--*****";"(null)";"(null)";"(null)" +"781";"AIRPORT/AIRBASE";"605";"AIRPORT/AIRBASE";"S*G*IBA---H****";"(null)";"(null)";"(null)" +"342";"AIR TRACK";"6";"AIRTRACK";"S*A*------*****";"(null)";"(null)";"(null)" +"777";"AMMUNITION AND EXPLOSIVES PRODUCTION";"595";"AMMUNITIONANDEXPLOSIVESPRODUCTION";"S*G*IME---H****";"(null)";"(null)";"(null)" +"788";"AMPHIBIOUS WARFARE SHIP";"620";"AMPHIBIOUSWARFARESHIP";"S*S*CA----*****";"(null)";"(null)";"(null)" +"414";"ANTI ARMOUR";"110";"ANTIARMOUR";"S*G*UCAA--*****";"(null)";"(null)";"(null)" +"422";"ANTI ARMOUR AIR ASSAULT";"114";"ANTIARMOURAIRASSAULT";"S*G*UCAAS-*****";"(null)";"(null)";"(null)" +"421";"ANTI ARMOUR AIRBORNE";"113";"ANTIARMOURAIRBORNE";"S*G*UCAAM-*****";"(null)";"(null)";"(null)" +"424";"ANTI ARMOUR ARMOURED";"117";"ANTIARMOURARMOURED";"S*G*UCAAA-*****";"(null)";"(null)";"(null)" +"427";"ANTI ARMOUR ARMOURED AIR ASSAU";"120";"ANTIARMOURARMOUREDAIRASSAULT";"S*G*UCAAAS*****";"(null)";"(null)";"(null)" +"425";"ANTI ARMOUR ARMOURED TRACKED";"118";"ANTIARMOURARMOUREDTRACKED";"S*G*UCAAAT*****";"(null)";"(null)";"(null)" +"426";"ANTI ARMOUR ARMOURED WHEELED";"119";"ANTIARMOURARMOUREDWHEELED";"S*G*UCAAAW*****";"(null)";"(null)";"(null)" +"419";"ANTI ARMOUR DISMOUNTED";"111";"ANTIARMOURDISMOUNTED";"S*G*UCAAD-*****";"(null)";"(null)";"(null)" +"420";"ANTI ARMOUR LIGHT";"112";"ANTIARMOURLIGHT";"S*G*UCAAL-*****";"(null)";"(null)";"(null)" +"428";"ANTI ARMOUR MOTORIZED";"121";"ANTIARMOURMOTORIZED";"S*G*UCAAO-*****";"(null)";"(null)";"(null)" +"429";"ANTI ARMOUR MOTORIZED AIR ASSA";"122";"ANTIARMOURMOTORIZEDAIRASSAULT";"S*G*UCAAOS*****";"(null)";"(null)";"(null)" +"423";"ANTI ARMOUR MOUNTAIN";"115";"ANTIARMOURMOUNTAIN";"S*G*UCAAU-*****";"(null)";"(null)";"(null)" +"718";"ANTITANK GUN";"515";"ANTITANKGUN";"S*G*EWG---*****";"130";"Soldier AT (RPG7)";"718" +"722";"ANTITANK GUN HEAVY";"519";"ANTITANKGUNHEAVY";"S*G*EWGH--*****";"(null)";"(null)";"(null)" +"720";"ANTITANK GUN LIGHT";"517";"ANTITANKGUNLIGHT";"S*G*EWGL--*****";"(null)";"(null)";"(null)" +"721";"ANTITANK GUN MEDIUM";"518";"ANTITANKGUNMEDIUM";"S*G*EWGM--*****";"(null)";"(null)";"(null)" +"719";"ANTITANK GUN RECOILLESS";"516";"ANTITANKGUNRECOILLESS";"S*G*EWGR--*****";"(null)";"(null)";"(null)" +"695";"ANTITANK ROCKET LAUNCHER";"492";"ANTITANKROCKETLAUNCHER";"S*G*EWT---*****";"(null)";"(null)";"(null)" +"698";"ANTITANK ROCKET LAUNCHER HEAVY";"495";"ANTITANKROCKETLAUNCHERHEAVY";"S*G*EWTH--*****";"(null)";"(null)";"(null)" +"696";"ANTITANK ROCKET LAUNCHER LIGHT";"493";"ANTITANKROCKETLAUNCHERLIGHT";"S*G*EWTL--*****";"(null)";"(null)";"(null)" +"697";"ANTITANK ROCKET LAUNCHER MEDIU";"494";"ANTITANKROCKETLAUNCHERMEDIUM";"S*G*EWTM--*****";"(null)";"(null)";"(null)" +"392";"ARMOUR";"93";"ARMOUR";"S*G*UCA---*****";"(null)";"(null)";"(null)" +"752";"ARMOURED CARRIER WITH SCATTERA";"561";"ARMOUREDCARRIERWITHSCATTERABLEMINES";"S*G*EVEMSM*****";"(null)";"(null)";"(null)" +"734";"ARMOURED INFANTRY";"542";"ARMOUREDINFANTRY";"S*G*EVAI--*****";"(null)";"(null)";"(null)" +"732";"ARMOURED PERSONNEL CARRIER";"540";"ARMOUREDPERSONNELCARRIER";"S*G*EVAA--*****";"(null)";"(null)";"(null)" +"733";"ARMOURED PERSONNEL CARRIER REC";"541";"ARMOUREDPERSONNELCARRIERRECOVERY";"S*G*EVAAR-*****";"(null)";"(null)";"(null)" +"724";"ARMOURED VEHICLE";"532";"ARMOUREDVEHICLE";"S*G*EVA---*****";"231";"Marder";"724" +"750";"ARMOURED VEHICLE MOUNTED";"558";"ARMOUREDVEHICLEMOUNTED";"S*G*EVEMA-*****";"(null)";"(null)";"(null)" +"397";"ARMOUR TRACK";"94";"ARMOURTRACK";"S*G*UCAT--*****";"(null)";"(null)";"(null)" +"398";"ARMOUR TRACK AIRBORNE";"95";"ARMOURTRACKAIRBORNE";"S*G*UCATA-*****";"(null)";"(null)";"(null)" +"399";"ARMOUR TRACK AMPHIBIOUS";"96";"ARMOURTRACKAMPHIBIOUS";"S*G*UCATW-*****";"(null)";"(null)";"(null)" +"400";"ARMOUR TRACK AMPHIBIOUS RECOVE";"97";"ARMOURTRACKAMPHIBIOUSRECOVERY";"S*G*UCATWR*****";"(null)";"(null)";"(null)" +"403";"ARMOUR TRACK HEAVY";"100";"ARMOURTRACK,HEAVY";"S*G*UCATH-*****";"(null)";"(null)";"(null)" +"401";"ARMOUR TRACK LIGHT";"98";"ARMOURTRACK,LIGHT";"S*G*UCATL-*****";"(null)";"(null)";"(null)" +"402";"ARMOUR TRACK MEDIUM";"99";"ARMOURTRACK,MEDIUM";"S*G*UCATM-*****";"(null)";"(null)";"(null)" +"404";"ARMOUR TRACK RECOVERY";"101";"ARMOURTRACK,RECOVERY";"S*G*UCATR-*****";"(null)";"(null)";"(null)" +"405";"ARMOUR WHEELED";"102";"ARMOUR,WHEELED";"S*G*UCAW--*****";"(null)";"(null)";"(null)" +"406";"ARMOUR WHEELED AIR ASSAULT";"103";"ARMOUR,WHEELEDAIRASSAULT";"S*G*UCAWS-*****";"(null)";"(null)";"(null)" +"407";"ARMOUR WHEELED AIRBORNE";"104";"ARMOUR,WHEELEDAIRBORNE";"S*G*UCAWA-*****";"227";"BMP3 IFV";"407" +"408";"ARMOUR WHEELED AMPHIBIOUS";"105";"ARMOUR,WHEELEDAMPHIBIOUS";"S*G*UCAWA-*****";"(null)";"(null)";"(null)" +"409";"ARMOUR WHEELED AMPHIBIOUS RECO";"1325";"ARMOURWHEELEDAMPHIBIOUSRECOVERY";"S*G*UCAWWR*****";"(null)";"(null)";"(null)" +"412";"ARMOUR WHEELED HEAVY";"108";"ARMOUR,WHEELEDHEAVY";"S*G*UCAWH-*****";"(null)";"(null)";"(null)" +"410";"ARMOUR WHEELED LIGHT";"106";"ARMOUR,WHEELEDLIGHT";"S*G*UCAWL-*****";"(null)";"(null)";"(null)" +"411";"ARMOUR WHEELED MEDIUM";"107";"ARMOUR,WHEELEDMEDIUM";"S*G*UCAWM-*****";"(null)";"(null)";"(null)" +"413";"ARMOUR WHEELED RECOVERY";"109";"ARMOUR,WHEELEDRECOVERY";"S*G*UCAWR-*****";"10";"Armored Recovery Vehicle";"413" +"789";"ASSAULT VESSEL";"621";"ASSAULTVESSEL";"S*S*CALA--*****";"(null)";"(null)";"(null)" +"776";"ATOMIC ENERGY PRODUCTION";"593";"ATOMICENERGYPRODUCTION";"S*G*IMF---H****";"(null)";"(null)";"(null)" +"430";"AVIATION";"123";"AVIATION";"S*G*UCV---*****";"(null)";"(null)";"(null)" +"431";"AVIATION FIXED WING";"124";"FIXEDWING";"S*G*UCVF--*****";"(null)";"(null)";"(null)" +"433";"AVIATION FIXED WING ATTACK";"126";"ATTACKFIXEDWING";"S*G*UCVFA-*****";"(null)";"(null)";"(null)" +"436";"AVIATION FIXED WING RECON";"129";"RECONFIXEDWING";"S*G*UCVFR-*****";"(null)";"(null)";"(null)" +"432";"AVIATION FIXED WING UTILITY";"125";"UTILITYFIXEDWING";"S*G*UCVFU-*****";"(null)";"(null)";"(null)" +"435";"AVIATION FORWARD AIR CONTROLLER (FAC)";"128";"FORWARDAIRCONTROLLER(FAC)";"S*G*UCVUFC*****";"(null)";"(null)";"(null)" +"437";"AVIATION ROTARY WING";"130";"ROTARYWING";"S*G*UCVR--*****";"(null)";"(null)";"(null)" +"440";"AVIATION ROTARY WING C2";"138";"C2ROTARYWING";"S*G*UCVRUC*****";"(null)";"(null)";"(null)" +"441";"AVIATION ROTARY WING MEDEVAC";"139";"MEDEVACROTARYWING";"S*G*UCVRUE*****";"(null)";"(null)";"(null)" +"439";"AVIATION ROTARY WING UTILITY";"134";"UTILITYROTARYWING";"S*G*UCVRU-*****";"(null)";"(null)";"(null)" +"434";"AVIATION TACTICAL AIR CONTROL";"127";"TACTICALAIRCONTROLPARTY(TACP)";"S*G*UCVUTP*****";"(null)";"(null)";"(null)" +"445";"AVIATION UAV CONTROL";"1326";"AVIATIONUAVCONTROL";"S*G*UCVU--*****";"(null)";"(null)";"(null)" +"446";"AVIATION UAV LAUNCHER";"1327";"AVIATIONUAVLAUNCHER";"S*G*UCVU--*****";"(null)";"(null)";"(null)" +"447";"AVIATION UAV RECOVERY";"1337";"AVIATIONUAVRECOVERY";"S*G*UCVU--*****";"(null)";"(null)";"(null)" +"444";"AVIATION UNMANNED AERIAL VEHIC";"145";"UNMANNEDAERIALVEHICLEFIXEDWING";"S*G*UCVUF-*****";"(null)";"(null)";"(null)" +"448";"AVIATION UNMANNED AERIAL VEHICLE ROTARY WING";"146";"UNMANNEDAERIALVEHICLEROTARYWING";"S*G*UCVUR-*****";"(null)";"(null)";"(null)" +"443";"AVIATION UNMANNED AVIATION VEH";"144";"UNMANNEDAERIALVEHICLE";"S*G*UCVU--*****";"(null)";"(null)";"(null)" +"442";"AVIATION VERTICAL/SHORT TAKEOF";"143";"VERTICAL/SHORTTAKEOFFANDLANDING(V/STOL)";"S*G*UCVV--*****";"(null)";"(null)";"(null)" +"769";"BIOLOGICAL";"581";"BIOLOGICAL";"S*G*IRNB--H****";"(null)";"(null)";"(null)" +"745";"BRIDGE";"553";"BRIDGE";"S*G*EVEB--*****";"(null)";"(null)";"(null)" +"778";"BRIDGE";"599";"BRIDGE";"S*G*IMNB--H****";"(null)";"(null)";"(null)" +"739";"BUS";"547";"BUS";"S*G*EVUB--*****";"(null)";"(null)";"(null)" +"735";"C2V/ACV";"543";"C2V/ACV";"S*G*EVAC--*****";"(null)";"(null)";"(null)" +"548";"CBRN";"256";"COMBATSUPPORTCBRN";"S*G*UUA---*****";"(null)";"(null)";"(null)" +"768";"CBRN";"580";"CBRN";"S*G*IRN---H****";"(null)";"(null)";"(null)" +"560";"CBRN BIOLOGICAL";"268";"BIOLOGICAL";"S*G*UUAB--*****";"(null)";"(null)";"(null)" +"561";"CBRN BIOLOGICAL RECON EQUIPPED";"269";"RECONEQUIPPED";"S*G*UUABR-*****";"(null)";"(null)";"(null)" +"549";"CBRN CHEMICAL";"257";"CHEMICAL";"S*G*UUAC--*****";"(null)";"(null)";"(null)" +"556";"CBRN CHEMICAL RECON";"264";"CHEMICALRECON";"S*G*UUACR-*****";"(null)";"(null)";"(null)" +"553";"CBRN CHEMICAL SMOKE";"261";"SMOKE";"S*G*UUACS-*****";"(null)";"(null)";"(null)" +"555";"CBRN CHEMICAL SMOKE ARMOUR";"263";"ARMOURSMOKE";"S*G*UUACSA*****";"(null)";"(null)";"(null)" +"550";"CBRN CHEMICAL SMOKE/DECON";"258";"SMOKE/DECON";"S*G*UUACC-*****";"(null)";"(null)";"(null)" +"551";"CBRN CHEMICAL SMOKE/DECON MECHANIZED";"259";"MECHANIZEDSMOKE/DECON";"S*G*UUACCK*****";"(null)";"(null)";"(null)" +"552";"CBRN CHEMICAL SMOKE/DECON MOTO";"260";"MOTORIZEDSMOKE/DECON";"S*G*UUACCM*****";"(null)";"(null)";"(null)" +"554";"CBRN CHEMICAL SMOKE MOTORIZED";"262";"MOTORIZEDSMOKE";"S*G*UUACSM*****";"(null)";"(null)";"(null)" +"557";"CBRN CHEMICAL WHEELED ARMOURED VEHICLE";"265";"CHEMICALWHEELEDARMOUREDVEHICLE";"S*G*UUACRW*****";"(null)";"(null)";"(null)" +"558";"CBRN CHEMICAL WHEELED ARMOURED VEHICLE RECONNAISAN";"266";"CHEMICALWHEELEDARMOUREDVEHICLERECONNAISSANCESURVEI";"S*G*UUACRS*****";"(null)";"(null)";"(null)" +"562";"CBRN DECON";"270";"DECONTAMINATION";"S*G*UUAD--*****";"(null)";"(null)";"(null)" +"564";"CBRN DECON EQUIPMENT";"1330";"CBRNDECONEQUIPMENT";"S*G*UUADE-*****";"(null)";"(null)";"(null)" +"565";"CBRN DECON EQUIPMENT/TROOP";"1331";"CBRNDECONEQUIPMENT/TROOP";"S*G*UUADET*****";"(null)";"(null)";"(null)" +"563";"CBRN DECON TROOP";"1329";"CBRNDECONTROOP";"S*G*UUADT-*****";"(null)";"(null)";"(null)" +"760";"CBRN EQUIPMENT";"571";"CBRNEQUIPMENT";"S*G*EXN---*****";"(null)";"(null)";"(null)" +"566";"CBRN LABORATORY";"1332";"CBRNLABORATORY";"S*G*UUAL--*****";"(null)";"(null)";"(null)" +"559";"CBRN NUCLEAR";"267";"NUCLEAR";"S*G*UUAN--*****";"(null)";"(null)";"(null)" +"770";"CHEMICAL";"582";"CHEMICAL";"S*G*IRNC--H****";"(null)";"(null)";"(null)" +"356";"CIVIL FIXED WING";"73";"FIXEDWING";"S*A*CF----*****";"(null)";"(null)";"(null)" +"755";"CIVILIAN VEHICLE";"565";"CIVILIANVEHICLE";"S*G*EVC---*****";"(null)";"(null)";"(null)" +"357";"CIVIL ROTARY WING";"74";"ROTARYWING";"S*A*CH----*****";"(null)";"(null)";"(null)" +"604";"COMBAT SERVICE SUPPORT";"321";"COMBATSERVICESUPPORT";"S*G*US----*****";"(null)";"(null)";"(null)" +"736";"COMBAT SERVICE SUPPORT VEHICLE";"544";"COMBATSERVICESUPPORTVEHICLE";"S*G*EVAS--*****";"(null)";"(null)";"(null)" +"547";"COMBAT SUPPORT";"255";"COMBATSUPPORT";"S*G*UU----*****";"(null)";"(null)";"(null)" +"748";"CONSTRUCTION VEHICLE";"556";"CONSTRUCTIONVEHICLE";"S*G*EVEC--*****";"(null)";"(null)";"(null)" +"742";"CROSS-COUNTRY TRUCK";"550";"CROSS-COUNTRYTRUCK";"S*G*EVUX--*****";"212";"TATRA Tanker Fuel";"742" +"742";"CROSS-COUNTRY TRUCK";"550";"CROSS-COUNTRYTRUCK";"S*G*EVUX--*****";"50";"Truck 2t off-road";"742" +"772";"DECON";"585";"DECON";"S*G*IPD---H****";"(null)";"(null)";"(null)" +"676";"DIRECT FIRE GUN";"520";"DIRECTFIREGUN";"S*G*EWD---*****";"(null)";"(null)";"(null)" +"681";"DIRECT FIRE GUN HEAVY";"525";"DIRECTFIREGUNHEAVY";"S*G*EWDH--*****";"(null)";"(null)";"(null)" +"682";"DIRECT FIRE GUN HEAVY SELF-PRO";"526";"DIRECTFIREGUNHEAVYSELF-PROPELLED";"S*G*EWDHS-*****";"(null)";"(null)";"(null)" +"677";"DIRECT FIRE GUN LIGHT";"521";"DIRECTFIREGUNLIGHT";"S*G*EWDL--*****";"(null)";"(null)";"(null)" +"678";"DIRECT FIRE GUN LIGHT SELF PRO";"522";"DIRECTFIREGUNLIGHTSELF-PROPELLED";"S*G*EWDLS-*****";"(null)";"(null)";"(null)" +"679";"DIRECT FIRE GUN MEDIUM";"523";"DIRECTFIREGUNMEDIUM";"S*G*EWDM--*****";"(null)";"(null)";"(null)" +"680";"DIRECT FIRE GUN MEDIUM SELF-PR";"524";"DIRECTFIREGUNMEDIUMSELF-PROPELLED";"S*G*EWDMS-*****";"(null)";"(null)";"(null)" +"753";"DOZER";"562";"DOZER";"S*G*EVED--*****";"215";"CAT Excavator";"753" +"746";"EARTHMOVER";"554";"EARTHMOVER";"S*G*EVEE--*****";"(null)";"(null)";"(null)" +"774";"ELCECTRIC POWER FACILITY";"590";"ELECTRICPOWERFACILITY";"S*G*IUE---H****";"(null)";"(null)";"(null)" +"758";"EMPLACED SENSOR";"568";"EMPLACEDSENSOR";"S*G*ESE---*****";"(null)";"(null)";"(null)" +"467";"ENGINEER";"159";"ENGINEER";"S*G*UCE---*****";"(null)";"(null)";"(null)" +"472";"ENGINEER COMBAT";"160";"ENGINEERCOMBAT";"S*G*UCEC--*****";"(null)";"(null)";"(null)" +"473";"ENGINEER COMBAT AIR ASSAULT";"161";"ENGINEERCOMBATAIRASSAULT";"S*G*UCECS-*****";"(null)";"(null)";"(null)" +"478";"ENGINEER COMBAT AIRBORNE";"162";"ENGINEERCOMBATAIRBORNE";"S*G*UCECA-*****";"(null)";"(null)";"(null)" +"475";"ENGINEER COMBAT BRIDGING";"171";"ENGINEERCOMBATBRIDGING";"S*G*UCEC--*****";"(null)";"(null)";"(null)" +"481";"ENGINEER COMBAT HEAVY";"166";"ENGINEERCOMBATHEAVY";"S*G*UCECH-*****";"(null)";"(null)";"(null)" +"479";"ENGINEER COMBAT LIGHT";"164";"ENGINEERCOMBATLIGHT";"S*G*UCECL-*****";"(null)";"(null)";"(null)" +"482";"ENGINEER COMBAT MECH (TRACK)";"167";"ENGINEERCOMBATMECHANIZED(TRACK)";"S*G*UCECT-*****";"(null)";"(null)";"(null)" +"480";"ENGINEER COMBAT MEDIUM";"165";"ENGINEERCOMBATMEDIUM";"S*G*UCECM-*****";"(null)";"(null)";"(null)" +"476";"ENGINEER COMBAT MINE CLEARING";"172";"ENGINEERCOMBATMINECLEARING";"S*G*UCEC--*****";"(null)";"(null)";"(null)" +"477";"ENGINEER COMBAT MINE LAYING";"173";"ENGINEERCOMBATMINELAYING";"S*G*UCEC--*****";"(null)";"(null)";"(null)" +"483";"ENGINEER COMBAT MOTORIZED";"168";"ENGINEERCOMBATMOTORIZED";"S*G*UCECW-*****";"(null)";"(null)";"(null)" +"484";"ENGINEER COMBAT MOUNTAIN";"169";"ENGINEERCOMBATMOUNTAIN";"S*G*UCECO-*****";"(null)";"(null)";"(null)" +"474";"ENGINEER COMBAT RECON";"170";"ENGINEERCOMBATRECON";"S*G*UCECR-*****";"(null)";"(null)";"(null)" +"485";"ENGINEER CONSTRUCTION";"175";"ENGINEERCONSTRUCTION";"S*G*UCEN--*****";"(null)";"(null)";"(null)" +"744";"ENGINEER VEHICLE";"552";"ENGINEERVEHICLE";"S*G*EVE---*****";"(null)";"(null)";"(null)" +"657";"ENVIRONMENTAL PROTECTION";"463";"ENVIRONMENTALPROTECTION";"S*G*USXEP-*****";"(null)";"(null)";"(null)" +"576";"EW CORPS";"280";"CORPS";"S*G*UUMSEC*****";"(null)";"(null)";"(null)" +"575";"EW THEATRE";"279";"THEATRE";"S*G*UUMSET*****";"(null)";"(null)";"(null)" +"602";"EXPLOSIVE ORDNANCE DISPOSAL";"317";"EXPLOSIVEORDINANCEDISPOSAL";"S*G*UUE---*****";"(null)";"(null)";"(null)" +"490";"FIELD ARTELLERY HOWITZER/GUN AIRBORNE";"181";"AIRBORNE";"S*G*UCFHA-*****";"(null)";"(null)";"(null)" +"495";"FIELD ARTELLERY HOWITZER/GUN AMPHIBIOUS";"187";"AMPHIBIOUS";"S*G*UCFHX-*****";"(null)";"(null)";"(null)" +"494";"FIELD ARTELLERY HOWITZER/GUN HEAVY";"186";"HEAVY";"S*G*UCFHH-*****";"(null)";"(null)";"(null)" +"492";"FIELD ARTELLERY HOWITZER/GUN LIGHT";"184";"LIGHT";"S*G*UCFHL-*****";"(null)";"(null)";"(null)" +"493";"FIELD ARTELLERY HOWITZER/GUN MEDIUM";"185";"MEDIUM";"S*G*UCFHM-*****";"(null)";"(null)";"(null)" +"491";"FIELD ARTELLERY HOWITZER/GUN MOUNTAIN";"183";"MOUNTAIN";"S*G*UCFHO-*****";"(null)";"(null)";"(null)" +"496";"FIELD ARTELLERY ROCKET";"188";"ROCKET";"S*G*UCFR--*****";"(null)";"(null)";"(null)" +"498";"FIELD ARTILEERY ROCKET SINGLE";"190";"SINGLEROCKETSELFPROPELLED";"S*G*UCFRSS*****";"(null)";"(null)";"(null)" +"486";"FIELD ARTILLERY";"177";"FIELDARTILLERY";"S*G*UCF---*****";"(null)";"(null)";"(null)" +"521";"FIELD ARTILLERY ARTILLERY SURV";"214";"ARTILLERYSURVEY";"S*G*UCFS--*****";"(null)";"(null)";"(null)" +"523";"FIELD ARTILLERY FIRE DIRECTION CENTRE";"224";"FIREDIRECTIONCENTRE";"S*G*UCF---*****";"(null)";"(null)";"(null)" +"487";"FIELD ARTILLERY HOWITZER/GUN";"178";"HOWITZER/GUN";"S*G*UCFH--*****";"(null)";"(null)";"(null)" +"489";"FIELD ARTILLERY HOWITZER/GUN AIR ASSAULT";"180";"AIRASSAULT";"S*G*UCFHS-*****";"(null)";"(null)";"(null)" +"488";"FIELD ARTILLERY HOWITZER/GUN SELF-PROPELLED";"179";"SELFPROPELLED";"S*G*UCFHE-*****";"(null)";"(null)";"(null)" +"522";"FIELD ARTILLERY METEOROLOGICAL";"219";"METEOROLOGICAL";"S*G*UCFO--*****";"(null)";"(null)";"(null)" +"513";"FIELD ARTILLERY MORTAR";"205";"MORTAR";"S*G*UCFM--*****";"(null)";"(null)";"(null)" +"514";"FIELD ARTILLERY MORTAR SP TRAC";"206";"SELFPROPELLED(SP)TRACKEDMORTAR";"S*G*UCFMS-*****";"(null)";"(null)";"(null)" +"515";"FIELD ARTILLERY MORTAR SP WHEE";"207";"SPWHEELEDMORTAR";"S*G*UCFMSW*****";"(null)";"(null)";"(null)" +"516";"FIELD ARTILLERY MORTAR TOWED";"208";"TOWEDMORTAR";"S*G*UCFMT-*****";"(null)";"(null)";"(null)" +"518";"FIELD ARTILLERY MORTAR TOWED A";"210";"TOWEDAIRASSAULTMORTAR";"S*G*UCFMTS*****";"(null)";"(null)";"(null)" +"517";"FIELD ARTILLERY MORTAR TOWED A";"209";"TOWEDAIRBORNEMORTAR";"S*G*UCFMTA*****";"(null)";"(null)";"(null)" +"519";"FIELD ARTILLERY MORTAR TOWED M";"212";"TOWEDMOUNTAINMORTAR";"S*G*UCFMTO*****";"(null)";"(null)";"(null)" +"524";"FIELD ARTILLERY OBERSERVER";"225";"OBERSERVER";"S*G*UCF---*****";"(null)";"(null)";"(null)" +"502";"FIELD ARTILLERY ROCKET MULTI R";"194";"MULTIROCKETSELFPROPELLED";"S*G*UCFRMS*****";"(null)";"(null)";"(null)" +"504";"FIELD ARTILLERY ROCKET MULTI ROCKET LAUNCER MULTI";"196";"MULTIROCKETTOWED";"S*G*UCFRMT*****";"(null)";"(null)";"(null)" +"503";"FIELD ARTILLERY ROCKET MULTI ROCKET LAUNCER MULTI";"195";"MULTIROCKETTRUCK";"S*G*UCFRMR*****";"(null)";"(null)";"(null)" +"501";"FIELD ARTILLERY ROCKET MULTI ROCKET LAUNCHER";"193";"MULTIROCKETLAUNCHER";"S*G*UCFRM-*****";"(null)";"(null)";"(null)" +"497";"FIELD ARTILLERY ROCKET SINGLE";"189";"SINGLEROCKETLAUNCHER";"S*G*UCFRS-*****";"(null)";"(null)";"(null)" +"499";"FIELD ARTILLERY ROCKET SINGLE ROCKET LAUNCHER SING";"191";"SINGLEROCKETTRUCK";"S*G*UCFRSR*****";"(null)";"(null)";"(null)" +"500";"FIELD ARTILLERY ROCKET SINGLE ROCKET LAUNCHER SING";"192";"SINGLEROCKETTOWED";"S*G*UCFRST*****";"(null)";"(null)";"(null)" +"506";"FIELD ARTILLERY TARGET ACQUISI";"198";"RADAR";"S*G*UCFTR-*****";"(null)";"(null)";"(null)" +"505";"FIELD ARTILLERY TARGET ACQUISITION";"197";"TARGETACQUISITION";"S*G*UCFT--*****";"(null)";"(null)";"(null)" +"510";"FIELD ARTILLERY TARGET ACQUISITION DISMOUNTED";"202";"TARGETACQUISITIONDISMOUNTED";"S*G*UCFTCD*****";"(null)";"(null)";"(null)" +"508";"FIELD ARTILLERY TARGET ACQUISITION FLASH (OPTICAL)";"200";"FLASH(OPTICAL)";"S*G*UCFTF-*****";"(null)";"(null)";"(null)" +"509";"FIELD ARTILLERY TARGET ACQUISITION MOUNTED";"201";"TARGETACQUISITIONMOUNTED";"S*G*UCFTC-*****";"(null)";"(null)";"(null)" +"512";"FIELD ARTILLERY TARGET ACQUISITION NAVAL GUNFIRE";"204";"TARGETACQUISITIONNAVALGUNFIRE";"S*G*UCFTA-*****";"(null)";"(null)";"(null)" +"507";"FIELD ARTILLERY TARGET ACQUISITION SOUND";"199";"SOUND";"S*G*UCFTS-*****";"(null)";"(null)";"(null)" +"511";"FIELD ARTILLERY TARGET ACQUISITION TRACKED";"203";"TARGETACQUISITIONTRACKED";"S*G*UCFTCM*****";"(null)";"(null)";"(null)" +"345";"FIXED WING DRONE (RPV/UAE)";"36";"DRONE(RPV/UAV)";"S*A*MFQ---*****";"(null)";"(null)";"(null)" +"761";"FLAME THROWER";"572";"FLAMETHROWER";"S*G*EXF---*****";"(null)";"(null)";"(null)" +"660";"GENERAL HEADQUATERS AND SERVICE";"466";"GENERALHEADQUARTERSANDSERVICE";"S*G*UH----*****";"(null)";"(null)";"(null)" +"779";"GOVERNMENT LEADERSHIP";"603";"GOVERNMENTLEADERSHIP";"S*G*IG----H****";"(null)";"(null)";"(null)" +"703";"GRENADE LAUNCHER";"500";"GRENADELAUNCHER";"S*G*EWZ---*****";"(null)";"(null)";"(null)" +"706";"GRENADE LAUNCHER HEAVY";"503";"GRENADELAUNCHERHEAVY";"S*G*EWZH--*****";"(null)";"(null)";"(null)" +"704";"GRENADE LAUNCHER LIGHT";"501";"GRENADELAUNCHERLIGHT";"S*G*EWZL--*****";"(null)";"(null)";"(null)" +"705";"GRENADE LAUNCHER MEDIUM";"502";"GRENADELAUNCHERMEDIUM";"S*G*EWZM--*****";"(null)";"(null)";"(null)" +"662";"GROUND TRACK EQUIPMENT";"468";"GROUNDTRACKEQUIPMENT";"S*G*E-----*****";"(null)";"(null)";"(null)" +"358";"GROUND TRACK UNIT";"77";"UNIT";"S*G*U-----*****";"217";"Soldier (Role Player)";"358" +"358";"GROUND TRACK UNIT";"77";"UNIT";"S*G*U-----*****";"106";"Soldier";"358" +"363";"GROUND TRACK UNIT COMBAT";"78";"COMBAT";"S*G*UC----*****";"(null)";"(null)";"(null)" +"520";"GROUND TRACK UNIT COMBAT FIELD ARTILLERY MORTAR AM";"213";"AMPHIBIOUSMORTAR";"S*G*UCFML-*****";"(null)";"(null)";"(null)" +"659";"GROUND TRACK UNIT GENERAL HEADQUATERS";"465";"UNITGENERALHEADQUARTERS";"S*G*UH----*****";"(null)";"(null)";"(null)" +"723";"GROUND VEHICLE";"531";"GROUNDVEHICLE";"S*G*EV----*****";"(null)";"(null)";"(null)" +"702";"HEAVY MACHINE GUN";"499";"HEAVYMACHINEGUN";"S*G*EWRH--*****";"129";"Soldier HMG";"702" +"785";"HOSPITAL";"609";"HOSPITAL";"S*G*IXH---H****";"(null)";"(null)";"(null)" +"711";"HOWITZER";"508";"HOWITZER";"S*G*EWH---*****";"(null)";"(null)";"(null)" +"716";"HOWITZER HEAVY";"513";"HOWITZERHEAVY";"S*G*EWHH--*****";"(null)";"(null)";"(null)" +"717";"HOWITZER HEAVY SELF-PROPELLED";"514";"HOWITZERHEAVYSELF-PROPELLED";"S*G*EWHHS-*****";"(null)";"(null)";"(null)" +"712";"HOWITZER LIGHT";"509";"HOWITZERLIGHT";"S*G*EWHL--*****";"(null)";"(null)";"(null)" +"713";"HOWITZER LIGHT SELF-PROPELLED";"510";"HOWITZERLIGHTSELF-PROPELLED";"S*G*EWHLS-*****";"(null)";"(null)";"(null)" +"714";"HOWITZER MEDIUM";"511";"HOWITZERMEDIUM";"S*G*EWHM--*****";"(null)";"(null)";"(null)" +"715";"HOWITZER MEDIUM SELF-PROPELLED";"512";"HOWITZERMEDIUMSELF-PROPELLED";"S*G*EWHMS-*****";"(null)";"(null)";"(null)" +"449";"INFANTRY";"149";"INFANTRY";"S*G*UCI---*****";"161";"TM";"449" +"449";"INFANTRY";"149";"INFANTRY";"S*G*UCI---*****";"226";"TEST PLAYER";"449" +"449";"INFANTRY";"149";"INFANTRY";"S*G*UCI---*****";"232";"0Soldier";"449" +"449";"INFANTRY";"149";"INFANTRY";"S*G*UCI---*****";"128";"TM Leader";"449" +"463";"INFANTRY AIR ASSAULT";"154";"INFANTRYAIRASSAULT";"S*G*UCIS--*****";"(null)";"(null)";"(null)" +"462";"INFANTRY AIRBORNE";"153";"INFANTRYAIRBORNE";"S*G*UCIA--*****";"(null)";"(null)";"(null)" +"466";"INFANTRY FIGHTING VEHICLE";"157";"INFANTRYFIGHTINGVEHICLE";"S*G*UCII--*****";"(null)";"(null)";"(null)" +"454";"INFANTRY LIGHT";"150";"INFANTRYLIGHT";"S*G*UCIL--*****";"(null)";"(null)";"(null)" +"464";"INFANTRY MECHANIZED";"155";"INFANTRYMECHANIZED";"S*G*UCIZ--*****";"(null)";"(null)";"(null)" +"456";"INFANTRY MOTORIZED";"151";"INFANTRYMOTORIZED";"S*G*UCIM--*****";"(null)";"(null)";"(null)" +"461";"INFANTRY MOUNTAIN";"152";"INFANTRYMOUNTAIN";"S*G*UCIO--*****";"(null)";"(null)";"(null)" +"465";"INFANTRY NAVAL";"156";"INFANTRYNAVAL";"S*G*UCIN--*****";"(null)";"(null)";"(null)" +"600";"INFORMATION OPERATIONS";"315";"INFORMATIONWARFAREUNIT";"S*G*UUI---*****";"(null)";"(null)";"(null)" +"765";"INSTALLATION";"576";"INSTALLATION";"S*G*I-----H****";"225";"MEU";"765" +"765";"INSTALLATION";"576";"INSTALLATION";"S*G*I-----H****";"224";"Building";"765" +"666";"INTERMEDIATE RANGE AD MISSILE";"473";"INTERMEDIATERANGEADMISSILELAUNCH";"S*G*EWMAI-*****";"(null)";"(null)";"(null)" +"670";"INTERMEDIATE RANGE SS MISSILE";"478";"INTERMEDIATERANGESSMISSILELAUNCH";"S*G*EWMSI-*****";"(null)";"(null)";"(null)" +"382";"INTERNAL SECURITY FORCES";"246";"INTERNALSECURITYFORCES";"S*G*UCS---*****";"(null)";"(null)";"(null)" +"390";"INTERNAL SECURITY FORCES AVIAT";"254";"AVIATION";"S*G*UCSA--*****";"(null)";"(null)";"(null)" +"387";"INTERNAL SECURITY FORCES GROUN";"251";"MECHANIZEDGROUND";"S*G*UCSGA-*****";"(null)";"(null)";"(null)" +"384";"INTERNAL SECURITY FORCES GROUND";"248";"GROUND";"S*G*UCSG--*****";"(null)";"(null)";"(null)" +"385";"INTERNAL SECURITY FORCES GROUND DISMOUNTED";"249";"DISMOUNTEDGROUND";"S*G*UCSGD-*****";"(null)";"(null)";"(null)" +"386";"INTERNAL SECURITY FORCES GROUND MOTORIZED";"250";"MOTORIZEDGROUND";"S*G*UCSGM-*****";"(null)";"(null)";"(null)" +"389";"INTERNAL SECURITY FORCES RAILROAD";"253";"RAILROAD";"S*G*UCSR--*****";"(null)";"(null)";"(null)" +"383";"INTERNAL SECURITY FORCES RIVERINE";"247";"RIVERINE";"S*G*UCSW--*****";"(null)";"(null)";"(null)" +"388";"INTERNAL SECURITY FORCES WHEEL";"252";"WHEELEDMECHANIZED";"S*G*UCSM--*****";"(null)";"(null)";"(null)" +"791";"LANDING CRAFT";"623";"LANDINGCRAFT";"S*S*CALC--*****";"(null)";"(null)";"(null)" +"790";"LANDING SHIP";"622";"LANDINGSHIP";"S*S*CALS--*****";"(null)";"(null)";"(null)" +"601";"LANDING SUPPORT";"316";"LANDINGSUPPORT";"S*G*UUX---*****";"(null)";"(null)";"(null)" +"762";"LAND MINES";"573";"LANDMINES";"S*G*EXM---*****";"(null)";"(null)";"(null)" +"763";"LAND MINES, LETHAL";"574";"LANDMINES,LETHAL";"S*G*EXMC--*****";"(null)";"(null)";"(null)" +"759";"LASER";"570";"LASER";"S*G*EXL---*****";"(null)";"(null)";"(null)" +"584";"LAW ENFORCEMENT UNIT";"292";"LAWENFORCEMENTUNIT";"S*G*UUL---*****";"(null)";"(null)";"(null)" +"587";"LAW ENFORCEMENT UNIT CIVILIAN LAW ENFORCEMENT";"295";"CIVILIANLAWENFORCEMENT";"S*G*UULC--*****";"(null)";"(null)";"(null)" +"588";"LAW ENFORCEMENT UNIT CRIMINAL INVESTIGATION DIVISI";"297";"CENTRALINTELLIGENCEDIVISION(CID)";"S*G*UULD--*****";"(null)";"(null)";"(null)" +"586";"LAW ENFORCEMENT UNIT MILITARY POLICE";"294";"MILITARYPOLICE";"S*G*UULM--*****";"(null)";"(null)";"(null)" +"585";"LAW ENFORCEMENT UNIT SHORE PATROL";"293";"SHOREPATROL";"S*G*UULS--*****";"(null)";"(null)";"(null)" +"794";"LEISURE CRAFT";"665";"LEISURECRAFT";"S*S*XR----*****";"(null)";"(null)";"(null)" +"764";"LESS THAN LETHAL";"575";"LESSTHANLETHAL";"S*G*EXML--*****";"(null)";"(null)";"(null)" +"737";"LIGHT ARMOURED VEHICLE";"545";"LIGHTARMOUREDVEHICLE";"S*G*EVAL--*****";"(null)";"(null)";"(null)" +"701";"LIGHT MACHINE GUN";"498";"LIGHTMACHINEGUN";"S*G*EWRL--*****";"131";"Soldier LMG";"701" +"741";"LIMITED CROSS-COUNTRY TRUCK";"549";"LIMITEDCROSS-COUNTRYTRUCK";"S*G*EVUL--*****";"(null)";"(null)";"(null)" +"667";"LONG RANGE AD MISSILE LAUNCH";"474";"LONGRANGEADMISSILELAUNCH";"S*G*EWMAL-*****";"(null)";"(null)";"(null)" +"671";"LONG RANGE SS MISSILE LAUNCH";"479";"LONGRANGESSMISSILELAUNCH";"S*G*EWMSL-*****";"(null)";"(null)";"(null)" +"648";"MAINTENANCE";"442";"MAINTENANCE";"S*G*USX---*****";"(null)";"(null)";"(null)" +"654";"MAINTENANCE BATTLE DAMAGE REPA";"460";"BATTLEDAMAGEREPAIR";"S*G*USXBDR*****";"(null)";"(null)";"(null)" +"653";"MAINTENANCE ELECTRO-OPTICAL";"457";"ELECTRO-OPTICAL";"S*G*USXE--*****";"(null)";"(null)";"(null)" +"649";"MAINTENANCE HEAVY";"445";"MAINTENANCEHEAVY";"S*G*USXH--*****";"(null)";"(null)";"(null)" +"651";"MAINTENANCE ORDINANCE";"451";"ORDINANCE";"S*G*USXO--*****";"(null)";"(null)";"(null)" +"652";"MAINTENANCE ORDINANCE MISSILE";"454";"ORDINANCEMISSILE";"S*G*USXOM-*****";"(null)";"(null)";"(null)" +"655";"MAINTENANCE PREVENTIVE MAINTEN";"461";"PREVENTIVEMAINTANANCE";"S*G*USXPM-*****";"(null)";"(null)";"(null)" +"650";"MAINTENANCE RECOVERY";"448";"MAINTENANCERECOVERY";"S*G*USXR--*****";"(null)";"(null)";"(null)" +"623";"MEDICAL DENTAL";"373";"MEDICALDENTAL";"S*G*USMD--*****";"(null)";"(null)";"(null)" +"784";"MEDICAL FACILITY";"608";"MEDICALFACILITY";"S*G*IX----H****";"(null)";"(null)";"(null)" +"624";"MEDICAL PSYCHOLOGICAL";"376";"MEDICALPSYCHOLOGICAL";"S*G*USMP--*****";"(null)";"(null)";"(null)" +"621";"MEDICAL TREATMENT FACILITY";"367";"MEDICALTREATMENTFACILITY";"S*G*USMM--*****";"(null)";"(null)";"(null)" +"622";"MEDICAL VETERINARY";"370";"MEDICALVETERINARY";"S*G*USMV--*****";"(null)";"(null)";"(null)" +"343";"MILITARY";"7";"MILITARY";"S*A*M-----*****";"(null)";"(null)";"(null)" +"780";"MILITARY BASE/FACILITY";"604";"MILITARYBASE/FACILITY";"S*G*IB----H****";"(null)";"(null)";"(null)" +"344";"MILITARY FIXED WING";"8";"FIXEDWING";"S*A*MF----*****";"(null)";"(null)";"(null)" +"567";"MILITARY INTELLIGENCE";"271";"MILITARYINTELLIGENCE";"S*G*UUM---*****";"(null)";"(null)";"(null)" +"568";"MILITARY INTELLIGENCE AERIAL EXPLOITATION";"272";"AERIALEXPLOITATION";"S*G*UUMA--*****";"(null)";"(null)";"(null)" +"577";"MILITARY INTELLIGENCE COUNTER INTELLIGENCE";"281";"COUNTERINTELLIGENCE";"S*G*UUMC--*****";"(null)";"(null)";"(null)" +"573";"MILITARY INTELLIGENCE ELECTRON";"277";"INTERCEPT";"S*G*UUMSEI*****";"(null)";"(null)";"(null)" +"572";"MILITARY INTELLIGENCE ELECTRON";"276";"DIRECTIONFINDING";"S*G*UUMSED*****";"(null)";"(null)";"(null)" +"571";"MILITARY INTELLIGENCE ELECTRON";"275";"ARMOUREDWHEELEDVEHICLE";"S*G*UUMSEA*****";"(null)";"(null)";"(null)" +"570";"MILITARY INTELLIGENCE ELECTRONIC WARFARE";"274";"ELECTRONICWARFARE";"S*G*UUMSE-*****";"(null)";"(null)";"(null)" +"574";"MILITARY INTELLIGENCE ELECTRONIC WARFARE JAMMING";"278";"JAMMING";"S*G*UUMSEJ*****";"(null)";"(null)";"(null)" +"582";"MILITARY INTELLIGENCE INTERROGATION";"290";"INTERROGATION";"S*G*UUMQ--*****";"(null)";"(null)";"(null)" +"583";"MILITARY INTELLIGENCE JOINT INTELLIGENCE CENTRE";"291";"JOINTINTELLIGENCECENTRE";"S*G*UUMJ--*****";"(null)";"(null)";"(null)" +"581";"MILITARY INTELLIGENCE OPERATIONS";"288";"OPERATIONS";"S*G*UUMO--*****";"(null)";"(null)";"(null)" +"569";"MILITARY INTELLIGENCE SIGNALS";"273";"SIGNALINTELLIGENCE(SIGINT)";"S*G*UUMS--*****";"(null)";"(null)";"(null)" +"579";"MILITARY INTELLIGENCE SURVEILL";"283";"GROUNDSURVEILLANCERADAR";"S*G*UUMRG-*****";"(null)";"(null)";"(null)" +"578";"MILITARY INTELLIGENCE SURVEILLANCE";"282";"SURVEILLANCE";"S*G*UUMR--*****";"(null)";"(null)";"(null)" +"580";"MILITARY INTELLIGENCE SURVEILLANCE SENSOR";"284";"SENSOR";"S*G*UUMRS-*****";"(null)";"(null)";"(null)" +"346";"MILITARY ROTARY WING";"39";"ROTARYWING";"S*A*MH----*****";"195";"Apache";"346" +"766";"MINE";"578";"MINE";"S*G*IRM---H****";"(null)";"(null)";"(null)" +"749";"MINE LAYING VEHICLE";"557";"MINELAYINGVEHICLE";"S*G*EVEM--*****";"4";"Trailer, Minelaying";"749" +"675";"MISSILE LAUNCHER AT HEAVY";"483";"MISSILELAUNCHERATHEAVY";"S*G*EWMTH-*****";"(null)";"(null)";"(null)" +"674";"MISSILE LAUNCHER AT MEDIUM";"482";"MISSILELAUNCHERATMEDIUM";"S*G*EWMTM-*****";"(null)";"(null)";"(null)" +"663";"MISSILE LAUNCHERS";"470";"MISSILELAUNCHERS";"S*G*EWM---*****";"(null)";"(null)";"(null)" +"672";"MISSILE LAUNCHERS ANTI TANK (A";"480";"MISSILELAUNCHERSANTITANK(AT)";"S*G*EWMT--*****";"(null)";"(null)";"(null)" +"673";"MISSILE LAUNCHERS AT LIGHT";"481";"MISSILELAUNCHERSATLIGHT";"S*G*EWMTL-*****";"(null)";"(null)";"(null)" +"544";"MISSILE (SURF-SURF)";"243";"MISSILE(SURFACE-SURFACE)";"S*G*UCM---*****";"(null)";"(null)";"(null)" +"546";"MISSILE (SURF-SURF) STRATEGIC";"245";"MISSILE(SURFACE-SURFACE)STRATEGIC";"S*G*UCMS--*****";"(null)";"(null)";"(null)" +"545";"MISSILE (SURF-SURF) TACTICAL";"244";"MISSILE(SURFACE-SURFACE)TACTICAL";"S*G*UCMT--*****";"(null)";"(null)";"(null)" +"707";"MORTAR";"504";"MORTAR";"S*G*EWO---*****";"209";"Soldier Mortar";"707" +"710";"MORTAR HEAVY";"507";"MORTARHEAVY";"S*G*EWOH--*****";"(null)";"(null)";"(null)" +"708";"MORTAR LIGHT";"505";"MORTARLIGHT";"S*G*EWOL--*****";"(null)";"(null)";"(null)" +"709";"MORTAR MEDIUM";"506";"MORTARMEDIUM";"S*G*EWOM--*****";"(null)";"(null)";"(null)" +"747";"MULTIFUNCTIONAL EARTHMOVER/DIG";"555";"MULTIFUNCTIONALEARTHMOVER/DIGGER";"S*G*EVEE--*****";"(null)";"(null)";"(null)" +"691";"MULTIPLE ROCKET LAUNCHER";"488";"MULTIPLEROCKETLAUNCHER";"S*G*EWX---*****";"(null)";"(null)";"(null)" +"694";"MULTIPLE ROCKET LAUNCHER HEAVY";"491";"MULTIPLEROCKETLAUNCHERHEAVY";"S*G*EWXH--*****";"(null)";"(null)";"(null)" +"692";"MULTIPLE ROCKET LAUNCHER LIGHT";"489";"MULTIPLEROCKETLAUNCHERLIGHT";"S*G*EWXL--*****";"(null)";"(null)";"(null)" +"693";"MULTIPLE ROCKET LAUNCHER MEDIU";"490";"MULTIPLEROCKETLAUNCHERMEDIUM";"S*G*EWXM--*****";"(null)";"(null)";"(null)" +"771";"NUCLEAR";"583";"NUCLEAR";"S*G*IRNN--H****";"(null)";"(null)";"(null)" +"188";"O/C Force";"5";"SPACESTATION";"S*P*T-----*****";"113";"O/C Staff";"188" +"188";"O/C Force";"5";"SPACESTATION";"S*P*T-----*****";"180";"O/C TM";"188" +"188";"O/C Force";"5";"SPACESTATION";"S*P*T-----*****";"114";"O/C Force";"188" +"807";"O/C Force CE";"5";"SPACESTATION";"S*P*T-----*****";"(null)";"(null)";"(null)" +"793";"OILER/TANKER";"655";"OILER/TANKER";"S*S*XMO---*****";"(null)";"(null)";"(null)" +"792";"PATROL";"630";"PATROL";"S*S*CP----*****";"(null)";"(null)";"(null)" +"767";"PETROLEUM/GAS/OIL";"579";"PETROLEUM/GAS/OIL";"S*G*IRP---H****";"(null)";"(null)";"(null)" +"656";"PIPELINE";"462";"PIPELINE";"S*G*USXP--*****";"(null)";"(null)";"(null)" +"775";"PUBLIC WATER SERVICES";"591";"PUBLICWATERSERVICES";"S*G*IUP---H****";"(null)";"(null)";"(null)" +"757";"RADAR";"567";"RADAR";"S*G*ESR---*****";"(null)";"(null)";"(null)" +"525";"RECONNAISSANCE";"226";"RECONNAISSANCE";"S*G*UCR---*****";"(null)";"(null)";"(null)" +"535";"RECONNAISSANCE AIR ASSAULT";"234";"RECONNAISSANCEAIRASSAULT";"S*G*UCRS--*****";"(null)";"(null)";"(null)" +"536";"RECONNAISSANCE AIRBORNE";"235";"RECONNAISSANCEAIRBORNE";"S*G*UCRA--*****";"(null)";"(null)";"(null)" +"539";"RECONNAISSANCE AMPHIBIOUS";"238";"RECONNAISSANCEMARINE";"S*G*UCRR--*****";"(null)";"(null)";"(null)" +"540";"RECONNAISSANCE AMPHIBIOUS DIVI";"239";"RECONNAISSANCEMARINEDIVISION";"S*G*UCRRD-*****";"(null)";"(null)";"(null)" +"541";"RECONNAISSANCE AMPHIBIOUS FORC";"240";"RECONNAISSANCEMARINEFORCE";"S*G*UCRRF-*****";"(null)";"(null)";"(null)" +"530";"RECONNAISSANCE CAVALRY";"228";"RECONNAISSANCECAVALRY";"S*G*UCRV--*****";"(null)";"(null)";"(null)" +"534";"RECONNAISSANCE CAVALRY AIR";"232";"RECONNAISSANCECAVALRYAIR";"S*G*UCRVO-*****";"(null)";"(null)";"(null)" +"531";"RECONNAISSANCE CAVALRY ARMOURE";"229";"RECONNAISSANCECAVALRYARMOURED";"S*G*UCRVA-*****";"(null)";"(null)";"(null)" +"533";"RECONNAISSANCE CAVALRY GROUND";"231";"RECONNAISSANCECAVALRYGROUND";"S*G*UCRVG-*****";"(null)";"(null)";"(null)" +"532";"RECONNAISSANCE CAVALRY MOTORIZ";"230";"RECONNAISSANCECAVALRYMOTORIZED";"S*G*UCRVM-*****";"(null)";"(null)";"(null)" +"538";"RECONNAISSANCE LIGHT";"237";"RECONNAISSANCELIGHT";"S*G*UCRLL--*****";"(null)";"(null)";"(null)" +"542";"RECONNAISSANCE MARINE LIGHT AR";"241";"RECONNAISSANCEMARINELIGHTARMOUREDRECONNAISSANCE(LA";"S*G*UCRRL-*****";"(null)";"(null)";"(null)" +"537";"RECONNAISSANCE MOUNTAIN";"236";"RECONNAISSANCEMOUNTAIN";"S*G*UCRO--*****";"(null)";"(null)";"(null)" +"543";"RECONNAISSANCE SURVEILLANCE LO";"242";"RECONNAISSANCELONGRANGESURVEILLANCE(LRS)";"S*G*UCRX--*****";"(null)";"(null)";"(null)" +"700";"RIFLE";"497";"RIFLE";"S*G*EWRR--*****";"210";"Soldier Assault Rifle";"700" +"699";"RIFLE/AUTOMATIC WEAPON";"496";"RIFLE/AUTOMATICWEAPON";"S*G*EWR---*****";"205";"Soldier Tgt";"699" +"699";"RIFLE/AUTOMATIC WEAPON";"496";"RIFLE/AUTOMATICWEAPON";"S*G*EWR---*****";"216";"Soldier M203";"699" +"699";"RIFLE/AUTOMATIC WEAPON";"496";"RIFLE/AUTOMATICWEAPON";"S*G*EWR---*****";"153";"Soldier MP";"699" +"438";"ROTARY WING ATTACK";"131";"ATTACKROTARYWING";"S*G*UCVRA-*****";"(null)";"(null)";"(null)" +"347";"ROTARY WING ATTACK";"40";"ATTACK";"S*A*MHA---*****";"(null)";"(null)";"(null)" +"352";"ROTARY WING DRONE (RPV/UAV)";"49";"DRONE(RPV/UAV)";"S*A*MHQ---*****";"(null)";"(null)";"(null)" +"348";"ROTARY WING MEDEVAC";"55";"MEDEVAC";"S*A*MHO---*****";"(null)";"(null)";"(null)" +"351";"ROTARY WING RECCE";"48";"RECONNAISSANCE";"S*A*MHR---*****";"(null)";"(null)";"(null)" +"349";"ROTARY WING SOF";"56";"SPECIALOPERATIONSFORCE(SOF)";"S*A*MHM---*****";"(null)";"(null)";"(null)" +"350";"ROTARY WING UTILITY";"42";"UTILITY";"S*A*MHU---*****";"(null)";"(null)";"(null)" +"782";"SEAPORT/NAVAL BASE";"606";"SEAPORT/NAVALBASE";"S*G*IBN---H****";"(null)";"(null)";"(null)" +"786";"SEA SURFACE INSTALLATION, OIL RIG / PLATFORM";"611";"SEASURFACEINSTALLATION,OILRIG/PLATFORM";"S*G*IRR---H****";"(null)";"(null)";"(null)" +"787";"SEA SURFACE TRACK";"612";"SEASURFACETRACK";"S*S*------*****";"(null)";"(null)";"(null)" +"740";"SEMI";"548";"SEMI";"S*G*EVUS--*****";"(null)";"(null)";"(null)" +"756";"SENSOR";"566";"SENSOR";"S*G*ES----*****";"(null)";"(null)";"(null)" +"616";"SERVICE SUPPORT MEDICAL";"364";"MEDICAL";"S*G*USM---*****";"(null)";"(null)";"(null)" +"641";"SERVICE SUPPORT TRANSPORTATION";"424";"TRANSPORTATION";"S*G*UST---*****";"53";"TATRA Logistics Carrier";"641" +"665";"SHORT RANGE AD MISSILE LAUNCHE";"472";"SHORTRANGEADMISSILELAUNCHERS";"S*G*EWMAS-*****";"(null)";"(null)";"(null)" +"669";"SHORT RANGE SS MISSILE LAUNCH";"477";"SHORTRANGESSMISSILELAUNCH";"S*G*EWMSS-*****";"(null)";"(null)";"(null)" +"593";"SIGNALS UNIT FORWARD COMMUNICATIONS";"303";"FORWARDCOMMUNICATIONS";"S*G*UUSF--*****";"(null)";"(null)";"(null)" +"597";"SIGNALS UNIT SIGNAL SUPPORT";"312";"SIGNALSUPPORT";"S*G*UUSS--*****";"(null)";"(null)";"(null)" +"589";"SIGNAL UNIT";"298";"SIGNALUNIT";"S*G*UUS---*****";"(null)";"(null)";"(null)" +"590";"SIGNAL UNIT AREA";"299";"AREA";"S*G*UUSA--*****";"(null)";"(null)";"(null)" +"592";"SIGNAL UNIT COMMAND OPERATIONS";"302";"COMMANDOPERATIONS";"S*G*UUSO--*****";"(null)";"(null)";"(null)" +"591";"SIGNAL UNIT COMMUNICATION CONF";"300";"COMMUNICATIONCONFIGUREDPACKAGE";"S*G*UUSC--*****";"(null)";"(null)";"(null)" +"599";"SIGNAL UNIT ELECTRONIC RANGING";"314";"ELECTRONICRANGING";"S*G*UUSX--*****";"(null)";"(null)";"(null)" +"594";"SIGNAL UNIT RADIO UNIT";"308";"RADIOUNIT";"S*G*UUSR--*****";"(null)";"(null)";"(null)" +"596";"SIGNAL UNIT RADIO UNIT RELAY";"311";"RELAY";"S*G*UUSRW-*****";"(null)";"(null)";"(null)" +"595";"SIGNAL UNIT RADIO UNIT TACTICA";"309";"TACTICALSATELLITE";"S*G*UUSRS-*****";"(null)";"(null)";"(null)" +"598";"SIGNAL UNIT TELEPHONE SWITCH";"313";"TELEPHONESWITCH";"S*G*UUSW--*****";"(null)";"(null)";"(null)" +"687";"SINGLE ROCKET LAUNCHER";"484";"SINGLEROCKETLAUNCHER";"S*G*EWS---*****";"(null)";"(null)";"(null)" +"690";"SINGLE ROCKET LAUNCHER HEAVY";"487";"SINGLEROCKETLAUNCHERHEAVY";"S*G*EWSH--*****";"(null)";"(null)";"(null)" +"688";"SINGLE ROCKET LAUNCHER LIGHT";"485";"SINGLEROCKETLAUNCHERLIGHT";"S*G*EWSL--*****";"(null)";"(null)";"(null)" +"689";"SINGLE ROCKET LAUNCHER MEDIUM";"486";"SINGLEROCKETLAUNCHERMEDIUM";"S*G*EWSM--*****";"(null)";"(null)";"(null)" +"796";"SOF AVIATION FIXED WING";"707";"SOFUNITFIXEDWING";"S*F*AF----*****";"(null)";"(null)";"(null)" +"797";"SOF AVIATION FIXED WING ATTACK";"708";"SOFUNITATTACK";"S*F*AFA---*****";"(null)";"(null)";"(null)" +"798";"SOF AVIATION FIXED WING UTILIT";"710";"SOFUNITUTILITY";"S*F*AFU---*****";"(null)";"(null)";"(null)" +"799";"SOF AVIATION FIXED WING UTILIT";"711";"SOFUNITUTILITY(LIGHT)";"S*F*AFUL--*****";"(null)";"(null)";"(null)" +"800";"SOF AVIATION ROTARY WING";"715";"SOFUNITROTARYWING";"S*F*AH----*****";"(null)";"(null)";"(null)" +"803";"SOF UNIT GROUND";"727";"SOFUNITGROUND";"S*F*G-----*****";"(null)";"(null)";"(null)" +"805";"SOF UNIT GROUND RANGER";"729";"SOFUNITRANGER";"S*F*GSR---*****";"(null)";"(null)";"(null)" +"804";"SOF UNIT GROUND SPECIAL FORCES";"728";"SOFUNITSPECIALFORCES";"S*F*GS----*****";"(null)";"(null)";"(null)" +"801";"SOF UNIT NAVAL";"722";"SOFUNITSOFUNITNAVAL";"S*F*SN----*****";"(null)";"(null)";"(null)" +"802";"SOF UNIT NAVAL SPECIAL BOAT";"725";"SOFUNITSPECIALBOAT";"S*F*SNB---*****";"(null)";"(null)";"(null)" +"806";"SOF UNIT PSYCHOLOGICAL OPERATI";"730";"SOFUNITPSYCHOLOGICALOPERATIONS(PSYOPS)";"S*F*GSP---*****";"(null)";"(null)";"(null)" +"658";"SPECIAL C2 HEADQUATERS COMPONE";"464";"SPECIALC2HEADQUARTERSCOMPONENT";"S*G*UH----*****";"(null)";"(null)";"(null)" +"795";"SPECIAL OPERATIONS FORCE (SOF)";"705";"SPECIALOPERATIONSFORCE(SOF)UNIT";"S*F*------*****";"(null)";"(null)";"(null)" +"625";"SUPPLY";"379";"SUPPLY";"S*G*USS---*****";"(null)";"(null)";"(null)" +"633";"SUPPLY CLASS I";"382";"SUPPLYCLASSI";"S*G*USS1--*****";"(null)";"(null)";"(null)" +"634";"SUPPLY CLASS II";"385";"SUPPLYCLASSII";"S*G*USS2--*****";"(null)";"(null)";"(null)" +"635";"SUPPLY CLASS III";"388";"SUPPLYCLASSIII";"S*G*USS3--*****";"(null)";"(null)";"(null)" +"636";"SUPPLY CLASS III AVIATION";"391";"SUPPLYCLASSIIIAVIATION";"S*G*USS3A-*****";"(null)";"(null)";"(null)" +"637";"SUPPLY CLASS IV";"394";"SUPPLYCLASSIV";"S*G*USS4--*****";"(null)";"(null)";"(null)" +"627";"SUPPLY CLASS IX";"409";"SUPPLYCLASSIX";"S*G*USS9--*****";"(null)";"(null)";"(null)" +"638";"SUPPLY CLASS V";"397";"SUPPLYCLASSV";"S*G*USS5--*****";"(null)";"(null)";"(null)" +"639";"SUPPLY CLASS VI";"400";"SUPPLYCLASSVI";"S*G*USS6--*****";"(null)";"(null)";"(null)" +"640";"SUPPLY CLASS VII";"403";"SUPPLYCLASSVII";"S*G*USS7--*****";"(null)";"(null)";"(null)" +"626";"SUPPLY CLASS VIII";"406";"SUPPLYCLASSVIII";"S*G*USS8--*****";"(null)";"(null)";"(null)" +"628";"SUPPLY CLASS X";"412";"SUPPLYCLASSX";"S*G*USSX--*****";"(null)";"(null)";"(null)" +"629";"SUPPLY LAUNDRY/BATH";"415";"SUPPLYLAUNDRY/BATH";"S*G*USSL--*****";"(null)";"(null)";"(null)" +"632";"SUPPLY MATERIEL (STOCK)";"1335";"SUPPLYMATERIEL(STOCK)";"S*G*US**--*****";"(null)";"(null)";"(null)" +"630";"SUPPLY WATER";"418";"SUPPLYWATER";"S*G*USSW--*****";"(null)";"(null)";"(null)" +"631";"SUPPLY WATER PURIFICATION";"421";"SUPPLYWATERPURIFICATION";"S*G*USSWP-*****";"(null)";"(null)";"(null)" +"668";"SURFACE- SURFACE (SS) MISSILE";"476";"SURFACE-SURFACE(SS)MISSILELAUNCHER";"S*G*EWMS--*****";"(null)";"(null)";"(null)" +"355";"SURFACE TO AIR MISSILE";"65";"SURFACETOAIRMISSILE(SAM)";"S*A*WMSA--*****";"(null)";"(null)";"(null)" +"354";"SURFACE TO SURFACE MISSILE";"64";"SURFACETOSURFACEMISSILE(SSM)";"S*A*WMSS--*****";"(null)";"(null)";"(null)" +"391";"SURVEILLANCE";"1334";"SURVEILLANCE";"S*G*F-S---*****";"(null)";"(null)";"(null)" +"725";"TANK";"533";"TANK";"S*G*EVAT--*****";"233";"0Vehicle";"725" +"725";"TANK";"533";"TANK";"S*G*EVAT--*****";"43";"MBT";"725" +"725";"TANK";"533";"TANK";"S*G*EVAT--*****";"230";"LEOPARD 2A5";"725" +"730";"TANK HEAVY";"538";"TANKHEAVY";"S*G*EVATH-*****";"(null)";"(null)";"(null)" +"731";"TANK HEAVY RECOVERY";"539";"TANKHEAVYRECOVERY";"S*G*EVATY-*****";"(null)";"(null)";"(null)" +"726";"TANK LIGHT";"534";"TANKLIGHT";"S*G*EVATL-*****";"(null)";"(null)";"(null)" +"727";"TANK LIGHT RECOVERY";"535";"TANKLIGHTRECOVERY";"S*G*EVATW-*****";"110";"BMP3 Recovery";"727" +"728";"TANK MEDIUM";"536";"TANKMEDIUM";"S*G*EVATM-*****";"(null)";"(null)";"(null)" +"729";"TANK MEDIUM RECOVERY";"537";"TANKMEDIUMRECOVERY";"S*G*EVATX-*****";"(null)";"(null)";"(null)" +"773";"TELECOMMUNICATIONS FACILITY";"589";"TELECOMMUNICATIONSFACILITY";"S*G*IUT---H****";"(null)";"(null)";"(null)" +"603";"TOPOGRAPHIC";"318";"TOPOGRAPHIC";"S*G*UUT---*****";"(null)";"(null)";"(null)" +"751";"TRAILER MOUNTED";"559";"TRAILERMOUNTED";"S*G*EVEMT-*****";"(null)";"(null)";"(null)" +"754";"TRAIN LOCOMOTIVE";"564";"TRAINLOCOMOTIVE";"S*G*EVST--*****";"(null)";"(null)";"(null)" +"645";"TRANSPORTATION APOD/APOE";"436";"APOD/APOE";"S*G*USTA--*****";"(null)";"(null)";"(null)" +"647";"TRANSPORTATION INTERMODAL";"1328";"TRANSPORTATIONINTERMODAL";"S*G*UST---*****";"(null)";"(null)";"(null)" +"646";"TRANSPORTATION MISSILE";"439";"MISSILE";"S*G*USTI--*****";"(null)";"(null)";"(null)" +"642";"TRANSPORTATION MOVEMENT CONTRO";"427";"MOVEMENTCONTROLCENTRE(MCC)";"S*G*USTM--*****";"(null)";"(null)";"(null)" +"643";"TRANSPORTATION RAILHEAD";"430";"RAILHEAD";"S*G*USTR--*****";"(null)";"(null)";"(null)" +"644";"TRANSPORTATION SPOD/SPOE";"433";"SPOD/SPOE";"S*G*USTS--*****";"(null)";"(null)";"(null)" +"783";"TRANSPORT FACILITY";"607";"TRANSPORTFACILITY";"S*G*IT----H****";"(null)";"(null)";"(null)" +"661";"UNITS GENERAL LIAISON";"467";"UNITGENERALLIAISON";"S*G*UHGL--*****";"(null)";"(null)";"(null)" +"738";"UTILITY VEHICLE";"546";"UTILITYVEHICLE";"S*G*EVU---*****";"54";"HMMWV Utility";"738" +"738";"UTILITY VEHICLE";"546";"UTILITYVEHICLE";"S*G*EVU---*****";"221";"Vehicle Tgt";"738" +"743";"WATER CRAFT";"551";"WATERCRAFT";"S*G*EVUR--*****";"213";"TATRA Tanker Water";"743" +"353";"WEAPON";"61";"WEAPON";"S*A*W-----*****";"(null)";"(null)";"(null)" diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/CMakeLists.txt new file mode 100755 index 000000000..0113ced47 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/CMakeLists.txt @@ -0,0 +1,42 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# PlanModel +SET(GeometryModel_SRCS + geometry_model.dtd + geometry_model.cpp + geometry_model.h + geometry_model_export.h + geometry_model_factory.cpp + geometry_model_factory.h + geometry_model_q_dom.cpp + geometry_model_q_dom.h + geometry_node_visitor.cpp + geometry_node_visitor.h + geometry_pixelTextBox.cpp +) + +SOURCE_GROUP(GeometryModel FILES ${GeometryModel_SRCS}) + +NGL_ADD_LIBRARY(WS situationMapGeometryModel SHARED ${GeometryModel_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES( situationMapGeometryModel + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +NGL_TARGET_LINK_LIBRARIES(situationMapGeometryModel + PRIVATE + NGLLogger + PUBLIC + situationMapCommon + Qt6::Core + Qt6::Gui + Qt6::Xml +) + +NGL_INSTALL_TARGET(situationMapGeometryModel) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.cpp new file mode 100755 index 000000000..eb4c80970 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.cpp @@ -0,0 +1,38 @@ +/** +** +** @RDE_HEADER@ +** GeometryModel to write and read PlanEditor plans. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 11.11.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "geometry_model.h" + +namespace SituationMap +{ + + GeometryModel::GeometryModel(QObject *parent) : QObject(parent) {;} + + GeometryModel::GeometryModel(const GeometryModel &) : QObject() {;} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.dtd b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.dtd new file mode 100755 index 000000000..6ae4ce58b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.dtd @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.h new file mode 100755 index 000000000..250a8db31 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model.h @@ -0,0 +1,1028 @@ +/** +** +** @RDE_HEADER@ +** GeometryModel to write and read PlanEditor plans. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 11.11.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +// clazy:excludeall=const-signal-or-slot + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "geometry_model_export.h" + + +class QIODevice; +class QTransform; + +namespace SituationMap +{ + +class Referenced; + +/** + Interface to Read/Write plan(s) +*/ +class GEOMETRYMODEL_EXPORT GeometryModel : public QObject +{ + Q_OBJECT + +public: + + class GEOMETRYMODEL_EXPORT Node + { + public: + + class Implementation; + + Node(); + Node(const Implementation &other); // do not use explicit here to avoid a lot of writing stuff to explicit casts + + Node(const Node &other); + + virtual ~Node(); + + Node &operator = (const Node &other); + + Node &operator = (Node &&other); + + const Implementation &get() const; + Implementation &get(); + + bool isValid() const; + bool isRootNode() const; + + Node parentNode() const; + + bool hasChildNodes() const; + + Node next() const; + Node firstChildNode() const; + Node firstChildNode(const QString &nodeName) const; + + bool operator ==(const Node &) const; + bool operator !=(const Node &) const; + + + uint makeConnection(Referenced *) const; + bool unlinkConnection() const; + uint connectedId() const; + + Referenced *connected() const; + + private: + + Implementation *_d; + + }; // Node + + + explicit GeometryModel(QObject *parent = nullptr); + + GeometryModel(const GeometryModel &); //!< Copy-Constructor + GeometryModel& operator = (const GeometryModel &) = delete; // copy allowed but no assigment? + + /** + Converts the geometry model to its textual representation. + @{ + */ + virtual QByteArray toByteArray() const = 0; + + /** + Converts the geometry model to QString by retaining userData. + */ + virtual QByteArray toByteArrayDebug() const = 0; + /** @} */ + + /** + Converts textual representation of geometry model to binary representation. + @{ + */ + virtual bool fromByteArray(const QByteArray &content) = 0; + /** @} */ + + /** + Reads textual representation of geometry model from device and converts to binary representation. + */ + virtual bool fromDevice(QIODevice *device) = 0; + + /** + Converts textual representation of geometry model to binary representation by retaining userData. + */ + virtual bool fromByteArrayDebug(const QByteArray &content) = 0; + + /** + Check for existens of primitives. + */ + virtual bool isEmpty() const = 0; + + /** + Retrieve the root node to traverse the model. + */ + virtual Node getRootNode() const = 0; + + /** + Retrieve the node to store default map settings. + */ + virtual Node getMapDefaultSettingsNode() const = 0; + + + using NodeList = QList; + + + //! Types of Primitives for \sa { addPrimitive(), getType() } + enum class PrimitiveType + { + DictionarySymbol + , ParticipantSymbol + , Ellipse + , Line + , Polyline + , Rectangle + , Spline + , Symbol + , Text + , Node + , RootNode + , MapDefaults + , Unknown + }; // PrimitiveType + + //! Horizontal Alignment-Enum for Text + enum class HorizontalAlign + { + Left = 0, + HCenter = 1, + Right = 2 + }; + + //! Vertical Alignment-Enum for Text + enum class VerticalAlign + { + Bottom = 0, + VCenter = 1, + Top = 2 + }; + + //! Enum for positioning Arrows at (Poly)Lines + enum class Arrows {NoArrows, Start, End, Both}; + + //! Enum for Fill-Patterns + enum class FillPattern {None, Dots, Hor1, Hor2, Ver1, Ver2, Diag1, Diag2, Solid}; + + //! Enum for Fill-Style of Ellipsis + enum class FillStyle {Chord, Pieslice, Open}; + + //! Enum for Line-Styles + enum class LineStyle {LineSolid, Dashed, Dotted, DashedDotted}; + + //! Enum for Text-Fonts + enum class Font {SansSerif, Serif, Fixed, Decorative}; + + //! Enum for different types of maps - extends the types from GDW::MapType in + //! GDW/src/GDWUtils/LoadMapControl.h + enum class MapType {Raster, Vector, Contour, Elevation, RasterElevation, MapUnknown}; + + //! Enum for tactical state of a symbol: ts_* = tactical state... + + enum class vbg_et + { + EINSATZBEREIT = 0, + LEICHTER_TREFFER = 1, + MITTLERER_TREFFER = 2, + SCHWERER_TREFFER = 3, + BEWEGUNGSUNFAEHIG = 4, + BEWEGUNGSUNFAEHIG_SICHTB = 5, + AUSFALL_WAFFENANLAGE = 6, + AUSFALL_WAFFENANLAGE_SICHTB = 7, + TOTALAUSFALL = 8, + ZEITLICHER_AUSFALL = 9, + ANNAEHENDER_TREFFER = 10, + TREFFER_OHNE_WIRKUNG = 11, + }; + + using t_vbg_et = vbg_et; + /* + enum t_w_vbg_et { + ts_einsatzbereit, ts_treffer_ohne_wirkung, ts_annaehender_treffer, + ts_zeitlicher_ausfall, ts_leichter_treffer, ts_mittlerer_treffer, + ts_schwerer_treffer, ts_bewegungsunfaehig, ts_bewegungsunfaehig_sichtb, + ts_ausfall_waffenanlage, ts_ausfall_waffenanlage_sichtb, ts_totalausfall + }; + */ + + //! Re-order Primitives + enum class PrimitivOrder { Lower, Raise }; + + //! Enum for all known attributes + enum class Attribute + { + None, // Only for changed signal, can not be set + AbcProtection, // Available for: symbol - Type: bool + Affiliation, // Available for: symbol - Type: Symbol::t_affil + Alignment, // Available for: text_data - Type: HorizontalAlign + AnchorHorizontal, // Available for: text - Type: HorizontalAlign + AnchorVertical, // Available for: text - Type: t_valign + ArrowClampHeight, // Available for: line polyline spline - Type: double + ArrowHeight, // Available for: line polyline spline - Type: double + ArrowLineWidth, // Available for: line polyline spline - Type: double + Arrows, // Available for: line polyline spline - Type: t_arrows + ArrowWidth, // Available for: line polyline spline - Type: double + Box, // Available for: text - Type: bool + Center, // Available for: ellpise rectangle - Type QPointF + Close, // Available for: polyline spline - Type: bool + Color, // Available for: ellipse line polyline rectangle spline text - Type: QColor + Extent, // Available for: ellipse - Type: double + Fill, // Available for: ellipse polyline rectangle spline text - Type: bool + FillColor, // Available for: ellipse polyline rectangle spline text - Type: QColor + FillPattern, // Available for: ellipse polyline rectangle spline text - Type: t_fillPattern + FillStyle, // Available for: ellipse - Type: t_fillStyle + Font, // Available for: text - Type: t_font + FontBold, // Available for: text - Type: bool + FontItalic, // Available for: text - Type: bool + FontSize, // Available for: text - Type: int + Height, // Available for: ellipse rectangle - Type: double + Injury, // Available for: ParticipantSymbol - Type int + LineColor, // Available for: text - Type: QColor + LineStyle, // Available for: ellipse line polyline rectangle spline text - Type: t_lineStyle + LineWidth, // Available for: ellipse line polyline rectangle spline text - Type: double + MarginHorizontal, // Available for: text - Type: int + MarginVertical, // Available for: text - Type: int + Mobility, // Available for: symbol - Type: Symbol::t_mob + SIDC, // Available for: dictionary symbol - Type: QString + SIDC_Color, // Available for: dictionary symbol - Type: QColor + SIDC_Attributes, // Available for: dictionary symbol - Type: QString AttributeName=AttributeValue;AttributeName.... + SymbolId, // Available for: symbol - Type: int + SymbolOffsetX, // Available for: symbol - Type: int + SymbolOffsetY, // Available for: symbol - Type: int + SymbolDirection, // Available for: symbol - Type: double + ForceId, // Available for: symbol - Type: int + SymbolState, // Available for: - Type: enum t_state (aus symbol_types.h) + SymbolUncertainty, // Available for: - Type: enum t_uncertainty (aus symbol_types.h) + SymbolExplicitScale, // Available for: symbol + DamageForceId, // Available for: symbol - Type: int + PeMapBrightness, // Available for: plan - Type: double + PeMapCenterX, // Available for: plan - Type: int + PeMapCenterY, // Available for: plan - Type: int + PeMapSaturation, // Available for: plan - Type: double + PeMapRotation, // Available for: plan - Type: double + PeMapType, // Available for: plan - Type: t_mapType + PeMapZoom, // Available for: plan - Type: double + PeShowUtm, // Available for: plan - Type: bool + + PeSymbolOpacity, // Available for: plan - Type: double 0..1.0 (0=keine Deckkraft, 1.0 deckend) + PeSymbolFillType, // Available for: plan - Type: Type: enum t_fillType (aus symbol_types.h) + PeSymbolScale, // Available for: plan - Type: double 0..2.0 + + MetaDataTitle, + MetaDataAuthor, + MetaDataDate, + MetaDataDesciption, + MetaDataMinScale, + MetaDataMaxScale, + + MetaPixelBasedTextBoxes, + + Rotate, // Available for: ellipse rectangle text - Type: double + Rounding, // Available for: rectangle - Type: double + Start, // Available for: ellipse - Type: double + Text_EF, // Available for: symbol - Type: QString + Text_G, // Available for: symbol - Type: QString + Text_H, // Available for: symbol - Type: QString + Text_KLNPJ, // Available for: symbol - Type: QString + Text_M, // Available for: symbol - Type: QString + Text_T, // Available for: symbol - Type: QString + Text_V, // Available for: symbol - Type: QString + Text_W, // Available for: symbol - Type: QString + Text_XY, // Available for: symbol - Type: QString + Text_Z, // Available for: symbol - Type: QString + UtmEast, // Available for: plan - Type: int + UtmGrid, // Available for: plan - Type: QString + UtmNorth, // Available for: plan - Type: int + UtmZone, // Available for: plan - Type: QString + Width, // Available for: ellipse rectangle - Type: double + att_vbg_et, // Available for: symbol - Type: t_w_vbg_et + MaxAttributes + }; // enum class Attribute + + + /** + Set a value for a global model-attribute. If the attribute does not exist in the + model or the given value is invalid, a setAttributeError()-signal is emitted. + Signal changed() ist emitted. + \sa { getModelAttribute(), setAttributeError(), hasAttribute(), attributeType() } + \param attribute: the attribute to set + \param value: the value of the attribute + */ + virtual void setModelAttribute(Attribute attribute, const QVariant &value) = 0; + + + /** + Set a value for a plan-editor-attribute. If the attribute does not exist in + the plan-editor or the given value is invalid, a setAttributeError()-signal + is emitted. + Signal planEditorChanged() ist emitted. + \sa { getAttribute(), setAttributeError(), hasAttribute(), attributeType() } + \param attribute: the attribute to set + \param value: the value of the attribute + */ + virtual void setModelEditorAttribute(Attribute attribute, const QVariant &value) = 0; + + + /** + Set a value for a primitive-attribute. If the attribute does not exist in + the primitive or the given value is invalid, a setAttributeError()-signal + is emitted. + Signal changed() ist emitted. + \sa { getAttribute(), setAttributeError(), hasAttribute(), attributeType() } + \param node: reference to the primitive-node + \param attribute: the attribute to set + \param value: the value of the attribute + */ + virtual void setPrimitiveAttribute(const Node &node, Attribute attribute, const QVariant &value) = 0; + + + /** + Move a primitive or a group-node into another group-node. If the moved + primitive is already child of a group-node, it ist moved to the group + and removed from the previous one. If the target is not a group-node, + a typeError()-signal is emitted. + Signal changed() ist emitted. + \param primitive: the primitive or group-node to move into a group + \param target: the target group-node + */ + virtual void movePrimitiveIntoGroup(const Node &primitive, const Node &target) = 0; + + + /** + Remove a group, but not its children. This means, that all children of the + given group are moved one level up the tree and then the group is deleted. + If group is not a group-node, a typeError()-signal is emitted. + Signal changed() ist emitted. + \param group: the group + */ + virtual void releaseGroup(const Node &group) = 0; + + + /** + Remove a group AND all its children. + If group is not a group-node, a typeError()-signal is emitted. + Signal changed() ist emitted. + \param group: the group + */ + virtual void deleteGroup(const Node &group) = 0; + + + /** + Remove and delete the given primitive from the plan (and possibly all of + its children). + Signal changed() ist emitted. + \param primitive: reference to the primitive-node + */ + virtual void removePrimitive(const Node &primitive) = 0; + + + /** + Remove and delete all primitives from the plan (and possibly all of + their children), that match the userData in the given list. + Signal changed() ist emitted, after all primitves have been removed. + \param primitive: reference to the primitive-node + */ + virtual void removePrimitivesByUserData(const QList &userDataList) = 0; + + + /** + Remove Text from a primitive, if any. + Signal changed() ist emitted. + + \param parent: The node where to remove the text from + */ + virtual void removeText(Node &parent) = 0; + + + /** + Set the text-data of a text-node. If the given node is not a text-node, + a typeError()-signal is emitted. + Signal changed() ist emitted. + \sa{ addText(), getText(), getTextData() } + + \param textNode: the text-node + \param data: the text-data + \param textAlign: the alignment of the text-data + */ + virtual void setTextData(const Node &textNode, const QString &data, HorizontalAlign textAlign) = 0; + + + /** + Translate a group or primitive. + Signal changed() ist emitted. + \param node: reference to the primitive-node + \param x: translate horizontally by this value + \param y: translate vertically by this value + */ + virtual void translateNode(const Node &node, double x, double y) = 0; + + + /** + Move a group or primitive to the given coordinates. + Signal changed() ist emitted. + \param node: reference to the primitive-node + \param x: move horizontally to this value + \param y: move vertically to this value + */ + virtual void moveNode(const Node &node, double x, double y) = 0; + + + /** + Scale the group or primitive. A primitive is scaled around the center of + its bounding box. If a group is scaled, all children are scaled around + the center of the groups bounding box. + Signal changed() ist emitted. + \param node: reference to the primitive-node + \param scaleX: the x-scale-factor + \param scaleY: the y-scale-factor + */ + virtual void scaleNode(const Node &node, double scaleX, double scaleY) = 0; + + + /** + Rotate a group or primitive. A primitive is rotated around the center of + its bounding box. If a group is rotated, all children are rotated around + the center of the groups bounding box. + Signal changed() ist emitted. + \param node: reference to the primitive or group + \param angle: the rotation angle (ccw, 360 degree) + */ + virtual void rotateNode(const Node &node, double angle) = 0; + + + /** + Reorder Primitives in the plan. + Signal changed() ist emitted. + \param nodes: The primitives to reorder + \param order: Move primitives up (Raise) or down (Lower) + */ + virtual void reorderPrimitives(const NodeList &nodes, PrimitivOrder order) = 0; + + + /** + Merge another Node into this one. + All Primitives and GroupNodes of other are copied and added (appended) to + this. + Signal changed() ist emitted. + + \param other: the other plan or Node + \param skipUndo: if true, the undo-stack is not updated + \param intoNewGroup: If true, all merged primitives go into a new Group, + while groups from the merged plan are removed. + */ + + virtual void merge(const Node &other, bool skipUndo = false, bool intoNewGroup = false) = 0; + + /** + Merge another plan into this one. + All Primitives and GroupNodes of other are copied and added (appended) to + this. + Signal changed() ist emitted. + + \param other: the other plan or Node + \param skipUndo: if true, the undo-stack is not updated + \param intoNewGroup: If true, all merged primitives go into a new Group, + while groups from the merged plan are removed. + */ + virtual void merge(GeometryModel *other, bool skipUndo = false, bool intoNewGroup = false) = 0; + + + /** + Undo the last change on the undo()-stack. To check, if there are + undo()-steps available, check the size of the undo-stack with undoSteps() + If inside of an undo-block, the block is closed, even if nested. + Signal changed() ist emitted. + \param steps: Go this many steps back in undo-history. If less than 1, undo + does nothing, if greater than undo-steps available, undo goes + only back that many steps. + */ + virtual void undo(unsigned int steps = 1) = 0; + + /** + Redo the last change on redo-stack. To check, if there are + redo-steps available, check the size of the redo-stack with redoSteps() + \param steps: Go this many steps back in undo-history. If less than 1, redo + does nothing, if greater than redo-steps available, redo goes + only back that many steps. + */ + virtual void redo(unsigned int steps = 1) = 0; + + + /** + Enable or disable undo(). While undo() is disabled, no new actions will be + recorded in the undo-stack. + \param enabled: Bool to enable/disable undo() + */ + virtual void enableUndo(bool enable) = 0; + + + /** + Start a block of collected undo-steps. All modifications until endUndoBlock() + are collected into one undo-Step. Nested undoBlocks are not possible. If + already inside an undo-block, this method does nothing. + undo() ends an undo-block. + */ + virtual void startUndoBlock() = 0; + + + /** + End a block of collected undo-steps. All collected modifications since the + startUndoBlock() are now pushed into one undo-Step. Nested undoBlocks are + not possible. If not inside an undo-block, this method does nothing. + undo() also ends an undo-block. + \param eventDescription: describe the last change. If == "", use last + automatic entry. + */ + virtual void endUndoBlock(const QString &eventDescription = QString()) = 0; + + /** + Abort recording a block of collected undo-steps. + */ + virtual void abortUndoBlock() = 0; + + /** + Set the maximum number of undo-Steps. Default is 100. + \param max: number of undo-Steps + */ + virtual void setMaxUndoSteps(unsigned int max) = 0; + + /** + Return maximum number of undo-steps. + */ + virtual unsigned int maxUndoSteps() const = 0; + + + /** + Counting modifications of the model. + */ + virtual unsigned int modificationCounter() const = 0; + + + /** + Reset modification counter. + */ + virtual void resetModificationCounter() = 0; + + + /** + Remove all User-Data-Values-Attributes and empty groups from the model. + ATTENTION: After this, many methods may return unexpected results. + This slot/method should only be used before the model is written to a file + or database. + For each cleaned Attribute the signal userDataCleanup() is emitted. + */ + virtual void cleanModel() = 0; + + + /** + Clear the model. This deletes all primitives and groups. + Signal changed() is emitted. + */ + virtual void clearModel() = 0; + + + /** + Add a primitive to the plan. If the given primitive-type does not exist, + a typeError()-signal is emitted. + Signal changed() ist emitted. + + \sa { typeError() } + \param type: the type of a primitive + \param points: a list of points to define the position/geometry of the primitive + \param attributes: a list of attribute/value-pairs. If the list contains + attributes, that are not supported by the primitive, or + values, that do not fit into the attributes, a + setAttributeError()-signal is emitted. + \param userData: An int for creating simIDs + \param textData: Add this text to the Primitive, if this is a text-Primitive + \param textAlign: the alignment of the text-data, only used for text-primitive + \return a reference to the new tree-element + */ + virtual Node addPrimitive(PrimitiveType type + , const QList &points + , const QList > &attributes + , int userData = SituationMap::IdHandler::BadId + , const QString &textData = QString() + , HorizontalAlign textAlign = HorizontalAlign::Left) = 0; + + + /** + Set the defining points of a primitive + If the number of points does not match the Primitives-definition, an + attributeError() is emitted. + \param node: reference to the primitive-node + \param points: a list of points to define the position/geometry of the primitive + \return true on success, false otherwise (i.e. wrong number of points) + */ + virtual bool setPrimitivePoints(const Node &node, const QList &points) = 0; + + + /** + Add a text-primitive to another primitive. A typeError()-signal is emitted, + if the given parent-node can not have text. The returned node is a text-node. + Signal changed() ist emitted. + + \sa { typeError(), addPrimitive(), setPrimitiveAttribute(), setTextData() } + + \param parent: The node where to insert text + \param textData: Initial string for the text-element. + \param textAlign: Initial aligment of the text-data. + \param attributes: a list of attribute/value-pairs. If the list contains + attributes, that are not supported by text-primitives, or + values, that do not fit into the attributes, a + setAttributeError()-signal is emitted. + \param userData: An int for creating simIDs + \return a reference to the new tree-element + */ + virtual Node addText(const Node &parent + , const QString &textData + , HorizontalAlign textAlign + , const QList > &attributes + , int userData = SituationMap::IdHandler::BadId) = 0; + + + /** + Retrieve the text-element from a primitive, if any. + \param parent: the primitive where to get the text-node from + \return the text-node, or a null-node, if parent has no text. + */ + virtual Node getText(const Node &parent) const = 0; + + + /** + Retrieve the text-data from a text-node. If the given node is not a + text-node, a typeError() is emitted and an empty string returned. + \sa{ getText() } + + \param textNode: the text-node + \return a string with the text-data + */ + virtual QString getTextData(const Node &textNode) const = 0; + + + /** + Retrieve the defining points of a primitive + If the primitive has no points, a signal planModelError() is emitted, + since the model does not contain points where the user believes it should. + + \param node: reference to the primitive-node + \return QList containing the QPointF of the primitive + */ + virtual QList getPrimitivePoints(const Node &node) const = 0; + + + /** + Return the bounding box of a primitive or group of primitives or the + whole plan. If the given node has no bounding box (i.e. not a primitive) + the resulting rect is not valid. + \sa {translateNode(), moveNode(), rotateNode(), scaleNode() QRectF::isValid()} + \param node: The primitive or group. + */ + virtual QRectF getBoundingBox(const Node &node) const = 0; + + /** + Set user defined name of node, that will be used in GUI to label nodes. + \param node The primitive or group + \param name The name string + */ + virtual void setNodeDisplayName(const Node &node, const QString &name) = 0; + + /** + Set user defined name of node, that will be used in GUI to label nodes. + \param node The primitive or group + */ + virtual QString getNodeDisplayName(const Node &node) const = 0; + + + /** + Get a value for a global model-attribute. If the attribute does not exist in the + model a setAttributeError()-signal is emitted. + \sa { setModelAttribute(), setAttributeError(), hasAttribute(), attributeType() } + \param attribute: the attribute to get + */ + virtual QVariant getModelAttribute(Attribute attribute) const = 0; + + + /** + Get the value of an attribute. If the attribute does not exist in the given + node, a setAttributeError()-signal is emitted. + \sa { setAttributeError(), hasAttribute(), attributeType() } + \param node: reference to the primitive-node + \param attribute: the attribute to get + */ + virtual QVariant getAttribute(const Node &node, Attribute attribute) const = 0; + + /** + Get the value of an attribute of class type T from geometry model of type GeometryModelType. + \see getAttribute + */ + template T getAttribute(const Node &node, Attribute attribute) const + { + const GeometryModelType *geoModelPtr = dynamic_cast(this); + if (geoModelPtr) + return geoModelPtr->template getAttribute(node, attribute); + return T(); + } // template T getAttribute( const Node & node, Attribute attribute ) const + + + /** + Check, if a given node has a specific attribute. + \sa { setPlanAttribute(), getAttribute(), setAttributeError(), attributeType() } + \param node: reference to the primitive-node + \param attribute: the attribute to check + */ + virtual bool hasAttribute(const Node &node, Attribute attribute) const = 0; + + + /** + Get the type a an attribute. The type returned is a QVariant-type. + \sa { setPlanAttribute(), getAttribute(), setAttributeError(), hasAttribute() } + \param node: reference to the primitive-node + \param attribute: the attribute to check + */ + virtual QMetaType::Type attributeType(const Attribute &attribute) const = 0; + + + /** + Get a list of supported attributes from a primitive/node-type. If the given + type does not exist, a typeError()-signal is emitted. + \sa { setPlanAttribute(), getAttribute(), setAttributeError(), hasAttribute(), attributeType() } + \param type: the type of a primitive + */ + virtual QList supportedAttributes(PrimitiveType type) const = 0; + + + /** + Create a group from a list of primitives. + Signal changed() ist emitted. + \sa{ addPrimitive() } + \param primitives: A list of Elements which build the new group. May be + empty. Primitives can be added later to the group with + movePrimitiveIntoGroup() + \return a reference to the new group + */ + virtual Node createGroup(const NodeList &primitives) = 0; + + + /** + If the given Primitive is in a group, return the Group-Node, otherwise + return a Null-Node. + \param primitive: The primitive to look up. + \return A group-node or a Null-Node + */ + virtual Node isInGroup(const Node &primitive) const = 0; + + + /** + Return all primitives inside this group. If the given Node isn't a group, a + typeError() is emitted. + \param node: A group-node + \return: List of primitives in this group, possibly empty. + */ + virtual NodeList getGroupPrimitives(const Node &node) = 0; + + + /** + Find a primitive by its corresponding userData-int. If there is no + Primitive that corresponds to the userData, a Null-Node is returned. + \sa{ QDomNode::isNull() } + \param userData: An int for creating simIDs + \return pointer to primitive-node or NULL + */ + virtual Node findPrimitive(int userData) const = 0; + + + /** + Return \c true in case of existing user data. Otherwise the return value is false. + */ + virtual bool hasUserData(const Node &node) const = 0; + + /** + Get the userData from its corresponding primitive if any. + If no userData is associated with this Primitive, 0 is + returned and a typeError()-signal is sent and the bool opinted to by + ok is set to false. Otherwise it is set to true. + \param node: the primitive-node + \return userData-int + */ + virtual int getUserData(const Node &node, bool *ok = nullptr) const = 0; + + + /** + Set the userData of a primitive. + If this primitive can not have userData, a typeError()-signal is sent. + \param node: pointer to the primitive-node + \param userData: an int + */ + virtual void setUserData(const Node &node, int userData) = 0; + + + /** + Create and return a MultiMap with all UserData as Keys and the Primitives, + that contain that data as values. Used for searching Primitives by their + UserData (for example Selection-Check in GDW) + \param node: Start search for UserData at this node. This is normally a + Null-Node to search the whole tree. + \return QMultiMap with UserData and corresponding Primitives + */ + virtual QMultiMap userDataMap(const Node &node = Node()) = 0; + + + /** + Create a copy of a group or primitive and insert it into the tree. If a + group is copied, a deep copy including all its children is created. + Signal changed() ist emitted. + \param node: the group or primitive to copy. + \param userData: An int for creating simIDs + \return a reference to the new group (or NULL) + */ + virtual Node copy(const Node &node, int userData = SituationMap::IdHandler::BadId) = 0; + + + /** + Return the type of the given primitive. + \param node: the primitive or group + \return the type + */ + virtual PrimitiveType getType(const Node &node) const = 0; + + + /** + Return the number of undo-steps available. + \return number of undo-steps on the undo-stack + */ + virtual unsigned int undoSteps() const = 0; + + + /** + Return the number of redo-steps available. + \return number of redo-steps on the redo-stack + */ + virtual unsigned int redoSteps() const = 0; + + + /** + Return the undo history containing strings that describe the available + undo-steps + \return A list of strings + */ + virtual QStringList undoHistory() const = 0; + + + /** + Return the redo history containing strings that describe the available + redo-steps + \return A list of strings + */ + virtual QStringList redoHistory() const = 0; + + /** + Clear the undo-stack + */ + virtual void clearUndoStack() = 0; + + /** + Clear the redo-stack + */ + virtual void clearRedoStack() = 0; + + /** + Return a attribute-name from the enum for signals and logging + \param attribute: enum-value of attribute + \return name of attribute as string + */ + virtual QString attributeToString(const Attribute &attribute) const = 0; + + + /** + Parsing the attribute string of a dictionary symbol. The result is written to \c attrMap + \param sidcAttributes Attribute string + \param attrMap Result of parsing process + \return true in case of none errors, else false + */ + + virtual bool parseSymbolAttributeString(const QString &sidcAttributes, QMap &attrMap) const = 0; + + /** + Build attribute string to store at dictionary symbol. + */ + virtual QString buildSymbolAttributeString(const QMap &attrMap) const = 0; + +signals: + /** + A signal, that is emitted, when the plan changed. Only real changes are + signaled to reduce unnecessary updates. + + \param node: the changed node (if this is a Null-Node, the Root-Node of + the plan has changed) + \param att: The attribute, that changed. If att==MaxAttributes, then + multiple attributes are changed + \param coordsChanged: if true, the coordinated of the primitive are changed + */ + void changed(const Node &node, GeometryModel::Attribute att, bool coordsChanged); + + + /** + A signal, that is emitted, when the settings of the plan-editor have + changed. + \param what: Short description of the change for logging + */ + void modelChanged(const QString &what); + + + /** + A signal, that is emitted, when an invalid type-parameter was given + as argument. + \param error: Short description of the error for logging + */ + void typeError(const QString &error); + + + /** + A signal, that is emitted, when an invalid attribute-parameter was given + as argument. + \param error: Short description of the error for logging + */ + void attributeError(const QString &error); + + + /** + A signal, that is emitted, when a method detects an error or inconsistency + in the model. + \param error: Short description of the error for logging + */ + void modelError(const QString &error); + + + /** + A signal, that is emitted, when a methode detects an error. + */ + void criticalError(const QString &error); + + + /** + This signal is emitted for each userData, when cleanModel() is called. + \param userData: an int + */ + void userDataCleanup(int userData); + + /** + The signal is emitted when undo has been called. + */ + void nodeCleanup(const Node &node); + + /** + This signal is emitted for each connection created with \c Node::makeConnection() function, when cleanModel() is called. + \param connectedPtr: an IdHandler::AccessableId * + */ + void connectionCleanup(Referenced *connectedPtr); + + /** + This signal is emitted when the modification counter has been changed + */ + void modificationCount(unsigned int); +}; + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_export.h new file mode 100755 index 000000000..2f951b621 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_export.h @@ -0,0 +1,21 @@ +/** + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef GEOMETRYMODEL_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define GEOMETRYMODEL_EXPORT +# else +# if defined(MAKE_SITUATIONMAPGEOMETRYMODEL_LIB) +# define GEOMETRYMODEL_EXPORT Q_DECL_EXPORT +# else +# define GEOMETRYMODEL_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.cpp new file mode 100755 index 000000000..18de4ac9f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.cpp @@ -0,0 +1,58 @@ +/** +** +** @RDE_HEADER@ +** GeometryModelFactory to create geometry model +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 20.11.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + + +#include "geometry_model_factory.h" +#include "geometry_model_q_dom.h" + +namespace SituationMap +{ + +GeometryModelFactory *GeometryModelFactory::singleton() +{ + static GeometryModelFactory s_factory; + return &s_factory; +} /* GeometryModelFactory::singleton() */ + + +GeometryModel *GeometryModelFactory::create() +{ + return new GeometryModelQDom(); +} // GeometryModelFactory::create() + + +GeometryModel *GeometryModelFactory::clone(GeometryModel *geoModel) +{ + GeometryModelQDom * qdomModel = dynamic_cast(geoModel); + if (qdomModel) + return new GeometryModelQDom(*qdomModel); + + return nullptr; +} // GeometryModelFactory::clone() + +} /* namespace SituationMap */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.h new file mode 100755 index 000000000..7a66aaa4b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_factory.h @@ -0,0 +1,59 @@ +/** +** +** @RDE_HEADER@ +** GeometryModelFactory to create geometry model +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 20.11.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "geometry_model_q_dom.h" + + +namespace SituationMap +{ + +/** + @brief Factory class to create GeometryModels + + @see GeometryModel GeometryModelQDom +*/ + +class GEOMETRYMODEL_EXPORT GeometryModelFactory +{ +public: + + static GeometryModelFactory *singleton(); + + GeometryModelFactory() = default; + ~GeometryModelFactory() = default; + GeometryModelFactory(const GeometryModelFactory &) = delete; + GeometryModelFactory operator =(const GeometryModelFactory &) = delete; + + GeometryModel *create(); + GeometryModel *clone( GeometryModel * ); + +}; /* GeometryModelFactory */ + +} /* namespace SituationMap */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.cpp new file mode 100755 index 000000000..4908635d7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.cpp @@ -0,0 +1,2711 @@ +/** +** +** @RDE_HEADER@ +** GeometryModel implementation to write and read PlanEditor plans from/to XML and manipulate DOM-tree. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Stephan Kuhagen +** Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** ??.??.200?, SK : First edition for use with Qt4 +** 11.11.2015, JT : Splitting interface and implementation +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "geometry_model_q_dom.h" + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + + +template inline _Ty doExchange(_Ty &_Val, _Other && _New_val) +{ + // assign _New_val to _Val, return previous _Val + _Ty _Old_val = std::move(_Val); + _Val = std::forward<_Other>(_New_val); + return (_Old_val); +} + +namespace SituationMap +{ + +const int CoordinateDoublePrecision = 20; + +bool GeometryModelQDom::_staticInitDone = false; + +QMultiMap< GeometryModel::PrimitiveType , GeometryModel::Attribute > GeometryModelQDom::_primitiveSupportedAttributes; +QMap < QString , GeometryModel::PrimitiveType> GeometryModelQDom::_primitiveTagsTypes; +QMap < GeometryModel::PrimitiveType , QString > GeometryModelQDom::_primitiveTagsStrings; +QMap < QPair, QString > GeometryModelQDom::_attributeDefaults; +QMap < GeometryModel::Attribute , QMetaType::Type > GeometryModelQDom::_attributeTypes; + + +static const QLatin1String mapSettingsName("MapSettings"); +static const QLatin1String rootNodeName("GeometryModel"); +static const QLatin1String connectionIdName("connectionId"); +static const QLatin1String userDataName("userData"); + + +// convert to QString (with non locale-aware conversions) +QString GeometryModelQDom::variant2XmlString(const QVariant &v) +{ + switch (v.type()) + { + case QVariant::Int : return QString::fromLatin1(QByteArray::number(v.toInt())); break; + case QVariant::UInt : return QString::fromLatin1(QByteArray::number(v.toUInt())); break; + case QVariant::LongLong : return QString::fromLatin1(QByteArray::number(v.toLongLong())); break; + case QVariant::ULongLong : return QString::fromLatin1(QByteArray::number(v.toULongLong())); break; + case QVariant::Double : return QString::fromLatin1(QByteArray::number(v.toDouble(), 'f', -1)); break; + case QVariant::PointF : + { + const QPointF &point = v.toPointF(); + return QString::number(point.x(), 'f', CoordinateDoublePrecision) + QLatin1Char(' ') + QString::number(point.y(), 'f', CoordinateDoublePrecision); + } break; + + case QVariant::Color : + { + const QColor &color = v.value(); + + return QStringLiteral("#%1%2%3%4") + .arg(color.red() , 2, 16, QLatin1Char('0')) + .arg(color.green(), 2, 16, QLatin1Char('0')) + .arg(color.blue() , 2, 16, QLatin1Char('0')) + .arg(color.alpha(), 2, 16, QLatin1Char('0')); + } break; + + default: break; + } + + return v.toString(); +} // QString GeometryModelQDom::variant2XmlString( const QVariant & v ) + + + +QColor GeometryModelQDom::colorFromString(const QString &attColor) +{ + QColor result = Qt::black; // in case of wrong attColor the default is black. + + if (attColor.startsWith(QLatin1Char('#')) && attColor.length() == 9) + { + result = QColor::fromString(attColor.left(7)); + // Get Alpha-Channel + bool ok; + int alpha = attColor.right(2).toInt(&ok, 16); + if (ok) result.setAlpha(alpha); + } + else result = QColor::fromString(attColor); + + return result; +} // QColor GeometryModelQDom::colorFromString( const QString & attColor ) + + +void GeometryModelQDom::valueFromVariant(const QVariant &v, QPointF &result) { result = GeometryModelQDom::pointFromString(v.toString()); } +void GeometryModelQDom::valueFromVariant(const QVariant &v, QColor &result) { result = GeometryModelQDom::colorFromString(v.toString()); } + + +QPointF GeometryModelQDom::pointFromString(const QString &attPoint) +{ + QPointF result; + const auto xyStrList = attPoint.split(QLatin1Char(' ')); + + if (xyStrList.size() == 2) + { + bool okX = false; + bool okY = false; + + const double &x = xyStrList[0].toDouble(&okX); + const double &y = xyStrList[1].toDouble(&okY); + + if (okX && okY) + { + result.setX(x); + result.setY(y); + } + } + return result; +} // QPointF GeometryModelQDom::pointFromString( const QString & attPoint ) + + +#ifdef _WINDOWS +#pragma warning ( disable : 4172 ) +#endif + +class GeometryModel::Node::Implementation +{ + mutable QDomNode _domNode; + + static SituationMap::IdHandler *_idHandler; + + static SituationMap::IdHandler &idHandler() + { + if (!_idHandler) + _idHandler = new SituationMap::IdHandler(); + return *_idHandler; + } // idHandler() + + +public: + + Implementation() : _domNode() {;} + + Implementation(const QDomNode &domNode) : _domNode(domNode) + { + // The QDomNode is simply a smart pointer to internal QDom objects. For this the + // QDomNode handles the derived classes itself, so we don't habe to distinguish + // the different QDomNode classes, like QDomElement, QDomAttr ... Just use the copy + // ctor of QDomNode. + } // Implementation(const QDomNode & domNode) + + Implementation(const QDomElement &domElement) : _domNode(domElement) {;} + + Implementation(const Implementation &other) : Implementation(other._domNode) {;} + + ~Implementation() = default; + + Implementation &operator = (const QDomNode &domNode) + { + _domNode = domNode; + return *this; + } // operator = + + Implementation &operator = (const Implementation &other) + { + if (this != &other) _domNode = other._domNode; + return *this; + } // operator = + + + QDomNode *operator ->() const { return &_domNode; } + + const QDomNode &node() const { return _domNode; } + QDomNode &node() { return _domNode; } + + bool isValid() const { return !_domNode.isNull(); } + bool isRootNode() const { return (_domNode.nodeName() == rootNodeName); } + bool hasChildNodes() const { return _domNode.hasChildNodes(); } + + Node parentNode() const { return Node(Implementation(_domNode.parentNode())); } + Node next() const { return Node(Implementation(_domNode.nextSiblingElement())); } + Node firstChildNode() const { return Node(Implementation(_domNode.firstChildElement())); } + Node firstChildNode(const QString &nodeName) const { return Node(Implementation(_domNode.firstChildElement(nodeName))); } + + bool operator == (const Implementation &other) const { return (_domNode == other._domNode); } + bool operator != (const Implementation &other) const { return (_domNode != other._domNode); } + + + Referenced *connected() const + { + uint id = connectedId(); + if (id != SituationMap::IdHandler::BadId) + return static_cast(idHandler().accessId(id)); + return nullptr; + } // connected() const + + + uint connectedId() const + { + if (isValid()) + { + QDomElement e = _domNode.toElement(); + if (e.hasAttribute(connectionIdName)) + { + QString value = e.attribute(connectionIdName); + bool ok = false; + uint id = value.toUInt(&ok, 10); + + if ((ok) && (idHandler().isIdInUse(id))) + return id; + e.removeAttribute(connectionIdName); + } + } + return SituationMap::IdHandler::BadId; + } // connectedId() const + + + uint makeConnection(Referenced *objPtr) + { + if (objPtr != nullptr) + { + uint id = idHandler().createId(static_cast(objPtr)); + if (id != SituationMap::IdHandler::BadId) + { + QDomElement e = _domNode.toElement(); + e.setAttribute(connectionIdName, id); + return id; + } + } + return SituationMap::IdHandler::BadId; + } // makeConnection(SituationMap::IdHandler::AccessableId * objPtr) + + + bool unlinkConnection() const + { + uint id = connectedId(); + if (id != SituationMap::IdHandler::BadId) + { + idHandler().freeId(id); + QDomElement e = _domNode.toElement(); + e.removeAttribute(connectionIdName); + return true; + } + return false; + } // unlinkConnection() + +}; // GeometryModel::Node::Implementation + + +SituationMap::IdHandler *GeometryModel::Node::Implementation::_idHandler = nullptr; + + +#ifdef _WINDOWS +#pragma warning ( default : 4172 ) +#endif + + +GeometryModel::Node::Node() : _d(new Implementation()) {;} +GeometryModel::Node::Node(const Node &other) : _d(new Implementation(*(other._d))) {;} +GeometryModel::Node::Node(const Implementation &other) : _d(new Implementation(other)) {;} + +GeometryModel::Node::~Node() { delete _d; } + +GeometryModel::Node &GeometryModel::Node::operator= (const Node &other) +{ + if (this != &other) // self-assignment check + { + delete _d; + _d = new Implementation(*(other._d)); + } + return *this; +} + +GeometryModel::Node &GeometryModel::Node::operator = (Node &&other) +{ + if (this != &other) // self-assignment check + { + delete _d; + _d = doExchange(other._d, nullptr); + } + return *this; +} + + +const GeometryModel::Node::Implementation &GeometryModel::Node::get() const { return *_d; } +GeometryModel::Node::Implementation &GeometryModel::Node::get() { return *_d; } +bool GeometryModel::Node::isValid() const { return _d->isValid(); } +bool GeometryModel::Node::isRootNode() const { return _d->isRootNode(); } +GeometryModel::Node GeometryModel::Node::parentNode() const { return _d->parentNode(); } +bool GeometryModel::Node::hasChildNodes() const { return _d->hasChildNodes(); } +GeometryModel::Node GeometryModel::Node::next() const { return _d->next(); } +GeometryModel::Node GeometryModel::Node::firstChildNode() const { return _d->firstChildNode(); } +GeometryModel::Node GeometryModel::Node::firstChildNode(const QString &nodeName) const { return _d->firstChildNode(nodeName); } +bool GeometryModel::Node::operator ==(const Node &other) const { return ((*_d) == (*(other._d))); } +bool GeometryModel::Node::operator !=(const Node &other) const { return ((*_d) != (*(other._d))); } + +uint GeometryModel::Node::makeConnection(Referenced *objPtr) const { return _d->makeConnection(objPtr); } +bool GeometryModel::Node::unlinkConnection() const { return _d->unlinkConnection(); } +uint GeometryModel::Node::connectedId() const { return _d->connectedId(); } +Referenced *GeometryModel::Node::connected() const { return _d->connected(); } + + +GeometryModelQDom::GeometryModelQDom(QObject *parent) + : GeometryModel(parent) + , QDomDocument(QLatin1String(rootNodeName) + QLatin1String("Document")) + , _skipChangedSignal(false) + , _undoEnabled(true) + , _maxUndoSteps(100) + , _undoStack() + , _undoHistory() + , _redoStack() + , _redoHistory() + , _undoBlock() + , _undoHistoryBlock() + , _insideUndoBlock(false) + , _modificationCounter(0) +{ + init(); + + // OpenSuse13.2: + // Don´t really know why, but ModuleTest::checkOverlayHandler2/3 (dealing with GeometryModel) + // lead to SegFault at end of program, if 'cleanupStaticMember' is not added: + qAddPostRoutine(&GeometryModelQDom::cleanupStaticMember); + +} // GeometryModelQDom::GeometryModelQDom(QObject *parent) + + +GeometryModelQDom::GeometryModelQDom(const GeometryModelQDom &other) + : GeometryModel(other) + , QDomDocument(other.cloneNode().toDocument()) + , _skipChangedSignal(false) + , _undoEnabled(other._undoEnabled) + , _maxUndoSteps(other._maxUndoSteps) + , _undoStack() + , _undoHistory() + , _redoStack() + , _redoHistory() + , _undoBlock() + , _undoHistoryBlock() + , _insideUndoBlock(false) + , _modificationCounter(0) +{ +} // GeometryModelQDom::GeometryModelQDom(const GeometryModelQDom &other) + + +GeometryModelQDom::~GeometryModelQDom() +{ + cleanModel(); +} + + + +GeometryModelQDom & GeometryModelQDom::operator = (const GeometryModelQDom &other) +{ + fromByteArray(other.toByteArray()); + _undoEnabled = other._undoEnabled; + _maxUndoSteps = other._maxUndoSteps; + return *this; +} // GeometryModelQDom & GeometryModelQDom::operator = (const GeometryModelQDom &other) + + +/* ~~~~~~~~~~~~~~~~ create XML representation of GeometryModel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void xmlContent( const QDomDocument & domDocument, QByteArray & xmlContent ) +{ + xmlContent = domDocument.toByteArray(3); +} + +template MemoryClass xmlContent(GeometryModelQDom localCopy) +{ + MemoryClass xmlContentMem; + + // 'clean' just localCopy, not the original + // cleanModel removes userData; in other words: unlink; remove uniqueMapIds + localCopy.cleanModel(); + + QDomElement rootNode = localCopy.firstChildElement(rootNodeName); + + qDebug() << " xmlContent rootNode.isNull " << rootNode.isNull() + << ", rootNode.hasChildNodes " << rootNode.hasChildNodes() + << ", rootNode.hasAttributes " << rootNode.hasAttributes(); + + // rootNode may have ChildNodes and/or Attribute (like metaData) + // pay attention also to attributes + if ((rootNode.isNull()) || !(rootNode.hasChildNodes() || rootNode.hasAttributes()) ) + { + qDebug() << " empty -> use constant default string"; + // Don't return an empty string because file mamagement service will get an error when loading + // the empty file content. + xmlContentMem = MemoryClass::fromStdString(std::string("\n\n")); + } + else + xmlContent( localCopy, xmlContentMem ); + + qDebug() << " xmlContentMem " << xmlContentMem; + return xmlContentMem; +} // template MemoryClass xmlString(GeometryModelQDom localCopy) + + +QByteArray GeometryModelQDom::toByteArray() const +{ + return xmlContent(*this); +} + +//QString GeometryModelQDom::toString() const +//{ +// return xmlContent(*this); +//} +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + +QByteArray GeometryModelQDom::toByteArrayDebug() const { return QDomDocument::toByteArray(3); } // complete XML content for debug purposes + + +/* ~~~~~~~~~~~~~~~~ restore GeometryModel from XML representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +template bool parseXmlString(ContentClass content, GeometryModelQDom &geoModel) +{ + qDebug() << " parseXmlString 1"; + geoModel.clearUndoStack(); + geoModel.clearRedoStack(); + geoModel.cleanModel(); + bool ok = static_cast(geoModel.QDomDocument::setContent(content)); + + qDebug() << " parseXmlString geoModel.cleanModel() " << geoModel.toString(); + + geoModel.blockSignals(true); + { + geoModel.cleanModel(); + + qDebug() << " parseXmlString geoModel.cleanModel() " << geoModel.toString();; + + QDomElement rootNode = geoModel.firstChildElement(rootNodeName); + if (rootNode.isNull()) + { + QDomElement root = geoModel.createElement(rootNodeName); + geoModel.appendChild(root); + } + + } + geoModel.blockSignals(false); + qDebug() << " parseXmlString ok = " << ok; + return ok; +} // template bool parseXmlString(MemoryClass memory, GeometryModelQDom &geoModel) + + +bool GeometryModelQDom::fromByteArray(const QByteArray &content) +{ + if (content.isEmpty()) // allow empty content + { + blockSignals(true); + clearModel(); + blockSignals(false); + clearUndoStack(); + clearRedoStack(); + return true; + } + return parseXmlString(content,*this); +} // bool GeometryModelQDom::fromByteArray(const QByteArray &content) + + +bool GeometryModelQDom::fromDevice(QIODevice *device) +{ + if (device->size() < 1) // allow empty content + { + blockSignals(true); + clearModel(); + blockSignals(false); + clearUndoStack(); + clearRedoStack(); + return true; + } + return parseXmlString(device,*this); +} // bool GeometryModelQDom::fromDevice (QIODevice *device) + + +bool GeometryModelQDom::fromByteArrayDebug(const QByteArray &content) +{ + clearUndoStack(); + clearRedoStack(); + cleanModel(); + auto result = QDomDocument::setContent(content); + return static_cast(result); +} // bool GeometryModelQDom::fromStringDebug(const QByteArray &content) +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + +void GeometryModelQDom::setModelAttribute(Attribute attribute, const QVariant &value) +{ + QDomElement root = firstChildElement(rootNodeName); + qDebug() << "GeometryModelQDom isNull = " << root.isNull(); + + //const bool bEnableUndoRedo = true; //attribute != Attribute::MetaPixelBasedTextBoxes; + + pushUndo(QLatin1String("setModelAttribute")); + + if (supportedAttributes(PrimitiveType::RootNode).contains(attribute)) + root.setAttribute(attributeToString(attribute), variant2XmlString(value)); + else + { + emit attributeError(tr("Unknown or unsupported attribute for model root node: %1").arg(attributeToString(attribute))); + popUndo(); + return; + } + emitChangedSignal(Node(), attribute, false); +} // void GeometryModelQDom::setModelAttribute(Attribute attribute, const QVariant &value) + + +QVariant GeometryModelQDom::getModelAttribute(Attribute attribute) const +{ + QDomElement root = firstChildElement(rootNodeName); + + if (supportedAttributes(PrimitiveType::RootNode).contains(attribute)) + return getAttribute(root, attribute); + else + emit const_cast(this)->attributeError(tr("Unknown or unsupported attribute for model root node: %1").arg(attributeToString(attribute))); + return QVariant(); +} // QVariant GeometryModelQDom::getModelAttribute(Attribute attribute) const + + +void GeometryModelQDom::setModelEditorAttribute(Attribute attribute, const QVariant &value) +{ + QDomElement root = firstChildElement(mapSettingsName); + + switch (attribute) + { + case Attribute::PeMapBrightness: + case Attribute::PeMapCenterX: + case Attribute::PeMapCenterY: + case Attribute::PeMapSaturation: + case Attribute::PeMapZoom: + case Attribute::PeShowUtm: + case Attribute::PeSymbolOpacity: + case Attribute::PeSymbolFillType: + case Attribute::PeSymbolScale: + case Attribute::PeMapRotation: + case Attribute::PeMapType: + root.setAttribute(attributeToString(attribute), variant2XmlString(value)); + break; + + default: + emit attributeError(tr("Unknown or unsupported attribute for model-editor: %1").arg(attributeToString(attribute))); + return; + } + emitChangedSignal(Node(), attribute, false); +} // void GeometryModelQDom::setModelEditorAttribute(Attribute attribute, const QVariant &value) + + +void GeometryModelQDom::setPrimitiveAttribute(const Node &node, Attribute attribute, const QVariant &value) +{ + pushUndo(QLatin1String("setPrimitiveAttribute")); + // Check, if Attribute is supported by Primitive; if so, set it + if (hasAttribute(node, attribute)) + node.get()->toElement().setAttribute(attributeToString(attribute), variant2XmlString(value)); + else + { + emit attributeError(tr("Unknown or unsupported attribute for primitive: %1, Attribute: %2").arg(node.get()->nodeName(), attributeToString(attribute))); + + popUndo(); + return; + } + emitChangedSignal(node, attribute, false); +} // void GeometryModelQDom::setPrimitiveAttribute(QDomNode node...) + + +GeometryModel::Node GeometryModelQDom::addPrimitive( PrimitiveType type + , const QList &points + , const QList > &attributes + , int userData + , const QString &textData + , HorizontalAlign textAlign) +{ + QDomElement root = firstChildElement(rootNodeName); + bool undoEnabledRestore = _undoEnabled; + pushUndo(QLatin1String("addPrimitive")); + _undoEnabled = false; + // Create new node and set required attributes + + QDomElement primitiveDom; + + if (_primitiveTagsStrings.contains(type)) + primitiveDom = createElement(_primitiveTagsStrings[type]); + else + { + emit typeError(tr("Unknown Primitive-Type %1").arg(static_cast(type))); + _undoEnabled = undoEnabledRestore; + popUndo(); + return Node(); + } + + Node primitiveNode(primitiveDom); + + // No changed() inside this block + _skipChangedSignal = true; + + if (setPrimitivePoints(Node(primitiveDom), points)) + { + // Init Primitive with default-Values + using index = QPair; + QList primAttributes = supportedAttributes(type); + for (int i = 0; i < primAttributes.size(); ++i) + { + Attribute ap = primAttributes.at(i); + setPrimitiveAttribute(primitiveNode, ap, _attributeDefaults[index(type, ap)]); + } + + // Set Attributes given as parameter + for (int i = 0; i < attributes.size(); ++i) + setPrimitiveAttribute(primitiveNode, (attributes.at(i)).first, (attributes.at(i)).second); + + // Add textData, if any + if (! textData.isNull()) + setTextData(primitiveNode, textData, textAlign); + + // Insert userData as Attribute + if ((type != PrimitiveType::Node) && (type != PrimitiveType::RootNode)) + { + if (SituationMap::IdHandler::isIdValid(userData)) + { + QString userDataStr = QString::number(userData, 10); + primitiveDom.setAttribute(userDataName, userDataStr); + } + } + + Node result(root.appendChild(primitiveDom)); + _skipChangedSignal = false; + + emitChangedSignal(result, Attribute::MaxAttributes, true); + + _undoEnabled = undoEnabledRestore; + return result; + } + else + { + _undoEnabled = undoEnabledRestore; + popUndo(); + return Node(); + } +} // QDomNode GeometryModelQDom::addPrimitive(PrimitiveType type, ...) + + +GeometryModel::Node GeometryModelQDom::addText(const Node &parent + , const QString &textData + , HorizontalAlign textAlign + , const QList > &attributes + , int userData) +{ + pushUndo(QLatin1String("addText")); + + // Check, if parent is of correct type + QString tag = parent.get()->nodeName(); + PrimitiveType type = getType(parent); + + // Get Position (center of bounding box) + // TODO: Position of text depends on Parent-Type + // TODO: on moveNode, translateNode,... transform text also + QRectF parentBox = getBoundingBox(parent); + QList pointList; + + switch (type) + { + case PrimitiveType::Ellipse: + case PrimitiveType::Line: + case PrimitiveType::Polyline: + case PrimitiveType::Rectangle: + case PrimitiveType::Spline: + pointList.append(parentBox.topLeft()); + break; + + default: + emit typeError(tr("Can not add text to type %1").arg(tag)); + popUndo(); + return Node(); + } + + // Create Text-Node and reparent to parent + Node textNode(addPrimitive(PrimitiveType::Text, pointList, attributes, userData)); + parent.get()->appendChild(textNode.get().node()); + + // Set textData and alignment + QDomElement textDataNode = (textNode.get()->appendChild(createElement(QLatin1String("text_data")))).toElement(); + textDataNode.setAttribute(QLatin1String("alignment"), QString::number((int)textAlign, 10)); + QDomCDATASection cdata = createCDATASection(textData); + emitChangedSignal(parent, Attribute::MaxAttributes, false); + return Node(Node::Implementation(textDataNode.appendChild(cdata))); +} // QDomNode GeometryModelQDom::addText(QDomNode parent, const QString &textData,... + + +GeometryModel::Node GeometryModelQDom::getRootNode() const { return Node(Node::Implementation(firstChildElement(rootNodeName))); } + + +bool GeometryModelQDom::isEmpty() const +{ + QDomElement rootNode = firstChildElement(rootNodeName); + return (!rootNode.hasChildNodes()); +} // bool GeometryModelQDom::isEmpty() const + + +GeometryModel::Node GeometryModelQDom::getText(const Node &parent) const +{ + // Check, if parent is of correct type + PrimitiveType type = getType(parent); + + switch (type) + { + case PrimitiveType::Ellipse: + case PrimitiveType::Line: + case PrimitiveType::Polyline: + case PrimitiveType::Rectangle: + case PrimitiveType::Spline: + return Node(Node::Implementation(parent.get()->firstChildElement(QLatin1String("text")))); + + default: + return Node(); + } +} // QDomNode GeometryModelQDom::getText(QDomNode parent) + + +QString GeometryModelQDom::getTextData(const Node &node) const +{ + QString result; + // Check, if parent is of correct type + QString tag = node.get()->nodeName(); + if (tag != QLatin1String("text")) + emit const_cast(this)->typeError(tr("Can not get text-data from %1").arg(tag)); + else + { + QDomNode textData = node.get()->firstChildElement(QLatin1String("text_data")); + QDomNode data = textData.firstChild(); + if (! data.isNull()) + result = data.nodeValue(); + } + return result; +} // QString GeometryModelQDom::getTextData(QDomNode textNode) + + +QList GeometryModelQDom::getPrimitivePoints(const Node &node) const { return getPrimitivePoints(node.get().node()); } + + +QList GeometryModelQDom::getPrimitivePoints(const QDomNode &node) const +{ + const QString &tag = node.nodeName(); + PrimitiveType type = getType(node); + + // set the number of required points for this primitive... + QList result; + int minPoints = 0; + int maxPoints = 0; + + switch (type) + { + + case PrimitiveType::DictionarySymbol: + { + minPoints = 1; + maxPoints = INT_MAX; + } break; + + case PrimitiveType::Ellipse: + case PrimitiveType::Rectangle: + { + minPoints = 4; + maxPoints = 4; + } break; + + case PrimitiveType::ParticipantSymbol: + case PrimitiveType::Symbol: + case PrimitiveType::Text: + { + minPoints = 1; + maxPoints = 1; + } break; + + case PrimitiveType::Line: + { + minPoints = 2; + maxPoints = 2; + } break; + + case PrimitiveType::Polyline: + case PrimitiveType::Spline: + { + minPoints = 3; + maxPoints = INT_MAX; + } break; + + default: + { + emit const_cast(this)->modelError(tr("No points in primitive of type %1").arg(tag)); + return result; + } + } // switch + + // ... and get the points + QDomNodeList nodes = node.childNodes(); + if (nodes.isEmpty()) + emit const_cast(this)->modelError(tr("No points found in primitive of type %1").arg(tag)); + else + { + for (int i = 0; i < nodes.size(); ++i) + { + QDomElement p = nodes.at(i).toElement(); + if (p.nodeName() != QLatin1String("point")) continue; + if (!p.hasAttribute(QLatin1String("x")) || !p.hasAttribute(QLatin1String("y"))) + emit const_cast(this)->modelError(tr("Invalid point in primitive of type %1").arg(tag)); + else + { + bool okx, oky; + double x = p.toElement().attribute(QLatin1String("x")).toDouble(&okx); + double y = p.toElement().attribute(QLatin1String("y")).toDouble(&oky); + if (!(okx && oky)) + emit const_cast(this)->modelError(tr("Invalid point in primitive of type %1").arg(tag)); + else + result.append(QPointF(x, y)); + } + } + } + + // Check points + if ((result.size() < minPoints) || (result.size() > maxPoints)) + emit const_cast(this)->attributeError(tr("Wrong number of points for new primitive %1").arg(_primitiveTagsStrings[type])); + + return result; +} // QList GeometryModelQDom::getPrimitivePoints(QDomNode node) const + + +QRectF GeometryModelQDom::getBoundingBox(const Node &node) const { return getBoundingBox(node.get().node()); } + + +QRectF GeometryModelQDom::getBoundingBox(const QDomNode &node) const +{ + PrimitiveType type = getType(node); + + QRectF result; + if ((type == PrimitiveType::Node) || (type == PrimitiveType::RootNode) || (node == *this)) + { + // Recursive over all children + QRectF merge; + QDomNodeList children; + if ((type == PrimitiveType::Node) || (type == PrimitiveType::RootNode)) + { + // Children of other Node + children = node.childNodes(); + } + else + children = node.firstChildElement(rootNodeName).childNodes(); + for (int i = 0; i < children.size(); ++i) + { + merge = getBoundingBox(children.at(i)); + if (! merge.isNull()) + result = result.isNull() ? merge : result.united(merge); + } + + } + else + { + // TODO Add calculation for rotated Primitives + + bool ok; + QList points = getPrimitivePoints(node); + double width, height; + + switch (type) + { + case PrimitiveType::Ellipse: + case PrimitiveType::Rectangle: + width = getAttribute(node, Attribute::Width).toDouble(&ok); + height = getAttribute(node, Attribute::Height).toDouble(&ok); + if (ok && (points.size() == 1)) + { + result.setRect(points[0].x(), points[0].y(), width, height); + result.moveCenter(points[0]); + } + break; + + case PrimitiveType::Line: + if (points.size() == 2) + result = QRectF(points[0], points[1]).normalized(); + break; + + case PrimitiveType::DictionarySymbol: + if (points.size() > 2) + { + result = QRectF(points.at(0), points.at(1)); + for (int i = 2; i < points.count(); ++i) + result = result.united(QRectF(points.at(0), points.at(i))); + } + else + { + result = QRectF(points[0].x(), points[0].y(), 1.0, 1.0); + result.moveCenter(points[0]); + } + break; + + case PrimitiveType::Polyline: + case PrimitiveType::Spline: + if (points.size() > 2) + { + result = QRectF(points.at(0), points.at(1)); + for (int i = 2; i < points.count(); ++i) + result = result.united(QRectF(points.at(0), points.at(i))); + } + break; + + case PrimitiveType::ParticipantSymbol: + case PrimitiveType::Symbol: + case PrimitiveType::Text: + if (points.size() == 1) + { + // TODO Calculate real dimensions of Textbox and Symbol? + // Only possible with more information from GDW implementation, Map Scale etc. + result = QRectF(0, 0, 0, 0); + result.moveCenter(points[0]); + } + break; + + default: + break; + } + } // if ( type == Node ) {...} else... + + return result; +} // QRectF GeometryModelQDom::getBoundingBox(QDomNode node) + + + +void GeometryModelQDom::movePrimitiveIntoGroup(const Node &primitive, const Node &target) +{ + pushUndo(QLatin1String("movePrimitiveIntoGroup")); + + QString tag = primitive.get()->nodeName(); + if (target.get()->nodeName() != QLatin1String("node")) + { + emit typeError(tr("Can not move primitive into %1").arg(target.get()->nodeName())); + popUndo(); + return; + } + if (_primitiveTagsTypes.contains(tag)) + { + target.get()->appendChild(primitive.get().node()); + emitChangedSignal(primitive, Attribute::MaxAttributes, false); + } +} // void GeometryModelQDom::movePrimitiveIntoGroup(QDomNode primitive, QDomNode target) + + +void GeometryModelQDom::releaseGroup(const Node &group) +{ + pushUndo(QLatin1String("releaseGroup")); + + if (group.get()->nodeName() != QLatin1String("node")) + { + emit typeError(tr("Given node is not a group: %1").arg(group.get()->nodeName())); + popUndo(); + return; + } + + QDomNode parent = group.get()->parentNode(); + while (group.get()->hasChildNodes()) + parent.appendChild(group.get()->firstChild()); + parent.removeChild(group.get().node()); + + emitChangedSignal(Node(), Attribute::MaxAttributes, false); + +} // void GeometryModelQDom::releaseGroup(QDomNode group) + + +void GeometryModelQDom::deleteGroup(const Node &group) +{ + if (!group.isValid()) return; + + pushUndo(QLatin1String("deleteGroup")); + + if (group.get()->nodeName() != QLatin1String("node")) + { + emit typeError(tr("Given node is not a group: %1").arg(group.get()->nodeName())); + popUndo(); + return; + } + + QDomNode parent = group.get()->parentNode(); + parent.removeChild(group.get().node()); + + emitChangedSignal(Node(), Attribute::MaxAttributes, false); +} // void GeometryModelQDom::deleteGroup(QDomNode group) + + +void GeometryModelQDom::removePrimitive(const Node &primitive) +{ + _skipChangedSignal = true; + removePrimitive(primitive.get().node()); + _skipChangedSignal = false; +} + + +void GeometryModelQDom::removePrimitive(const QDomNode &primitive) +{ + // could be a group node + while (primitive.hasChildNodes()) + { + removePrimitive(primitive.firstChild()); + } + + // Remove userData + bool ok; + int userData = getUserData(primitive, &ok); + if (ok) + { + primitive.toElement().removeAttribute(userDataName); + emit userDataCleanup(userData); + } + + // pushUndo() here, because we must not save the primitive with userData, since + // the GeISiR2-Primitive is removed from the Map + pushUndo(QLatin1String("removePrimitive")); + QDomNode parent = primitive.parentNode(); + QDomNode oldChild = parent.removeChild(primitive); + + if (! oldChild.isNull()) + emitChangedSignal(Node(), Attribute::MaxAttributes, false); + else popUndo(); +} // void GeometryModelQDom::removePrimitive(QDomNode primitive) + + +void GeometryModelQDom::removePrimitivesByUserData(const QList &userDataList) +{ + _skipChangedSignal = true; + int removed = 0; + // First delete userData, then make the undo-backup + QList nodeList; + + nodeList.reserve(userDataList.size()); + for (int i = 0; i < userDataList.size(); ++i) + { + Node node = findPrimitive(userDataList.at(i)); + node.get()->toElement().removeAttribute(userDataName); + nodeList.append(node.get().node()); + } + startUndoBlock(); + for (int i = 0; i < nodeList.size(); ++i) + { + removePrimitive(nodeList.at(i)); + ++removed; + } + endUndoBlock(QLatin1String("removePrimitivesByUserData")); + _skipChangedSignal = false; + + if (removed > 0) + emitChangedSignal(Node(), Attribute::MaxAttributes, false); +} // void GeometryModelQDom::removePrimitivesByUserData(QList userDataList) + + +void GeometryModelQDom::removeText(Node &parent) +{ + pushUndo(QLatin1String("removeText")); + + // Check, if parent is of correct type + PrimitiveType type = getType(parent); + + switch (type) + { + case PrimitiveType::Ellipse: + case PrimitiveType::Line: + case PrimitiveType::Polyline: + case PrimitiveType::Rectangle: + case PrimitiveType::Spline: + parent.get()->removeChild(parent.get()->firstChildElement(QLatin1String("text"))); + emitChangedSignal(parent, Attribute::MaxAttributes, false); + break; + + default: + popUndo(); + return; + } +} // void GeometryModelQDom::removeText(QDomNode parent) + + +void GeometryModelQDom::setTextData(const Node &textNode, const QString &data, HorizontalAlign textAlign) +{ + pushUndo(QLatin1String("setTextData")); + + // Check, if parent is of correct type + const QString &tag = textNode.get()->nodeName(); + if (tag != QLatin1String("text")) + { + emit typeError(tr("Can not set text-data for %1").arg(tag)); + popUndo(); + } + else + { + QDomNode textData = textNode.get()->firstChildElement(QLatin1String("text_data")); + if (textData.isNull()) + textData = (textNode.get()->appendChild(createElement(QLatin1String("text_data")))).toElement(); + textData.toElement().setAttribute(QLatin1String("alignment"), QString::number((int)textAlign, 10)); + textData.removeChild(textData.firstChild()); + textData.appendChild(createCDATASection(data)); + + emitChangedSignal(textNode, Attribute::MaxAttributes, false); + } +} // void GeometryModelQDom::setTextData(QDomNode textNode, const QString &data, HorizontalAlign textAlign) + + +void GeometryModelQDom::clearModel() +{ + qDebug() << " GeometryModelQDom::clearModel()"; + + QDomElement root = firstChildElement(rootNodeName); + pushUndo(QLatin1String("clearPlan")); + + bool oldSCS = _skipChangedSignal; + bool oldUE = _undoEnabled; + _skipChangedSignal = true; + _undoEnabled = false; + while (root.hasChildNodes()) + removePrimitive(root.firstChild()); + _skipChangedSignal = oldSCS; + _undoEnabled = oldUE; + + const auto supportedAttribs = supportedAttributes(GeometryModel::PrimitiveType::RootNode); + for (const GeometryModel::Attribute &attr : supportedAttribs) + { + const QString & attrName = attributeToString(attr); + if (root.hasAttribute(attrName)) + { + root.removeAttribute(attrName); + } + } + emitChangedSignal(Node(), Attribute::MaxAttributes, false); +} // void GeometryModelQDom::clearModel() + + +bool GeometryModelQDom::setPrimitivePoints(const Node &node, const QList &points) +{ + PrimitiveType type = getType(node); + + // Check number of points and insert them + int minPoints = 0; + int maxPoints = 0; + switch (type) + { + case PrimitiveType::DictionarySymbol: + { + minPoints = 1; + maxPoints = INT_MAX; + } break; + + case PrimitiveType::Ellipse: + case PrimitiveType::Rectangle: + { + minPoints = 4; + maxPoints = 4; + } break; + + case PrimitiveType::ParticipantSymbol: + case PrimitiveType::Symbol: + case PrimitiveType::Text: + { + minPoints = 1; + maxPoints = 1; + } break; + + case PrimitiveType::Line: + { + minPoints = 2; + maxPoints = 2; + } break; + + case PrimitiveType::Polyline: + case PrimitiveType::Spline: + { + minPoints = 3; + maxPoints = INT_MAX; + } break; + + case PrimitiveType::RootNode: + case PrimitiveType::Node: + { + if (points.isEmpty()) return true; + } break; + + default: break; + } + + if ((points.size() < minPoints) || (points.size() > maxPoints)) + { + emit attributeError(tr("Wrong number of points for primitive %1").arg(_primitiveTagsStrings[type])); + return false; + } + else + { + + pushUndo(QLatin1String("setPrimitivePoints")); + + // Remove old Points before Adding + QDomElement p = node.get()->firstChildElement(QLatin1String("point")); + while (! p.isNull()) + { + node.get()->removeChild(p); + p = node.get()->firstChildElement(QLatin1String("point")); + } + + for (int i = 0; i < points.size(); ++i) + { + QDomElement point = (node.get()->appendChild(createElement(QLatin1String("point")))).toElement(); + // QString::number() does NOT honor locale settings: + point.setAttribute(QLatin1String("x"), QString::number(points.at(i).x(), 'f', CoordinateDoublePrecision)); // to enforce "1.23" instead of "1,23" + point.setAttribute(QLatin1String("y"), QString::number(points.at(i).y(), 'f', CoordinateDoublePrecision)); + } + emitChangedSignal(node, Attribute::None, true); + } + return true; +} // bool GeometryModelQDom::setPrimitivePoints(QDomNode node, QList points) + + +void GeometryModelQDom::translateNode(const Node &node, double x, double y) { translateNode(node.get().node(), x, y); } + +void GeometryModelQDom::translateNode(const QDomNode &node, double x, double y) +{ + pushUndo(QLatin1String("translateNode")); + + QTransform t; + t = t.translate(x, y); + + if (node == *this) + { + // translate complete plan + QDomElement root = node.firstChildElement(rootNodeName); + QDomNodeList children = root.childNodes(); + // No changed() inside this block + _skipChangedSignal = true; + for (int i = 0; i < children.size(); ++i) + translateNode(children.at(i), x, y); + _skipChangedSignal = false; + } + else + { + QString tag = node.nodeName(); + if (tag == QLatin1String("node")) + { + QDomNodeList children = node.childNodes(); + for (int i = 0; i < children.size(); ++i) + translateNode(children.at(i), x, y); + } + else + transformNode(node, t); + } + + emitChangedSignal(Node(Node::Implementation(node)), Attribute::None, true); +} // void GeometryModelQDom::translateNode(QDomNode node, double x, double y) + + +void GeometryModelQDom::moveNode(const Node &node, double x, double y) { moveNode(node.get().node(), x, y); } + + +void GeometryModelQDom::moveNode(const QDomNode &node, double x, double y) +{ + pushUndo(QLatin1String("moveNode")); + + // Move to (x,y) + QRectF bb = getBoundingBox(node); + QPointF center = bb.center(); + + translateNode(node, -center.x() + x, -center.y() + y); + + emitChangedSignal(Node(Node::Implementation(node)), Attribute::None, true); +} // void GeometryModelQDom::moveNode(QDomNode node, double x, double y) + + +void GeometryModelQDom::scaleNode(const Node &node, double scaleX, double scaleY) { scaleNode(node.get().node(), scaleX, scaleY); } + + +void GeometryModelQDom::scaleNode(const QDomNode &node, double scaleX, double scaleY) +{ + pushUndo(QLatin1String("scaleNode")); + + // Translate to (0.0, 0.0) + QRectF bb = getBoundingBox(node); + QPointF center = bb.center(); + translateNode(node, -center.x(), -center.y()); + + // Scale + QTransform t; + t = t.scale(scaleX, scaleY); + QString tag = node.nodeName(); + if (tag == QLatin1String("node")) + { + QDomNodeList children = node.childNodes(); + for (int i = 0; i < children.size(); ++i) + transformNode(children.at(i), t); + } + else transformNode(node, t); + + + // Translate back + translateNode(node, center.x(), center.y()); + + emitChangedSignal(Node(Node::Implementation(node)), Attribute::None, true); +} // void GeometryModelQDom::scaleNode(QDomNode node, double scaleX, double scaleY) + + +void GeometryModelQDom::rotateNode(const Node &node, double angle) { rotateNode(node.get().node(), angle); } + +void GeometryModelQDom::rotateNode(const QDomNode &node, double angle) +{ + pushUndo(QLatin1String("rotateNode")); + + // Translate to (0.0, 0.0) + QRectF bb = getBoundingBox(node); + QPointF center = bb.center(); + translateNode(node, -center.x(), -center.y()); + + // Rotate + QTransform t; + t = t.rotate(angle); + QString tag = node.nodeName(); + if (tag == QLatin1String("node")) + { + QDomNodeList children = node.childNodes(); + for (int i = 0; i < children.size(); ++i) + transformNode(children.at(i), t); + } + else transformNode(node, t); + + + // Translate back + translateNode(node, center.x(), center.y()); + + emitChangedSignal(Node(Node::Implementation(node)), Attribute::Rotate, true); + +} // void GeometryModelQDom::rotateNode(QDomNode node, double angle) + + +void GeometryModelQDom::reorderPrimitives(const NodeList &nodes, PrimitivOrder order) +{ + QDomElement root = firstChildElement(rootNodeName); + if (nodes.length() > 0) + { + pushUndo(QLatin1String("reorderPrimitives")); + + if (order == GeometryModelQDom::PrimitivOrder::Lower) + { + QDomNode before; + for (int i = nodes.length() - 1; i >= 0; --i) + before = root.insertBefore(nodes.at(i).get().node(), before); + } + else if (order == GeometryModelQDom::PrimitivOrder::Raise) + { + for (int i = 0; i < nodes.length(); ++i) + root.insertAfter(nodes.at(i).get().node(), QDomNode()); + } + + emitChangedSignal(Node(), Attribute::MaxAttributes, false); + } +} // void GeometryModelQDom::reorderPrimitives(const QList &nodes, t_PrimOrder order) + + +void GeometryModelQDom::merge(const Node &other, bool skipUndo, bool intoNewGroup) { merge(other.get().node(), skipUndo, intoNewGroup); } + + +void GeometryModelQDom::merge(GeometryModel *other, bool skipUndo, bool intoNewGroup) +{ + const GeometryModelQDom *otherQDom = dynamic_cast(other); + merge(*otherQDom, skipUndo, intoNewGroup); + +} // void GeometryModelQDom::merge( GeometryModel * other, bool skipUndo, bool intoNewGroup ) + + +void GeometryModelQDom::merge(const QDomNode &other, bool skipUndo, bool intoNewGroup) +{ + if ((other == *this) || (other.ownerDocument() == *this)) + { + // Do not merge with itself or own child + return; + } + + if (! skipUndo) + pushUndo(QLatin1String("merge")); + + QString tag = other.nodeName(); + if (other.nodeType() == QDomNode::DocumentNode) + { + // Its a complete plan, add recursive + QDomElement root = other.firstChildElement(rootNodeName); + // No changed() inside this block + _skipChangedSignal = true; + if (intoNewGroup) + { + QList nodeList; + QDomNodeList domNodeList = root.childNodes(); + nodeList.reserve(domNodeList.size()); + for (int i = 0; i < domNodeList.size(); ++i) + nodeList.append(domNodeList.at(i)); + const QDomNode &group = createGroup(nodeList); + setNodeDisplayName(Node(group), tr("Imported Group")); + } + else + { + while (root.hasChildNodes()) + merge(root.firstChild(), true); + } + _skipChangedSignal = false; + } + else if ((getType(other) == PrimitiveType::Unknown) || (getType(other) == PrimitiveType::MapDefaults)) + { + // Unknown type error + emit typeError(tr("Can not merge with %1").arg(tag)); + if (! skipUndo) + popUndo(); + return; + } + else + { + // Insert + QDomElement docRoot = firstChildElement(rootNodeName); + docRoot.appendChild(other); + } + + emitChangedSignal(Node(), Attribute::MaxAttributes, false); +} // void PlanModel::merge(QDomNode other, bool skipUndo, bool intoNewGroup) + + +unsigned int GeometryModelQDom::modificationCounter() const { return _modificationCounter; } + + +void GeometryModelQDom::resetModificationCounter() +{ + _modificationCounter = 0; + emit modificationCount(_modificationCounter); +} // void GeometryModelQDom::resetModificationCounter() + + +void GeometryModelQDom::undo(unsigned int steps) +{ + if (undoSteps() < 1) return; + if (steps < 1) return; + + if (_insideUndoBlock) + endUndoBlock(); + + emit nodeCleanup(Node(firstChildElement(rootNodeName))); + while ((steps > 0) && (undoSteps() > 0)) + { + QDomNode root = firstChildElement(rootNodeName); + QDomNode undoNode = _undoStack.pop(); + + _redoStack.push(root.cloneNode()); + _redoHistory.push(_undoHistory.pop()); + + root = replaceChild(undoNode, root); + + if (root.isNull()) // was replaceChild successful? + { + emit criticalError(tr("Undo function failed.")); + break; + } + else + { + --steps; + } + --_modificationCounter; + emit modificationCount(_modificationCounter); + } + + emitChangedSignal(Node(), Attribute::MaxAttributes, true); + +} // void GeometryModelQDom::undo(unsigned int steps) + + +void GeometryModelQDom::redo(unsigned int steps) +{ + if (redoSteps() < 1) return; + if (steps < 1) return; + + emit nodeCleanup(Node(firstChildElement(rootNodeName))); + + while ((steps > 0) && (redoSteps() > 0)) + { + QDomNode root = firstChildElement(rootNodeName); + QDomNode redoNode = _redoStack.pop(); + + _undoStack.push(root.cloneNode()); + _undoHistory.push(_redoHistory.pop()); + + root = replaceChild(redoNode, root); + + if (root.isNull()) // was replaceChild successful? + { + emit criticalError(tr("Redo function failed.")); + break; + } + --steps; + ++_modificationCounter; + emit modificationCount(_modificationCounter); + } + emitChangedSignal(Node(), Attribute::MaxAttributes, true); + +} // void GeometryModelQDom::redo(unsigned int steps) + + +void GeometryModelQDom::enableUndo(bool enable) +{ + _undoEnabled = enable; +} // void GeometryModelQDom::enableUndo(bool enable) + + +void GeometryModelQDom::startUndoBlock() +{ + if (! _insideUndoBlock) + { + QDomElement root = firstChildElement(rootNodeName); + _undoBlock = root.cloneNode(); + } + _insideUndoBlock = true; +} // void GeometryModelQDom::startUndoBlock() + + +void GeometryModelQDom::abortUndoBlock() +{ + if (_insideUndoBlock) + _undoBlock.clear(); +} // void GeometryModelQDom::abortUndoBlock() + + +void GeometryModelQDom::endUndoBlock(const QString &eventDescription) +{ + if (_insideUndoBlock) + { + if (_undoEnabled && (! _undoBlock.isNull())) + { + while (undoSteps() >= _maxUndoSteps) + { + // Size-limit reached, remove first element + _undoStack.erase(_undoStack.begin()); + _undoHistory.erase(_undoHistory.begin()); + } + _undoStack.push(_undoBlock); + _undoHistory.push((eventDescription.isEmpty() ? _undoHistoryBlock : eventDescription)); + clearRedoStack(); + ++_modificationCounter; + emit modificationCount(_modificationCounter); + } + } + _insideUndoBlock = false; +} // void GeometryModelQDom::endUndoBlock(const QString &eventDescription) + + +void GeometryModelQDom::setMaxUndoSteps(unsigned int max) +{ + _maxUndoSteps = (max < 1) ? 1 : max; + + while (undoSteps() > _maxUndoSteps) + { + // shrink to max size, if needed. + _undoStack.erase(_undoStack.begin()); + _undoHistory.erase(_undoHistory.begin()); + } +} // void GeometryModelQDom::setMaxUndoSteps(int max) + + +unsigned int GeometryModelQDom::maxUndoSteps() const +{ + return _maxUndoSteps; +} + + +void GeometryModelQDom::cleanModel() +{ + qDebug() << " GeometryModelQDom::cleanModel()"; + + QMap::iterator it = _primitiveTagsTypes.begin(); + while (it != _primitiveTagsTypes.end()) + { + QDomNodeList nodes = elementsByTagName(it.key()); + for (int i = 0; i < nodes.size(); ++i) + { + if ((_primitiveTagsTypes[it.key()] == PrimitiveType::Node) || (_primitiveTagsTypes[it.key()] == PrimitiveType::RootNode)) + { + // Remove empty groups + QDomNode group = nodes.at(i); + + if ( ! (group.hasChildNodes() || group.hasAttributes()) ) + { + group.parentNode().removeChild(group); + } + } + else + { + // Remove userData + const QDomNode &node = nodes.at(i); + if (hasUserData(node)) + { + bool ok; + int userData = getUserData(node, &ok); + if (ok) + { + nodes.at(i).toElement().removeAttribute(userDataName); + emit userDataCleanup(userData); + } + } + } + + GeometryModel::Node::Implementation nodeImpl(nodes.at(i)); + emit connectionCleanup(nodeImpl.connected()); + nodeImpl.unlinkConnection(); + } + ++it; + } + + resetModificationCounter(); +} // void GeometryModelQDom::cleanModel() + + +void GeometryModelQDom::setNodeDisplayName(const Node &node, const QString &name) +{ + const QString attName = QLatin1String("GuiDisplayName"); + QDomElement e = node.get().node().toElement(); + + if (e.isNull()) + { + qCritical() << " node not found"; + return; + } + + e.setAttribute(attName, name); + + emitChangedSignal(node, Attribute::MaxAttributes, false); + +} // void GeometryModelQDom::setNodeDisplayName(const Node & node, const QString & name) + + +QString GeometryModelQDom::getNodeDisplayName(const Node &node) const +{ + const QString attName = QLatin1String("GuiDisplayName"); + QDomElement e = node.get().node().toElement(); + + return (e.hasAttribute(attName)) ? e.attribute(attName) : QString(); + +} // QString GeometryModelQDom::getNodeDisplayName(const Node & node) const + + +QVariant GeometryModelQDom::getAttribute(const Node &node, Attribute attribute) const { return getAttribute(node.get().node(), attribute); } + +QVariant GeometryModelQDom::getAttribute(const QDomNode &node, Attribute attribute) const +{ + QString att = attributeToString(attribute); + QDomElement e = node.toElement(); + + if (e.hasAttribute(att)) + { + QString value = e.attribute(att); + + if (_attributeTypes[attribute] == QMetaType::QColor) + { + // Return Colors as QString and let the user convert it with static method + // PlanModel::colorFromString() - QVariant ignores alpha in Colors + return QVariant(colorFromString(value)); + } + else if (_attributeTypes[attribute] == QMetaType::QPointF) + return QVariant(pointFromString(value)); + else + { + if (_attributeTypes[attribute] == QMetaType::User) + { + // Return PlanModel-Enums as String-Value + // TODO: Implement UserTypes? + return QVariant(value); + } + else + { + // Convert other values to their native type + QVariant result(value); + return result; + } + } + } + else + { + // Check for default + if (hasAttribute(node, attribute)) + { + using index = QPair; + QVariant result = QVariant::fromValue(_attributeDefaults[index(PrimitiveType::Unknown, attribute)]); + return result; + } + else + { + emit const_cast(this)->attributeError(att + tr(": Attribute unknown in primitive %1").arg(node.nodeName())); + return QVariant(); + } + } + + return QVariant(); +} // QVariant GeometryModelQDom::getAttribute(QDomNode node, Attribute attribute) + + +bool GeometryModelQDom::hasAttribute(const Node &node, Attribute attribute) const { return hasAttribute(node.get().node(), attribute); } + + +bool GeometryModelQDom::hasAttribute(const QDomNode &node, Attribute attribute) const +{ + QString tag = node.nodeName(); + if (_primitiveTagsTypes.contains(tag)) + { + PrimitiveType t = _primitiveTagsTypes[tag]; + if (_primitiveSupportedAttributes.contains(t)) + return supportedAttributes(t).contains(attribute); + } + return false; +} // bool GeometryModelQDom::hasAttribute(QDomNode node, Attribute attribute) const + + +QMetaType::Type GeometryModelQDom::attributeType(const Attribute &attribute) const +{ + if (_attributeTypes.contains(attribute)) return _attributeTypes[attribute]; + else return QMetaType::UnknownType; +} // QMetaType::Type GeometryModelQDom::attributeType(Attribute attribute) const + + +QList GeometryModelQDom::supportedAttributes(PrimitiveType type) const +{ + if (_primitiveSupportedAttributes.contains(type)) return _primitiveSupportedAttributes.values(type); + else return QList(); +} // QList GeometryModelQDom::supportedAttributes(PrimitiveType type) const + + +GeometryModel::Node GeometryModelQDom::createGroup(const NodeList &primitives) +{ + pushUndo(QLatin1String("createGroup")); + + QDomElement root = firstChildElement(rootNodeName); + QDomElement node = createElement(_primitiveTagsStrings[PrimitiveType::Node]); + QDomNode group = root.appendChild(node); + if (! group.isNull()) + { + for (int i = 0; i < primitives.size(); ++i) + { + // For performance-reasons we use direct appendChild(), but + // normally you should use movePrimitiveIntoGroup(primitives.at(i), group); + group.appendChild(primitives.at(i).get().node()); + } + } + + emitChangedSignal(Node(), Attribute::MaxAttributes, false); + + return Node(Node::Implementation(group)); +} // QDomNode PlanModel::createGroup(QList primitives) + + +QDomNode GeometryModelQDom::createGroup(const QList &primitives) +{ + pushUndo(QLatin1String("createGroup")); + + QDomElement root = firstChildElement(rootNodeName); + QDomElement node = createElement(_primitiveTagsStrings[PrimitiveType::Node]); + QDomNode group = root.appendChild(node); + if (! group.isNull()) + { + for (int i = 0; i < primitives.size(); ++i) + { + // For performance-reasons we use direct appendChild(), but + // normally you should use movePrimitiveIntoGroup(primitives.at(i), group); + group.appendChild(primitives.at(i)); + } + } + + emitChangedSignal(Node(), Attribute::MaxAttributes, false); + + return group; +} // QDomNode PlanModel::createGroup(QList primitives) + + +GeometryModel::Node GeometryModelQDom::isInGroup(const Node &node) const +{ + QDomNode parent = node.get()->parentNode(); + return Node(Node::Implementation((getType(parent) == PrimitiveType::Node) ? parent : QDomNode())); +} // QDomNode GeometryModelQDom::isInGroup(QDomNode primitive) const + + +GeometryModel::NodeList GeometryModelQDom::getGroupPrimitives(const Node &node) +{ + PrimitiveType myNodeType = getType(node); + if ((myNodeType != PrimitiveType::Node) && (myNodeType != PrimitiveType::RootNode)) + { + emit typeError(tr("Given node is not a group: %1").arg(node.get()->nodeName())); + return NodeList(); + } + + const QDomNodeList &domPrimitiveList = node.get()->childNodes(); + GeometryModel::NodeList primitiveList; + + primitiveList.reserve(domPrimitiveList.size()); + for (int i = 0; i < domPrimitiveList.size(); ++i) + primitiveList += Node(Node::Implementation(domPrimitiveList.at(i))); + + return primitiveList; + +} // QDomNodeList GeometryModelQDom::getGroupPrimitives(QDomNode node) const + + +GeometryModel::Node GeometryModelQDom::findPrimitive(int userData) const +{ + // TODO: This is inefficient!!! Search only for Att userData! + QString userDataString = QString::number(userData, 10); + QMap::iterator it = _primitiveTagsTypes.begin(); + while (it != _primitiveTagsTypes.end()) + { + QDomNodeList nodes = elementsByTagName(it.key()); + for (int i = 0; i < nodes.size(); ++i) + { + QDomElement e = nodes.at(i).toElement(); + if (e.hasAttribute(userDataName) && (e.attribute(userDataName) == userDataString)) + return Node(Node::Implementation(nodes.at(i))); + } + ++it; + } + + return Node(); +} // QDomNode GeometryModelQDom::findPrimitive(int userData) const + + +bool GeometryModelQDom::hasUserData(const Node &node) const { return hasUserData(node.get().node()); } +int GeometryModelQDom::getUserData(const Node &node, bool *ok) const { return getUserData(node.get().node(), ok); } + + +bool GeometryModelQDom::hasUserData(const QDomNode &node) const { return node.toElement().hasAttribute(userDataName); } + + +int GeometryModelQDom::getUserData(const QDomNode &node, bool *okPtr) const +{ + if (okPtr) *okPtr = false; // if provided, initialize + + int result = 0; + bool ok = false; + + QDomElement e = node.toElement(); + if (e.hasAttribute(userDataName)) + { + QString value = e.attribute(userDataName); + result = value.toInt(&ok, 10); + if (!(ok)) + emit const_cast(this)->modelError(tr("Can not convert value to int: %1").arg(value)); + } + else + { + PrimitiveType type = getType(node); + + if ((type != PrimitiveType::Node) && (type != PrimitiveType::RootNode) && (type != PrimitiveType::Unknown)) + { + qWarning() << "Missing user data at node " << node.nodeName() << " connectedId=" << Node::Implementation(node).connectedId() + << " [maybe m_internal->m_modelClipboard->clearModel()]"; +// #ifdef LTS_DEBUG +// // *** To locate a bug stop program here and start debugger, should be removed for final release +// assert(false); +// // *** +// #endif + emit const_cast(this)->typeError(tr("No UserData in %1").arg(node.nodeName())); + } + return SituationMap::IdHandler::BadId; + } + if (okPtr) *okPtr = ok; + return result; +} // void* GeometryModelQDom::getUserData(QDomNode node, bool *ok) + + +void GeometryModelQDom::setUserData(const Node &node, int userData) +{ + PrimitiveType type = getType(node); + + if ((type == PrimitiveType::Node) || (type == PrimitiveType::RootNode) || (type == PrimitiveType::Unknown)) + emit typeError(tr("Can not set UserData in %1").arg(node.get()->nodeName())); + else + { + if (!SituationMap::IdHandler::isIdValid(userData)) + { + QDomElement e = node.get()->toElement(); + e.removeAttribute(userDataName); + } + else + { + QString userDataString = QString::number(userData, 10); + QDomElement e = node.get()->toElement(); + e.setAttribute(userDataName, userDataString); + } + emitChangedSignal(node, Attribute::MaxAttributes, false); + } +} // void GeometryModelQDom::setUserData(QDomNode node, int userData) + + +QMultiMap GeometryModelQDom::userDataMap(const Node &node) +{ + QMultiMap domResult = userDataMap(node.get().node()); + QMultiMap result; + + QMultiMap::const_iterator iter = domResult.constBegin(); + + for (; iter != domResult.constEnd(); ++iter) + result.insert(iter.key(), Node(Node::Implementation(iter.value()))); + + return result; +} // QMultiMap GeometryModelQDom::userDataMap(const Node & node) + + +QMultiMap GeometryModelQDom::userDataMap(const QDomNode &node) const +{ + QMultiMap result; + if ((node.ownerDocument() != *this) && (! node.isNull())) + { + // Not a Node of this Document, ignore + return result; + } + + QDomNodeList children; + if ((node == *this) || node.isNull()) + children = firstChildElement(rootNodeName).childNodes(); + else if ((getType(node) == PrimitiveType::Node) || (getType(node) == PrimitiveType::RootNode)) + children = node.childNodes(); + + for (int i = 0; i < children.size(); ++i) + { + QDomNode curNode = children.at(i); + if (getType(curNode) == PrimitiveType::Node) + { + // If its a node, search deep and merge results... + QMultiMap deepResult = userDataMap(curNode); + QMultiMap::const_iterator it = deepResult.constBegin(); + while (it != deepResult.constEnd()) + { + int key = it.key(); + QList values = deepResult.values(key); + for (int k = 0; k < values.size(); ++k) + result.insert(key, values.at(k)); + ++it; + } + + } + else + { + // ...get UserData and append to result + bool ok; + int userData = getUserData(curNode, &ok); + if (ok) + result.insert(userData, curNode); + } + } + return result; +} // QMultiMap GeometryModelQDom::userDataMap(QDomNode node) + + +static void resetUserData(const QDomNode &node) +{ + const QDomNodeList &childNodes = node.childNodes(); + for (int i = 0; i < childNodes.size(); ++i) + resetUserData(childNodes.at(i)); + node.toElement().removeAttribute(userDataName); + node.toElement().removeAttribute(connectionIdName); + +} // resetUserData (QDomNode & node) + + +GeometryModel::Node GeometryModelQDom::copy(const Node &node, int) +{ + QDomElement root = firstChildElement(rootNodeName); + pushUndo(QLatin1String("copy")); + + QDomNode clone = node.get()->cloneNode(true); + + resetUserData(clone); + + QDomNode result = root.appendChild(clone); + emitChangedSignal(Node(), Attribute::MaxAttributes, false); + return Node(Node::Implementation(result)); +} // GeometryModel::Node GeometryModelQDom::copy(Node node, int userData) + + +GeometryModelQDom::PrimitiveType GeometryModelQDom::getType(const Node &node) const { return getType(node.get().node()); } + + +GeometryModelQDom::PrimitiveType GeometryModelQDom::getType(const QDomNode &node) const +{ + QString tag = node.nodeName(); + if (_primitiveTagsTypes.contains(tag)) return _primitiveTagsTypes[tag]; + else return PrimitiveType::Unknown; +} // PrimitiveType GeometryModelQDom::getType(QDomNode node) const + + +unsigned int GeometryModelQDom::redoSteps() const { return _redoStack.size(); } +unsigned int GeometryModelQDom::undoSteps() const { return _undoStack.size(); } + + +QString GeometryModelQDom::attributeToString(const Attribute &attribute) const +{ + switch (attribute) + { + case Attribute::AbcProtection: return QLatin1String("abc_protection"); + case Attribute::Affiliation: return QLatin1String("affil"); + case Attribute::Alignment: return QLatin1String("alignment"); + case Attribute::AnchorHorizontal: return QLatin1String("anchor_horizontal"); + case Attribute::AnchorVertical: return QLatin1String("anchor_vertical"); + case Attribute::ArrowClampHeight: return QLatin1String("arrow_clamp_height"); + case Attribute::ArrowHeight: return QLatin1String("arrow_height"); + case Attribute::ArrowLineWidth: return QLatin1String("arrow_line_width"); + case Attribute::Arrows: return QLatin1String("arrows"); + case Attribute::ArrowWidth: return QLatin1String("arrow_width"); + case Attribute::Box: return QLatin1String("box"); + case Attribute::Center: return QLatin1String("center_point"); + case Attribute::Close: return QLatin1String("close"); + case Attribute::Color: return QLatin1String("color"); + case Attribute::Extent: return QLatin1String("extent"); + case Attribute::Fill: return QLatin1String("fill"); + case Attribute::FillColor: return QLatin1String("FillColor"); + case Attribute::FillPattern: return QLatin1String("FillPattern"); + case Attribute::FillStyle: return QLatin1String("FillStyle"); + case Attribute::Font: return QLatin1String("font"); + case Attribute::FontBold: return QLatin1String("FontBold"); + case Attribute::FontItalic: return QLatin1String("FontItalic"); + case Attribute::FontSize: return QLatin1String("FontSize"); + case Attribute::Height: return QLatin1String("height"); + case Attribute::LineColor: return QLatin1String("linecolor"); + case Attribute::LineStyle: return QLatin1String("linestyle"); + case Attribute::LineWidth: return QLatin1String("linewidth"); + case Attribute::MarginHorizontal: return QLatin1String("margin_horizontal"); + case Attribute::MarginVertical: return QLatin1String("margin_vertical"); + case Attribute::Mobility: return QLatin1String("mobility"); + case Attribute::SIDC: return QLatin1String("sidc"); + case Attribute::SIDC_Color: return QLatin1String("sidc_color"); + case Attribute::SIDC_Attributes: return QLatin1String("sidc_attributes"); + case Attribute::SymbolId: return QLatin1String("symbol_id"); + case Attribute::SymbolOffsetX: return QLatin1String("symbol_offsetX"); + case Attribute::SymbolOffsetY: return QLatin1String("symbol_offsetY"); + case Attribute::SymbolDirection: return QLatin1String("symbol_direction"); + case Attribute::ForceId: return QLatin1String("force_id"); + case Attribute::SymbolState: return QLatin1String("symbolState"); + case Attribute::SymbolUncertainty: return QLatin1String("symbolUncertainty"); + case Attribute::SymbolExplicitScale: return QLatin1String("explicitScale"); + case Attribute::DamageForceId: return QLatin1String("damage_force_id"); + case Attribute::Injury: return QLatin1String("injury"); + case Attribute::PeMapBrightness: return QLatin1String("pe_map_brightness"); + case Attribute::PeMapCenterX: return QLatin1String("pe_map_centerx"); + case Attribute::PeMapCenterY: return QLatin1String("pe_map_centery"); + case Attribute::PeMapSaturation: return QLatin1String("pe_map_saturation"); + case Attribute::PeMapZoom: return QLatin1String("pe_map_zoom"); + case Attribute::PeMapRotation: return QLatin1String("pe_map_rotation"); + case Attribute::PeMapType: return QLatin1String("pe_map_type"); + case Attribute::PeShowUtm: return QLatin1String("pe_show_utm"); + + case Attribute::PeSymbolOpacity: return QLatin1String("symbol_opacity"); + case Attribute::PeSymbolFillType: return QLatin1String("pe_symbol_fill_type"); + case Attribute::PeSymbolScale: return QLatin1String("pe_symbol_scale"); + + case Attribute::MetaDataTitle: return QLatin1String("meta_data_title"); + case Attribute::MetaDataAuthor: return QLatin1String("meta_data_author"); + case Attribute::MetaDataDate: return QLatin1String("meta_data_date"); + case Attribute::MetaDataDesciption: return QLatin1String("meta_data_description"); + case Attribute::MetaDataMinScale: return QLatin1String("meta_data_min_scale"); + case Attribute::MetaDataMaxScale: return QLatin1String("meta_data_max_scale"); + case Attribute::MetaPixelBasedTextBoxes: return QLatin1String("meta_pixel_based_text_boxes"); + + case Attribute::Rotate: return QLatin1String("rotate"); + case Attribute::Rounding: return QLatin1String("rounding"); + case Attribute::Start: return QLatin1String("start"); + case Attribute::Text_EF: return QLatin1String("text_ef"); + case Attribute::Text_G: return QLatin1String("text_g"); + case Attribute::Text_H: return QLatin1String("text_h"); + case Attribute::Text_KLNPJ: return QLatin1String("text_jklnp"); + case Attribute::Text_M: return QLatin1String("text_m"); + case Attribute::Text_T: return QLatin1String("text_t"); + case Attribute::Text_V: return QLatin1String("text_v"); + case Attribute::Text_W: return QLatin1String("text_w"); + case Attribute::Text_XY: return QLatin1String("text_xy"); + case Attribute::Text_Z: return QLatin1String("text_z"); + case Attribute::UtmEast: return QLatin1String("utm_east"); + case Attribute::UtmGrid: return QLatin1String("utm_grid"); + case Attribute::UtmNorth: return QLatin1String("utm_north"); + case Attribute::UtmZone: return QLatin1String("utm_zone"); + case Attribute::Width: return QLatin1String("width"); + case Attribute::att_vbg_et: return QLatin1String("w_vbg_et"); + default: return QLatin1String("Unknown Attribute"); + } +} // QString GeometryModelQDom::attributeToString(Attribute attribute) const + + + +void GeometryModelQDom::init() +{ + using index = QPair; + + // Only once + if (! _staticInitDone) + { + // Create mapping between primitive-types and their string/tag-name + + _primitiveTagsTypes[QLatin1String("participantSymbol")] = PrimitiveType::ParticipantSymbol; + _primitiveTagsTypes[QLatin1String("dictionarySymbol")] = PrimitiveType::DictionarySymbol; + _primitiveTagsTypes[QLatin1String("ellipse")] = PrimitiveType::Ellipse; + _primitiveTagsTypes[QLatin1String("line")] = PrimitiveType::Line; + _primitiveTagsTypes[QLatin1String("polyline")] = PrimitiveType::Polyline; + _primitiveTagsTypes[QLatin1String("rectangle")] = PrimitiveType::Rectangle; + _primitiveTagsTypes[QLatin1String("spline")] = PrimitiveType::Spline; + _primitiveTagsTypes[QLatin1String("symbol")] = PrimitiveType::Symbol; + _primitiveTagsTypes[QLatin1String("text")] = PrimitiveType::Text; + _primitiveTagsTypes[QLatin1String("node")] = PrimitiveType::Node; + _primitiveTagsTypes[rootNodeName] = PrimitiveType::RootNode; + + _primitiveTagsStrings[PrimitiveType::ParticipantSymbol] = QLatin1String("participantSymbol"); + _primitiveTagsStrings[PrimitiveType::DictionarySymbol] = QLatin1String("dictionarySymbol"); + _primitiveTagsStrings[PrimitiveType::Ellipse] = QLatin1String("ellipse"); + _primitiveTagsStrings[PrimitiveType::Line] = QLatin1String("line"); + _primitiveTagsStrings[PrimitiveType::Polyline] = QLatin1String("polyline"); + _primitiveTagsStrings[PrimitiveType::Rectangle] = QLatin1String("rectangle"); + _primitiveTagsStrings[PrimitiveType::Spline] = QLatin1String("spline"); + _primitiveTagsStrings[PrimitiveType::Symbol] = QLatin1String("symbol"); + _primitiveTagsStrings[PrimitiveType::Text] = QLatin1String("text"); + _primitiveTagsStrings[PrimitiveType::Node] = QLatin1String("node"); + + + // Don't set + // _primitiveTagsStrings[PrimitiveType::RootNode] = rootNodeName; + // because the root node can not be created from interface. + + // Create mapping between node-types and supported attributes + // "Unknown" is used for plan-attributes + + _primitiveSupportedAttributes.insert(PrimitiveType::RootNode, Attribute::MetaDataTitle); + _primitiveSupportedAttributes.insert(PrimitiveType::RootNode, Attribute::MetaDataAuthor); + _primitiveSupportedAttributes.insert(PrimitiveType::RootNode, Attribute::MetaDataDate); + _primitiveSupportedAttributes.insert(PrimitiveType::RootNode, Attribute::MetaDataDesciption); + _primitiveSupportedAttributes.insert(PrimitiveType::RootNode, Attribute::MetaDataMinScale); + _primitiveSupportedAttributes.insert(PrimitiveType::RootNode, Attribute::MetaDataMaxScale); + _primitiveSupportedAttributes.insert(PrimitiveType::RootNode, Attribute::MetaPixelBasedTextBoxes); + + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeMapBrightness); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeMapCenterX); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeMapCenterY); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeMapSaturation); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeMapZoom); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeMapRotation); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeMapType); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeShowUtm); + + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeSymbolOpacity); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeSymbolFillType); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::PeSymbolScale); + + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::UtmEast); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::UtmGrid); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::UtmNorth); + _primitiveSupportedAttributes.insert(PrimitiveType::MapDefaults, Attribute::UtmZone); + + _primitiveSupportedAttributes.insert(PrimitiveType::DictionarySymbol, Attribute::SIDC); + _primitiveSupportedAttributes.insert(PrimitiveType::DictionarySymbol, Attribute::SIDC_Attributes); + + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::SIDC); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::SIDC_Attributes); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::ForceId); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::SymbolState); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::DamageForceId); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Injury); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_EF); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_G); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_H); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_KLNPJ); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_M); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_T); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_V); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_W); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_XY); + _primitiveSupportedAttributes.insert(PrimitiveType::ParticipantSymbol, Attribute::Text_Z); + + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Center); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Color); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Height); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Width); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Extent); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Fill); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::FillColor); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::FillPattern); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::FillStyle); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::LineStyle); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::LineWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Rotate); + _primitiveSupportedAttributes.insert(PrimitiveType::Ellipse, Attribute::Start); + + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::ArrowClampHeight); + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::ArrowHeight); + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::ArrowLineWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::Arrows); + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::ArrowWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::Color); + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::LineStyle); + _primitiveSupportedAttributes.insert(PrimitiveType::Line, Attribute::LineWidth); + + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::ArrowClampHeight); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::ArrowHeight); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::ArrowLineWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::Arrows); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::ArrowWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::Close); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::Color); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::Fill); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::FillColor); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::FillPattern); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::LineStyle); + _primitiveSupportedAttributes.insert(PrimitiveType::Polyline, Attribute::LineWidth); + + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::Center); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::Color); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::Height); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::Width); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::Fill); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::FillColor); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::FillPattern); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::LineStyle); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::LineWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::Rotate); + _primitiveSupportedAttributes.insert(PrimitiveType::Rectangle, Attribute::Rounding); + + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::ArrowClampHeight); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::ArrowHeight); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::ArrowLineWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::Arrows); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::ArrowWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::Close); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::Color); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::Fill); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::FillColor); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::FillPattern); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::LineStyle); + _primitiveSupportedAttributes.insert(PrimitiveType::Spline, Attribute::LineWidth); + + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::AbcProtection); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Affiliation); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Mobility); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::SymbolId); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::SymbolOffsetX); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::SymbolOffsetY); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::SymbolDirection); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::ForceId); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::SymbolState); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::SymbolUncertainty); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::SymbolExplicitScale); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::DamageForceId); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_EF); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_G); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_H); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_KLNPJ); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_M); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_T); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_V); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_W); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_XY); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::Text_Z); + _primitiveSupportedAttributes.insert(PrimitiveType::Symbol, Attribute::att_vbg_et); + + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::AnchorHorizontal); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::AnchorVertical); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::Box); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::Color); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::Fill); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::FillColor); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::FillPattern); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::Font); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::FontBold); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::FontItalic); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::FontSize); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::LineColor); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::LineStyle); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::LineWidth); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::MarginHorizontal); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::MarginVertical); + _primitiveSupportedAttributes.insert(PrimitiveType::Text, Attribute::Rotate); + + // Init all default-values for all required attributes + QFont defaultFont; + int fontSize = defaultFont.pixelSize(); + + // TODO: Find better defaults + + // "Unknown" is used for plan-attributes + _attributeDefaults.insert(index(PrimitiveType::DictionarySymbol, Attribute::SIDC), QLatin1String("------------")); + + _attributeDefaults.insert(index(PrimitiveType::ParticipantSymbol, Attribute::SIDC), QLatin1String("------------")); + _attributeDefaults.insert(index(PrimitiveType::ParticipantSymbol, Attribute::SIDC_Color), variant2XmlString(QColor(0, 0, 0, 0))); + + _attributeDefaults.insert(index(PrimitiveType::RootNode, Attribute::MetaDataTitle), QString()); + _attributeDefaults.insert(index(PrimitiveType::RootNode, Attribute::MetaDataAuthor), QString()); + _attributeDefaults.insert(index(PrimitiveType::RootNode, Attribute::MetaDataDate), QString()); + _attributeDefaults.insert(index(PrimitiveType::RootNode, Attribute::MetaDataDesciption), QString()); + _attributeDefaults.insert(index(PrimitiveType::RootNode, Attribute::MetaDataMinScale), QString()); + _attributeDefaults.insert(index(PrimitiveType::RootNode, Attribute::MetaDataMaxScale), QString()); + + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Center), QString()); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Color), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Height), QLatin1String("0.0")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Width), QLatin1String("0.0")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Extent), QLatin1String("360")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Fill), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::FillColor), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::FillPattern), QLatin1String("none")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::FillStyle), QLatin1String("pieslice")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::LineStyle), QLatin1String("solid")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::LineWidth), QLatin1String("0.1")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Rotate), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Ellipse, Attribute::Start), QLatin1String("0")); + + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::ArrowClampHeight), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::ArrowHeight), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::ArrowLineWidth), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::Arrows), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::ArrowWidth), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::Color), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::LineStyle), QLatin1String("solid")); + _attributeDefaults.insert(index(PrimitiveType::Line, Attribute::LineWidth), QLatin1String("0.1")); + + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::ArrowClampHeight), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::ArrowHeight), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::ArrowLineWidth), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::Arrows), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::ArrowWidth), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::Close), QLatin1String("1")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::Color), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::Fill), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::FillColor), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::FillPattern), QLatin1String("none")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::LineStyle), QLatin1String("solid")); + _attributeDefaults.insert(index(PrimitiveType::Polyline, Attribute::LineWidth), QLatin1String("0.1")); + + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::Center), QString()); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::Color), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::Height), QLatin1String("0.0")); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::Width), QLatin1String("0.0")); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::Fill), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::FillColor), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::FillPattern), QLatin1String("none")); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::LineStyle), QLatin1String("solid")); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::LineWidth), QLatin1String("0.1")); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::Rotate), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Rectangle, Attribute::Rounding), QLatin1String("0.0")); + + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::ArrowClampHeight), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::ArrowHeight), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::ArrowLineWidth), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::Arrows), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::ArrowWidth), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::Close), QLatin1String("1")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::Color), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::Fill), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::FillColor), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::FillPattern), QLatin1String("none")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::LineStyle), QLatin1String("solid")); + _attributeDefaults.insert(index(PrimitiveType::Spline, Attribute::LineWidth), QLatin1String("0.1")); + + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::AbcProtection), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Affiliation), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Mobility), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::SymbolId), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::SymbolOffsetX), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::SymbolOffsetY), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::SymbolDirection), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::ForceId), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::SymbolState), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::SymbolUncertainty), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::SymbolExplicitScale), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::DamageForceId), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_EF), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_G), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_H), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_KLNPJ), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_M), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_T), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_V), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_W), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_XY), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::Text_Z), QString()); + _attributeDefaults.insert(index(PrimitiveType::Symbol, Attribute::att_vbg_et), QString()); + + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::AnchorHorizontal), QLatin1String("center")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::AnchorVertical), QLatin1String("center")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::Box), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::Color), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::Fill), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::FillColor), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::FillPattern), QLatin1String("none")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::Font), defaultFont.key()); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::FontBold), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::FontItalic), QLatin1String("0")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::FontSize), QString::number(fontSize)); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::LineColor), QColor().name()); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::LineStyle), QLatin1String("solid")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::LineWidth), QLatin1String("0.1")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::MarginHorizontal), QLatin1String("1")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::MarginVertical), QLatin1String("1")); + _attributeDefaults.insert(index(PrimitiveType::Text, Attribute::Rotate), QLatin1String("0")); + + _attributeTypes[Attribute::AbcProtection] = QMetaType::Bool; // Type: bool + _attributeTypes[Attribute::Affiliation] = QMetaType::User; // Type: Symbol::t_affil + _attributeTypes[Attribute::Alignment] = QMetaType::User; // Type: HorizontalAlign + _attributeTypes[Attribute::AnchorHorizontal] = QMetaType::User; // Type: HorizontalAlign + _attributeTypes[Attribute::AnchorVertical] = QMetaType::User; // Type: t_valign + _attributeTypes[Attribute::ArrowClampHeight] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::ArrowHeight] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::ArrowLineWidth] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::Arrows] = QMetaType::User; // Type: t_arrows + _attributeTypes[Attribute::ArrowWidth] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::Box] = QMetaType::Bool; // Type: bool + _attributeTypes[Attribute::Center] = QMetaType::QPointF; // Type: PointF + _attributeTypes[Attribute::Close] = QMetaType::Bool; // Type: bool + _attributeTypes[Attribute::Color] = QMetaType::QColor; // Type: QColor + _attributeTypes[Attribute::Extent] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::Fill] = QMetaType::Bool; // Type: bool + _attributeTypes[Attribute::FillColor] = QMetaType::QColor; // Type: QColor + _attributeTypes[Attribute::FillPattern] = QMetaType::User; // Type: t_fillPattern + _attributeTypes[Attribute::FillStyle] = QMetaType::User; // Type: t_fillStyle + _attributeTypes[Attribute::Font] = QMetaType::User; // Type: t_font + _attributeTypes[Attribute::FontBold] = QMetaType::Bool; // Type: bool + _attributeTypes[Attribute::FontItalic] = QMetaType::Bool; // Type: bool + _attributeTypes[Attribute::FontSize] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::LineColor] = QMetaType::QColor; // Type: QColor + _attributeTypes[Attribute::LineStyle] = QMetaType::User; // Type: t_lineStyle + _attributeTypes[Attribute::LineWidth] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::MarginHorizontal] = QMetaType::User; // Type: int + _attributeTypes[Attribute::MarginVertical] = QMetaType::User; // Type: int + _attributeTypes[Attribute::Mobility] = QMetaType::User; // Type: Symbol::t_mob + _attributeTypes[Attribute::SIDC] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::SIDC_Color] = QMetaType::QColor; // Type: QColor + _attributeTypes[Attribute::SIDC_Attributes] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::SymbolId] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::SymbolOffsetX] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::SymbolOffsetY] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::SymbolDirection] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::ForceId] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::SymbolState] = QMetaType::User; // Type: SymbolTypes::t_state + _attributeTypes[Attribute::SymbolUncertainty] = QMetaType::User; // Type: SymbolTypes::t_uncertainty + _attributeTypes[Attribute::SymbolExplicitScale] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::DamageForceId] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::Injury] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::PeMapBrightness] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::PeMapCenterX] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::PeMapCenterY] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::PeMapSaturation] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::PeMapZoom] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::PeMapRotation] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::PeMapType] = QMetaType::User; // Type: t_mapType + _attributeTypes[Attribute::PeShowUtm] = QMetaType::Bool; // Type: bool + + _attributeTypes[Attribute::PeSymbolOpacity] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::PeSymbolFillType] = QMetaType::User; // Type: SymbolTypes::t_filltype + _attributeTypes[Attribute::PeSymbolScale] = QMetaType::Bool; // Type: bool + + _attributeTypes[Attribute::MetaDataTitle] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::MetaDataAuthor] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::MetaDataDate] = QMetaType::QDateTime; + _attributeTypes[Attribute::MetaDataDesciption] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::MetaDataMinScale] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::MetaDataMaxScale] = QMetaType::Double; // Type: double + + _attributeTypes[Attribute::Rotate] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::Rounding] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::Start] = QMetaType::Double; // Type: double + _attributeTypes[Attribute::Text_EF] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_G] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_H] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_KLNPJ] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_M] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_T] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_V] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_W] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_XY] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::Text_Z] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::UtmEast] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::UtmGrid] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::UtmNorth] = QMetaType::Int; // Type: int + _attributeTypes[Attribute::UtmZone] = QMetaType::QString; // Type: QString + _attributeTypes[Attribute::att_vbg_et] = QMetaType::User; // Type: t_w_vbg_et + + _staticInitDone = true; + } + + QDomElement root = createElement(rootNodeName); + appendChild(root); + + clearUndoStack(); +} // void GeometryModelQDom::init() + +void GeometryModelQDom::cleanupStaticMember() +{ + _primitiveTagsTypes.clear(); + _primitiveTagsStrings.clear(); + _primitiveSupportedAttributes.clear(); + _attributeDefaults.clear(); + _attributeTypes.clear(); +} + + +GeometryModel::Node GeometryModelQDom::getMapDefaultSettingsNode() const +{ + QDomElement mapSettings = firstChildElement(mapSettingsName); + + if (mapSettings.isNull()) + { + GeometryModelQDom *THIS = const_cast(this); // cast const away + + mapSettings = THIS->createElement(mapSettingsName); + + THIS->appendChild(mapSettings); + + using index = QPair; + + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeMapBrightness), QLatin1String("100")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeMapCenterX), QLatin1String("0.0")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeMapCenterY), QLatin1String("0.0")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeMapSaturation), QLatin1String("100")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeMapZoom), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeMapRotation), QLatin1String("0.0")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeMapType), QLatin1String("MapPrimitiveType::Unknown")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeShowUtm), QLatin1String("1")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::UtmEast), QLatin1String("10000")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::UtmGrid), QLatin1String("XY")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::UtmNorth), QLatin1String("10000")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::UtmZone), QLatin1String("00Z")); + + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeSymbolOpacity), QLatin1String("1.0")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeSymbolFillType), QLatin1String("filled")); + _attributeDefaults.insert(index(PrimitiveType::MapDefaults, Attribute::PeSymbolScale), QLatin1String("1.0")); + + // Create root-node and set required attributes + + + QList modelAttributes = supportedAttributes(PrimitiveType::MapDefaults); + for (int i = 0; i < modelAttributes.size(); ++i) + { + Attribute at = modelAttributes.at(i); + THIS->setModelEditorAttribute(at, _attributeDefaults[index(PrimitiveType::MapDefaults, at)]); + } + } + + return Node(Node::Implementation(mapSettings)); + +} // GeometryModelQDom::getMapDefaultSettingsNode + + +void GeometryModelQDom::transformNode(const QDomNode &node, const QTransform &t) +{ + QString tag = node.nodeName(); + if (!_primitiveTagsTypes.contains(tag)) + { + emit modelError(tr("Can not translate node of type %1").arg(tag)); + return; + } + + QDomNodeList nodes = node.childNodes(); + for (int i = 0; i < nodes.size(); ++i) + { + QDomElement p = nodes.at(i).toElement(); + if (p.nodeName() != QLatin1String("point")) + continue; + if (!p.hasAttribute(QLatin1String("x")) || !p.hasAttribute(QLatin1String("y"))) + emit modelError(tr("Invalid point in primitive of type %1").arg(tag)); + else + { + bool okx, oky; + double oldx = p.toElement().attribute(QLatin1String("x")).toDouble(&okx); + double oldy = p.toElement().attribute(QLatin1String("y")).toDouble(&oky); + if (!(okx && oky)) + emit modelError(tr("Invalid point in primitive of type %1").arg(tag)); + else + { + QPointF point = t.map(QPointF(oldx, oldy)); + p.setAttribute(QLatin1String("x"), QString::number(point.x(), 'f', CoordinateDoublePrecision)); + p.setAttribute(QLatin1String("y"), QString::number(point.y(), 'f', CoordinateDoublePrecision)); + } + } + } +} // void GeometryModelQDom::transformNode(QDomNode node, QTransform t) + + +void GeometryModelQDom::emitChangedSignal(const Node &node, Attribute att, bool coordsChanged) +{ + if (!_skipChangedSignal) + emit changed(node, att, coordsChanged); +} // void GeometryModelQDom::emitChangedSignal(QDomNode node, Attribute att, bool coordsChanged) + + +void GeometryModelQDom::pushUndo(const QString &eventDescription) +{ + if (_undoEnabled && (! _insideUndoBlock)) + { + QDomElement root = firstChildElement(rootNodeName); + while (undoSteps() >= _maxUndoSteps) + { + // Size-limit reached, remove first element + _undoStack.erase(_undoStack.begin()); + _undoHistory.erase(_undoHistory.begin()); + } + _undoStack.push(root.cloneNode()); + _undoHistory.push(eventDescription); + } + if (_insideUndoBlock) + _undoHistoryBlock = eventDescription; +} // void GeometryModelQDom::pushUndo(const QString &eventDescription) + + +GeometryModel::Node GeometryModelQDom::popUndo() +{ + if (_undoEnabled && (! _insideUndoBlock)) + { + _undoHistory.pop(); + return Node(Node::Implementation(_undoStack.pop())); + } + return Node(); +} // void GeometryModelQDom::pushUndo() + + + +bool GeometryModelQDom::parseSymbolAttributeString(const QString &sidcAttributes, QMap &attrMap) const +{ + attrMap.clear(); + + if (sidcAttributes.isEmpty()) return true; + + bool ok = true; + const QStringList sidcAttrList = sidcAttributes.split(QLatin1Char(';')); + + for (const QString &attr : sidcAttrList) + { + const QStringList &attrPair = attr.split(QLatin1Char('=')); + if (attrPair.size() != 2) + { + ok = false; + QString errorString = tr("%1 Parsing symbol attribute list failed. %2").arg(QString::fromStdString(LOGFUNCTION), attr); + emit const_cast(this)->attributeError(errorString); + continue; + } + attrMap[attrPair.first()] = attrPair.last(); + } + return ok; +} // bool GeometryModelQDom::parseSymbolAttributeString( const QString & sidcAttributes, QMap & attrMap ) const + + +QString GeometryModelQDom::buildSymbolAttributeString(const QMap &attrMap) const +{ + QString attributeString; + + QMap::const_iterator iter = attrMap.cbegin(); + while (attrMap.cend() != iter) + { + const QString &key = iter.key(); + const QString &val = iter.value().toString(); + + if (!val.isEmpty()) + { + if (key.isEmpty()) + { + QString errorString = tr("%1 Empty key found").arg(QString::fromStdString(LOGFUNCTION)); + emit const_cast(this)->attributeError(errorString); + ++iter; + continue; + } + + if (val.contains(QLatin1Char('=')) || val.contains(QLatin1Char(';'))) + { + QString errorString = tr("%1 Attribute value has illegal characters <;=> : %2").arg(QString::fromStdString(LOGFUNCTION), val); + emit const_cast(this)->attributeError(errorString); + ++iter; + continue; + } + + if (!attributeString.isEmpty()) attributeString += QLatin1Char(';'); + attributeString += QStringLiteral("%1=%2").arg(key, val); + } + ++iter; + } + return attributeString; +} // QString GeometryModelQDom::buildSymbolAttributeString(const QMap & attrMap) const + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.h new file mode 100755 index 000000000..71a73140a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_model_q_dom.h @@ -0,0 +1,374 @@ +/** +** +** @RDE_HEADER@ +** GeometryModel implementation to write and read PlanEditor plans from/to XML and manipulate DOM-tree. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Stephan Kuhagen +** Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** ??.??.200?, SK : First edition for use with Qt4 +** 11.11.2015, JT : Splitting interface and implementation +** +** ----------------------------------------------------------------------------------------------- +*/ + +// clazy:excludeall=const-signal-or-slot + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "geometry_model.h" +class QTransform; + +namespace SituationMap +{ + +// exported for unit test only +class GEOMETRYMODEL_EXPORT GeometryModelQDom : public GeometryModel, public QDomDocument +{ + + Q_OBJECT + +public: + + explicit GeometryModelQDom(QObject *parent = nullptr); + + //! Copy-Constructor + GeometryModelQDom(const GeometryModelQDom &other); + + ~GeometryModelQDom(); + + GeometryModelQDom & operator = (const GeometryModelQDom &other); + + QByteArray toByteArray() const override; + QByteArray toByteArrayDebug() const override; + + bool fromByteArray(const QByteArray &content) override; + bool fromDevice(QIODevice *device) override; + bool fromByteArrayDebug(const QByteArray &content) override; + + bool isEmpty() const override; + + Node getRootNode() const override; + + Node getMapDefaultSettingsNode() const override; + + void setModelAttribute(GeometryModel::Attribute attribute, const QVariant &value) override; + + void setModelEditorAttribute(GeometryModel::Attribute attribute, const QVariant &value) override; + + void setPrimitiveAttribute(const GeometryModel::Node &node, GeometryModel::Attribute attribute, const QVariant &value) override; + + void movePrimitiveIntoGroup(const GeometryModel::Node &primitive, const GeometryModel::Node &target) override; + + void releaseGroup(const GeometryModel::Node &group) override; + + void deleteGroup(const GeometryModel::Node &group) override; + + void removePrimitive(const GeometryModel::Node &primitive) override; + + void removePrimitivesByUserData(const QList &userDataList) override; + + void removeText(GeometryModel::Node &parent) override; + + void setTextData(const GeometryModel::Node &textNode, const QString &data, GeometryModel::HorizontalAlign textAlign) override; + + void clearModel() override; + + void translateNode(const GeometryModel::Node &node, double x, double y) override; + + void moveNode(const GeometryModel::Node &node, double x, double y) override; + + void scaleNode(const GeometryModel::Node &node, double scaleX, double scaleY) override; + + + void rotateNode(const GeometryModel::Node &node, double angle) override; + + void reorderPrimitives(const QList &nodes, GeometryModel::PrimitivOrder order) override; + + void merge(const GeometryModel::Node &other, bool skipUndo = false, bool intoNewGroup = false) override; + + void merge(GeometryModel *other, bool skipUndo = false, bool intoNewGroup = false) override; + + unsigned int modificationCounter() const override; + + void resetModificationCounter() override; + + void undo(unsigned int steps = 1) override; + + void redo(unsigned int steps = 1) override; + + void enableUndo(bool enable) override; + + void startUndoBlock() override; + + void endUndoBlock(const QString &eventDescription = QString()) override; + + void abortUndoBlock() override; + + void setMaxUndoSteps(unsigned int max) override; + + unsigned int maxUndoSteps() const override; + + void cleanModel() override; + + + GeometryModel::Node addPrimitive(GeometryModel::PrimitiveType type + , const QList &points + , const QList > &attributes + , int userData = -1 + , const QString &textData = QString() + , GeometryModel::HorizontalAlign textAlign = HorizontalAlign::Left) override; + + + bool setPrimitivePoints(const GeometryModel::Node &node, const QList &points) override; + + GeometryModel::Node addText(const GeometryModel::Node &parent + , const QString &textData + , GeometryModel::HorizontalAlign textAlign + , const QList > &attributes + , int userData) override; + + GeometryModel::Node getText(const GeometryModel::Node &parent) const override; + + QString getTextData(const GeometryModel::Node &textNode) const override; + + QList getPrimitivePoints(const GeometryModel::Node &node) const override; + + QRectF getBoundingBox(const GeometryModel::Node &node) const override; + + void setNodeDisplayName(const Node &node, const QString &name) override; + + QString getNodeDisplayName(const Node &node) const override; + + QVariant getModelAttribute(Attribute attribute) const override; + + QVariant getAttribute(const GeometryModel::Node &node, GeometryModel::Attribute attribute) const override; + + template T getAttribute(const Node &node, Attribute attribute) const; + + bool hasAttribute(const GeometryModel::Node &node, GeometryModel::Attribute attribute) const override; + + QMetaType::Type attributeType(const GeometryModel::Attribute &attribute) const override; + + QList supportedAttributes(GeometryModel::PrimitiveType type) const override; + + GeometryModel::Node createGroup(const QList &primitives) override; + + GeometryModel::Node isInGroup(const GeometryModel::Node &primitive) const override; + + GeometryModel::NodeList getGroupPrimitives(const GeometryModel::Node &node) override; + + GeometryModel::Node findPrimitive(int userData) const override; + + bool hasUserData(const Node &node) const override; + + int getUserData(const GeometryModel::Node &node, bool *ok = nullptr) const override; + + void setUserData(const GeometryModel::Node &node, int userData) override; + + QMultiMap userDataMap(const GeometryModel::Node &node = GeometryModel::Node()) override; + + GeometryModel::Node copy(const GeometryModel::Node &node, int userData = 0) override; + + GeometryModel::PrimitiveType getType(const GeometryModel::Node &node) const override; + + unsigned int undoSteps() const override; + + unsigned int redoSteps() const override; + + QStringList undoHistory() const override { return _undoHistory.toList(); } + + QStringList redoHistory() const override { return _redoHistory.toList(); } + + void clearUndoStack() override + { + _undoStack.clear(); + _undoHistory.clear(); + } // clearUndoStack + + void clearRedoStack() override + { + _redoStack.clear(); + _redoHistory.clear(); + } // clearRedoStack + + QString attributeToString(const GeometryModel::Attribute &attribute) const override; + + bool parseSymbolAttributeString(const QString &sidcAttributes, QMap &attrMap) const override; + + QString buildSymbolAttributeString(const QMap &attrMap) const override; + + static void cleanupStaticMember(); + +private: + + static void valueFromVariant(const QVariant &v, QPointF &result); + static void valueFromVariant(const QVariant &v, QColor &result); + + /** + convert to QString (with non locale-aware conversions) + */ + static QString variant2XmlString(const QVariant &v); + + /** + Create a QColor from an attribute-value with alpha-channel (QVariant + ignores Alpha) + \param attColor: The QString-QVariant from an attribute of Type QColor + \return QColor with alpha-channel or invalid QColor, if the value could be parsed. + */ + static QColor colorFromString(const QString &attColor); + + /** + Create a QPoint from an attribute-value + \param attPoint: The QString-QVariant from an attribute of Type QPointF + \return QPointF + */ + static QPointF pointFromString(const QString &attPoint); + + //! A mapping between all primitives and the attributes they support + static QMultiMap _primitiveSupportedAttributes; + //! Mappings between all primitive-types and their string/tag-name + static QMap _primitiveTagsTypes; + static QMap _primitiveTagsStrings; + + //! Default-Values for all supported attributes + static QMap, QString> _attributeDefaults; + + //! Mapping between Attributes and their QVariant-Type. + static QMap _attributeTypes; + + //! Bool to check, if static class variables have been initialized + static bool _staticInitDone; + + /** + Init data of the object + */ + void init(); + + + /** + Transform the points of a primitive. + \sa {translateNode(), moveNode(), rotateNode(), scaleNode()} + \param node: The primitive to transform. + \param t: The transformation-matrix. + */ + void transformNode(const QDomNode &node, const QTransform &t); + + + GeometryModel::PrimitiveType getType(const QDomNode &node) const; + + bool hasUserData(const QDomNode &node) const; + + int getUserData(const QDomNode &node, bool *okPtr) const; + + QRectF getBoundingBox(const QDomNode &node) const; + + QList getPrimitivePoints(const QDomNode &node) const; + + bool hasAttribute(const QDomNode &node, GeometryModel::Attribute attribute) const; + + QVariant getAttribute(const QDomNode &node, GeometryModel::Attribute attribute) const; + + void removePrimitive(const QDomNode &primitive); + + void translateNode(const QDomNode &node, double x, double y); + + void moveNode(const QDomNode &node, double x, double y); + + void scaleNode(const QDomNode &node, double scaleX, double scaleY); + + void rotateNode(const QDomNode &node, double angle); + + void merge(const QDomNode &other, bool skipUndo = false, bool intoNewGroup = false); + + QDomNode createGroup(const QList &primitives); + + QMultiMap userDataMap(const QDomNode &node) const; + + + /** + Wrapper for signal changed(), checks _skipChangedSignal + + !!! ATTENTION: Do not use the signal changed directly. + Use emitChangedSignal() instead !!! + + \param what: Short description of the change for logging + \param node: the changed node (if this is a Null-Node, the Root-Node of + the plan has changed) + \param att: The attribute, that changed. If att==MaxAttributes, then + multiple attributes are changed + \param coordsChanged: if true, the coordinated of the primitive are changed + */ + void emitChangedSignal(const Node &node, Attribute att, bool coordsChanged); + + + //! Lock for temporarily blocking changed()-Signals + bool _skipChangedSignal; + + //! Undo-Stack + bool _undoEnabled; + unsigned int _maxUndoSteps; + QStack _undoStack; + QStack _undoHistory; + QStack _redoStack; + QStack _redoHistory; + + //! Undo-blocks + QDomNode _undoBlock; + QString _undoHistoryBlock; + bool _insideUndoBlock; + unsigned int _modificationCounter; + + //! Push/pop current state to undo-stack, if not locked + void pushUndo(const QString &eventDescription = QString()); + GeometryModel::Node popUndo(); +}; // GeometryModelQDom + + +template T GeometryModelQDom::getAttribute(const Node &node, Attribute attribute) const +{ + const QVariant &v = getAttribute(node, attribute); + + switch (qMetaTypeId()) + { + case QMetaType::QPointF: + case QMetaType::QColor: + { + T result; valueFromVariant(v, result); return result; + } break; + + default: break; + } + return v.value(); +} // template T GeometryModelQDom::getAttribute( const Node & node, Attribute attribute ) const + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.cpp new file mode 100755 index 000000000..f5b0d0944 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.cpp @@ -0,0 +1,75 @@ +/** +** +** @RDE_HEADER@ +** Node visitor for GeometryModel. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.07.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "geometry_node_visitor.h" + +namespace SituationMap +{ +GeometryNodeVisitor::GeometryNodeVisitor(GeometryModel *geoModel) : m_geometryModel(geoModel) {;} + +bool GeometryNodeVisitor::traverse() +{ + if (!m_geometryModel) + return true; + return traverse(m_geometryModel->getRootNode()); +} // bool GeometryNodeVisitor::traverse() + + +bool GeometryNodeVisitor::traverse(GeometryModel::Node node) +{ + if (!m_geometryModel) + return true; + + if (node.isValid()) + { + const GeometryModel::PrimitiveType primitiveType = m_geometryModel->getType(node); + + if ((GeometryModel::PrimitiveType::RootNode == primitiveType) || (GeometryModel::PrimitiveType::Node == primitiveType)) + { + if (node.hasChildNodes()) + { + node = node.firstChildNode(); + while (node.isValid()) + { + if (!traverse(node)) + return false; + node = node.next(); + } + } + } + + if (!apply(m_geometryModel, node, primitiveType)) + return false; + + } // if + + return true; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.h new file mode 100755 index 000000000..9dbcebbca --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_node_visitor.h @@ -0,0 +1,75 @@ +/** +** +** @RDE_HEADER@ +** Node visitor for GeometryModel. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.07.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "geometry_model.h" + +namespace SituationMap +{ + +/** + GeometryNopdeVisitor implements visitor pattern to traverse geoemtry model tree. The apply function will be + called for each node in geometry model. +*/ +class GEOMETRYMODEL_EXPORT GeometryNodeVisitor +{ +public: + + explicit GeometryNodeVisitor(GeometryModel *geoModel); + virtual ~GeometryNodeVisitor() = default; + GeometryNodeVisitor(const GeometryNodeVisitor &) = delete; + + /** + Traversing the complete geometry model + */ + bool traverse(); + + /** + Traversing the sub-tree of node. Coming in deepth first. + */ + bool traverse(GeometryModel::Node node); + +protected: + + /** + For each node the apply function will be called. If apply returns false the traversal is aborted. In + other case if return value is true the traversel is continued. + + @param geoModel Pointer to geometry model + @param node Node of geometry model + @param nodeType Type of node + @return false to abort traversael otherwise true + */ + virtual bool apply(GeometryModel *geoModel, const GeometryModel::Node &node, GeometryModel::PrimitiveType nodeType) = 0; + + GeometryModel *m_geometryModel; + +}; // GeometryNodeVisitor + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.cpp new file mode 100755 index 000000000..15ee116f7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.cpp @@ -0,0 +1,216 @@ +/** + * @RDE_HEADER@ + * PixelTextBoxData as Addon to GeometryModel + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2023-04-27 + */ + +#include "geometry_pixelTextBox.h" +#include +#include +#include +#include + +#include +#include + +namespace SituationMap +{ + +PixelTextBoxData::PixelTextBoxData(const QString &htmlText, const QColor &backgroundColor) + : m_htmlText(htmlText), m_backgroundColor(backgroundColor) +{ +} + +void PixelTextBoxData::reset() +{ + m_htmlText.clear(); + m_backgroundColor = QColor(); + m_position = QPoint(); + m_size = QSize(); +} + +bool PixelTextBoxData::isEmpty() const +{ + return m_htmlText.isEmpty(); +} + +QString PixelTextBoxData::toDebugString() const +{ + QJsonDocument jDoc(toJson()); + return QString::fromStdString(jDoc.toJson(QJsonDocument::JsonFormat::Compact).toStdString()); +} + +bool PixelTextBoxData::operator==(const PixelTextBoxData &o) const +{ + return m_htmlText == o.m_htmlText && + m_backgroundColor == o.m_backgroundColor && + m_position == o.m_position && + m_size == o.m_size; +} + + +QJsonObject PixelTextBoxData::toJson() const +{ + QJsonObject obj; + obj[QStringLiteral("text")] = m_htmlText; + obj[QStringLiteral("backgroundColor")] = m_backgroundColor.name(); + + obj[QStringLiteral("position")] = QJsonObject({{QStringLiteral("x"), m_position.x()}, + {QStringLiteral("y"), m_position.y()}, + }); + + obj[QStringLiteral("size")] = QJsonObject({{QStringLiteral("width"), m_size.width()}, + {QStringLiteral("height"), m_size.height()}, + }); + return obj; +} + + +bool get(const QJsonObject &obj, const QString &key, int &val) +{ + const auto jVal = obj[key]; + if (!jVal.isDouble()) + { + qCritical() << " isDouble failed"; + } + val = jVal.toInt(); + return true; +} + + +PixelTextBoxData PixelTextBoxData::fromJson(const QJsonValue &json) +{ + if (json.isUndefined()) + return {}; + if (!json.isObject()) + return {}; + + PixelTextBoxData ret; + + // text + ret.m_htmlText = json[QStringLiteral("text")].toString(); + + // backgroundColor + QString temp = json[QStringLiteral("backgroundColor")].toString(); + ret.m_backgroundColor = QColor::fromString(temp); + + // position: + { + const auto jVal = json[QStringLiteral("position")]; + get(jVal.toObject(), QStringLiteral("x"), ret.m_position.rx()); + get(jVal.toObject(), QStringLiteral("y"), ret.m_position.ry()); + } + + // size: + { + const auto jVal = json[QStringLiteral("size")]; + get(jVal.toObject(), QStringLiteral("width"), ret.m_size.rwidth()); + get(jVal.toObject(), QStringLiteral("height"), ret.m_size.rheight()); + } + + return ret; +} + +QString PixelTextBoxData::toJsonStr(QJsonDocument::JsonFormat format) const +{ + QJsonObject obj = toJson(); + QJsonDocument jDoc(obj); + return QString::fromStdString(jDoc.toJson(format).toStdString()); +} + +PixelTextBoxData PixelTextBoxData::fromJsonStr(const QString &jsonStr) +{ + if (jsonStr.isEmpty()) + return {}; + + QJsonParseError parseError; + QJsonDocument jDoc(QJsonDocument::fromJson(jsonStr.toStdString().c_str(), &parseError)); + + if (parseError.error != QJsonParseError::NoError) + { + qCritical() << " QJsonParseError->errorString: '" << parseError.errorString() << "'" + << ", offset: " << parseError.offset; + return {}; + } + + QJsonObject jObj = jDoc.object(); + if (jObj.isEmpty()) + return {}; + + return PixelTextBoxData::fromJson(jObj); +} + +// +// Container +// + +QJsonObject PixelTextBoxData::pixelTextBoxContainerToJson(const QVector &ptbdCont) +{ + QJsonArray arr; + for (const PixelTextBoxData &item : std::as_const(ptbdCont)) + arr.push_back(item.toJson()); + + QJsonObject jObj; + jObj[QStringLiteral("PixelTextBoxData")] = arr; + return jObj; +} +QVector PixelTextBoxData::pixelTextBoxContainerFromJson(const QJsonValue &json) +{ + QVector ret; + + if (!json.isObject()) + return ret; + + const QJsonObject jObj = json.toObject(); + if (!jObj.contains(QStringLiteral("PixelTextBoxData"))) + return ret; + + const QJsonArray &array = jObj[QStringLiteral("PixelTextBoxData")].toArray(); + ret.reserve(array.size()); + for(int i = 0; i < array.size(); ++i) + ret.push_back(PixelTextBoxData::fromJson(array.at(i))); + return ret; +} + +QString PixelTextBoxData::pixelTextBoxContainerToJsonStr(const QVector &ptbdCont, QJsonDocument::JsonFormat format) +{ + QJsonObject obj = pixelTextBoxContainerToJson(ptbdCont); + QJsonDocument jDoc(obj); + return QString::fromStdString(jDoc.toJson(format).toStdString()); +} +QVector PixelTextBoxData::pixelTextBoxContainerFromJsonStr(const QString &jsonStr) +{ + if (jsonStr.isEmpty()) + return {}; + + QJsonParseError parseError; + QJsonDocument jDoc(QJsonDocument::fromJson(jsonStr.toStdString().c_str(), &parseError)); + + if (parseError.error != QJsonParseError::NoError) + { + qCritical() << " QJsonParseError->errorString: '" << parseError.errorString() << "'" + << ", offset: " << parseError.offset; + return {}; + } + + QJsonObject jObj = jDoc.object(); + if (jObj.isEmpty()) + return {}; + + return PixelTextBoxData::pixelTextBoxContainerFromJson(jObj); +} + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +GEOMETRYMODEL_EXPORT std::ostream &operator<<(std::ostream &stream, const PixelTextBoxData &value) +{ + stream << value.toDebugString().toStdString().c_str(); + return stream; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.h new file mode 100755 index 000000000..a1484a6ce --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GeometryModel/geometry_pixelTextBox.h @@ -0,0 +1,69 @@ +/** + * @RDE_HEADER@ + * PixelTextBoxData as Addon to GeometryModel + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2023-04-27 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "geometry_model_export.h" + +namespace SituationMap +{ + +class GEOMETRYMODEL_EXPORT PixelTextBoxData +{ +public: + PixelTextBoxData() = default; + explicit PixelTextBoxData(const QString &htmlText, const QColor &backgroundColor); + + void reset(); + bool isEmpty() const; + + QString toDebugString() const; + + QString m_htmlText; + QColor m_backgroundColor; + QPoint m_position; + QSize m_size; + + // operator + bool operator==(const PixelTextBoxData &o) const; + + + // Json: + QJsonObject toJson() const; + static PixelTextBoxData fromJson(const QJsonValue &json); + + QString toJsonStr(QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented) const; + static PixelTextBoxData fromJsonStr(const QString &jsonStr); + + static QJsonObject pixelTextBoxContainerToJson(const QVector &ptbdCont); + static QVector pixelTextBoxContainerFromJson(const QJsonValue &json); + + static QString pixelTextBoxContainerToJsonStr(const QVector &ptbdCont, QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented); + static QVector pixelTextBoxContainerFromJsonStr(const QString &jsonStr); + +}; + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +GEOMETRYMODEL_EXPORT std::ostream &operator<< (std::ostream &stream, + const PixelTextBoxData &value); + + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.cpp new file mode 100755 index 000000000..d2ebd3d64 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.cpp @@ -0,0 +1,556 @@ +/** + * @RDE_HEADER@ + * AbstractGraphicalObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "abstract_graphical_object.h" +#include // operator<< + + +// ESRI +#include +#include +#include +#include + + +// Qt +#include +#include +#include +#include +#include + + +namespace SituationMap +{ + +AbstractGraphicalObject::AbstractGraphicalObject(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : m_geoView(geoView) + , m_graphicsLayer(graphicsLayer) + , m_uniqueMapId(uniqueMapId) + , m_debugInfo(debugInfo) + , m_logger(getLogger("AbstractGraphicalObject")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + m_pickable = (m_uniqueMapId != INVALID_MAP_ID); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +AbstractGraphicalObject::~AbstractGraphicalObject() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << ", m_graphicsLayer-overlayId=" << (m_graphicsLayer ? m_graphicsLayer->overlayId() : QString(QStringLiteral("unknown"))); + +#if ACRGIS_VERSION >= 0x640F00 // 100.15.0 + // removeOne ist helpful to let ModuleTest succeed: 'situationMapModuleTest checkOverlayHandler3 none' + if (m_graphic && m_graphicsLayer && m_graphicsLayer->graphics()) + m_graphicsLayer->graphics()->removeOne(m_graphic); + delete m_graphic; + m_graphic = nullptr; +#else + +/* + // cleanup + // to make sure, the the graphic is removed from internal container BEFORE + // graphic is deleted, follow the documentation: use Layer::removeGraphic + // (instead 'delete m_graphic') + if (m_graphic && m_graphicsLayer && m_graphicsLayer->graphics()) + m_graphicsLayer->graphics()->removeOne(m_graphic); + + if (m_graphic) + { + delete m_graphic; + m_graphic = nullptr; + } +*/ + + // Race condition between removeOne() and delete m_graphic : + // BUG-000116867 is for the deadlock + // BUG-000117464 is for the crash + // Use following workaround until ESRI will release the fix (with 100.5!?) + if (m_graphic && m_graphicsLayer && m_graphicsLayer->graphics()) + { + const int idx = m_graphicsLayer->graphics()->indexOf(m_graphic); + + if (idx < 0) + ERROR_LOG(m_logger) << LOGFUNCTION << " indexOf failed, debugInfo='" << m_debugInfo << "'"; + + auto graphicCopy = m_graphic; + + QObject::connect (m_graphicsLayer->graphics(), &Esri::ArcGISRuntime::GraphicListModel::graphicRemoved, + m_graphic, [idx, graphicCopy](int index) + { + if (idx == index) + delete graphicCopy; + }); + m_graphicsLayer->graphics()->removeOne(m_graphic); + } + // : Race condition between removeOne() and delete m_graphic + + // m_esriSimpleLineSymbol AND m_esriSimpleFillSymbol will be destroyed m_graphic + +#endif // ACRGIS_VERSION >= 0x640F00 + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +Esri::ArcGISRuntime::Graphic *AbstractGraphicalObject::graphic() +{ + return m_graphic; +} + +Esri::ArcGISRuntime::GraphicsOverlay *AbstractGraphicalObject::graphicsLayer() +{ + return m_graphicsLayer; +} + +Esri::ArcGISRuntime::Geometry AbstractGraphicalObject::getGraphicGeometry() const +{ + if (! m_graphic) + return Esri::ArcGISRuntime::Geometry(); + return m_graphic->geometry(); +} + +bool AbstractGraphicalObject::updateView() +{ + if (!changed()) + return true; + + bool success = updateViewImpl(); + resetChanged(); + return success; +} + + + +QString AbstractGraphicalObject::debugInfo() const +{ + return m_debugInfo; +} +void AbstractGraphicalObject::setDebugInfo(const QString &debugInfo) +{ + m_debugInfo = debugInfo; +} + +int AbstractGraphicalObject::drawOrder() const +{ + return m_drawOrder.value(); +} +void AbstractGraphicalObject::setDrawOrder(int drawOrder) +{ + m_drawOrder = drawOrder; + // if (m_graphic) + // m_graphic->setDrawOrder(m_drawOrder.value()); +} + + +QString AbstractGraphicalObject::toString() const +{ + QString str; + + if (!m_debugInfo.isEmpty()) + str.append(QStringLiteral("debugInfo=%1").arg(m_debugInfo)); + if (m_graphicsLayer) + str.append(QStringLiteral(" layer='%1'").arg(m_graphicsLayer->objectName())); + + str.append(QStringLiteral(" m_drawOrder='%1'").arg(m_drawOrder.value())); + + if (m_graphic) + str.append(QStringLiteral(" zIndex='%1'").arg(m_graphic->zIndex())); + + str.append(QStringLiteral(" visibility='%1'").arg(m_visibility.value())); + str.append(QStringLiteral(" selected='%1'").arg(m_selected.value())); + + str.append(QStringLiteral(" scale='%1'").arg(m_scale.value())); + str.append(QStringLiteral(" positions.count='%1'").arg(m_positions.value().count())); + + return str; +} + +UniqueMapId AbstractGraphicalObject::uniqueMapId() const +{ + return m_uniqueMapId; +} + +void AbstractGraphicalObject::setUniqueMapId(UniqueMapId uniqueId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId=" << uniqueId; + + m_uniqueMapId = uniqueId; + + // use attribute to store application obj id (SimWorld) at graphic + if (m_graphic) + { + // use attributeModel to store application obj id (SimWorld) at graphic + Esri::ArcGISRuntime::AttributeListModel *attListModel = m_graphic->attributes(); + if (attListModel) + attListModel->insertAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute), m_uniqueMapId); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no AttributeListModel, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId=" << m_uniqueMapId; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId=" << uniqueId; +} + +Point3D AbstractGraphicalObject::origin() const +{ + return m_positions.value().value(0); // value => if index is out of range -> default object +} +void AbstractGraphicalObject::setOrigin(const Point3D &origin) +{ + // TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + // << " origin=" << origin; + + if (m_positions.value().isEmpty()) + m_positions.refValue().append(origin); + else + { + // https://stackoverflow.com/a/1573715/2352507 + // Any comparison(except with "!=") with NaN returns false. + // NaN != NaN -> true!!! + if (m_positions.value().value(0) != origin) // NaN != NaN -> true!!! + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " value(0) = " << m_positions.value().value(0) << " origin = " << origin; + m_positions.refValue().clear(); + m_positions.refValue().append(origin); + } + } + + // TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + // << " origin=" << origin + // << " changed=" << m_positions.changed() + // << " SIZE=" << m_positions.value().size(); +} + +void AbstractGraphicalObject::setOrigin(const QPointF &origin) +{ + setOrigin(Point3D(origin.x(), origin.y(), .0)); +} + +PositionCont AbstractGraphicalObject::positions() const +{ + return m_positions.value(); +} +void AbstractGraphicalObject::setPositions(const PositionCont &posCont) +{ + m_positions = posCont; + + // TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + // << " changed=" << m_positions.changed(); +} + + +bool AbstractGraphicalObject::isSinglePoint() const +{ + return m_positions.value().count() == 1; +} +bool AbstractGraphicalObject::isMultiPoint() const +{ + return m_positions.value().count() > 1; +} +bool AbstractGraphicalObject::isArea() const +{ + return m_positions.value().count() > 2; +} + +LineStyle AbstractGraphicalObject::lineStyle() const +{ + return m_lineStyle.value(); +} +LineStyle &AbstractGraphicalObject::rLineStyle() +{ + return m_lineStyle.refValue(); +} +void AbstractGraphicalObject::setLineStyle(const LineStyle &lineStyle) +{ + m_lineStyle = lineStyle; +} + +FillStyle AbstractGraphicalObject::fillStyle() const +{ + return m_fillStyle.value(); +} +FillStyle &AbstractGraphicalObject::rFillStyle() +{ + return m_fillStyle.refValue(); +} +void AbstractGraphicalObject::setFillStyle(const FillStyle &fillStyle) +{ + m_fillStyle = fillStyle; +} + + +bool AbstractGraphicalObject::visibility() const +{ + return m_visibility.value(); +} +void AbstractGraphicalObject::setVisibility(bool visibility) +{ + m_visibility = visibility; + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " visibility=" << m_visibility; +} + +bool AbstractGraphicalObject::selected() const +{ + return m_selected.value(); +} +void AbstractGraphicalObject::setSelected(bool selected) +{ + m_selected = selected; +} + + +bool AbstractGraphicalObject::pickable() const +{ + return m_pickable.value(); +} +void AbstractGraphicalObject::setPickable(bool pickable) +{ + // allow pickable only if m_uniqueMapId is valid: + m_pickable = pickable && (m_uniqueMapId != INVALID_MAP_ID); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " pickable=" << m_pickable; +} + + +float AbstractGraphicalObject::scale() const +{ + return m_scale.value(); +} +void AbstractGraphicalObject::setScale(float scale) +{ + m_scale = scale; +} + + +bool AbstractGraphicalObject::addUpdateGraphic() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " updateGraphic"; + + bool successGraphic = true; + + if (! m_graphic) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " no m_graphic -> return true"; + return true; + } + + // append just once :-) + if (! m_graphicsLayer->graphics()->contains(m_graphic)) + { + m_graphicsLayer->graphics()->append(m_graphic); + successGraphic = m_graphicsLayer->graphics()->contains(m_graphic); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " successGraphic=" << successGraphic; + return successGraphic; +} + + +bool AbstractGraphicalObject::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId =" << m_uniqueMapId + << " origin =" << this->origin() + << " visibility=" << m_visibility; + bool success = true; + + // some checks: + if (!m_graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " no graphicsLayer"; + return false; + } + + if (!m_graphic) + { + m_graphic = new Esri::ArcGISRuntime::Graphic(); + m_graphic->setObjectName(m_debugInfo); + m_graphic->setZIndex(m_drawOrder.value()); + + // use attributeModel to store application obj id (SimWorld) at graphic + Esri::ArcGISRuntime::AttributeListModel *attLisModel = m_graphic->attributes(); + if (attLisModel) + attLisModel->insertAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute), m_uniqueMapId); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no AttributeListModel, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId=" << m_uniqueMapId; + } + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success=" << success; + return success; +} + + +bool AbstractGraphicalObject::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId=" << m_uniqueMapId + << " origin=" << this->origin() + << " visibility=" << m_visibility + << " selected=" << m_selected + << " drawOrder=" << m_drawOrder + << " pickable=" << m_pickable + << " pickableCHG=" << m_pickable.changed(); + + if (QApplication::instance()->thread()->currentThreadId() != QThread::currentThreadId()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " wrong Thread, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId=" << m_uniqueMapId; + abort(); + } + + + if (m_graphic) + { + m_graphic->setZIndex(m_drawOrder.value()); + m_graphic->setVisible(m_visibility.value()); + m_graphic->setSelected(m_selected.value()); + + // Picking: + if (m_pickable.changed()) + { + // use attributeModel to store m_pickable at graphic + Esri::ArcGISRuntime::AttributeListModel *attLisModel = m_graphic->attributes(); + if (attLisModel) + attLisModel->insertAttribute(QLatin1String(SituationMap::IsPickableAttribute), m_pickable.value()); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no AttributeListModel, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId=" << m_uniqueMapId; + } + } + m_pickable.reset(); + + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " uniqueMapId =" << m_uniqueMapId; + return true; +} + +bool AbstractGraphicalObject::assignLineStyleToGraphic() +{ + if (!m_graphic) + return false; + + const LineStyle &ls = m_lineStyle.value(); + + if (!m_esriSimpleLineSymbol) + { + m_esriSimpleLineSymbol = + new Esri::ArcGISRuntime::SimpleLineSymbol(convert(ls.linePattern()), ls.lineColor(), ls.lineWidth() * m_scale.value(), m_graphic); + } else { + m_esriSimpleLineSymbol->setStyle(convert(ls.linePattern())); + m_esriSimpleLineSymbol->setColor(ls.lineColor()); + m_esriSimpleLineSymbol->setWidth(ls.lineWidth() * m_scale.value()); + } + m_graphic->setSymbol(m_esriSimpleLineSymbol.data()); + + return true; +} + +bool AbstractGraphicalObject::assignFillStyleToGraphic() +{ + if (!m_graphic) + return false; + + const LineStyle &ls = m_lineStyle.value(); + const FillStyle &fs = m_fillStyle.value(); + + // 1. the Line + if (!m_esriSimpleLineSymbol) + { + m_esriSimpleLineSymbol = + new Esri::ArcGISRuntime::SimpleLineSymbol(convert(ls.linePattern()), ls.lineColor(), ls.lineWidth() * m_scale.value(), m_graphic); + } else { + m_esriSimpleLineSymbol->setStyle(convert(ls.linePattern())); + m_esriSimpleLineSymbol->setColor(ls.lineColor()); + m_esriSimpleLineSymbol->setWidth(ls.lineWidth() * m_scale.value()); + } + + // 1. the Fill + if (!m_esriSimpleFillSymbol) + { + m_esriSimpleFillSymbol = + new Esri::ArcGISRuntime::SimpleFillSymbol(convert(fs.fillPattern()), fs.fillColor(), m_esriSimpleLineSymbol, m_graphic); + } else { + m_esriSimpleFillSymbol->setStyle(convert(fs.fillPattern())); + m_esriSimpleFillSymbol->setColor(fs.fillColor()); + m_esriSimpleFillSymbol->setOutline(m_esriSimpleLineSymbol); + } + m_graphic->setSymbol(m_esriSimpleFillSymbol.data()); + + return true; +} + +Esri::ArcGISRuntime::SimpleLineSymbolStyle AbstractGraphicalObject::convert(const LinePattern linePattern) +{ + switch (linePattern) + { + case LinePattern::Dash: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Dash; + case LinePattern::DashDot: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::DashDot; + case LinePattern::DashDotDot: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::DashDotDot; + case LinePattern::Dot: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Dot; + case LinePattern::Null: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Null; + case LinePattern::Solid: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid; + } // switch + + return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid; +} + + +Esri::ArcGISRuntime::SimpleFillSymbolStyle AbstractGraphicalObject::convert(const FillPattern fillPattern) +{ + switch (fillPattern) + { + case FillPattern::BackwardDiagonal: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::BackwardDiagonal; + case FillPattern::Cross: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Cross; + case FillPattern::DiagonalCross: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::DiagonalCross; + case FillPattern::ForwardDiagonal: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::ForwardDiagonal; + case FillPattern::Horizontal: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Horizontal; + case FillPattern::Null: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Null; + case FillPattern::Solid: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid; + case FillPattern::Vertical: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Vertical; + } // switch + + return Esri::ArcGISRuntime::SimpleFillSymbolStyle::BackwardDiagonal; +} + +int AbstractGraphicalObject::countOfPolygonFirstPart(const Esri::ArcGISRuntime::Multipart &polylinePolygon) +{ + if (polylinePolygon.isEmpty() || polylinePolygon.parts().isEmpty()) + return -1; + + return polylinePolygon.parts().part(0).pointCount(); +} + + +const Esri::ArcGISRuntime::SpatialReference AbstractGraphicalObject::spatialReference() const +{ + return m_geoView->spatialReference(); +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.h new file mode 100755 index 000000000..749776324 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/abstract_graphical_object.h @@ -0,0 +1,213 @@ +/** + * @RDE_HEADER@ + * AbstractGraphicalObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include +#include + +#include + +// ESRI +#include +#include + +// Qt +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class GeoView; +class Graphic; +class GraphicsOverlay; +class Geometry; +class Multipart; +class SpatialReference; +} +} + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//forward +class GraphicalObjectText; + + +class SITUATIONMAP_EXPORT AbstractGraphicalObject +{ + +public: + AbstractGraphicalObject(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + virtual ~AbstractGraphicalObject(); + + AbstractGraphicalObject(const AbstractGraphicalObject &other) = delete; + AbstractGraphicalObject &operator=(AbstractGraphicalObject &other) = delete; + + virtual Esri::ArcGISRuntime::Graphic *graphic(); + virtual Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer(); + + virtual QString toString() const; + + virtual UniqueMapId uniqueMapId() const; + virtual void setUniqueMapId(UniqueMapId uniqueId); + + virtual QString debugInfo() const; + virtual void setDebugInfo(const QString &debugInfo); + + virtual int drawOrder() const; + virtual void setDrawOrder(int drawOrder); + + // Data access: + + // for SinglePoint: + virtual Point3D origin() const; + virtual void setOrigin(const Point3D &origin); + virtual void setOrigin(const QPointF &origin); // ToDO accept this?? + + // for MultiPoint: + virtual PositionCont positions() const; + virtual void setPositions(const PositionCont &posCont); + + virtual bool isSinglePoint() const; + virtual bool isMultiPoint() const; + virtual bool isArea() const; + + virtual LineStyle lineStyle() const; + virtual LineStyle &rLineStyle(); + /** + * @brief setLineStyle (Font-)size is limited to 500. + * (High values can lead to coredump with 'SymbolDrawHelper::create_line_image FAILED, SkBitmap is NULL or empty') + * @param lineStyle to set. + */ + virtual void setLineStyle(const LineStyle &lineStyle); + + virtual FillStyle fillStyle() const; + virtual FillStyle &rFillStyle(); + virtual void setFillStyle(const FillStyle &fillStyle); + + // Scale : + virtual float scale() const; + virtual void setScale(float scale); + // : Scale + + + // Visibility : + virtual bool visibility() const; + virtual void setVisibility(bool visibility); + // : Visibility + + // Selection, Picking : + bool selected() const; + void setSelected(bool selected); + + bool pickable() const; + /** + * @brief setPickable The method allows pickable=true only if uniqueMapIdis valid. + * @param pickable new value. + */ + void setPickable(bool pickable); + // : Selection, Picking + + + virtual Esri::ArcGISRuntime::Geometry getGraphicGeometry() const; + + bool updateView(); + + bool addUpdateGraphic(); + + static Esri::ArcGISRuntime::SimpleLineSymbolStyle convert(const LinePattern linePattern); + static Esri::ArcGISRuntime::SimpleFillSymbolStyle convert(const FillPattern fillPattern); + + bool assignLineStyleToGraphic(); + bool assignFillStyleToGraphic(); + + + static int countOfPolygonFirstPart(const Esri::ArcGISRuntime::Multipart &polylinePolygon); + + +protected: + // Internal/Admin + const Esri::ArcGISRuntime::SpatialReference spatialReference() const; + + virtual bool changed() + { + return m_drawOrder.changed() + || m_positions.changed() + || m_visibility.changed() + || m_selected.changed() + || m_pickable.changed() + || m_scale.changed() + || m_lineStyle.changed() + || m_fillStyle.changed(); + } + + virtual bool updateViewImpl() = 0; + + virtual void resetChanged() + { + m_drawOrder.reset(); + m_positions.reset(); + m_visibility.reset(); + m_selected.reset(); + m_pickable.reset(); + m_scale.reset(); + m_lineStyle.reset(); + m_fillStyle.reset(); + } + + + virtual bool create() = 0; + + + const Esri::ArcGISRuntime::GeoView *m_geoView = nullptr; + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + Esri::ArcGISRuntime::Graphic *m_graphic = nullptr; + SimpleValue m_drawOrder = 0; + + // Data + UniqueMapId m_uniqueMapId; + + + QString m_debugInfo; ///< for debugging + + SimpleValue m_positions; + + SimpleValue m_visibility = true; + + SimpleValue m_selected = false; + SimpleValue m_pickable = true; + + SimpleValue m_scale = 1.0; + + SimpleValue m_lineStyle; + SimpleValue m_fillStyle; + QPointer m_esriSimpleLineSymbol = nullptr; + QPointer m_esriSimpleFillSymbol = nullptr; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.cpp new file mode 100755 index 000000000..9b60d8462 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.cpp @@ -0,0 +1,96 @@ +/** + * @RDE_HEADER@ + * EllipseParameters + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "ellipse_parameters.h" + + +// ESRI + + +// Qt +#include + + +namespace SituationMap +{ + +float EllipseParameters::lengthX() const +{ + return m_lengthX; +} + +void EllipseParameters::setLengthX(float lengthX) +{ + m_lengthX = lengthX; +} +float EllipseParameters::lengthY() const +{ + return m_lengthY; +} + +void EllipseParameters::setLengthY(float lengthY) +{ + m_lengthY = lengthY; +} +float EllipseParameters::directionDegree() const +{ + return m_directionDegree; +} + +void EllipseParameters::setDirectionDegree(float directionDegree) +{ + m_directionDegree = directionDegree; +} +int EllipseParameters::maxSegmentLength() const +{ + return m_maxSegmentLength; +} + +void EllipseParameters::setMaxSegmentLength(int value) +{ + m_maxSegmentLength = value; +} + +int EllipseParameters::maxPointCount() const +{ + return m_maxPointCount; +} + +void EllipseParameters::setMaxPointCount(int maxPointCount) +{ + m_maxPointCount = maxPointCount; +} + + +bool EllipseParameters::isValid() const +{ + return ((m_lengthX > 0) && (m_lengthY > 0)) ; +} + + + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<< (std::ostream &stream, + const EllipseParameters &value) +{ + QString str; + str.append(QStringLiteral(" lengthX=%1").arg(value.lengthX())); + str.append(QStringLiteral(" lengthY=%1").arg(value.lengthY())); + str.append(QStringLiteral(" directionDegree=%1").arg(value.directionDegree())); + str.append(QStringLiteral(" maxSegmentLength=%1").arg(value.maxSegmentLength())); + + stream << str.toStdString(); + return stream; +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.h new file mode 100755 index 000000000..a8609a3a0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/ellipse_parameters.h @@ -0,0 +1,95 @@ +/** + * @RDE_HEADER@ + * EllipseParameters + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include + +#include + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + + + +//---------------------------------------------------------------------------- +class SITUATIONMAP_EXPORT EllipseParameters +{ +public: + + static constexpr unsigned int MAX_POINT_COUNT_DEFAULT = 30; + + EllipseParameters() = default; + + float lengthX() const; + void setLengthX(float lengthX); + + float lengthY() const; + void setLengthY(float lengthY); + + float directionDegree() const; + void setDirectionDegree(float directionDegree); + + int maxSegmentLength() const; + void setMaxSegmentLength(int value); + + int maxPointCount() const; + void setMaxPointCount(int maxPointCount); + + bool isValid() const; + + /** + * @brief operator== Compare two objects + * @param other to be compared with + * @return true if identical, otherwise false. + */ + bool operator==(const EllipseParameters &other) const + { + return ((this->m_lengthX == other.m_lengthX) && + (this->m_lengthY == other.m_lengthY) && + (this->m_directionDegree == other.m_directionDegree) && + (this->m_maxSegmentLength == other.m_maxSegmentLength) && + (this->m_maxPointCount == other.m_maxPointCount) + ); + } + + /** + * @brief operator!= Compare two objects + * @param other to be compared with + * @return true if NOT identical, otherwise false. + */ + bool operator!=(const EllipseParameters &other) const + { + return !(*this == other); + } + + + +private: + float m_lengthX = 0; + float m_lengthY = 0; + float m_directionDegree = 0; + int m_maxSegmentLength = 10; // meter + int m_maxPointCount = MAX_POINT_COUNT_DEFAULT; +}; + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +SITUATIONMAP_EXPORT std::ostream &operator<< (std::ostream &stream, + const EllipseParameters &value); + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.cpp new file mode 100755 index 000000000..589367333 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.cpp @@ -0,0 +1,190 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectAttributes + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_attributes.h" +#include +#include // operator<< +#include + + +// ESRI +#include +#include + +// Qt + + +namespace SituationMap +{ + + +class GraphicalObjectAttributes::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + MapImpl *m_mapImpl = nullptr; + QString m_logicalLayerName; + + // OWN data: + SimpleValue m_attributes; + +}; // class GraphicalObjectAttributes::Private +//----------------------------------------------------------------------------- + + + +GraphicalObjectAttributes::GraphicalObjectAttributes(MapImpl *mapImpl, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + const QString &debugInfo, + const UniqueMapId &uniqueMapId) + : BaseClass(mapImpl->getEsriGeoView(), graphicsLayer, debugInfo, uniqueMapId), + m_logger(getLogger("GraphicalObjectAttributes")), + d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectAttributes::~GraphicalObjectAttributes() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +bool GraphicalObjectAttributes::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " m_visibility = " << m_visibility; + + bool success = BaseClass::create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + bool successGraphic = addUpdateGraphic(); // CHECK + if (!successGraphic) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addUpdateGraphic failed, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; + return false; + } + + // real work: + // empty + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; + return success; +} + +QVariantMap GraphicalObjectAttributes::attributes() const +{ + return d->m_attributes.value(); +} + +void GraphicalObjectAttributes::setAttributes(const QVariantMap &attributes) +{ + d->m_attributes = attributes; +} + +bool GraphicalObjectAttributes::changed() +{ + return BaseClass::changed() + || d->m_attributes.changed(); +} +void GraphicalObjectAttributes::resetChanged() +{ + BaseClass::resetChanged(); + d->m_attributes.reset(); +} + +Esri::ArcGISRuntime::Geometry GraphicalObjectAttributes::getGraphicGeometry() const +{ + if (m_graphic) return m_graphic->geometry(); + + ERROR_LOG(m_logger) << LOGFUNCTION << "Geometry is undefined"; + + return Esri::ArcGISRuntime::Geometry(); +} // Esri::ArcGISRuntime::Geometry GraphicalObjectAttributes::getGraphicGeometry() const + + +bool GraphicalObjectAttributes::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " visibility=" << m_visibility + << " selected=" << m_selected + << " origin=" << this->origin() + << " scale=" << m_scale.value(); + + bool success = create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + if (!BaseClass::updateViewImpl()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + + // real work: + if (m_positions.changed()) + { + const PositionCont &posCont = m_positions.value(); + if (posCont.size() == 1) + { + const Point3D &origin = this->origin(); + Esri::ArcGISRuntime::Point originGeometry(origin.x(), + origin.y(), + origin.z(), + spatialReference()); + m_graphic->setGeometry(originGeometry); + } + } + + if (d->m_attributes.changed()) + { + QVariantMap rawAttributes = d->m_attributes.value(); + + // add basics: + rawAttributes[QLatin1String(SituationMap::UniqueMapIdAttribute)] = m_uniqueMapId; + rawAttributes[QLatin1String(SituationMap::IsPickableAttribute)] = m_pickable.value(); + m_graphic->attributes()->setAttributesMap(rawAttributes); + + if (m_logger.isDebugEnabled()) + qCritical() << " XXXXX set rawAttributes " << rawAttributes; + } + + if (m_graphic) + { + m_graphic->setZIndex(m_drawOrder.value()); + m_graphic->setVisible(m_visibility.value()); + m_graphic->setSelected(m_selected.value()); + } + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure, debugInfo='" << m_debugInfo << "'"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success = " << success + << " visibility=" << m_visibility; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.h new file mode 100755 index 000000000..56d84c23a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_attributes.h @@ -0,0 +1,88 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectAttributes + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" + +// Esri + + +// Qt + + +// forward: + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward: +class MapImpl; + + +/** + * @brief The GraphicalObjectAttributes class + */ +class SITUATIONMAP_EXPORT GraphicalObjectAttributes : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + + /** + * @brief GraphicalObjectAttributes + * @param mapImpl map to be used. + * @param logicalLayerName logical layer name to be used. + * @param debugInfo debugInfo to be used + * @param uniqueMapId uniqueMapId to be used. (Optional to fulfill rubberband requirements) + */ + GraphicalObjectAttributes(MapImpl *mapImpl, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + const QString &debugInfo, + const UniqueMapId &uniqueMapId = INVALID_MAP_ID); + + GraphicalObjectAttributes(const GraphicalObjectAttributes &other) = delete; + GraphicalObjectAttributes &operator=(GraphicalObjectAttributes &other) = delete; + + virtual ~GraphicalObjectAttributes(); + + + QVariantMap attributes() const; + void setAttributes(const QVariantMap &attributes); + + Esri::ArcGISRuntime::Geometry getGraphicGeometry() const override; + +protected: + // Internal/Admin + virtual bool create() override; + + bool changed() override; + void resetChanged() override; + + bool updateViewImpl() override; + + // logical + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.cpp new file mode 100755 index 000000000..78ce4bb26 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.cpp @@ -0,0 +1,162 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectCube + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_cube.h" +#include // operator<< + + +// ESRI +#include +#include + + +// Qt + + +namespace SituationMap +{ + + +GraphicalObjectCube::GraphicalObjectCube(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_logger(getLogger("GraphicalObjectCube")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectCube::~GraphicalObjectCube() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +bool GraphicalObjectCube::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + bool success = BaseClass::create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + } + + // real work: + // empty + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + // << " drawOrder = " << m_graphic->drawOrder() + << " success = " << success; + return success; +} + + +bool GraphicalObjectCube::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + // some checks: + if (!this->isArea()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + if (!create()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + if (!BaseClass::updateViewImpl()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + // real work: + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'"; + + + // Attribute 'height': + QVariantMap attMap = m_graphic->attributes()->attributesMap(); + + attMap[QStringLiteral("height")] = QVariant(m_height.value()); + // + // Attribute 'PartyName' is replaced by Attribute 'Extrusion_Color' to be more flexible + // + + + // Attribute 'Extrusion_Color': + if (m_extrusionColorName.value().isEmpty()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " debugInfo='" << m_debugInfo << "'" + << " m_extrusionColorName is empty"; + // -> time for the defaultSymbol of the Renderer + attMap[QStringLiteral("Extrusion_Color")] = QStringLiteral("DOES_NOT_EXIST"); + } + else + attMap[QStringLiteral("Extrusion_Color")] = QVariant(m_extrusionColorName.value()); + + m_graphic->attributes()->setAttributesMap(attMap); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " attributesMap[height] ='" << attMap[QStringLiteral("height")].toDouble() << "'"; + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " attributesMap[Extrusion_Color] ='" << attMap[QStringLiteral("Extrusion_Color")].toString() << "'"; + + // geometry: + Esri::ArcGISRuntime::PolygonBuilder builder(spatialReference()); + + const PositionCont &posCont = m_positions.value(); + for (const Point3D &pos : posCont) + { + builder.addPoint(pos.x(), pos.y()); + } + + if (! builder.isSketchValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " builder -> sketch invalid"; + return false; + } + m_graphic->setGeometry(builder.toGeometry()); + + + // add + bool successGraphic = addUpdateGraphic(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " drawOrder = " << m_graphic->zIndex(); + return successGraphic; +} + + + + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<<(std::ostream &stream, + const GraphicalObjectCube &value) +{ + stream << value.toString(); + return stream; +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.h new file mode 100755 index 000000000..15866adb9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_cube.h @@ -0,0 +1,100 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectCube + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" + + +// Esri + + +// Qt + + +// forward: + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The GraphicalObjectCube class for 3D + * + */ +class SITUATIONMAP_EXPORT GraphicalObjectCube : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + explicit GraphicalObjectCube(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectCube(); + + double height() const + { + return m_height.value(); + } + void setHeight(double height) + { + m_height = height; + } + + const QString &extrusionColorName() const + { + return m_extrusionColorName.value(); + } + void setExtrusionColorName(const QString &extrusionColorName) + { + m_extrusionColorName = extrusionColorName; + } + +protected: + // Internal/Admin + bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_height.changed() + || m_extrusionColorName.changed(); + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_height.reset(); + m_extrusionColorName.reset(); + } + + bool updateViewImpl() override; + + + // Data + SimpleValue m_height; + SimpleValue m_extrusionColorName; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +SITUATIONMAP_EXPORT std::ostream &operator<< (std::ostream &stream, + const GraphicalObjectCube &value); + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.cpp new file mode 100755 index 000000000..578dc1010 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.cpp @@ -0,0 +1,344 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectEllipse + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_ellipse.h" +#include // operator<< + + +// ESRI +#include +#include +#include +#include +#include + + +// Qt +#include + + +namespace SituationMap +{ + + +GraphicalObjectEllipse::GraphicalObjectEllipse(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_logger(getLogger("GraphicalObjectEllipse")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectEllipse::~GraphicalObjectEllipse() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + delete m_internalParameters; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +bool GraphicalObjectEllipse::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + bool success = BaseClass::create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + // real work: + // empty + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + // << " drawOrder = " << m_graphic->drawOrder() + << " success = " << success; + return success; +} + + +bool GraphicalObjectEllipse::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + // some checks: + if (!this->isSinglePoint()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + if (!create()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + if (!BaseClass::updateViewImpl()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + if (!m_ellipseParameters.value().isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " ellipseParameters not valid"; + return false; + } + + + // real work: + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " update geometry"; + + bool successEllipse = true; + if (m_ellipseParameters.changed() || m_positions.changed()) + { + // apply attributes: + parameterToInternal(); + + Esri::ArcGISRuntime::Polygon ellipse; + successEllipse = createEllipse(ellipse); + if (successEllipse) + m_graphic->setGeometry(ellipse); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " createEllipse failed"; + return false; + } + } + + assignFillStyleToGraphic(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'"; + + bool successGraphic = addUpdateGraphic(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " drawOrder = " << m_graphic->zIndex() + << " successEllipse = " << successEllipse + << " successGraphic = " << successGraphic; + return successEllipse && successGraphic; +} + +void GraphicalObjectEllipse::parameterToInternal() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + if (!m_internalParameters) + { + m_internalParameters = new Esri::ArcGISRuntime::GeodesicEllipseParameters(); + m_internalParameters->setGeometryType(Esri::ArcGISRuntime::GeometryType::Polygon); + } + + // TODO !!??: set xy correct, calc maxSegLength... + + + const EllipseParameters &ellipseParameterRef = m_ellipseParameters.value(); + + // 1a, 1b + m_internalParameters->setMaxSegmentLength(ellipseParameterRef.maxSegmentLength()); + m_internalParameters->setMaxPointCount(ellipseParameterRef.maxPointCount()); + + // 2 + const Point3D &origin = this->origin(); + Esri::ArcGISRuntime::Point mapPoint(origin.x(), origin.y(), origin.z(), spatialReference()); // center of ellipse + + Esri::ArcGISRuntime::Point normalizedPt = + Esri::ArcGISRuntime::GeometryEngine::normalizeCentralMeridian(mapPoint); + m_internalParameters->setCenter(normalizedPt); + // m_internalParameters->setCenter(mapPoint); + + // 3, 4 + m_internalParameters->setSemiAxis1Length(ellipseParameterRef.lengthX()); + m_internalParameters->setSemiAxis2Length(ellipseParameterRef.lengthY()); + + // 5 + m_internalParameters->setAxisDirection(ellipseParameterRef.directionDegree()); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +bool GraphicalObjectEllipse::createEllipse(Esri::ArcGISRuntime::Polygon &ellipse) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + bool success = createEllipsePolygon(*m_internalParameters, ellipse); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success = " << success +// << " pointCount = " << ellipse.pointCount() + << " dimension = " << ellipse.dimension(); + return success; +} + +bool GraphicalObjectEllipse::createEllipsePolygon(const Esri::ArcGISRuntime::GeodesicEllipseParameters &ellipseParameters, + Esri::ArcGISRuntime::Polygon &polygon) +{ + LOGGER_T m_logger = getLogger("GraphicalObjectEllipse"); + + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QElapsedTimer timer; + + if (m_logger.isInfoEnabled()) + timer.start(); + + + bool success = false; + + Esri::ArcGISRuntime::Geometry ellipseGeometry = + Esri::ArcGISRuntime::GeometryEngine::ellipseGeodesic(ellipseParameters); + if (ellipseGeometry.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " There was an error computing the geodesic ellipse. Try a different segment length."; + } + else + { + polygon = Esri::ArcGISRuntime::Polygon(ellipseGeometry); + success = true; + } + int count = AbstractGraphicalObject::countOfPolygonFirstPart(polygon); + DEBUG_LOG(m_logger) << LOGFUNCTION + << " count=" << count + << " (semiAxis1Length=" << ellipseParameters.semiAxis1Length() + << " , semiAxis2Length=" << ellipseParameters.semiAxis2Length() + << " , maxPointCount=" << ellipseParameters.maxPointCount() + << " , maxSegmentLength=" << ellipseParameters.maxSegmentLength() + << " , dimension = " << polygon.dimension() + << ")"; + + + + if (m_logger.isDebugEnabled()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " took " << timer.elapsed() << "ms" + << " (semiAxis1Length=" << ellipseParameters.semiAxis1Length() + << " , semiAxis2Length=" << ellipseParameters.semiAxis2Length() + << " , maxPointCount=" << ellipseParameters.maxPointCount() + << " , maxSegmentLength=" << ellipseParameters.maxSegmentLength() + << " , dimension = " << polygon.dimension() + << ")"; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " success = " << success; + return success; +} + +bool GraphicalObjectEllipse::createCirclePolygon(const Esri::ArcGISRuntime::Point &originMap, + float radius, + unsigned int maxPointCount, + Esri::ArcGISRuntime::Polygon &polygon) +{ + LOGGER_T m_logger = getLogger("GraphicalObjectEllipse"); + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " origin=" << originMap.x() << " ; " << originMap.y() << " ; " << originMap.z() + << " radius=" << radius; + + Esri::ArcGISRuntime::GeodesicEllipseParameters ePara; + ePara.setGeometryType(Esri::ArcGISRuntime::GeometryType::Polygon); + + // 1 + ePara.setMaxSegmentLength(10); + if (maxPointCount == 0) + ePara.setMaxPointCount(EllipseParameters::MAX_POINT_COUNT_DEFAULT); + else + ePara.setMaxPointCount(maxPointCount); + + // 2 + const Esri::ArcGISRuntime::Point &mapPoint = originMap; // center of ellipse + + Esri::ArcGISRuntime::Point normalizedPt = + Esri::ArcGISRuntime::GeometryEngine::normalizeCentralMeridian(mapPoint); + ePara.setCenter(normalizedPt); + + // 3, 4 + ePara.setSemiAxis1Length(radius); + ePara.setSemiAxis2Length(radius); + + // 5 + ePara.setAxisDirection(0); + + return createEllipsePolygon(ePara, polygon); +} + +bool GraphicalObjectEllipse::createCirclePolygon(const Esri::ArcGISRuntime::Point &originMap, + float radius, + unsigned int maxPointCount, + PositionCont &posCont) +{ + Esri::ArcGISRuntime::Polygon circle; + bool success = GraphicalObjectEllipse::createCirclePolygon(originMap, + radius, + maxPointCount, + circle); + + if (success && !circle.isEmpty()) + { + posCont.clear(); + + const Esri::ArcGISRuntime::ImmutablePart &immutablePart = circle.parts().part(0); + const int size = immutablePart.pointCount(); + + for (int i = 0; i < size; ++i) + { + const Esri::ArcGISRuntime::Point &p = immutablePart.point(i); + posCont.append(Point3D(p.x(), p.y(), p.z())); + } + } + return success; +} + + + +EllipseParameters GraphicalObjectEllipse::ellipseParameters() const +{ + return m_ellipseParameters.value(); +} + +EllipseParameters &GraphicalObjectEllipse::rEllipseParameters() +{ + return m_ellipseParameters.refValue(); +} + +void GraphicalObjectEllipse::setEllipseParameters(const EllipseParameters &ellipseParameters) +{ + m_ellipseParameters = ellipseParameters; +} + + + + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<<(std::ostream &stream, + const GraphicalObjectEllipse &value) +{ + stream << value.toString(); + return stream; +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.h new file mode 100755 index 000000000..19b34ccf1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_ellipse.h @@ -0,0 +1,121 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectEllipse + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" +#include "ellipse_parameters.h" + + +// Esri + + +// Qt + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Polygon; +class Point; +class GeodesicEllipseParameters; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class SITUATIONMAP_EXPORT GraphicalObjectEllipse : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + GraphicalObjectEllipse(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectEllipse(); + + EllipseParameters ellipseParameters() const; + EllipseParameters &rEllipseParameters(); + void setEllipseParameters(const EllipseParameters &ellipseParameters); + + + static bool createEllipsePolygon(const Esri::ArcGISRuntime::GeodesicEllipseParameters &ellipseParameters, + Esri::ArcGISRuntime::Polygon &polygon); + + static bool createCirclePolygon(const Esri::ArcGISRuntime::Point &originMap, + float radius, + unsigned int maxPointCount, + Esri::ArcGISRuntime::Polygon &polygon); + + static bool createCirclePolygon(const Esri::ArcGISRuntime::Point &originMap, + float radius, + unsigned int maxPointCount, + PositionCont &posCont); + + +protected: + // Internal/Admin + bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_ellipseParameters.changed(); + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_ellipseParameters.reset(); + } + + + bool updateViewImpl() override; + + + // Data + Esri::ArcGISRuntime::GeodesicEllipseParameters *m_internalParameters = nullptr; + SimpleValue m_ellipseParameters; + + void parameterToInternal(); + virtual bool createEllipse(Esri::ArcGISRuntime::Polygon &ellipse); + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + // Forbid some overloaded methods without effects of this object; + virtual PositionCont positions() const override + { + return PositionCont(); + } + virtual void setPositions(const PositionCont &posCont) override + { + Q_UNUSED(posCont); + } + +}; + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<< (std::ostream &stream, + const GraphicalObjectEllipse &value); + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.cpp new file mode 100755 index 000000000..52786aeed --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.cpp @@ -0,0 +1,169 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectModelSceneMarker + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_model_scene_marker.h" +#include // operator<< +#include + + +// ESRI +#include +#include + + +// Qt +#include +#include + +namespace SituationMap +{ + +GraphicalObjectModelSceneMarker::GraphicalObjectModelSceneMarker(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_logger(getLogger("GraphicalObjectModelSceneMarker")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectModelSceneMarker::~GraphicalObjectModelSceneMarker() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + delete m_modelSceneSymbol; + m_modelSceneSymbol = nullptr; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectModelSceneMarker::setUrl(const QUrl &url) +{ + m_url = url; +} + +QUrl GraphicalObjectModelSceneMarker::url() const +{ + return m_url.value(); +} + +void GraphicalObjectModelSceneMarker::setHeading(float heading) +{ + m_heading = heading; +} + +float GraphicalObjectModelSceneMarker::heading() const +{ + return m_heading.value(); +} + + +bool GraphicalObjectModelSceneMarker::create() +{ + return BaseClass::create(); +} + +bool GraphicalObjectModelSceneMarker::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " origin =" << this->origin() // .x() << " , " << this->origin().y() + << " visibility=" << m_visibility + << " pickable=" << m_pickable + << " pickable.changed=" << m_pickable.changed() + << " selected=" << m_selected + << " m_url = " << m_url.value().toString() + << " m_drawOrder = " << m_drawOrder + << " m_scale = " << m_scale.value() + << " m_heading = " << m_heading.value(); + + // some checks: + if (!isSinglePoint()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo='" << m_debugInfo << "'" + << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + QFile fi(m_url.value().path()); + if (!fi.exists()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " m_url does not exists: '" << fi.fileName() << "'"; + m_url.reset(); + } + + if (m_url.value().isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo='" << m_debugInfo << "'" + << " no url provided (or does not exist)"; + return false; + } + + bool success = create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + const Point3D &origin = this->origin(); + Esri::ArcGISRuntime::Point originGeometry(origin.x(), + origin.y(), + origin.z(), + spatialReference()); + m_graphic->setGeometry(originGeometry); + + // url changed? + if ( (m_modelSceneSymbol && (m_modelSceneSymbol->url() != m_url.value())) || + m_scale.changed()) + { + // force re-create ModelSceneSymbol: + delete m_modelSceneSymbol; + m_modelSceneSymbol = nullptr; + } + + if (!m_modelSceneSymbol) + { + m_modelSceneSymbol = new Esri::ArcGISRuntime::ModelSceneSymbol(m_url.value(), m_scale.value()); // url, scale + } + + m_modelSceneSymbol->setHeading(m_heading.value()); // degree, clockwise, 0 is north/12 +// m_modelSceneSymbol->setAnchorPosition(Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Bottom); + m_graphic->setSymbol(m_modelSceneSymbol); + + + + success = BaseClass::updateViewImpl(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + success = addUpdateGraphic(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success=" << success + << " m_drawOrder = " << m_drawOrder + << " m_graphic->drawOrder = " << m_graphic->zIndex(); + return success; +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.h new file mode 100755 index 000000000..e58fa84ee --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_model_scene_marker.h @@ -0,0 +1,111 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectModelSceneMarker + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" +#include +#include + + +// Esri +#include + + +// Qt +#include +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Graphic; +class GraphicsOverlay; +class ModelSceneSymbol; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SITUATIONMAP_EXPORT GraphicalObjectModelSceneMarker : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + GraphicalObjectModelSceneMarker(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectModelSceneMarker(); + + // Data access: + // to be cont. + void setUrl(const QUrl &url); + QUrl url() const; + + // degree ; 0 is north/12 + void setHeading(float heading); + float heading() const; + + +protected: + + // Internal/Admin + bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_url.changed() + || m_heading.changed() + ; + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_url.reset(); + m_heading.reset(); + } + + bool updateViewImpl() override; + + // used ModelSceneSymbol: + Esri::ArcGISRuntime::ModelSceneSymbol *m_modelSceneSymbol{nullptr}; + + SimpleValue m_url; // e.g. /shd/CTC/TOOLS/ArcGIS/Runtime/Data/3D/Bristol/Collada/Bristol.dae + SimpleValue m_heading{0.0}; // degree, clockwise, 0 is north/12 + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + // Forbid some overloaded methods without effects of this object; + PositionCont positions() const override + { + return PositionCont(); + } + void setPositions(const PositionCont &posCont) override + { + Q_UNUSED(posCont); + Q_ASSERT(false); + } +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.cpp new file mode 100755 index 000000000..0d7974431 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.cpp @@ -0,0 +1,323 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectPath + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_multi_path.h" +#include // operator<< + + +// ESRI +#include +#include +#include +#include +#include +#include +#include + + +// Qt + + +namespace SituationMap +{ + + +GraphicalObjectMultiPath::GraphicalObjectMultiPath(const GraphicalObjectPathType type + , const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , UniqueMapId uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_pathType(type) + , m_logger(getLogger("GraphicalObjectMultiPath")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; +} + +GraphicalObjectMultiPath::~GraphicalObjectMultiPath() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; +} + +PositionCont GraphicalObjectMultiPath::path(int pathIndex) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " pathIndex=" << pathIndex; + if (hasPosCont(pathIndex)) + return getPosCont(pathIndex).value(); + return {}; +} +void GraphicalObjectMultiPath::setPath(int pathIndex, const PositionCont &positions) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " pathIndex=" << pathIndex + << " positions=" << positions; + m_positionsCont[pathIndex] = positions; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectMultiPath::clearPath(int pathIndex) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " pathIndex=" << pathIndex; + if (pathIndex == -1) + { + m_positions.refValue().clear(); + m_positionsCont.clear(); + } + else + { + if (hasPosCont(pathIndex)) + { + // Do not remove the whole entry to let changed() work + m_positionsCont[pathIndex].refValue().clear(); + } + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectMultiPath::clearAllPaths() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + m_positions.refValue().clear(); + m_positionsCont.clear(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectMultiPath::reduceTo(const QList &remainingPathIndexes) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " remainingPathIndexes=" << remainingPathIndexes; + QList keys = m_positionsCont.keys(); + for (int key : std::as_const(keys)) + { + if (!remainingPathIndexes.contains(key)) + { + // Do not remove the whole entry to let chanded() work + m_positionsCont[key].refValue().clear(); + } + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +QList GraphicalObjectMultiPath::pathIndexInUse() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + QList ret; +///// ret << 0; // pathIndex=0 -> m_positions + + QMapIterator > mapIt(m_positionsCont); + while (mapIt.hasNext()) + { + mapIt.next(); + const SimpleValue &posData = mapIt.value(); + const PositionCont &posCont = posData.value(); + if (! posCont.isEmpty()) + ret += mapIt.key(); + } // while + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; + return ret; +} + + +bool GraphicalObjectMultiPath::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + bool success = BaseClass::create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; + return success; +} + + +bool GraphicalObjectMultiPath::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + bool success = create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + success = BaseClass::updateViewImpl(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + success = updatePositions(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success = " << success + << " drawOrder = " << m_graphic->zIndex(); + return success; +} + +bool GraphicalObjectMultiPath::updatePositions() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + bool success = true; + + int pathIdxToInsert = 0; + const int minPoints = this->pathType() == GraphicalObjectPathType::Polyline ? 2 : 3; + + QSharedPointer builder; + + // some preparations: ( do no check isMultiPoint / isArea: m_positions should typically be empty!!! + switch (m_pathType) + { + case GraphicalObjectPathType::Polyline: + builder = QSharedPointer(new Esri::ArcGISRuntime::PolylineBuilder(spatialReference())); + + break; + case GraphicalObjectPathType::Polygon: + builder = QSharedPointer(new Esri::ArcGISRuntime::PolygonBuilder(spatialReference())); + break; + } // switch + + QSharedPointer partCollection = + QSharedPointer(new Esri::ArcGISRuntime::PartCollection(builder->spatialReference())); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << ", m_positionsCont.size() /#rings=" << m_positionsCont.size(); + + QMutableMapIterator > mapIt(m_positionsCont); + // + // LOOP PER SINGLE POLY + // + while (mapIt.hasNext()) + { + mapIt.next(); + const int key = mapIt.key(); + + const SimpleValue &posData = mapIt.value(); + const PositionCont &posCont = posData.value(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << ", key / #ring=" << key + << ", rings.size()=" << posCont.size(); + + // maybe some pathIdx are cleared by clearPath() or similar: + if (posCont.isEmpty()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " posCont empty, cleared? -> remove/cleanup" + << " pathIdxToInsert = " << pathIdxToInsert + << " mapIt.key() = " << key; + mapIt.remove(); + continue; + } + + if (posCont.count() < minPoints) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " incorrect number of positions" + << " (" << posCont.count() << ")" + << ", minPoints=" << minPoints + << ", pathIdxToInsert = " << pathIdxToInsert + << ", mapIt.key() = " << key + << ", m_debugInfo = " << m_debugInfo; + + success = false; + } + else + { + Esri::ArcGISRuntime::Part part(builder->spatialReference()); + + // + // LOOP PER POINT + // + for (auto pos : std::as_const(posCont)) + { + part.addPoint(pos.x(), pos.y(), pos.z()); + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << ", key / #ring=" << key + << ", addPoint=" << pos; + + } + + partCollection->addPart(&part); + } // if (posCont.count() < minPoints) + + } // while + + builder->setParts(partCollection.get()); + + // + // builder to geometry; + // + switch (m_pathType) + { + case GraphicalObjectPathType::Polyline: + { + const Esri::ArcGISRuntime::Polyline &multipartPolyline = + static_cast(builder.data())->toPolyline(); + m_graphic->setGeometry(multipartPolyline); + assignLineStyleToGraphic(); + } + break; + case GraphicalObjectPathType::Polygon: + { + const Esri::ArcGISRuntime::Polygon &multipartPolygon = + static_cast(builder.data())->toPolygon(); + m_graphic->setGeometry(multipartPolygon); + //assignLineStyleToGraphic(); + assignFillStyleToGraphic(); + } + break; + } // switch + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'"; + success = addUpdateGraphic(); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success = " << success + << " drawOrder = " << m_graphic->zIndex(); + return success; +} + +bool GraphicalObjectMultiPath::hasPosCont(int idx) const +{ + return m_positionsCont.contains(idx); +} + +SimpleValue &GraphicalObjectMultiPath::getPosCont(int idx) +{ + return m_positionsCont[idx]; +} + +const SimpleValue GraphicalObjectMultiPath::getPosCont(int idx) const +{ + return m_positionsCont[idx]; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.h new file mode 100755 index 000000000..5ca871e57 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_multi_path.h @@ -0,0 +1,105 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectPath + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "graphical_object_path.h" + + +// Esri + + +// Qt +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + + +class SITUATIONMAP_EXPORT GraphicalObjectMultiPath : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + + GraphicalObjectMultiPath(const GraphicalObjectPathType type + , const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , UniqueMapId uniqueMapId); + + ~GraphicalObjectMultiPath(); + + GraphicalObjectPathType pathType() const + { + return m_pathType; + } + + virtual PositionCont path(int pathIndex) const; + virtual void setPath(int pathIndex, const PositionCont &positions); + + /*! \name Subsidiary polyline/polygons */ + //@{ + virtual void clearPath(int pathIndex); + virtual void clearAllPaths(); + virtual QList pathIndexInUse(); + virtual void reduceTo(const QList &remainingPathIndexes); + //@} + +protected: + // Internal/Admin + bool updatePositions(); + bool changed() override + { + bool baseClassChanged = BaseClass::changed(); + if (baseClassChanged) + return true; + + for (const SimpleValue &posData : std::as_const(m_positionsCont)) + { + if (posData.changed()) + return true; + } + return false; + } + + void resetChanged() override + { + BaseClass::resetChanged(); + + for (SimpleValue &posData : m_positionsCont) + posData.reset(); + } + + bool create() override; + bool updateViewImpl() override; + + // logical + const GraphicalObjectPathType m_pathType; + //bool updatePositions(); + +private: + // Attention: will contain 'positions' of idx == 1 and higher : + bool hasPosCont(int idx) const; + SimpleValue &getPosCont(int idx); + const SimpleValue getPosCont(int idx) const; + + QMap > m_positionsCont; + // : Attention: will contain 'positions' of idx == 1 and higher + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.cpp new file mode 100755 index 000000000..729152d5c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.cpp @@ -0,0 +1,260 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectPath + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_path.h" +#include // operator<< + + +// ESRI +#include +#include +#include +#include +#include +#include +#include + +// Qt + + +namespace SituationMap +{ + +QLatin1String toString(const GraphicalObjectPathType subType) +{ + switch (subType) + { + case GraphicalObjectPathType::Polyline: + return QLatin1String("Polyline"); + break; + case GraphicalObjectPathType::Polygon: + return QLatin1String("Polygon"); + break; + } // switch + return QLatin1String("GraphicalObjectPathTypeERROR"); +} + + +GraphicalObjectPath::GraphicalObjectPath(const GraphicalObjectPathType type + , const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_pathType(type) + , m_logger(getLogger("GraphicalObjectPath")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; +} + +GraphicalObjectPath::~GraphicalObjectPath() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " m_pathType=" << m_pathType; +} + +bool GraphicalObjectPath::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + bool success = BaseClass::create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success = " << success + // << " drawOrder = " << m_graphic->drawOrder() + ; + return success; +} + + +bool GraphicalObjectPath::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " m_positions= " << m_positions.value() + << ", m_maxSegmentLength=" << m_maxSegmentLength; + + QSharedPointer builder; + + // some checks: + switch (m_pathType) + { + case GraphicalObjectPathType::Polyline: + if (!this->isMultiPoint()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + builder = QSharedPointer(new Esri::ArcGISRuntime::PolylineBuilder(spatialReference())); + + break; + case GraphicalObjectPathType::Polygon: + if (!this->isArea()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + builder = QSharedPointer(new Esri::ArcGISRuntime::PolygonBuilder(spatialReference())); + break; + } // switch + + + if (! builder) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " no builder"; + return false; + } + + bool success = create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + success = BaseClass::updateViewImpl(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + + // real work: + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " update geometry"; + + + const PositionCont &posCont = m_positions.value(); + for (const Point3D &pos : posCont) + builder->addPoint(pos.x(), pos.y(), pos.z()); + + if (! builder->isSketchValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " builder -> sketch invalid"; + return false; + } + + // + // apply attributes: + // + + // N. Thoden, 13.02.2018 + // Case #02062713 [BOS] BasicSceneView_lineNotComplete + // If a line has a line of ~10km (Borderline) + // the line may 'move' on the ground (3D, RenderMode::Dynamic, SurfarcePlacement::Draped) + // To avoid these kind of effect, the advice is to densify the geometry + // [similar: Case #02054233 [BOS] LineArtefacts in 3D -> + // BUG-000111565 : Lines created on the GraphicsOverlay with "Dynamic" Rendering mode on 3D doesn't render completely when zooming in] + + // + // N. Thoden, 29.03.2018 + // Consulting with Luke Smallwood / using the V100.2.1. debug libs: + // densify leads to an assert, so avoid densify + // (until now densify does not lead to an improvement..) + + // + // N. Thoden, 19.04.2021 + // [GUEZE-495]: 3D Lagedarstellung Trefferlinien + // Linie fehlt teilweise + // jetzt (typeweise) per m_maxSegmentLength aktivierbar + + switch (m_pathType) + { + case GraphicalObjectPathType::Polyline: + { + const Esri::ArcGISRuntime::Polyline &polyline = + static_cast(builder.data())->toPolyline(); + if (m_maxSegmentLength > 0) + { + m_graphic->setGeometry(Esri::ArcGISRuntime::GeometryEngine::densifyGeodetic(polyline, + m_maxSegmentLength, + Esri::ArcGISRuntime::LinearUnit::meters(), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic)); + } else { + m_graphic->setGeometry(polyline); + } // if (densify) + assignLineStyleToGraphic(); + } + break; + case GraphicalObjectPathType::Polygon: + { + const Esri::ArcGISRuntime::Polygon &polygon = + static_cast(builder.data())->toPolygon(); + + if (m_maxSegmentLength > 0) + { + m_graphic->setGeometry(Esri::ArcGISRuntime::GeometryEngine::densifyGeodetic(polygon, + m_maxSegmentLength, + Esri::ArcGISRuntime::LinearUnit::meters(), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic)); + } else { + m_graphic->setGeometry(polygon); + } + assignFillStyleToGraphic(); + } + break; + } // switch + + const int count = AbstractGraphicalObject::countOfPolygonFirstPart(m_graphic->geometry()); + if (count > 1000) + WARNING_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << ", huge amount of vertices, countOfPolygonFirstPart = " << count; + else + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << ", amount of vertices, countOfPolygonFirstPart = " << count; + + success = addUpdateGraphic(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success = " << success + << " drawOrder = " << m_graphic->zIndex(); + return success; +} + +double GraphicalObjectPath::maxSegmentLength() const +{ + return m_maxSegmentLength; +} + +void GraphicalObjectPath::setMaxSegmentLength(double maxSegmentLength) +{ + m_maxSegmentLength = maxSegmentLength; +} + + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<<(std::ostream &stream, + const GraphicalObjectPathType &value) +{ + stream << toString(value).latin1(); + return stream; +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.h new file mode 100755 index 000000000..5bf117ffc --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_path.h @@ -0,0 +1,78 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectPath + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" + +// Esri + + +// Qt + + +// forward: + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +enum class GraphicalObjectPathType {Polyline, Polygon}; +QLatin1String toString(const GraphicalObjectPathType subType); + + +class SITUATIONMAP_EXPORT GraphicalObjectPath : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + + explicit GraphicalObjectPath(const GraphicalObjectPathType type + , const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectPath(); + + GraphicalObjectPathType pathType() const + { + return m_pathType; + } + + + double maxSegmentLength() const; + void setMaxSegmentLength(double maxSegmentLength); + +protected: + // Internal/Admin + bool create() override; + bool updateViewImpl() override; + + // logical + const GraphicalObjectPathType m_pathType; + double m_maxSegmentLength{-1}; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +SITUATIONMAP_EXPORT std::ostream &operator<< (std::ostream &stream, + const GraphicalObjectPathType &value); + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.cpp new file mode 100755 index 000000000..f6c85d5ad --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.cpp @@ -0,0 +1,572 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectPictureMarker + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_picture_marker.h" +#include // operator<< +#include +#include + + +// ESRI +#include +#include +#include +#include + + +// Qt +#include +#include + + +namespace SituationMap +{ +/** + * @brief getSimpleMarkerSymbolStyle + * @param markerSymbolType type to get + * @param filePath filePath of requested marker; if empty -> use return value, if !empty -> use filePath and get file out of qrc + * @param hotSpotPercent -0.5 to 0.5; e.g. x=center, y=bottom -> -0.5 + * @return corrosponding Esri::ArcGISRuntime::SimpleMarkerSymbolStyle, see filePath + */ +Esri::ArcGISRuntime::SimpleMarkerSymbolStyle getSimpleMarkerSymbolStyle(MarkerSymbolType markerSymbolType, + QString &filePath, + QPointF *hotSpotPercent = nullptr) +{ + if (hotSpotPercent) + *hotSpotPercent = QPointF(); + + filePath.clear(); + + Esri::ArcGISRuntime::SimpleMarkerSymbolStyle esriSMSS = + Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle; + + switch (markerSymbolType) + { + case SituationMap::MarkerSymbolType::Undefined: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle; + break; + + case SituationMap::MarkerSymbolType::Circle: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle; + break; + case SituationMap::MarkerSymbolType::Cross: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Cross; + break; + case SituationMap::MarkerSymbolType::Diamond: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Diamond; + break; + case SituationMap::MarkerSymbolType::Square: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Square; + break; + case SituationMap::MarkerSymbolType::X: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::X; + break; + case SituationMap::MarkerSymbolType::Triangle: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Triangle; + break; + + case SituationMap::MarkerSymbolType::PushPin2D: + esriSMSS = Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle; + filePath = QLatin1String(":/situationMap/pushPinRedClipArt.png"); + if (hotSpotPercent) + *hotSpotPercent = QPointF(0, -0.5); + + break; + } //switch + + return esriSMSS; +} + +GraphicalObjectPictureMarker::GraphicalObjectPictureMarker(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_logger(getLogger("GraphicalObjectPictureMarker")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectPictureMarker::~GraphicalObjectPictureMarker() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + resetPictureMarkerSymbol(); + resetImage(); + resetSimpleMarkerSymbol(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectPictureMarker::setImage(const QImage &image, const QSize &defaultSize) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " visibility=" << m_visibility + << " image.size()=" << image.size() + << " defaultSize=" << defaultSize + << " m_imageDefaultSize=" << m_imageDefaultSize + << " m_imageChanged=" << m_imageChanged + << " m_imageDefaultSize.changed()=" << m_imageDefaultSize.changed(); + resetSimpleMarkerSymbol(); + + if (m_image != image) + { + m_image = image; + m_imageChanged = true; + } + + // store defautlSize (member shouldn´t be modified) + m_imageDefaultSize = defaultSize; + if (m_imageDefaultSize.changed()) + m_imageChanged = true; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " visibility=" << m_visibility + << " image.size()=" << image.size() + << " defaultSize=" << defaultSize + << " m_imageDefaultSize=" << m_imageDefaultSize + << " m_imageChanged=" << m_imageChanged + << " m_imageDefaultSize.changed()=" << m_imageDefaultSize.changed(); +} + +void GraphicalObjectPictureMarker::setPictureMarkerSymbol(Esri::ArcGISRuntime::PictureMarkerSymbol *pms, QSize defaultSize) +{ + resetSimpleMarkerSymbol(); + resetImage(); + + if (m_isOwnPms) + { + delete m_pictureMarkerSymbol; + m_pictureMarkerSymbol = nullptr; + } + + if (m_pictureMarkerSymbol == pms && + m_imageDefaultSize.value() == defaultSize) + return; + m_isOwnPms = false; + m_pictureMarkerSymbol = pms; + m_imageDefaultSize = defaultSize; + m_imageChanged = true; +} + +void GraphicalObjectPictureMarker::resetImage() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + m_image = QImage(); + m_imageChanged = true; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectPictureMarker::resetPictureMarkerSymbol() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + if (m_isOwnPms) + delete m_pictureMarkerSymbol; + m_pictureMarkerSymbol = nullptr; + if (m_workaorundTimer) + m_workaorundTimer->stop(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectPictureMarker::resetSimpleMarkerSymbol() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + delete m_simpleMarkerSymbol; + m_simpleMarkerSymbol = nullptr; + m_markerSymbolType = MarkerSymbolType::Undefined; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + + +MarkerSymbolType GraphicalObjectPictureMarker::markerSymbolType() +{ + return m_markerSymbolType.value(); +} + +int GraphicalObjectPictureMarker::markerSymbolSize() +{ + return m_markerSize.value(); +} + +void GraphicalObjectPictureMarker::setMarkerSymbol(MarkerSymbolType markerSymbolType, + int size, const QColor &color) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " color=" << color; + + resetImage(); + + m_markerSymbolType = markerSymbolType; + m_markerSize = size; + m_markerColor = color; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +QSize GraphicalObjectPictureMarker::currentSize() const +{ + return m_currentSize; +} + +bool GraphicalObjectPictureMarker::create() +{ + return BaseClass::create(); +} + +int GraphicalObjectPictureMarker::xOffset() const +{ + return m_externalOffset.value().x(); +} +void GraphicalObjectPictureMarker::setXOffset(int xOffset) +{ + m_externalOffset.refValue().rx() = xOffset; +} +int GraphicalObjectPictureMarker::yOffset() const +{ + return m_externalOffset.value().y(); +} +void GraphicalObjectPictureMarker::setYOffset(int yOffset) +{ + m_externalOffset.refValue().ry() = yOffset; +} + +float GraphicalObjectPictureMarker::rotationAngle() +{ + return m_angle.value(); +} +void GraphicalObjectPictureMarker::setRotationAngle(float angle) +{ + m_angle = angle; +} + + +bool GraphicalObjectPictureMarker::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " origin =" << this->origin() // .x() << " , " << this->origin().y() + << " visibility=" << m_visibility + << " pickable=" << m_pickable + << " pickable.changed=" << m_pickable.changed() + << " selected=" << m_selected + << " m_angle = " << m_angle.value() + << " m_drawOrder = " << m_drawOrder + << " m_image.size() = " << m_image.size() + << " m_imageChanged = " << m_imageChanged + << " m_imageDefaultSize=" << m_imageDefaultSize + << " m_imageDefaultSize.changed()=" << m_imageDefaultSize.changed() + << " m_scale = " << m_scale.value(); + + // some checks: + if (m_positions.value().size() < 1) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo='" << m_debugInfo << "'" + << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + // + // Image or Marker ??? + // + QString filePath; + QPointF hotSpotPercent; + Esri::ArcGISRuntime::SimpleMarkerSymbolStyle esriSMSS = + getSimpleMarkerSymbolStyle(m_markerSymbolType.value(), + filePath, + &hotSpotPercent); + m_markerHotSpotPercent = hotSpotPercent; + + if (m_markerSymbolType.value() != MarkerSymbolType::Undefined) + { + if (m_markerSize.changed() || + m_markerSymbolType.changed() || + m_markerColor.changed() || + m_markerSymbolType.changed() || + m_angle.changed() || + m_markerHotSpotPercent.changed()) + { + if (!reCreateSymbol(filePath, esriSMSS)) + return false; + } + } + else + { + if (m_image.isNull() && (!m_isOwnPms && m_pictureMarkerSymbol == nullptr)) + { + // in context of class 'TacticalAppearance' the PictureMarker exist always... + DEBUG_LOG(m_logger) << LOGFUNCTION << " no image and m_markerSymbolType==Undefined -> nothing to do"; + // -> nothing to do: + return true; // -> nothing to do: + } + + if (m_scale.changed() || + m_imageChanged || + m_markerSymbolType.changed() || + m_markerSize.changed() || + m_angle.changed() || + !m_pictureMarkerSymbol) + { + if (!reCreateSymbol(filePath, esriSMSS)) + return false; + } + } + + const PositionCont &posCont = m_positions.value(); + if (posCont.size() == 1) + { + const Point3D &origin = this->origin(); + Esri::ArcGISRuntime::Point originGeometry(origin.x(), + origin.y(), + origin.z(), + spatialReference()); + m_graphic->setGeometry(originGeometry); + } + else + { + // Eception: Layer with name "AES2" should NOT have no renderer 8 to enable multipoint graphics + // [GUEZE-496] RPL Nr 770: 3D Lagedarstellung Einfrieren der SW Appl. + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " posCont.size() = " << posCont.size(); + Esri::ArcGISRuntime::MultipointBuilder builder(spatialReference()); + for (const Point3D &pos : posCont) + builder.points()->addPoint(pos.x(), pos.y(), pos.z()); + + if (! builder.isSketchValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " builder -> sketch invalid"; + return false; + } + m_graphic->setGeometry(builder.toGeometry()); + } + + bool success = BaseClass::updateViewImpl(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + + success = addUpdateGraphic(); + + + QSize imgSize = m_image.size(); + QSizeF pmSize; + if (m_pictureMarkerSymbol) + pmSize = QSizeF(m_pictureMarkerSymbol->width(), m_pictureMarkerSymbol->height()); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" +// << " success=" << success + << " m_drawOrder = " << m_drawOrder + << " m_graphic->drawOrder = " << m_graphic->zIndex() + << " m_image->size() = " << imgSize + << " pictureMarkerSymbol->size() = " << pmSize; + return success; +} + +bool GraphicalObjectPictureMarker::reCreateSymbol(const QString &filePath, + const Esri::ArcGISRuntime::SimpleMarkerSymbolStyle esriSMSS) +{ + const bool imageRequired = !m_image.isNull() || !filePath.isEmpty(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " filePath = '" << filePath << "'" + << " m_image->size() = " << m_image.size() + << " m_imageChanged = " << m_imageChanged + << " m_angle = " << m_angle.value() + << " m_currentSize = " << m_currentSize + << " m_imageDefaultSize = " << m_imageDefaultSize.value() + << " m_imageDefaultSize.changed() = " << m_imageDefaultSize.changed(); + + // + // Now we know what to use: Image or Marker + // + + if (!create()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + // what is used currently? + // image or m_simpleMarkerSymbol(esri)? + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " imageRequired=" << imageRequired + << " m_pictureMarkerSymbol=" << m_pictureMarkerSymbol + << " m_simpleMarkerSymbol=" << m_simpleMarkerSymbol; + + if (!filePath.isEmpty()) + { + // e.g. case PushPin2D + QImage img; + if (img.load(filePath)) + { + int iSize = qRound(m_markerSize.value() * m_scale.value()); + m_currentSize = QSize(iSize, iSize); + img = img.scaledToHeight(iSize, Qt::SmoothTransformation); + + QColor localColor = m_markerColor.value(); + + if (m_markerSymbolType.value() == MarkerSymbolType::PushPin2D && localColor.isValid()) + { + QColor colorToBeReplaced(255, 0, 0); + AdvancedSymbology::ExtensionRenderer::replaceColor(img, colorToBeReplaced.rgba(), localColor.rgba()); + } + setImage(img); + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " loading image failed, debugInfo='" << m_debugInfo << "'" + << " path='" << filePath << "'"; + return false; + } + } + + + // + // Transfer data to esriSymbols: + // + + // Image: + if (!m_image.isNull() || (!m_isOwnPms && m_pictureMarkerSymbol)) + { + resetSimpleMarkerSymbol(); + + QSize realImageSize; + if (!m_isOwnPms && m_pictureMarkerSymbol) + { + if (m_imageDefaultSize.value().isEmpty()) + m_currentSize = m_image.size() * m_scale.value(); + else + m_currentSize = m_imageDefaultSize.value() * m_scale.value(); + realImageSize = m_currentSize; + } + else + { + // scale localImage by m_scale + QImage localImage; + if (!filePath.isEmpty()) + { + // case PushPin2D -> already scaled by m_scale. So don´t scale again: + localImage = m_image; + m_currentSize = localImage.size(); + } + else + { + if (m_imageDefaultSize.value().isEmpty()) + m_currentSize = m_image.size() * m_scale.value(); + else + m_currentSize = m_imageDefaultSize.value() * m_scale.value(); + + // scaled required? + if (m_image.size() != m_currentSize) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " SCALE FROM " << m_image.size() << " TO " << m_currentSize; + localImage = m_image.scaled(m_currentSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + ++m_scaleCounter4Moduletest; + } else { + localImage = m_image; + m_currentSize = localImage.size(); + } + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_scaleCounter4Moduletest = " << m_scaleCounter4Moduletest + << ", localImage.size() = " << localImage.size(); + + delete m_pictureMarkerSymbol; + m_pictureMarkerSymbol = new Esri::ArcGISRuntime::PictureMarkerSymbol(localImage); + realImageSize = localImage.size(); + + + // Workaround : + // Norbert Thoden, 11.06.2020: + // do some calls again to fix ESRI-Bug: + // Bug: new Image('localImage#) / pictureMarkerSymbol, but sometimes it will not be shown :-( + // Using try and error, we recognize that an additional call seems to fix that issue: + if (!m_workaorundTimer) + { + m_workaorundTimer = new QTimer(m_graphic); + m_workaorundTimer->setInterval(150); + m_workaorundTimer->setSingleShot(true); + QObject::connect(m_workaorundTimer, &QTimer::timeout, m_graphic, [this]() { + if (!m_graphic) + return; + m_graphic->setZIndex(m_drawOrder.value()); + m_graphic->setVisible(m_visibility.value()); + m_graphic->setSelected(m_selected.value()); + }); + } + m_workaorundTimer->start(); + // : Workaround + + } + m_imageChanged = false; + + // set image to pms AND ALSO the Heigth and Width!!! + + // // !!! Important !!! + // // Case #01713501 - PictureMarker-offset + // // Esri::PictureMarkerSymbol:: setHeight/setWidth -> typographic points. + // // 36pt -> 48pixel -> factor 0.75 + // double pixelToPointFactor = 0.71; + // m_pictureMarkerSymbol->setHeight(m_image->size().height() * pixelToPointFactor); + // m_pictureMarkerSymbol->setWidth (m_image->size().width() * pixelToPointFactor); + // // !!! Important !!! + + QPointF markerHotSpot = QPointF(realImageSize.width() / 2.0 * m_markerHotSpotPercent.value().x(), + realImageSize.height() / 2.0 * m_markerHotSpotPercent.value().y()); + + QPointF finalOffset = (markerHotSpot.toPoint() * -1) * 1.33 // 1.33; // try and error (48pixel / 36pt) + + + (m_externalOffset.value() * 0.75); // 0.75; // try and error (36pt / 48pixel) + + m_pictureMarkerSymbol->setOffsetX(finalOffset.x()); + m_pictureMarkerSymbol->setOffsetY(finalOffset.y()); + + m_pictureMarkerSymbol->setAngle(m_angle.value()); + + m_graphic->setSymbol(m_pictureMarkerSymbol); + } + else + { + resetPictureMarkerSymbol(); + resetImage(); + if (!m_simpleMarkerSymbol) + m_simpleMarkerSymbol = new Esri::ArcGISRuntime::SimpleMarkerSymbol(); + + m_simpleMarkerSymbol->setSize(m_markerSize.value() * m_scale.value()); + m_simpleMarkerSymbol->setStyle(esriSMSS); + m_simpleMarkerSymbol->setAngle(m_angle.value()); + + m_simpleMarkerSymbol->setOffsetX(m_externalOffset.value().x()); + m_simpleMarkerSymbol->setOffsetY(m_externalOffset.value().y()); + + m_simpleMarkerSymbol->setColor(m_markerColor.value()); + + m_graphic->setSymbol(m_simpleMarkerSymbol); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " filePath = '" << filePath << "'" + << " m_image->size() = " << m_image.size() + << " m_imageChanged = " << m_imageChanged + << " m_currentSize = " << m_currentSize + << " m_imageDefaultSize = " << m_imageDefaultSize.value() + << " m_imageDefaultSize.changed() = " << m_imageDefaultSize.changed(); + + return true; +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.h new file mode 100755 index 000000000..fc66b4f50 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_picture_marker.h @@ -0,0 +1,153 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectPictureMarker + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" +#include +#include + + +// Esri +#include +#include + + +// Qt +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Graphic; +class GraphicsOverlay; +class PictureMarkerSymbol; +class SimpleMarkerSymbol; +} +} + +class QTimer; + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SITUATIONMAP_EXPORT GraphicalObjectPictureMarker : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + GraphicalObjectPictureMarker(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectPictureMarker(); + + // Data access: + const QImage &image() const { return m_image; } + QSize imageDefaultSize() const { return m_imageDefaultSize.value(); } + void setImage(const QImage &image, const QSize &defaultSize = QSize()); + + void setPictureMarkerSymbol(Esri::ArcGISRuntime::PictureMarkerSymbol *pms, QSize defaultSize); + + virtual MarkerSymbolType markerSymbolType(); + virtual int markerSymbolSize(); + virtual void setMarkerSymbol(MarkerSymbolType markerSymbolType, + int size, + const QColor &color = QColor()); + + virtual QSize currentSize() const; + + // Offset (x: to right ; y: to north) + virtual int xOffset() const; + virtual void setXOffset(int xOffset); + virtual int yOffset() const; + virtual void setYOffset(int yOffset); + + // rotation / angle + virtual float rotationAngle(); + virtual void setRotationAngle(float angle); + +protected: + + // Internal/Admin + bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_markerSymbolType.changed() + || m_markerSize.changed() + || m_markerColor.changed() + || m_angle.changed() + || m_markerHotSpotPercent.changed() + || m_externalOffset.changed() + || m_imageChanged + ; + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_markerSymbolType.reset(); + m_markerSize.reset(); + m_markerColor.reset(); + m_angle.reset(); + m_markerHotSpotPercent.reset(); + m_externalOffset.reset(); + m_imageChanged = false; + + m_imageDefaultSize.reset(); + } + + bool updateViewImpl() override; + + void resetImage(); + void resetPictureMarkerSymbol(); + void resetSimpleMarkerSymbol(); + bool reCreateSymbol(const QString &filePath, + const Esri::ArcGISRuntime::SimpleMarkerSymbolStyle esriSMSS); + + // used for image: + QImage m_image; + SimpleValue m_imageDefaultSize; + Esri::ArcGISRuntime::PictureMarkerSymbol *m_pictureMarkerSymbol = nullptr; + bool m_imageChanged = true; + bool m_isOwnPms = true; + + // used marker: + Esri::ArcGISRuntime::SimpleMarkerSymbol *m_simpleMarkerSymbol = nullptr; + QTimer *m_workaorundTimer = nullptr; // for re-new of Esri::ArcGISRuntime::PictureMarkerSymbol + + QSize m_currentSize; + int m_scaleCounter4Moduletest = 0; + + + SimpleValue m_markerSymbolType = MarkerSymbolType::Undefined; + SimpleValue m_markerSize = 10; + SimpleValue m_markerColor = QColor(Qt::red); + + SimpleValue m_angle = 0; /// + SimpleValue m_markerHotSpotPercent; ///< Store marker-specific offset. + SimpleValue m_externalOffset; ///< Stores external offset. + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.cpp new file mode 100755 index 000000000..2f6725743 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.cpp @@ -0,0 +1,163 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectRange + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_range.h" +#include // operator<< + + +// ESRI +#include +#include +#include +#include + + +// Qt + + +namespace SituationMap +{ + + +GraphicalObjectRange::GraphicalObjectRange(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_logger(getLogger("GraphicalObjectRange")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectRange::~GraphicalObjectRange() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + + +int GraphicalObjectRange::range() const +{ + return m_range.value(); +} + +float GraphicalObjectRange::angle() const +{ + return m_angleDegree.value(); +} + +void GraphicalObjectRange::setRange(int range, float angleDegree) +{ + m_angleDegree = angleDegree; + m_range = range; +} + + + +bool GraphicalObjectRange::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + EllipseParameters ellipsePara; + ellipsePara.setLengthX(m_range.value()); + ellipsePara.setLengthY(m_range.value()); + ellipsePara.setDirectionDegree(m_angleDegree.value()) ; + ellipsePara.setDirectionDegree((m_angleDegree.value() * -1) + 90); + ellipsePara.setMaxSegmentLength(10); + ellipsePara.setMaxPointCount(30); // limit to 30 to reduce the number of vertices. If to high, keep performance impact to 3D in mind!!! + + this->setEllipseParameters(ellipsePara); + + bool success = BaseClass::updateViewImpl(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " drawOrder = " << m_graphic->zIndex() + << " success = " << success; + return success; +} + + +bool GraphicalObjectRange::createEllipse(Esri::ArcGISRuntime::Polygon &ellipse) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " range=" << m_range + << " angle=" << m_angleDegree; + + bool success = BaseClass::createEllipse(ellipse); + + if (success && + !ellipse.isEmpty() && + !ellipse.parts().isEmpty() && + !ellipse.parts().part(0).isEmpty()) + { + const Point3D ¢er = this->origin(); + + // 'modify' ellipe (by copying, adding and re-assigning): + + // part 0 - ellipse + // we have to copy the point since the ellipse does not contain the z value anymore :-( + Esri::ArcGISRuntime::Part partCircle(ellipse.spatialReference()); + const Esri::ArcGISRuntime::ImmutablePart &im = ellipse.parts().part(0); + + for (int i = 0; i < im.pointCount(); ++i) + { + const Esri::ArcGISRuntime::Point &p = im.point(i); + partCircle.addPoint(Esri::ArcGISRuntime::Point(p.x(), + p.y(), + center.z() // p.z() + )); + } + + // part 1 - line to center + Esri::ArcGISRuntime::Part partLine(ellipse.spatialReference()); + partLine.addPoint(im.startPoint().x(), + im.startPoint().y(), + center.z()); + partLine.addPoint(center.x(), + center.y(), + center.z()); + // a polyGON requiree to have at least 3 points: + partLine.addPoint(center.x(), + center.y(), + center.z()+0.01); + + // combine them to a PartCollection: + Esri::ArcGISRuntime::PartCollection pc(ellipse.spatialReference()); + pc.addPart(&partCircle); + pc.addPart(&partLine); + + Esri::ArcGISRuntime::PolygonBuilder builder(ellipse.spatialReference()); + builder.setParts(&pc); + + ellipse = builder.toPolygon(); + + success = true; + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'"; + return success; +} + + + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<<(std::ostream &stream, + const GraphicalObjectRange &value) +{ + stream << value.toString(); + return stream; +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.h new file mode 100755 index 000000000..5151ea224 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_range.h @@ -0,0 +1,87 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectRange + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "graphical_object_ellipse.h" + + +// Esri + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class SITUATIONMAP_EXPORT GraphicalObjectRange : public GraphicalObjectEllipse +{ + + using BaseClass = GraphicalObjectEllipse; + +public: + GraphicalObjectRange(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectRange(); + + int range() const; + float angle() const; + + void setRange(int range, float angleDegree); + + + +protected: + // Internal/Admin + // bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_angleDegree.changed() + || m_range.changed(); + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_angleDegree.reset(); + m_range.reset(); + } + + + bool updateViewImpl() override; + + + bool createEllipse(Esri::ArcGISRuntime::Polygon &ellipse) override; + + +private: + SimpleValue m_angleDegree = 0; + SimpleValue m_range = 0; + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + +}; + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<< (std::ostream &stream, + const GraphicalObjectRange &value); + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.cpp new file mode 100755 index 000000000..cf24f1add --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.cpp @@ -0,0 +1,135 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectRendererAttributes + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_renderer_attributes.h" +#include // operator<< +#include + + +// ESRI +#include +#include + + +// Qt +#include +#include + +namespace SituationMap +{ + +GraphicalObjectRendererAttributes::GraphicalObjectRendererAttributes(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_logger(getLogger("GraphicalObjectRendererAttributes")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectRendererAttributes::~GraphicalObjectRendererAttributes() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectRendererAttributes::setAttributeMap(const QVariantMap &attribMap) +{ + m_attributeMap = attribMap; +} + +QVariantMap GraphicalObjectRendererAttributes::attributeMap() const +{ + return m_attributeMap.value(); +} + +bool GraphicalObjectRendererAttributes::create() +{ + return BaseClass::create(); +} + +bool GraphicalObjectRendererAttributes::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " origin =" << this->origin() // .x() << " , " << this->origin().y() + << " visibility=" << m_visibility + << " pickable=" << m_pickable + << " pickable.changed=" << m_pickable.changed() + << " selected=" << m_selected + << " m_attributeMap = " << m_attributeMap.value().size() + << " m_drawOrder = " << m_drawOrder; + + // some checks: + if (!isSinglePoint()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo='" << m_debugInfo << "'" + << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + bool success = create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + const Point3D &origin = this->origin(); + m_graphic->setGeometry(Esri::ArcGISRuntime::Point(origin.x(), + origin.y(), + origin.z(), + spatialReference())); + + if (m_attributeMap.changed()) + { + QVariantMap cur = m_graphic->attributes()->attributesMap(); + + if (m_logger.isDebugEnabled()) + qCritical() << " debugInfo='" << m_debugInfo << "'" << ", QVariantMap before unite " << cur; + + cur.unite(m_attributeMap.value()); + + if (m_logger.isDebugEnabled()) + qCritical() << " debugInfo='" << m_debugInfo << "'" << ", QVariantMap after unite " << cur; + + m_graphic->attributes()->setAttributesMap(cur); + } + + // keep anchor like Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Bottom in mind. + // assigned during setup the Renderer (LayerFactory, AdapterDds...) + + success = BaseClass::updateViewImpl(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + success = addUpdateGraphic(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success=" << success + << " m_drawOrder = " << m_drawOrder + << " m_graphic->drawOrder = " << m_graphic->zIndex(); + return success; +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.h new file mode 100755 index 000000000..47e07fcda --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_renderer_attributes.h @@ -0,0 +1,102 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectRendererAttributes + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" +#include +#include + + +// Esri +#include + + +// Qt +#include +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Graphic; +class GraphicsOverlay; +class ModelSceneSymbol; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SITUATIONMAP_EXPORT GraphicalObjectRendererAttributes : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + GraphicalObjectRendererAttributes(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectRendererAttributes(); + + // Data access: + // to be cont. + void setAttributeMap(const QVariantMap &attribMap); + QVariantMap attributeMap() const; + +protected: + + // Internal/Admin + bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_attributeMap.changed() + ; + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_attributeMap.reset(); + } + + bool updateViewImpl() override; + + // no ModelSceneSymbol since Renderer-based + + SimpleValue m_attributeMap; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + // Forbid some overloaded methods without effects of this object; + PositionCont positions() const override + { + return PositionCont(); + } + void setPositions(const PositionCont &posCont) override + { + Q_UNUSED(posCont); + Q_ASSERT(false); + } +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.cpp new file mode 100755 index 000000000..18946498d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.cpp @@ -0,0 +1,204 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectSceneMarker + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_scene_marker.h" +#include // operator<< +#include +#include + + +// ESRI +#include +#include + + +// Qt +#include +#include + + +namespace SituationMap +{ + +GraphicalObjectSceneMarker::GraphicalObjectSceneMarker(SceneMarkerType type + , const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_type(type) + , m_logger(getLogger("GraphicalObjectSceneMarker")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectSceneMarker::~GraphicalObjectSceneMarker() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + delete m_simpleMarkerSceneSymbol; + m_simpleMarkerSceneSymbol = nullptr; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void GraphicalObjectSceneMarker::setDiameter(double diameter) +{ + m_diameter = diameter; +} + +double GraphicalObjectSceneMarker::diameter() const +{ + return m_diameter.value(); +} + +bool GraphicalObjectSceneMarker::create() +{ + return BaseClass::create(); +} + +bool GraphicalObjectSceneMarker::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " origin =" << this->origin() // .x() << " , " << this->origin().y() + << " visibility=" << m_visibility + << " pickable=" << m_pickable + << " pickable.changed=" << m_pickable.changed() + << " selected=" << m_selected + << " m_diameter = " << m_diameter.value() + << " m_heading = " << m_heading.value() + << " m_pitch = " << m_pitch.value() + << " m_roll = " << m_roll.value() + << " m_drawOrder = " << m_drawOrder + << " m_lineStyle = " << m_lineStyle.value() + << " m_fillStyle = " << m_fillStyle.value() + << " fillColor = " << m_fillStyle.value().fillColor() + << " m_scale = " << m_scale.value(); + + if (m_graphicsLayer->renderingMode() != Esri::ArcGISRuntime::GraphicsRenderingMode::Dynamic) + for (int i=0; i<10; ++i) + ERROR_LOG(m_logger) << " RenderingMode::Dynamic required!!! , debugInfo='" << m_debugInfo << "'"; + + // some checks: + if (!isSinglePoint()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo='" << m_debugInfo << "'" + << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + bool success = create(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + const double radiusHeight = m_diameter.value()/2.0; + + Point3D origin = this->origin(); + + // Try to set AnchorPosition::Top, to let the cone rotate around top/tip. + // Using a cone, the anchor point does not work as expected, we have to move + if (m_type == SceneMarkerType::CONE) + { + // + // It´s a SCENE Marker. So we are per definition in 3D and origin is in degree. + // + // heading is in degree:m_heading.value() + // offset as vector: + QPointF offsetMeter(0, radiusHeight/2.0); + const int rotAngleDegree = (static_cast(m_heading.value())-90)%360; + DEBUG_LOG(m_logger) << " offsetMeter = " << offsetMeter << ", m_heading = " << m_heading.value() << ", rotAngleDegree = " << rotAngleDegree; + MathUtils::rotatePoint(offsetMeter, QPointF(), rotAngleDegree); + // new temporary origin... + // -> rad + origin *= LtsCoordTrans::deg2rad(); + LtsCoordTrans::UtmCoordinate newOrigin = LtsCoordTrans::UtmCoordinate::fromGeographic(origin.x(), origin.y()); + DEBUG_LOG(m_logger) << " newOrigin = " << newOrigin.mgrs(); + + // add rotated offset to temporary origin: + const QPointF origin2D = newOrigin.geographic(offsetMeter) * LtsCoordTrans::rad2deg(); + // -> deg + // write back; z-value stays untouched: + origin.rx() = origin2D.x(); + origin.ry() = origin2D.y(); + } + + const Esri::ArcGISRuntime::Point originGeometry(origin.x(), + origin.y(), + origin.z(), + spatialReference()); + m_graphic->setGeometry(originGeometry); + + if (!m_simpleMarkerSceneSymbol) + { + switch (m_type) + { + case SceneMarkerType::CONE: // Kegel + m_simpleMarkerSceneSymbol = + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::cone( + m_fillStyle.value().fillColor(), + radiusHeight, radiusHeight, // V100.8: cone: 'diameter' is interpreted as radius + Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Center); // V100.8: cone: Top does not work as aspected - grrr + break; + case SceneMarkerType::SPHERE: // Kugel + m_simpleMarkerSceneSymbol = + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::sphere( + m_fillStyle.value().fillColor(), + radiusHeight*2.0, + Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Center); + break; + } // switch + } + m_simpleMarkerSceneSymbol->setColor(m_fillStyle.value().fillColor()); + + if (m_type == SceneMarkerType::CONE) + { + m_simpleMarkerSceneSymbol->setWidth(radiusHeight); + m_simpleMarkerSceneSymbol->setHeight(radiusHeight); + m_simpleMarkerSceneSymbol->setDepth(radiusHeight); + } else { + m_simpleMarkerSceneSymbol->setWidth(m_diameter.value()); + m_simpleMarkerSceneSymbol->setHeight(m_diameter.value()); + m_simpleMarkerSceneSymbol->setDepth(m_diameter.value()); + } + + m_simpleMarkerSceneSymbol->setHeading(m_heading.value()); + m_simpleMarkerSceneSymbol->setPitch(m_pitch.value()); + m_simpleMarkerSceneSymbol->setRoll(m_roll.value()); + + m_graphic->setSymbol(m_simpleMarkerSceneSymbol); + + success = BaseClass::updateViewImpl(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + success = addUpdateGraphic(); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success=" << success + << " m_drawOrder = " << m_drawOrder + << " m_graphic->drawOrder = " << m_graphic->zIndex(); + return success; +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.h new file mode 100755 index 000000000..f42aebc71 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_scene_marker.h @@ -0,0 +1,137 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectSceneMarker + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" +#include +#include + + +// Esri +#include + + +// Qt +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Graphic; +class GraphicsOverlay; +class SimpleMarkerSceneSymbol; +} +} + +class QTimer; + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// +// +// +// !!! Layer with RenderingMode::Dynamic required !!! +// !!! Layer with RenderingMode::Dynamic required !!! +// !!! Layer with RenderingMode::Dynamic required !!! +// +// +// + +enum class SceneMarkerType {CONE, SPHERE}; + + +class SITUATIONMAP_EXPORT GraphicalObjectSceneMarker : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + + +public: + GraphicalObjectSceneMarker(SceneMarkerType type + , const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId); + + ~GraphicalObjectSceneMarker(); + + // Data access: + // to be cont. (type is sphere, to support cube, cylinder etc) + void setDiameter(double diameter); + double diameter() const; + + double heading() const { return m_heading.value(); } + void setHeading(double headingDegree) { m_heading = headingDegree; } + double pitch() const { return m_pitch.value(); } + void setPitch(double pitchDegree) { m_pitch = pitchDegree; } + double roll() const { return m_roll.value(); } + void setRoll(double rollDegree) { m_roll = rollDegree; } + +protected: + + // Internal/Admin + bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_diameter.changed() + || m_heading.changed() + || m_pitch.changed() + || m_roll.changed() + ; + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_diameter.reset(); + m_heading.reset(); + m_pitch.reset(); + m_roll.reset(); + } + + bool updateViewImpl() override; + + // used marker: + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *m_simpleMarkerSceneSymbol{nullptr}; + + SceneMarkerType m_type{SceneMarkerType::SPHERE}; + SimpleValue m_diameter{25}; + + SimpleValue m_heading{0}; // degree + SimpleValue m_pitch{0}; // degree + SimpleValue m_roll{0}; // degree + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + // Forbid some overloaded methods without effects of this object; + PositionCont positions() const override + { + return PositionCont(); + } + void setPositions(const PositionCont &posCont) override + { + Q_UNUSED(posCont); + Q_ASSERT(false); + } +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.cpp new file mode 100755 index 000000000..6673cfa41 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.cpp @@ -0,0 +1,340 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectText + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "graphical_object_text.h" +#include // operator<< + + +// ESRI +#include +#include +#include + +// Qt +#include + +#include +#include + +namespace SituationMap +{ + +GraphicalObjectText::GraphicalObjectText(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId) + : BaseClass(geoView, graphicsLayer, debugInfo, uniqueMapId) + , m_textSymbol(nullptr) + , m_logger(getLogger("GraphicalObjectText")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +GraphicalObjectText::~GraphicalObjectText() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " text='" << m_text << "'"; + + delete m_textSymbol; + m_textSymbol = nullptr; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " text='" << m_text << "'"; +} + +QString GraphicalObjectText::toString() const +{ + QString str; + // str.append( BaseClass::toString() ); + + if (!m_debugInfo.isEmpty()) + str.append(QStringLiteral("debugInfo=%1").arg(m_debugInfo)); + + if (m_graphicsLayer) + str.append(QStringLiteral(" layer='%1'").arg(m_graphicsLayer->objectName())); + + if (m_graphic) + str.append(QStringLiteral(" zIndex='%1'").arg(m_graphic->zIndex())); + + std::ostringstream stream; + stream << this->origin(); + str.append(QLatin1String(stream.str().c_str())); + + if (!m_text.value().isEmpty()) + str.append(QStringLiteral("text=%1").arg(m_text.value())); + + return str; +} + +QString GraphicalObjectText::text() const +{ + return m_text.value(); +} + +void GraphicalObjectText::setText(const QString &text) +{ + m_text = text; +} + +TextStyle GraphicalObjectText::textStyle() const +{ + return m_textStyle.value(); +} +TextStyle &GraphicalObjectText::rTextStyle() +{ + return m_textStyle.refValue(); +} +void GraphicalObjectText::setTextStyle(const TextStyle &textStyle) +{ + m_textStyle = textStyle; +} + +float GraphicalObjectText::xOffset() const +{ + return m_offset.value().x(); +} +void GraphicalObjectText::setXOffset(float xOffset) +{ + QPointF temp = m_offset.value(); + temp.rx() = xOffset; + m_offset = temp; +} +float GraphicalObjectText::yOffset() const +{ + return m_offset.value().y(); +} +void GraphicalObjectText::setYOffset(float yOffset) +{ + QPointF temp = m_offset.value(); + temp.ry() = yOffset; + m_offset = temp; +} + + +bool GraphicalObjectText::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " text='" << m_text << "'" + << " origin =" << this->origin() // .x() << " , " << this->origin().y() + << " visibility=" << m_visibility; + bool success = true; + + // some checks: + if (!m_graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " no graphicsLayer"; + return false; + } + + BaseClass::create(); + + if (!m_textSymbol) + m_textSymbol = new Esri::ArcGISRuntime::TextSymbol(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " text='" << m_text << "'" + << " success=" << success + << " drawOrder = " << m_graphic->zIndex(); + return success; +} + +bool GraphicalObjectText::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " text='" << m_text << "'" + << " origin =" << this->origin() // .x() << " , " << this->origin().y() + << " visibility=" << m_visibility + << " m_textStyle=" << m_textStyle + << " offset=" << m_offset; + + // some checks: + if (!this->isSinglePoint()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + bool success = true; + if (m_visibility.value()) + { + success = create(); // create only if visible + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + if (! BaseClass::updateViewImpl()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateViewImpl failed"; + } + + // FATAL_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + // << " text='" << m_text << "'" + // << " changed1=" << m_text.changed() + // << " changed2="<< m_textStyle.changed(); + + bool assignTextSymbol = false; + + // || m_visibility.changed()) -> szenario: create invisible object and make it visible + if (m_text.changed() || m_visibility.changed()) + { + m_textSymbol->setText(m_text.value()); + assignTextSymbol = true; + } + + // TextStyle to TextSymbol@ESRI : + if (m_textStyle.changed() + || m_scale.changed() + || m_visibility.changed() // -> szenario: create invisible object and make it visible + ) + { + const TextStyle &textStyle = m_textStyle.value(); + m_textSymbol->setColor(textStyle.textColor()); + + // High values can lead to coredump with 'SymbolDrawHelper::create_line_image FAILED, SkBitmap is NULL or empty' -> limit + m_textSymbol->setSize(std::min(500.0f, textStyle.size() * m_scale.value())); + m_textSymbol->setAngle(textStyle.angle()); + + m_textSymbol->setFontFamily(textStyle.fontFamily()); + + m_textSymbol->setFontWeight(textStyle.bold() ? Esri::ArcGISRuntime::FontWeight::Bold : Esri::ArcGISRuntime::FontWeight::Normal); + m_textSymbol->setFontStyle(textStyle.italic() ? Esri::ArcGISRuntime::FontStyle::Italic : Esri::ArcGISRuntime::FontStyle::Normal); + + Esri::ArcGISRuntime::FontDecoration fd = Esri::ArcGISRuntime::FontDecoration::None; + if (textStyle.underline()) + fd = Esri::ArcGISRuntime::FontDecoration::Underline; + if (textStyle.strikeThrough()) + fd = Esri::ArcGISRuntime::FontDecoration::LineThrough; + m_textSymbol->setFontDecoration(fd); + + float outlineWidth = 0; + if (textStyle.textOutlineWidth() > 0) + outlineWidth = textStyle.textOutlineWidth() * m_scale.value(); + if (m_textSymbol->size() <= 15) // small size -> outline reduces readability :-( + outlineWidth = 0.0; + m_textSymbol->setHaloWidth(outlineWidth); + + if (textStyle.textOutlineColor().isValid()) + m_textSymbol->setHaloColor(textStyle.textOutlineColor()); + +// qWarning() << " haloWidth = " << m_textSymbol->haloWidth(); + +// m_textSymbol->setHaloColor(Qt::white); +// m_textSymbol->setHaloWidth(outlineWidth); + +// qWarning() << " fontSize " << textStyle.size() * m_scale.value() +// <<" (textStyle.size() " << textStyle.size() +// << " m_scale.value() " << m_scale.value() << ")"; + +// qWarning() << " outlineWidth " << outlineWidth +// <<" (textStyle.textOutlineWidth() " << textStyle.textOutlineWidth() +// << " m_scale.value() " << m_scale.value() << ")"; + +// if (textStyle.backgroundColor().isValid()) + m_textSymbol->setBackgroundColor(textStyle.backgroundColor()); + +// m_textSymbol->setAntiAliased(textStyle.antiAliased()); + + m_textSymbol->setHorizontalAlignment(convert(textStyle.horizontalAlignment())); + m_textSymbol->setVerticalAlignment(convert(textStyle.verticalAlignment())); + + assignTextSymbol = true; + // : TextStyle to TextSymbol@ESRI + } + + // || m_visibility.changed()) -> szenario: create invisible object and make it visible + if (m_offset.changed() || m_visibility.changed()) + { + m_textSymbol->setOffsetX(m_offset.value().x()); + m_textSymbol->setOffsetY(m_offset.value().y()); + assignTextSymbol = true; + } + + if (assignTextSymbol) + m_graphic->setSymbol(m_textSymbol); + + + // check ChangedFlags: + // Object is created and text is invisble due to scale (meterPerPixel) + // map is zoomed in -> text should arise, but on m_visibility is changed: + if (m_visibility.changed() || m_positions.changed()) + { + const Point3D &origin = this->origin(); + m_graphic->setGeometry(Esri::ArcGISRuntime::Point(origin.x(), + origin.y(), + origin.z(), + spatialReference())); + success = addUpdateGraphic(); + } // m_visibility.changed() || m_positions.changed() + + } + else // if visible + { + BaseClass::updateViewImpl(); + success = addUpdateGraphic(); + } + + if (m_logger.isTraceEnabled()) + { + int drawOrder = -1; + if (m_graphic) + drawOrder = m_graphic->zIndex(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " text='" << m_text << "'" + << " success=" << success + << " drawOrder = " << drawOrder; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + // << " fontFamily='" << m_textSymbol->fontFamily() << "'" + ; + return success; +} + + + +Esri::ArcGISRuntime::HorizontalAlignment GraphicalObjectText::convert(const HorizontalAlignment horizontalAlignment) +{ + Esri::ArcGISRuntime::HorizontalAlignment esriAlignmenmt = + Esri::ArcGISRuntime::HorizontalAlignment::Left; + switch (horizontalAlignment) + { + case HorizontalAlignment::Left: esriAlignmenmt = Esri::ArcGISRuntime::HorizontalAlignment::Left; break; + case HorizontalAlignment::Center: esriAlignmenmt = Esri::ArcGISRuntime::HorizontalAlignment::Center; break; + case HorizontalAlignment::Right: esriAlignmenmt = Esri::ArcGISRuntime::HorizontalAlignment::Right; break; + case HorizontalAlignment::Justify: esriAlignmenmt = Esri::ArcGISRuntime::HorizontalAlignment::Justify; break; + } // switch + + return esriAlignmenmt; +} + +Esri::ArcGISRuntime::VerticalAlignment GraphicalObjectText::convert(const VerticalAlignment verticalAlignment) +{ + Esri::ArcGISRuntime::VerticalAlignment esriAlignmenmt = + Esri::ArcGISRuntime::VerticalAlignment::Bottom; + switch (verticalAlignment) + { + case VerticalAlignment::Bottom: esriAlignmenmt = Esri::ArcGISRuntime::VerticalAlignment::Bottom; break; + case VerticalAlignment::Middle: esriAlignmenmt = Esri::ArcGISRuntime::VerticalAlignment::Middle; break; + case VerticalAlignment::Top: esriAlignmenmt = Esri::ArcGISRuntime::VerticalAlignment::Top; break; + case VerticalAlignment::BaseLine: esriAlignmenmt = Esri::ArcGISRuntime::VerticalAlignment::Baseline; break; + } // switch + return esriAlignmenmt; +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.h new file mode 100755 index 000000000..f649c2917 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/graphical_object_text.h @@ -0,0 +1,125 @@ +/** + * @RDE_HEADER@ + * GraphicalObjectText + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" +#include +#include +#include +#include + + +// Esri + + +// Qt + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Graphic; +class GraphicsOverlay; +class PictureMarkerSymbol; +class TextSymbol; + +enum class HorizontalAlignment; +enum class VerticalAlignment; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class SITUATIONMAP_EXPORT GraphicalObjectText : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + GraphicalObjectText(const Esri::ArcGISRuntime::GeoView *geoView + , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer + , const QString &debugInfo + , const UniqueMapId &uniqueMapId + ); + + ~GraphicalObjectText(); + + QString toString() const override; // specialized + + // Data access: + QString text() const; + void setText(const QString &text); + + TextStyle textStyle() const; + TextStyle &rTextStyle(); + void setTextStyle(const TextStyle &textStyle); + + // Offset + virtual float xOffset() const; + virtual void setXOffset(float xOffset); + virtual float yOffset() const; + virtual void setYOffset(float yOffset); + + static Esri::ArcGISRuntime::HorizontalAlignment convert(const HorizontalAlignment horizontalAlignment); + static Esri::ArcGISRuntime::VerticalAlignment convert(const VerticalAlignment verticalAlignment); + + +protected: + // Internal/Admin + bool create() override; + + bool changed() override + { + return BaseClass::changed() + || m_text.changed() + || m_textStyle.changed() + || m_offset.changed(); + } + + void resetChanged() override + { + BaseClass::resetChanged(); + m_text.reset(); + m_textStyle.reset(); + m_offset.reset(); + } + + bool updateViewImpl() override; + + Esri::ArcGISRuntime::TextSymbol *m_textSymbol = nullptr; + + SimpleValue m_text; ///< to be displayed on map + SimpleValue m_textStyle; + SimpleValue m_offset; ///< Offset (pixel). + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + // Forbid some overloaded methods without effects of this object; + virtual PositionCont positions() const override + { + return PositionCont(); + } + virtual void setPositions(const PositionCont &posCont) override + { + Q_UNUSED(posCont); + } +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/stub_graphical_object.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/stub_graphical_object.h new file mode 100755 index 000000000..d19e3f43d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/stub_graphical_object.h @@ -0,0 +1,69 @@ +/** + * @RDE_HEADER@ + * StubGraphicalObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" + +#include + + +// Esri + + + +namespace SituationMap +{ + + + +/** + @class StubGraphicalObject stub_graphical_object.h + The class \c StubGraphicalObject is used as substitution for a graphical object. It does + nothing and the purpose is only to satisfy the interface of AbstractGraphicalObject. + @see AbstractGraphicalObject + */ +class StubGraphicalObject : public AbstractGraphicalObject +{ + +public: + + explicit StubGraphicalObject() + : AbstractGraphicalObject(nullptr, nullptr, QString(), UniqueMapId()) + { + } + + ~StubGraphicalObject() = default; + + StubGraphicalObject(const StubGraphicalObject &other) = delete; + StubGraphicalObject &operator=(StubGraphicalObject &other) = delete; + +protected: + + bool changed() override + { + return false; + } + + bool updateViewImpl() override + { + return true; + } + + bool create() override + { + return true; + } + +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.cpp new file mode 100755 index 000000000..796b0739b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.cpp @@ -0,0 +1,622 @@ +/** + * @RDE_HEADER@ + * TacticalAppearance + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "tactical_appearance.h" +#include +#include +#include +#include +#include +#include // operator<< +#include + + +// ESRI +#include +#include +#include +#include +#include + +// Qt +#include + + +namespace SituationMap +{ + + +class TacticalAppearance::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + MapImpl *m_mapImpl = nullptr; + QString m_logicalLayerName; + + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicOverlayTactical = nullptr; + AdvancedSymbology::SymbolDictionary *m_dictionary = nullptr; + AdvancedSymbology::SymbolResultWatcher m_symbolResultWatcher; + + // OWN data: + SimpleValue m_uniqueDesignation; + SimpleValue m_symbol; + + + AdvancedSymbology::SymbolDataToRender m_symDataToRender; + GraphicalObjectPictureMarker *m_pictureMarker = nullptr; + +}; // class TacticalAppearance::Private +//----------------------------------------------------------------------------- + + + +TacticalAppearance::TacticalAppearance(MapImpl *mapImpl, + const QString &logicalLayerName, + const QString &debugInfo, + const UniqueMapId &uniqueMapId) + : BaseClass(mapImpl->getEsriGeoView(), nullptr, debugInfo, uniqueMapId), + m_logger(getLogger("TacticalAppearance")), + d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << ", logicalLayerName='" << logicalLayerName << "'"; + + d->m_symDataToRender.sizeL = 100; + d->m_mapImpl = mapImpl; + d->m_logicalLayerName = logicalLayerName; + + if (!m_graphicsLayer && d->m_mapImpl) + m_graphicsLayer = d->m_mapImpl->getGraphicsLayer(d->m_logicalLayerName); + + if (!m_graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " getGraphicsLayer failed"; + } + + if (d->m_mapImpl) + d->m_dictionary = &(d->m_mapImpl->symbolDictionary()); + + + // lambda: + // m_symbolResultWatcher is canceled on destruction. + // So it`s safe that no signal will be fired afterwards. + // To avoid CLang warnings use m_symbolResultWatcher as context (3rd para), although it´s not intended. + // Better idea? + QObject::connect(&d->m_symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady, &d->m_symbolResultWatcher, [this]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " resultReady..."; + + QSharedPointer symResult = d->m_symbolResultWatcher.symbolResult(); + if (! symResult) + return; // should NEVER happen! (Which object should have triggered the signal???) + + if (d->m_pictureMarker) + { + d->m_pictureMarker->setImage(symResult->symbolImage().image()); + d->m_pictureMarker->updateView(); + } + }); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +TacticalAppearance::~TacticalAppearance() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + + // abort pending call to SymbolDictionary::findSymbol: + d->m_symbolResultWatcher.cancel(); + + delete d->m_pictureMarker; + d->m_pictureMarker = nullptr; + + removeGraphicFromGraphicOverlayTactical(); + + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void TacticalAppearance::removeGraphicFromGraphicOverlayTactical() +{ + if (m_logger.isTraceEnabled()) + { + if (m_graphic) + { + Esri::ArcGISRuntime::GraphicsOverlay *gOverlay = m_graphic->graphicsOverlay(); + if (gOverlay) + TRACE_LOG(m_logger) << LOGFUNCTION << " START debugInfo='" << m_debugInfo << "'" << " name = '" << gOverlay->overlayId() << "'"; + } + } + + if (m_graphic && d->m_graphicOverlayTactical && d->m_graphicOverlayTactical->graphics()) + d->m_graphicOverlayTactical->graphics()->removeOne(m_graphic); + + delete m_graphic; + m_graphic = nullptr; +} + + + +bool TacticalAppearance::useDictionaryRenderMode(const AdvancedSymbology::GeometryType &geomType) const +{ + bool usePictureMarker = isSinglePoint() && (AdvancedSymbology::GeometryType::Point == geomType || AdvancedSymbology::GeometryType::Undefined == geomType); + return !usePictureMarker || symbol().useAlwaysDictionaryRenderer; +} // bool TacticalAppearance::useDictionaryRenderMode() const + + +bool TacticalAppearance::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " m_visibility = " << m_visibility; + + bool success = true; + + // + // Prepare / create : + // + // a) singlePoint / exactly one coordinate -> use pictureMarker + // or + // b) multiPoint / more than one coordinate -> m_graphic / DictionaryRenderer + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This assumption doesn't work any more because some polyline and polygon symbols will have only one + * anchor point and the real geometry is defined by additional attributes like radius, with, length ... + * + * JT 2016/12/20 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + if (m_graphicsLayer && m_visibility.value()) + { + const SymbolData &symbolData_ref = d->m_symbol.value(); + const QString &baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(symbolData_ref.sidc); + const AdvancedSymbology::SymbolProperties &symbolProperties = d->m_dictionary->symbolProperties(baseSymbolCode); + AdvancedSymbology::GeometryType geomType = symbolProperties.geometryType(); + + if (useDictionaryRenderMode(geomType)) + { + if (!d->m_graphicOverlayTactical) + d->m_graphicOverlayTactical = d->m_mapImpl->getGraphicsLayerDict(d->m_logicalLayerName); + + if (!d->m_graphicOverlayTactical) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " getGraphicsLayerDict failed"; + return false; + } + + if (!m_graphic) + { + m_graphic = new Esri::ArcGISRuntime::Graphic(); + m_graphic->setObjectName(m_debugInfo); + m_graphic->setZIndex(m_drawOrder.value()); + + // use attributeModel to store application obj id (SimWorld) at graphic + Esri::ArcGISRuntime::AttributeListModel *attLisModel = m_graphic->attributes(); + if (attLisModel) + attLisModel->insertAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute), m_uniqueMapId); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no AttributeListModel, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; + + // Adding an empty graphic leads to Segmentation fault. + // (in Esri_runtimecore::Map_renderer::Dictionary_renderer::derive_geometry) + + bool successGraphic = addUpdateGraphic(); // CHECK + if (!successGraphic) + ERROR_LOG(m_logger) << LOGFUNCTION << " addUpdateGraphic failed, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; + + } // if (!m_graphic) + } + else + { + // m_graphic is needless now: + removeGraphicFromGraphicOverlayTactical(); + } + + } + + // : Prepare / create + + bool positionsEmpty = m_positions.value().isEmpty(); + if (positionsEmpty) + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " positions empty"; + + success = success && !positionsEmpty; + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " create failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; + return success; +} + +void TacticalAppearance::createPictureMarker() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + if (!d->m_pictureMarker && d->m_mapImpl) + { + d->m_pictureMarker = new GraphicalObjectPictureMarker(d->m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , m_debugInfo + QLatin1String("#PicMarkTacticalAppearance") + , m_uniqueMapId); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +void TacticalAppearance::removePictureMarker() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; + delete d->m_pictureMarker; + d->m_pictureMarker = nullptr; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; +} + +QString TacticalAppearance::uniqueDesignation() const +{ + return d->m_uniqueDesignation.value(); +} +void TacticalAppearance::setUniqueDesignation(const QString &uniqueDesignation) +{ + d->m_uniqueDesignation = uniqueDesignation; +} + +SymbolImage TacticalAppearance::symbolImage() const +{ + SymbolImage symImage; + if (d->m_pictureMarker) + { + symImage.setImage(d->m_pictureMarker->image()); + symImage.setValid(!symImage.image().isNull()); + } + return symImage; +} + + +SymbolData TacticalAppearance::symbol() const +{ + return d->m_symbol.value(); +} + +void TacticalAppearance::setSymbol(const SymbolData &symbol) +{ + d->m_symbol = symbol; +} + +bool TacticalAppearance::changed() +{ + return BaseClass::changed() + || d->m_uniqueDesignation.changed() + || d->m_symbol.changed(); +} +void TacticalAppearance::resetChanged() +{ + BaseClass::resetChanged(); + d->m_uniqueDesignation.reset(); + d->m_symbol.reset(); +} + +Esri::ArcGISRuntime::Geometry TacticalAppearance::getGraphicGeometry() const +{ + if (d->m_pictureMarker) return d->m_pictureMarker->getGraphicGeometry(); + if (m_graphic) return m_graphic->geometry(); + + ERROR_LOG(m_logger) << LOGFUNCTION << "Geometry is undefined"; + + return Esri::ArcGISRuntime::Geometry(); +} // Esri::ArcGISRuntime::Geometry TacticalAppearance::getGraphicGeometry() const + + +bool TacticalAppearance::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " symbol='" << d->m_symbol.value().sidc << "'" + << " visibility=" << m_visibility + << " selected=" << m_selected + << " origin=" << this->origin() + << " scale=" << m_scale.value(); + + const SymbolData &symbolData_ref = d->m_symbol.value(); + const QString &baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(symbolData_ref.sidc); + const AdvancedSymbology::SymbolProperties &symbolProperties = d->m_dictionary->symbolProperties(baseSymbolCode); + AdvancedSymbology::GeometryType geomType = symbolProperties.geometryType(); + + if (geomType == AdvancedSymbology::GeometryType::Undefined) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " GeometryType::Undefined, symbolProperties failed!?!"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << ", geomType=" << geomType + << ", useDictionaryRenderMode(geomType)=" << useDictionaryRenderMode(geomType); + + bool success = create(); + + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " create failed"; + return false; + } + + if (useDictionaryRenderMode(geomType)) + { + // real work: + if (m_positions.changed() || d->m_symbol.changed() || m_visibility.changed()) + { + auto buildPointClusterGeometry = [this]() -> Esri::ArcGISRuntime::Geometry + { + QSharedPointer builder(new Esri::ArcGISRuntime::MultipointBuilder(spatialReference())); + Esri::ArcGISRuntime::PointCollection points(spatialReference()); + const auto posCont = positions(); + for (const auto &pos : posCont) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " addPoint:" << pos.x() << " - " << pos.y(); + points.addPoint(pos.x(), pos.y()); + } + builder->setPoints(&points); + return builder->toGeometry(); + }; // buildPointClusterGeometry + + Esri::ArcGISRuntime::Geometry geom; + + switch (geomType) + { + case AdvancedSymbology::GeometryType::Undefined : break; + + case AdvancedSymbology::GeometryType::Point : + case AdvancedSymbology::GeometryType::Polyline : + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " m_positions.count()=" << m_positions.value().count(); + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * If a PolylineBuilder is used here to create symbol geometry the construction appropriate symbol standard (MIL2525C / MIL2525D / App6) + * will not be done and the symbol is always a simple polyline. Using MultipointBuilder to build symbol geometry the result is based on + * underlying symbol standard. + * 2016/12/9 JT + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + geom = buildPointClusterGeometry(); + + } break; + + case AdvancedSymbology::GeometryType::Polygon : + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " m_positions.count()=" << m_positions.value().count(); + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Some symbols have polygon geometry and could have only one or two positions. In case of less than 3 positions do the same as for + * polylines and use MultipointBuilder to create the symbol geometry. In other case use PolygonBuilder. + * 2016/12/13 JT + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + if (positions().size() > 2) + { + QSharedPointer builder(new Esri::ArcGISRuntime::PolygonBuilder(spatialReference())); + const auto posCont = positions(); + for (const auto &pos : posCont) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " addPoint:" << pos.x() << " - " << pos.y(); + builder->addPoint(pos.x(), pos.y()); + } + geom = builder->toGeometry(); + } + else + geom = buildPointClusterGeometry(); + + } break; + + } // switch + + if (geom.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Geometry NOT OK -> fallback symbol" << " (geometryType was " << geomType << ")"; + createPictureMarker(); + d->m_pictureMarker->setDebugInfo(m_debugInfo + QLatin1String("#fallback")); + + int size = symbolData_ref.size; + if (size < 10) size = 10; + if (size > 100) size = 100; + + d->m_pictureMarker->setMarkerSymbol(MarkerSymbolType::Diamond, size, symbolData_ref.color); + d->m_pictureMarker->setOrigin(this->origin()); + d->m_pictureMarker->setDrawOrder(m_drawOrder.value()); + d->m_pictureMarker->updateView(); + success = false; + } + else // geom is not empty + { + removePictureMarker(); + + if (m_graphic) + { + success = true; + + // move to graphic: + m_graphic->setGeometry(geom); + + // prepare rawAttributes for DictionaryRenderer: + + QVariantMap rawAttributes = d->m_symbol.value().attributeMap; + + // build from scratch to honor removed attributes: + // (Otherwise the will remain wrongly) + + rawAttributes[QLatin1String(SituationMap::UniqueMapIdAttribute)] = m_uniqueMapId; + rawAttributes[QLatin1String(SituationMap::IsPickableAttribute)] = m_pickable.value(); // set pickable attribute JT 2016/12/21 + + rawAttributes[QLatin1String("_type")] = QLatin1String("position_report"); + rawAttributes[QLatin1String("_action")] = QLatin1String("update"); + rawAttributes[QLatin1String("sidc")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of MIL2525 + rawAttributes[QLatin1String("sic")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of APP6 + + Esri::ArcGISRuntime::AttributeListModel * attributeModel = m_graphic->attributes(); + if (attributeModel) + attributeModel->setAttributesMap(rawAttributes); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " no AttributeListModel"; + + + +// // prepare rawAttributes for DictionaryRenderer: +// QVariantMap rawAttributes = d->m_symbol.value().attributeMap; + +// rawAttributes[QLatin1String(SituationMap::IsPickableAttribute)] = m_pickable.value(); // set pickable attribute JT 2016/12/21 + +// rawAttributes[QLatin1String("_type")] = QLatin1String("position_report"); +// rawAttributes[QLatin1String("_action")] = QLatin1String("update"); +// rawAttributes[QLatin1String("sidc")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of MIL2525 +// rawAttributes[QLatin1String("sic")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of APP6 +// // move to graphic: +// m_graphic->setGeometry(geom); + +// /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// * Replace: +// * m_graphic->attributes()->setAttributesMap(rawAttributes); +// * +// * because it doesn't work anymore in ArcGISRuntime V100.2 +// * JT, 2018/01/24 +// * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +// Esri::ArcGISRuntime::AttributeListModel * attributeModel = m_graphic->attributes(); +// if (attributeModel) +// { +// if (!attributeModel->isEmpty()) +// { +// for (auto it = rawAttributes.cbegin(); it != rawAttributes.cend(); ++it) +// { +// const QString &attrKey = it.key(); +// if (attributeModel->containsAttribute(attrKey)) +// { +// attributeModel->replaceAttribute(attrKey, rawAttributes[attrKey]); +// continue; +// } +// attributeModel->insertAttribute(attrKey, rawAttributes[attrKey]); +// } +// } +// else attributeModel->setAttributesMap(rawAttributes); +// } + + // 2021-08, N. Thoden + // While switching back to the behaviour that one logical layer has + // 1st getGraphicsLayer and 2nd getGraphicsLayerDict [GUEZE-496], + // the tactical appereance of DangerArea and similar disappears. + // Reason: m_graphicsLayer and m_graphicOverlayTactical are different now! + // It seems like that in former versions (< V100.8) the graphic could + // be added to more than one Esri layer. + // Now (V100.8) adding to a second layer fails. + // But we can remove the m_graphic from the default m_graphicsLayer + // before adding the the desired one: m_graphicOverlayTactical + if (m_graphicsLayer->graphics()->contains(m_graphic)) + m_graphicsLayer->graphics()->removeOne(m_graphic); + + // add to graphicOverlayTactical: + if (! d->m_graphicOverlayTactical->graphics()->contains(m_graphic)) + d->m_graphicOverlayTactical->graphics()->append(m_graphic); + + bool successGraphic = d->m_graphicOverlayTactical->graphics()->contains(m_graphic); + if (!successGraphic) + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " m_graphic not added to m_graphicOverlayTactical"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " no m_graphic"; + // success = false; + success = true; // if the DictionaryRenderer is used, m_graphic is null and that is ok. + } + + } // if (geom.isEmpty()) + } + + } + else // !useDictionaryRenderMode(geomType) + { + createPictureMarker(); + d->m_pictureMarker->setOrigin(this->origin()); + + // real work: + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " update geometry"; + + + if (d->m_symbol.changed() || m_scale.changed()) + { + if (d->m_dictionary) + { + int widthPixel = SituationMap::SymbolSizeLdefault; + widthPixel *= symbolData_ref.size * m_scale.value(); + + d->m_symDataToRender.sidc = symbolData_ref.sidc; + d->m_symDataToRender.fillColor = symbolData_ref.color; + d->m_symDataToRender.fillColorReplace = true; + d->m_symDataToRender.sizeL = widthPixel; + + if (d->m_pictureMarker->image().isNull()) + d->m_pictureMarker->setImage(d->m_dictionary->temporaryImage(d->m_symDataToRender.sizeL)); + d->m_symbolResultWatcher.setSymbolResult(d->m_dictionary->fetchSymbol(d->m_symDataToRender)); + success = true; + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " visibility=" << m_visibility + << " no dictionary"; + } + + } + + if (d->m_pictureMarker) + { + d->m_pictureMarker->setDrawOrder(m_drawOrder.value()); + d->m_pictureMarker->setVisibility(m_visibility.value()); + d->m_pictureMarker->setSelected(m_selected.value()); + d->m_pictureMarker->setPickable(m_pickable.value()); + + success = success && d->m_pictureMarker->updateView(); + } + } // else !useDictionaryRenderMode(geomType) + + + if (d->m_pictureMarker) + { + d->m_pictureMarker->setOrigin(this->origin()); // to avoid incorrect number of positions.. + d->m_pictureMarker->setVisibility(m_visibility.value()); + d->m_pictureMarker->setSelected(m_selected.value()); + success = d->m_pictureMarker->updateView() && success; + } + + // in case of no pictureMarker: + if (m_graphic) + { + m_graphic->setZIndex(m_drawOrder.value()); + m_graphic->setVisible(m_visibility.value()); + m_graphic->setSelected(m_selected.value()); + } + + // reset flags + d->m_symbol.reset(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure, debugInfo='" << m_debugInfo << "'"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " success = " << success + << " visibility=" << m_visibility; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.h new file mode 100755 index 000000000..d3e3a68c1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/GraphicalObjects/tactical_appearance.h @@ -0,0 +1,117 @@ +/** + * @RDE_HEADER@ + * TacticalAppearance + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_graphical_object.h" + +// Esri + + +// Qt + + +// forward: + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward: +class MapImpl; +class GraphicalObjectPictureMarker; + + +/** + * @brief The TacticalAppearance class displays sidcs. + * To do so, the number of coordinate lead to use + * - the GraphicalObjectPictureMarker (exactly one coordinate) + * or + * - the Message (more than one coordinate) + * + * Use cases: + * - Participant: sidc with #coords == 1, TacticalAppearance should get exactly one coordinate. + * (GraphicalObjectPictureMarker on ESRI::GraphicsLayer is used) + * - Tactical display of a Building: sidc #coords >= 2, TacticalAppearance should get list of positions. + * (Message on ESRI::MessageGroupLayer is used) + * - (Tactical) PreviewIcon of a Building: sidc #coords == 1, TacticalAppearance should get exactly one coordinate. + * (GraphicalObjectPictureMarker on ESRI::GraphicsLayer is used) + * - Tactical symbols of plan editor + * + * In case of GraphicalObjectPictureMarker the image creation is done by AdvancedSymbology::SymbolDictionary::symbolImageBySidcExtended(). + */ +class SITUATIONMAP_EXPORT TacticalAppearance : public AbstractGraphicalObject +{ + + using BaseClass = AbstractGraphicalObject; + +public: + + /** + * @brief TacticalAppearance + * @param mapImpl map to be used. + * @param logicalLayerName logical layer name to be used. + * @param debugInfo debugInfo to be used + * @param uniqueMapId uniqueMapId to be used. (Optional to fulfill rubberband requirements) + */ + TacticalAppearance(MapImpl *mapImpl, + const QString &logicalLayerName, + const QString &debugInfo, + const UniqueMapId &uniqueMapId = INVALID_MAP_ID); + + TacticalAppearance(const TacticalAppearance &other) = delete; + TacticalAppearance &operator=(TacticalAppearance &other) = delete; + + virtual ~TacticalAppearance(); + + + SymbolData symbol() const; + void setSymbol(const SymbolData &symbol); + + QString uniqueDesignation() const; + void setUniqueDesignation(const QString &uniqueDesignation); + + + SymbolImage symbolImage() const; + + Esri::ArcGISRuntime::Geometry getGraphicGeometry() const override; + +protected: + // Internal/Admin + virtual bool create() override; + virtual void createPictureMarker(); + virtual void removePictureMarker(); + + void removeGraphicFromGraphicOverlayTactical(); + + bool changed() override; + void resetChanged() override; + + + bool updateViewImpl() override; + + bool useDictionaryRenderMode(const AdvancedSymbology::GeometryType &geomType) const; + + // logical + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.cpp new file mode 100755 index 000000000..65430dfc2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.cpp @@ -0,0 +1,603 @@ +/** +** +** @RDE_HEADER@ +** Handles aggregation of participant layer +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 16.08.2017, JT : first edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "layer_aggregation_widget.h" + +#include + +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +using namespace SituationMap; + + +struct AggregationData +{ + QString layerName; + QString displayName; + double minScale = LayerParameter::UNDEFINED_SCALE; // 0 + double maxScale = LayerParameter::UNDEFINED_SCALE; // 0 + bool minScaleValid = false; + bool maxScaleValid = false; + +}; // AggregationData + + +struct AggregationWidgets +{ + QCheckBox *cbMinScaleValid = nullptr; + QPushButton *pbMinScale = nullptr; + QPushButton *pbMinScaleTo = nullptr; + + QCheckBox *cbMaxScaleValid = nullptr; + QPushButton *pbMaxScale = nullptr; + QPushButton *pbMaxScaleTo = nullptr; + + QPushButton *pbReset = nullptr; + QPushButton *pbApply = nullptr; +}; + + +class LayerAggregationWidget::InternalData +{ +public: + + Map *m_map = nullptr; + bool m_valid = false; + + LayerType m_layerType; + + QVector m_aggregationList; + QMap m_aggregationReset; + QMap m_aggregationWidgets; + + LOGGER_T m_logger; + + explicit InternalData(Map *map,LayerType layerType) + : m_map(map) + , m_layerType(layerType) + , m_logger(getLogger("LayerAggregation")) + {} + +}; // LayerAggregationWidget::InternalData + +//------------------------------------------------------------------------------ + +QString LayerAggregationWidget::scaleText(double scale) +{ + return (scale >= 1) ? QStringLiteral("1:%1").arg(int(scale)) : tr("undefined"); +} // QString LayerAggregationWidget::scaleText(double scale) + + +void LayerAggregationWidget::setMinScaleLabel(QPushButton *btn, double scale) +{ + btn->setText(tr("Min. Scale from Map\n%1").arg(scaleText(scale))); +} // void LayerAggregationWidget::setMinScaleLabel(QPushButton * btn, double scale) + + +void LayerAggregationWidget::setMaxScaleLabel(QPushButton *btn, double scale) +{ + btn->setText(tr("Max. Scale from Map\n%1").arg(scaleText(scale))); +} // void LayerAggregationWidget::setMaxScaleLabel(QPushButton * btn, double scale) + + +LayerAggregationWidget::LayerAggregationWidget(Map *map, LayerType layerType, const QRegularExpression &nameCondition, QWidget *parent) + : QWidget(parent), m_internal(new InternalData(map,layerType)) +{ + if (!nameCondition.isValid()) + { + ERROR_LOG(m_internal->m_logger) << LOGFUNCTION << " Illegal regular expression in nameCondition."; + return; + } + + if (map == nullptr) + { + ERROR_LOG(m_internal->m_logger) << LOGFUNCTION << " Invalid situation map, cannot initialize dialog"; + return; + } + + QGridLayout *layout = new QGridLayout(this); + + layout->setSpacing(10); + + const LayerFactory &layerFactory = map->layerFactory(); + + QIcon scaleToIcon(QStringLiteral("icon:search.svg")); + + const auto ln = layerFactory.layernames(layerType); + for (const QString &layerName : ln) + { + if (nameCondition.match(layerName).hasMatch()) + { + // get layer + AggregationData data; + AggregationWidgets &widgets = m_internal->m_aggregationWidgets[layerName]; + const LayerParameter &layerPara = layerFactory.layerParameter(layerName); + + data.layerName = layerName; + data.minScale = layerPara.minScale; + data.maxScale = layerPara.maxScale; + data.minScaleValid = layerPara.minScaleValid(); + data.maxScaleValid = layerPara.maxScaleValid(); + data.displayName = layerPara.displayName; + + int row = m_internal->m_aggregationList.size(); + + QLabel *laName = new QLabel(layerPara.displayName.isEmpty() ? layerName : layerPara.displayName, this); + widgets.pbReset = new QPushButton(this); + widgets.pbApply = new QPushButton(this); + + widgets.pbReset->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding)); + widgets.pbApply->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding)); + + QHBoxLayout *minLayout = new QHBoxLayout(); + + minLayout->addSpacing(20); + minLayout->addWidget(widgets.cbMinScaleValid = new QCheckBox(QString(), this)); + minLayout->addWidget(widgets.pbMinScale = new QPushButton(QString(), this)); + minLayout->addWidget(widgets.pbMinScaleTo = new QPushButton(scaleToIcon, QString(), this)); + minLayout->addSpacing(20); + setMinScaleLabel(widgets.pbMinScale, layerPara.minScale); + + QHBoxLayout *maxLayout = new QHBoxLayout(); + + maxLayout->addWidget(widgets.cbMaxScaleValid = new QCheckBox(QString(), this)); + maxLayout->addWidget(widgets.pbMaxScale = new QPushButton(QString(), this)); + maxLayout->addWidget(widgets.pbMaxScaleTo = new QPushButton(scaleToIcon, QString(), this)); + + maxLayout->addSpacing(20); + + setMaxScaleLabel(widgets.pbMaxScale, layerPara.maxScale); + + widgets.pbMinScale->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding)); + widgets.pbMaxScale->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding)); + + layout->addWidget(laName, row, 0); + layout->addLayout(minLayout, row, 1); + layout->addLayout(maxLayout, row, 2); + layout->addWidget(widgets.pbReset, row, 3); + layout->addWidget(widgets.pbApply, row, 4); + + m_internal->m_aggregationList << data; + m_internal->m_aggregationReset[layerName] = data; + + widgets.pbMinScale->setProperty("row", row); + widgets.pbMaxScale->setProperty("row", row); + widgets.cbMinScaleValid->setProperty("row", row); + widgets.cbMaxScaleValid->setProperty("row", row); + widgets.pbMinScaleTo->setProperty("row", row); + widgets.pbMaxScaleTo->setProperty("row", row); + + widgets.pbReset->setProperty("row", row); + widgets.pbApply->setProperty("row", row); + + connect(widgets.cbMinScaleValid, &QCheckBox::clicked, this, &LayerAggregationWidget::onSetValid); + connect(widgets.cbMaxScaleValid, &QCheckBox::clicked, this, &LayerAggregationWidget::onSetValid); + connect(widgets.pbMinScale, &QPushButton::clicked, this, &LayerAggregationWidget::onGetMinScale); + connect(widgets.pbMaxScale, &QPushButton::clicked, this, &LayerAggregationWidget::onGetMaxScale); + connect(widgets.pbMinScaleTo, &QPushButton::clicked, this, &LayerAggregationWidget::onZoomTo); + connect(widgets.pbMaxScaleTo, &QPushButton::clicked, this, &LayerAggregationWidget::onZoomTo); + connect(widgets.pbReset, &QPushButton::clicked, this, &LayerAggregationWidget::onReset); + connect(widgets.pbApply, &QPushButton::clicked, this, &LayerAggregationWidget::onApply); + + widgets.cbMinScaleValid->setChecked(data.minScaleValid); + widgets.cbMinScaleValid->setEnabled(data.minScaleValid); + widgets.cbMaxScaleValid->setChecked(data.maxScaleValid); + widgets.cbMaxScaleValid->setEnabled(data.maxScaleValid); + + widgets.pbMinScaleTo->setEnabled(data.minScaleValid); + widgets.pbMaxScaleTo->setEnabled(data.maxScaleValid); + + widgets.pbReset->setEnabled(false); + widgets.pbApply->setEnabled(false); + } + } + + retranslate(); + + if (m_internal->m_aggregationList.isEmpty()) + { + QLabel *lbEmpty = new QLabel(tr("Can't find any layer"), this); + QPixmap icon(QStringLiteral("icon:overlays.svg")); + + lbEmpty->setWindowIcon(icon); + layout->addWidget(lbEmpty, 0, 0, Qt::AlignCenter); + return; + } +} // LayerAggregation::LayerAggregation( Map * map, QObject * parent ) + + +void LayerAggregationWidget::retranslate() +{ + if (m_internal->m_map == nullptr) + return; + + const LayerFactory &layerFactory = m_internal->m_map->layerFactory(); + const auto ln = layerFactory.layernames(m_internal->m_layerType); + for (const QString &layerName : ln) + { + if (!m_internal->m_aggregationWidgets.contains(layerName)) continue; + + AggregationWidgets &widgets = m_internal->m_aggregationWidgets[layerName]; + + widgets.pbReset->setText(tr("Reset")); + widgets.pbApply->setText(tr("Apply to Map")); + + widgets.pbReset->setToolTip(tr("Restore initial scales of this level")); + widgets.pbApply->setToolTip(tr("Set scales to corresponding map layer temporarily")); + + widgets.cbMinScaleValid->setToolTip(tr("Activate or deactivate this scale value")); + widgets.pbMinScale ->setToolTip(tr("Get current map scale of map view")); + widgets.pbMinScaleTo ->setToolTip(tr("Set this scale value to map view as current scale")); + + widgets.cbMaxScaleValid->setToolTip(tr("Activate or deactivate this scale value")); + widgets.pbMaxScale ->setToolTip(tr("Get current map scale of map view")); + widgets.pbMaxScaleTo ->setToolTip(tr("Set this scale value to map view as current scale")); + } +} // void LayerAggregationWidget::retranslate() + + +LayerAggregationWidget::~LayerAggregationWidget() +{ + delete m_internal; m_internal = nullptr; +} // LayerAggregationWidget::~LayerAggregationWidget() + + +void LayerAggregationWidget::onZoomTo() +{ + QPushButton *senderButton = qobject_cast(sender()); + if (nullptr == senderButton) return; + + int row = senderButton->property("row").toInt(); + + if ((row < 0) || (row >= m_internal->m_aggregationList.size())) + return; + + const AggregationData &data = m_internal->m_aggregationList[row]; + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[data.layerName]; + double scale = 0.0; + + if ((widgets.pbMinScaleTo == senderButton) && (data.minScaleValid)) + scale = data.minScale; + + if ((widgets.pbMaxScaleTo == senderButton) && (data.maxScaleValid)) + scale = data.maxScale; + + if (scale < 1) + return; + + m_internal->m_map->setScale(scale); + +} // void LayerAggregationWidget::onZoomTo() + + +void LayerAggregationWidget::onGetMinScale() +{ + QPushButton *senderButton = qobject_cast(sender()); + if (nullptr == senderButton) return; + + int row = senderButton->property("row").toInt(); + + if ((row < 0) || (row >= m_internal->m_aggregationList.size())) + return; + + double scale = m_internal->m_map->scale(); + + AggregationData &data = m_internal->m_aggregationList[row]; + + if (data.maxScaleValid && (data.maxScale >= scale)) + { + const QString &msg = tr("Chosen scale is too small"); + LtsWidgets::LtsMessageDialog::showMessage(this, msg, tr("Layer Aggregation")); + return; + } + + data.minScale = scale; + data.minScaleValid = true; + + setMinScaleLabel(senderButton, data.minScale); + + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[data.layerName]; + widgets.cbMinScaleValid->setChecked(true); + widgets.cbMinScaleValid->setEnabled(true); + + widgets.pbMinScaleTo->setEnabled(true); + + widgets.pbReset->setEnabled(true); + widgets.pbApply->setEnabled(true); + + emit changesMade(); +} // void LayerAggregationWidget::onGetMinScale() + + +void LayerAggregationWidget::onGetMaxScale() +{ + QPushButton *senderButton = qobject_cast(sender()); + if (nullptr == senderButton) return; + + int row = senderButton->property("row").toInt(); + + if ((row < 0) || (row >= m_internal->m_aggregationList.size())) + return; + + double scale = m_internal->m_map->scale(); + + AggregationData &data = m_internal->m_aggregationList[row]; + + if (data.minScaleValid && (data.minScale <= scale)) + { + const QString &msg = tr("Chosen scale is too large"); + LtsWidgets::LtsMessageDialog::showMessage(this, msg, tr("Layer Aggregation")); + return; + } + + data.maxScale = scale; + data.maxScaleValid = true; + + setMaxScaleLabel(senderButton, data.maxScale); + + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[data.layerName]; + widgets.cbMaxScaleValid->setChecked(true); + widgets.cbMaxScaleValid->setEnabled(true); + + widgets.pbMaxScaleTo->setEnabled(true); + + widgets.pbReset->setEnabled(true); + widgets.pbApply->setEnabled(true); + + emit changesMade(); + +} // void LayerAggregationWidget::onGetMaxScale() + + +void LayerAggregationWidget::onSetValid(bool value) +{ + QCheckBox *senderButton = qobject_cast(sender()); + if (nullptr == senderButton) return; + + int row = senderButton->property("row").toInt(); + + if ((row < 0) || (row >= m_internal->m_aggregationList.size())) + return; + + AggregationData &data = m_internal->m_aggregationList[row]; + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[data.layerName]; + + if (senderButton == widgets.cbMinScaleValid) + { + if (data.maxScaleValid && value && (data.maxScale >= data.minScale)) + { + senderButton->setChecked(false); + const QString &msg = tr("Minimum scale is too small"); + LtsWidgets::LtsMessageDialog::showMessage(this, msg, tr("Layer Aggregation")); + return; + } + data.minScaleValid = value; + } + + if (senderButton == widgets.cbMaxScaleValid) + { + if (data.minScaleValid && value && (data.minScale <= data.maxScale)) + { + senderButton->setChecked(false); + const QString &msg = tr("Maximum scale is too large"); + LtsWidgets::LtsMessageDialog::showMessage(this, msg, tr("Layer Aggregation")); + return; + } + data.maxScaleValid = value; + } + + widgets.pbMinScaleTo->setEnabled(data.minScaleValid); + widgets.pbMaxScaleTo->setEnabled(data.maxScaleValid); + + widgets.pbReset->setEnabled(true); + widgets.pbApply->setEnabled(true); + + emit changesMade(); + +} // void LayerAggregationWidget::onSetValid(bool value) + + +void LayerAggregationWidget::doReset(int row) +{ + if ((row < 0) || (row >= m_internal->m_aggregationList.size())) + return; + + AggregationData &data = m_internal->m_aggregationList[row]; + data = m_internal->m_aggregationReset[data.layerName]; + + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[data.layerName]; + + setMinScaleLabel(widgets.pbMinScale, data.minScale); + setMaxScaleLabel(widgets.pbMaxScale, data.maxScale); + + widgets.cbMinScaleValid->setChecked(data.minScaleValid); + widgets.cbMinScaleValid->setEnabled(data.minScaleValid); + + widgets.cbMaxScaleValid->setChecked(data.maxScaleValid); + widgets.cbMaxScaleValid->setEnabled(data.maxScaleValid); + + widgets.pbMinScaleTo->setEnabled(data.minScaleValid); + widgets.pbMaxScaleTo->setEnabled(data.maxScaleValid); + + widgets.pbReset->setEnabled(false); + widgets.pbApply->setEnabled(!widgets.pbApply->isEnabled()); + +} // void doReset(SituationMap::Map * map, const AggregationData & data) + + +void LayerAggregationWidget::onReset() +{ + QObject *senderObj = sender(); + if (nullptr == senderObj) return; + + int row = senderObj->property("row").toInt(); + + if ((row < 0) || (row >= m_internal->m_aggregationList.size())) + return; + + doReset(row); + +} // void LayerAggregationWidget::onReset() + + +static void doApply(SituationMap::Map *map, const AggregationData &data) +{ + LayerFactory &layerFactory = map->layerFactory(); + LayerParameter layerParamter = layerFactory.layerParameter(data.layerName); + + layerParamter.minScale = data.minScaleValid ? data.minScale : LayerParameter::UNDEFINED_SCALE; // 0; + layerParamter.maxScale = data.maxScaleValid ? data.maxScale : LayerParameter::UNDEFINED_SCALE; // 0; + + layerFactory.setLayerParameter(data.layerName, layerParamter); +} // void doApply(SituationMap::Map * map, const AggregationData & data) + + +void LayerAggregationWidget::onApply() +{ + QObject *senderObj = sender(); + if (!senderObj) + return; + + int row = senderObj->property("row").toInt(); + + if ((row < 0) || (row >= m_internal->m_aggregationList.size())) + return; + + const AggregationData &data = m_internal->m_aggregationList[row]; + doApply(m_internal->m_map, data); + + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[data.layerName]; + widgets.pbApply->setEnabled(false); + +} // void LayerAggregationWidget::onApply() + + +void LayerAggregationWidget::onApplyAll() +{ + for (const AggregationData &data : std::as_const(m_internal->m_aggregationList)) + { + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[data.layerName]; + if (widgets.pbApply->isEnabled()) + { + doApply(m_internal->m_map, data); + widgets.pbApply->setEnabled(false); + } + } +} // void LayerAggregationWidget::onApplyAll() + + +void LayerAggregationWidget::onResetAll() +{ + for (const auto &aggData : std::as_const(m_internal->m_aggregationList)) + { + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[aggData.layerName]; + + doReset(widgets.pbApply->property("row").toInt()); + doApply(m_internal->m_map, aggData); + widgets.pbApply->setEnabled(false); + } +} // void LayerAggregationWidget::onResetAll() + + +void LayerAggregationWidget::onSetDefaultScales() +{ + if (m_internal->m_aggregationList.isEmpty()) + return; + + // Calculate scale defaults here ... + + double mapMinScaleSQRT = sqrt(m_internal->m_map->minScale()); + double mapMaxScaleSQRT = sqrt(m_internal->m_map->maxScale()); + + const int aggregationListCount = m_internal->m_aggregationList.size(); + double stepScaleSQRT = (mapMinScaleSQRT - mapMaxScaleSQRT) / static_cast(aggregationListCount); + + unsigned int step = 0; + + for (auto &aggData : m_internal->m_aggregationList) + { + double dScaleSQRT = mapMaxScaleSQRT + (step * stepScaleSQRT); + aggData.maxScaleValid = true; + aggData.maxScale = dScaleSQRT * dScaleSQRT; + + dScaleSQRT = mapMaxScaleSQRT + ((step + 1) * stepScaleSQRT); + aggData.minScaleValid = true; + aggData.minScale = dScaleSQRT * dScaleSQRT; + + } // for + + // disable start and end + if (aggregationListCount > 0) + { + // front -> max + m_internal->m_aggregationList.front().maxScale = 0; + m_internal->m_aggregationList.front().maxScaleValid = false; + // back -> min + m_internal->m_aggregationList.back().minScale = 0; + m_internal->m_aggregationList.back().minScaleValid = false; + } // if + + // update ui to m_aggregationList: + for (const auto &aggData : std::as_const(m_internal->m_aggregationList)) + { + const AggregationWidgets &widgets = m_internal->m_aggregationWidgets[aggData.layerName]; + widgets.cbMinScaleValid->setEnabled(aggData.minScaleValid); + widgets.cbMinScaleValid->setChecked(aggData.minScaleValid); + widgets.pbMinScaleTo->setEnabled(aggData.minScaleValid); + + widgets.cbMaxScaleValid->setEnabled(aggData.maxScaleValid); + widgets.cbMaxScaleValid->setChecked(aggData.maxScaleValid); + widgets.pbMaxScaleTo->setEnabled(aggData.maxScaleValid); + + widgets.pbApply->setEnabled(true); + widgets.pbReset->setEnabled(true); + + setMinScaleLabel(widgets.pbMinScale, aggData.minScale); + setMaxScaleLabel(widgets.pbMaxScale, aggData.maxScale); + } // for + + emit changesMade(); + +} // void LayerAggregationWidget::onSetDefaultScales() diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.h new file mode 100755 index 000000000..12c4efa88 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/layer_aggregation_widget.h @@ -0,0 +1,110 @@ +/** +** +** @RDE_HEADER@ +** Handles aggregation of participant layer +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 16.08.2017, JT : first edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include + +#include + + +class QPushButton; +class QRegularExpression; + +namespace SituationMap +{ +class Map; + +/** + @class LayerAggregationWidget layer_aggregation_widget.h + @brief Class to handle aggregation of participant layer +*/ + +class SITUATIONMAP_EXPORT LayerAggregationWidget : public QWidget +{ + Q_OBJECT + +public: + + /** + Constructor to build gui widget for handling aggregation layers. + + @param map Map the layer are part of + @param layerType Type of layer in general this will be LayerType::Operational + @param nameCondition If the nameCondition doesn't match with the layername the layer will be skipped and is not handled. + Example of an expression "^Participant.+". + @param parent Parent for Qt object hierarchy + */ + explicit LayerAggregationWidget(Map *map, LayerType layerType, const QRegularExpression &nameCondition, QWidget *parent); + + /** + Destructor to clean up memory. + */ + ~LayerAggregationWidget(); + + void retranslate(); + +public slots: + + void onApplyAll(); + void onResetAll(); + + void onSetDefaultScales(); + +signals: + + void changesMade(); + +protected slots: + + void onSetValid(bool value); + + void onGetMinScale(); + void onGetMaxScale(); + void onZoomTo(); + void onReset(); + void onApply(); + +private: + + void doReset(int row); + + static QString scaleText(double scale); + + static void setMinScaleLabel(QPushButton *btn, double scale); + static void setMaxScaleLabel(QPushButton *btn, double scale); + + class InternalData; + + InternalData *m_internal; + +}; // LayerAggregationWidget + +} /* namespace SituationMap */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.cpp new file mode 100755 index 000000000..16b9e09ea --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.cpp @@ -0,0 +1,105 @@ +/** +** +** @RDE_HEADER@ +** Dialog of aggregation of participant layer +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 18.08.2017, JT : first edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "participant_aggregation_dialog.h" +#include "layer_aggregation_widget.h" + +#include + +#include + + +using namespace SituationMap; + + +ParticipantAggregationDialog::ParticipantAggregationDialog(Map *map, QWidget *parent) + : LtsWidgets::LtsDialog(parent, LtsWidgets::ButtonCombination::OkApplyCancel) +{ + m_law = new LayerAggregationWidget(map, LayerType::Operational, QRegularExpression(QStringLiteral("^Participant.+"), QRegularExpression::CaseInsensitiveOption), this); + + if (map) + connect(map, &Map::beginDestroy, this, &ParticipantAggregationDialog::onMapDestroyed, Qt::UniqueConnection); + + QVBoxLayout *layout = new QVBoxLayout(this); + + layout->addWidget(m_law); + layout->addStretch(2); + + LtsWidgets::DialogButton btnSetDefault = LtsWidgets::DialogButton::Print; + + m_buttonBox->connectButton(btnSetDefault, m_law, &LayerAggregationWidget::onSetDefaultScales); + + m_buttonBox->setButtonEnabled(btnSetDefault, true); + m_buttonBox->setButtonVisible(btnSetDefault, true); + + connect(this, &ParticipantAggregationDialog::canceled, m_law, &LayerAggregationWidget::onResetAll); + connect(this, &ParticipantAggregationDialog::applied, m_law, &LayerAggregationWidget::onApplyAll); + + connect(m_law, &LayerAggregationWidget::changesMade, this, &ParticipantAggregationDialog::slotChangesMade); + + retranslate(); + + setOnlineHelpFilename(QStringLiteral("Situation-Participant_Aggregation")); + +} // ParticipantAggregationDialog::ParticipantAggregationDialog( Map * map, QWidget * parent ) + + +void ParticipantAggregationDialog::retranslate() +{ + // lupdate doesn't like translate(tr("foo")) ... + const QString txt(tr("Participant Aggregation")); + translate(txt); + + LtsWidgets::DialogButton btnSetDefault = LtsWidgets::DialogButton::Print; + + m_buttonBox->setText( btnSetDefault, tr("Set Scale Defaults")); + m_buttonBox->setToolTip( btnSetDefault, tr("Calculate scale values for each aggregation from map scale range")); + + m_law->retranslate(); + +} // void ParticipantAggregationDialog::retranslate() + + +void ParticipantAggregationDialog::dialogCanceled() +{ + emit canceled(); +} // void ParticipantAggregationDialog::dialogCanceled() + + +bool ParticipantAggregationDialog::apply() +{ + emit applied(); + return true; +} + +void ParticipantAggregationDialog::onMapDestroyed() +{ + // commit suicide + delete this; +} // bool ParticipantAggregationDialog::apply() + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.h new file mode 100755 index 000000000..2521f3765 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LayerAggregation/participant_aggregation_dialog.h @@ -0,0 +1,79 @@ +/** +** +** @RDE_HEADER@ +** Dialog of aggregation of participant layer +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 18.08.2017, JT : first edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include + +#include + +#include + + +namespace SituationMap +{ +class Map; +class LayerAggregationWidget; + +/** + @class ParticpantAggregation particpant_aggregation.h + @brief Class to handle aggregation of participant layer dialog +*/ + +class SITUATIONMAP_EXPORT ParticipantAggregationDialog : public LtsWidgets::LtsDialog +{ + Q_OBJECT + +public: + + explicit ParticipantAggregationDialog(SituationMap::Map *map, QWidget *parent); + ~ParticipantAggregationDialog() = default; + + void retranslate() override; + + void dialogCanceled() override; + +protected: + + bool apply() override; + +Q_SIGNALS: + void canceled(); + void applied(); + +protected Q_SLOTS: + void onMapDestroyed(); + +private: + + LayerAggregationWidget *m_law; + +}; // ParticipantAggregationDialog + +} /* namespace SituationMap */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/CMakeLists.txt new file mode 100755 index 000000000..ad1d98874 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# LIBRARY + +SET(LocalServer_SRCS + local_server_export + local_server.cpp + local_server.h +) + +SOURCE_GROUP(LocalServer FILES ${LocalServer_SRCS}) + +NGL_ADD_LIBRARY(WS situationMapLocalServer SHARED ${LocalServer_SRCS}) +NGL_TARGET_LINK_LIBRARIES(situationMapLocalServer + PRIVATE + ArcGIS::ArcGIS + NGLLogger + Qt6::Gui # can be removed once we get an official ESRI ArcGIS Qt version again + PUBLIC + Qt6::Core + situationMapCommon +) + +# ESRI: /usr/include/arcgis/LocalServer/LocalServer.h: '#include "Object.h"' +# so add required include path here as PRIVATE: +NGL_TARGET_INCLUDE_DIRECTORIES(situationMapLocalServer + PRIVATE + ${ARCGIS_INCLUDE_DIR}/arcgis + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +NGL_INSTALL_TARGET(situationMapLocalServer) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.cpp new file mode 100755 index 000000000..34624bdf7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.cpp @@ -0,0 +1,519 @@ +/** + * @RDE_HEADER@ + * LocalServer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "local_server.h" +#include + + +// ESRI +#include + +#include +#include + + +//Qt +#include + + +namespace Esri +{ +namespace ArcGISRuntime +{ + +QString toString(const Esri::ArcGISRuntime::Error &value) +{ + std::ostringstream stream; + stream << "Esri::ArcGISRuntime::Error: " + << " code=" << value.code() + << ", domain=" << (int)value.domain() + << ", extendedErrorType=" << (int)value.extendedErrorType() + << ", message='" << value.message() << "'" + << ", additionalMessage='" << value.additionalMessage() << "'"; + return QString::fromStdString(stream.str()); +} + +std::ostream &operator<< (std::ostream &stream, + const Esri::ArcGISRuntime::Error &value) +{ + stream << toString(value); + return stream; +} + +} // namespace ArcGISRuntime +} // namespace Esri + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +bool LocalServiceData::isEmpty() const +{ + return m_svcUrl.isEmpty(); +} + +void LocalServiceData::setSvcUrl(const QUrl &svcUrl) +{ + m_svcUrl = svcUrl; +} + +const QUrl &LocalServiceData::svcUrl() const +{ + return m_svcUrl; +} + +void LocalServiceData::setSync(bool sync) +{ + m_sync = sync; +} + +bool LocalServiceData::isSync() const +{ + return m_sync; +} + + +//---------------------------------------------------------------------------- + +static QString toString(const Esri::ArcGISRuntime::LocalServerStatus &value) +{ + switch (value) + { + case Esri::ArcGISRuntime::LocalServerStatus::Stopped: + return QLatin1String("Stopped"); + case Esri::ArcGISRuntime::LocalServerStatus::Starting: + return QLatin1String("Starting"); + case Esri::ArcGISRuntime::LocalServerStatus::Started: + return QLatin1String("Started"); + case Esri::ArcGISRuntime::LocalServerStatus::Stopping: + return QLatin1String("Stopping"); + case Esri::ArcGISRuntime::LocalServerStatus::Failed: + return QLatin1String("Failed"); + } // switch + return QLatin1String("Esri-LocalServerStatusERROR"); +} + + + +//----------------------------------------------------------------------------- +class LocalServer::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + Esri::ArcGISRuntime::LocalServer *m_localServer = nullptr; + + // Own data: + QMap m_gpServiceMap; + QMap m_serviceSubLocationMap; + + + QMap m_svcData; + + +}; // class LocalServer::Private +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +LocalServer *LocalServer::instance() +{ + static LocalServer *_instance = nullptr; + if (_instance == nullptr) + _instance = new LocalServer; + return _instance; +} + +//----------------------------------------------------------------------------- +LocalServer::LocalServer() + : BaseClass() + , m_logger(getLogger("LocalServer")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " installPath = " + << " '" << Esri::ArcGISRuntime::LocalServer::installPath() << "'"; + + QString newInstallPath; + + QString user = QString::fromLocal8Bit( qgetenv("USER") ); + if (user.compare(QLatin1Literal("ctc")) == 0) + newInstallPath = SituationMap::getResourcePath() + QLatin1String("/localserver100.2_ctc/"); // USER ctc + else + newInstallPath = SituationMap::getResourcePath() + QLatin1String("/localserver100.2/"); // USER ctc_ws + + Esri::ArcGISRuntime::LocalServer::setInstallPath(newInstallPath); + + if (Esri::ArcGISRuntime::LocalServer::isInstallValid()) + NOTICE_LOG(m_logger) << LOGFUNCTION << " installPath ok = " + << " '" << Esri::ArcGISRuntime::LocalServer::installPath() << "'"; + else + FATAL_LOG(m_logger) << LOGFUNCTION << " installPath failed = " + << " InstallPath = '" << Esri::ArcGISRuntime::LocalServer::installPath() << "'"; + + d->m_localServer = Esri::ArcGISRuntime::LocalServer::instance(); + + connect(d->m_localServer, &Esri::ArcGISRuntime::LocalServer::errorOccurred, + this, &LocalServer::onLocalServerError); + connect(d->m_localServer, &Esri::ArcGISRuntime::LocalServer::statusChanged, + this, &LocalServer::onLocalServerStatusChanged); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +LocalServer::~LocalServer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->stop(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void LocalServer::start() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_localServer->start(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} +void LocalServer::stop() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + stopAllLocalGeoprocessingServices(); + d->m_localServer->stop(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool LocalServer::isLocalServerStarted() const +{ + return d->m_localServer->status() == Esri::ArcGISRuntime::LocalServerStatus::Started; +} +bool LocalServer::isLocalServerStopped() const +{ + return d->m_localServer->status() == Esri::ArcGISRuntime::LocalServerStatus::Stopped; +} + + +bool LocalServer::setLocalGeoprocessingService(const QString &serviceName, + const QString &packagePath, + const QString &serviceSubLocation, + bool sync) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " serviceName: '" << serviceName << "'" + << " packagePath: '" << packagePath << "'" + << " serviceSubLocation: '" << serviceSubLocation << "'"; + if (d->m_gpServiceMap.contains(serviceName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName: '" << serviceName << "'" << " already provided"; + return false; + } + + QString localPackagePath = packagePath; + + { + QFileInfo fileinfo(packagePath); + if (!fileinfo.isAbsolute()) + localPackagePath = SituationMap::getResourcePath() + QLatin1String("/gpk/") + packagePath; + } + QFileInfo fileinfo(localPackagePath); + + if (!fileinfo.exists()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName: '" << serviceName << "'" + << " packagePath does not exist: " + << " localPackagePath: '" << localPackagePath << "'"; + return false; + } + if (!fileinfo.isReadable()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName: '" << serviceName << "'" + << " packagePath is not readable: " + << " localPackagePath: '" << localPackagePath << "'"; + return false; + } + + + NOTICE_LOG(m_logger) << LOGFUNCTION << " create LocalGeoprocessingService: " + << " localPackagePath: '" << localPackagePath << "'"; + + + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = + new Esri::ArcGISRuntime::LocalGeoprocessingService(localPackagePath, this); + lgps->setObjectName(QString(QLatin1Literal("LocalGeoprocessingService-%1")).arg(serviceName)); + + if (sync) + lgps->setServiceType(Esri::ArcGISRuntime::GeoprocessingServiceType::SynchronousExecute); // SynchronousExecute | AsynchronousSubmit | ... + else + lgps->setServiceType(Esri::ArcGISRuntime::GeoprocessingServiceType::AsynchronousSubmit); // SynchronousExecute | AsynchronousSubmit | ... + + connect(lgps, &Esri::ArcGISRuntime::LocalGeoprocessingService::errorOccurred, + this, &LocalServer::onLGPSError); + connect(lgps, &Esri::ArcGISRuntime::LocalGeoprocessingService::statusChanged, + this, &LocalServer::onLGPSStatusChanged); + + // add to own management: + d->m_gpServiceMap[serviceName] = lgps; + d->m_serviceSubLocationMap[serviceName] = serviceSubLocation; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " serviceName: '" << serviceName << "'" + << " localPackagePath: '" << localPackagePath << "'" + << " serviceSubLocation: '" << serviceSubLocation << "'"; + return true; +} + +bool LocalServer::startLocalGeoprocessingService(const QString &serviceName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_gpServiceMap.contains(serviceName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << " not found"; + return false; + } + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = d->m_gpServiceMap[serviceName]; + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + return false; + } + + if (isLocalServerStarted()) + lgps->start(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool LocalServer::stopLocalGeoprocessingService(const QString &serviceName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_gpServiceMap.contains(serviceName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << " not found"; + return false; + } + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = d->m_gpServiceMap[serviceName]; + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + return false; + } + + lgps->stop(); + return true; +} + +bool LocalServer::isLocalGeoprocessingServiceStarted(const QString &serviceName) const +{ + if (!d->m_gpServiceMap.contains(serviceName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << " not found"; + return false; + } + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = d->m_gpServiceMap[serviceName]; + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + return false; + } + return lgps->status() == Esri::ArcGISRuntime::LocalServerStatus::Started; +} +bool LocalServer::isLocalGeoprocessingServiceStopped(const QString &serviceName) const +{ + if (!d->m_gpServiceMap.contains(serviceName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << " not found"; + return false; + } + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = d->m_gpServiceMap[serviceName]; + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + return false; + } + return lgps->status() == Esri::ArcGISRuntime::LocalServerStatus::Stopped; +} + +bool LocalServer::isLocalGeoprocessingServiceSynchronousExecute(const QString &serviceName) const +{ + if (!d->m_gpServiceMap.contains(serviceName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << " not found"; + return false; + } + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = d->m_gpServiceMap[serviceName]; + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + return false; + } + return lgps->serviceType() == Esri::ArcGISRuntime::GeoprocessingServiceType::SynchronousExecute; +} + +bool LocalServer::isLocalGeoprocessingServiceAsynchronousSubmit(const QString &serviceName) const +{ + if (!d->m_gpServiceMap.contains(serviceName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << " not found"; + return false; + } + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = d->m_gpServiceMap[serviceName]; + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + return false; + } + return lgps->serviceType() == Esri::ArcGISRuntime::GeoprocessingServiceType::AsynchronousSubmit; +} + +bool LocalServer::localGeoprocessingServiceData(const QString &serviceName, LocalServiceData &localServiceDataOut) const +{ + if (!d->m_svcData.contains(serviceName)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << " not found"; + return false; + } + localServiceDataOut = d->m_svcData[serviceName]; + return true; +} + +void LocalServer::startAllPendingLocalGeoprocessingServices() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QMapIterator mapIt(d->m_gpServiceMap); + + while (mapIt.hasNext()) + { + mapIt.next(); + const QString &serviceName = mapIt.key(); + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = mapIt.value(); + + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + continue; + } + + if (lgps->status() == Esri::ArcGISRuntime::LocalServerStatus::Started) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", already started, continue"; + continue; + } + else + lgps->start(); + } // while + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void LocalServer::stopAllLocalGeoprocessingServices() +{ + QMapIterator mapIt(d->m_gpServiceMap); + + while (mapIt.hasNext()) + { + mapIt.next(); + const QString &serviceName = mapIt.key(); + Esri::ArcGISRuntime::LocalGeoprocessingService *lgps = mapIt.value(); + + if (!lgps) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " serviceName '" << serviceName << "'" << ", no service"; + continue; + } + lgps->stop(); + } // while + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void LocalServer::onLocalServerError(const Esri::ArcGISRuntime::Error &error) +{ + ERROR_LOG(m_logger) << LOGFUNCTION << " EsriError occured: " << error; +} + +void LocalServer::onLocalServerStatusChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + NOTICE_LOG(m_logger) << LOGFUNCTION << " LocalServer: status changed to: " << toString(d->m_localServer->status()); + + emit localServerStatusChanged(); + + if (d->m_localServer->status() == Esri::ArcGISRuntime::LocalServerStatus::Started) + { + NOTICE_LOG(m_logger) << LOGFUNCTION << " LocalServer: STARTED" + << " (url= '" << d->m_localServer->url().toString() << "')" + << " (Esri::ArcGISRuntime::LocalServer->tempDataPath= '" << d->m_localServer->tempDataPath() << "')"; + startAllPendingLocalGeoprocessingServices(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void LocalServer::onLGPSError(const Esri::ArcGISRuntime::Error &error) +{ + QString senderName; + Esri::ArcGISRuntime::LocalGeoprocessingService *lgs = qobject_cast(sender()); + if (lgs) + senderName = lgs->objectName(); + ERROR_LOG(m_logger) << LOGFUNCTION << " LocalGeoprocessingService -> EsriError occured: " << error; +} + +void LocalServer::onLGPSStatusChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + Esri::ArcGISRuntime::LocalGeoprocessingService *lgsp = qobject_cast(sender()); + + if (!lgsp) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " qobject_cast failed to get sender!?"; + return; + } + + QString senderName = lgsp->objectName(); + const QString &serviceName = d->m_gpServiceMap.key(lgsp); + + if (lgsp->status() == Esri::ArcGISRuntime::LocalServerStatus::Started) + { + NOTICE_LOG(m_logger) << LOGFUNCTION << " ***** LocalGeoprocessingService:" + << " sender= '" << senderName << "'" + << " status changed to: " << toString(lgsp->status()) + << " url= '" << lgsp->url().toString() << "'"; + + LocalServiceData svcData; + + const QString &serviceSubLocation = d->m_serviceSubLocationMap[serviceName]; + const QString &url = lgsp->url().toString() + QLatin1String("/") + serviceSubLocation; + svcData.setSvcUrl(QUrl(url)); + svcData.setSync(lgsp->serviceType() == Esri::ArcGISRuntime::GeoprocessingServiceType::SynchronousExecute); + + d->m_svcData[serviceName] = svcData; // store in own management + + emit localGeoprocessingServiceStatusChanged(serviceName); + emit localGeoprocessingServiceStarted(serviceName, svcData); + } + + NOTICE_LOG(m_logger) << LOGFUNCTION << " LocalGeoprocessingService:" + << " sender= '" << senderName << "'" + << " status changed to: " << toString(lgsp->status()); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.h new file mode 100755 index 000000000..0fc760b25 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server.h @@ -0,0 +1,128 @@ +/** + * @RDE_HEADER@ + * LocalServer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "local_server_export.h" +#include + +namespace Esri { +namespace ArcGISRuntime { +class Error; +} +} + + + +// Qt +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class LOCALSERVER_EXPORT LocalServiceData +{ +private: + QUrl m_svcUrl; + bool m_sync = false; +public: + bool isEmpty() const; + + void setSvcUrl(const QUrl &svcUrl); + const QUrl &svcUrl() const; + + void setSync(bool sync); + bool isSync() const; +}; + + + +/** + * @brief The LocalServer class wrap ESRI`s LocalServer. + * This is a container for geoprocessing services + * The LocalServer has to be started as well as the seperate instances of LocalGeoprocessingService. + * To ease the usage you should set the required number of LocalGeoprocessingService (with different serviceNames). + * Then simply start the LocalServer (and if that is up all pending will be started.) + */ +class LOCALSERVER_EXPORT LocalServer : public QObject +{ + Q_OBJECT + + using BaseClass = QObject; + +private: + explicit LocalServer(); + ~LocalServer(); + +public: + /*! \name Check Installation ( no guarantee )*/ + static bool isInstallValid(); + + + /*! \name Singleton */ + static LocalServer *instance(); + + + /*! \name LocalServer */ + //@{ + void start(); + void stop(); + + bool isLocalServerStarted() const; + bool isLocalServerStopped() const; + //@} + + /*! \name LocalGeoprocessingService */ + //@{ + bool setLocalGeoprocessingService(const QString &serviceName, const QString &packagePath, const QString &serviceSubLocation, bool sync); + bool startLocalGeoprocessingService(const QString &serviceName); + bool stopLocalGeoprocessingService(const QString &serviceName); + + bool isLocalGeoprocessingServiceStarted(const QString &serviceName) const; + bool isLocalGeoprocessingServiceStopped(const QString &serviceName) const; + + bool isLocalGeoprocessingServiceSynchronousExecute(const QString &serviceName) const; + bool isLocalGeoprocessingServiceAsynchronousSubmit(const QString &serviceName) const; + + bool localGeoprocessingServiceData(const QString &serviceName, LocalServiceData &localServiceDataOut) const; + //@} + +protected: + void startAllPendingLocalGeoprocessingServices(); + void stopAllLocalGeoprocessingServices(); + +protected Q_SLOTS: + void onLocalServerError(const Esri::ArcGISRuntime::Error &error); + void onLocalServerStatusChanged(); + + void onLGPSError(const Esri::ArcGISRuntime::Error &error); + void onLGPSStatusChanged(); + +Q_SIGNALS: + void localServerStatusChanged(); + + void localGeoprocessingServiceStatusChanged(const QString &serviceName); + void localGeoprocessingServiceStarted(const QString &serviceName, const LocalServiceData&localSvcData); + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server_export.h new file mode 100755 index 000000000..4d9f2e0a8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/LocalSrv/local_server_export.h @@ -0,0 +1,21 @@ +/** + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef LOCALSERVER_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LOCALSERVER_EXPORT +# else +# if defined(MAKE_SITUATIONMAPLOCALSERVER_LIB) +# define LOCALSERVER_EXPORT Q_DECL_EXPORT +# else +# define LOCALSERVER_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MIGRATION_STATUS.md b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MIGRATION_STATUS.md new file mode 100644 index 000000000..297141c50 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MIGRATION_STATUS.md @@ -0,0 +1,81 @@ +# SituationMap Qt6 Migration Status + +## Summary +The SituationMap module has been successfully migrated from Qt5 to Qt6 at the **source code level**. All Qt5 API calls have been updated to Qt6 equivalents. + +## Completed Changes + +### Qt6 API Migrations +1. **QStringView::slice() → sliced()** - Fixed in 5 files: + - Symbology/sidc_codec.cpp + - Symbology/sidc_builder.cpp + - Symbology/sidc_builder_base.cpp + - Symbology/symbol_properties.cpp + - SymbologyImpl/symbol_dictionary_impl.cpp + +2. **QSortFilterProxyModel filter changes** - Fixed in base_symbol_model.cpp: + - beginFilterChange()/endFilterChange() → invalidateFilter() (3 occurrences) + +3. **QFuture const-correctness** - Fixed in base_symbol_model.cpp: + - Added `mutable` keyword to 3 lambdas capturing QFuture for waitForFinished() calls + +### Directory Structure +All 22 subdirectories from Qt5 have been copied to Qt6: +- admin_scripts, ArcGISQt5Hack, ElevationMap, EvalApps, FunctionDialog +- GeometryModel, GraphicalObjects, LayerAggregation, LocalSrv +- MapImpl, MapObjects, MapObjectsImpl, MathUtils, ModuleTests +- Portal, README, Resources, Rubberbands, SideBar +- Symbology, SymbologyGui, Tests, UnitTests, UnitTestsGeoModel, Utils + +## Build Status + +### Without ArcGIS (BUILD_ARCGIS=OFF) +✅ Source code compiles successfully +⚠️ Some unrelated modules (e.g., EEMFakeServer) require Qt6WebSockets which is not installed + +### With ArcGIS (BUILD_ARCGIS=ON) +❌ **Linker errors** - ArcGIS SDK binary incompatibility + +## Critical Issue: ArcGIS SDK Compatibility + +### Problem +The ArcGIS Maps SDK for Qt v300.0.0 library (`libEsriRuntimeQt.a`) was built against **Qt 6.8+**, but the system has **Qt 6.2.4** installed. + +**Evidence:** +- ArcGIS SDK template requires: `Qt 6.8.2` minimum (per sdk/ideintegration/ArcGISRuntimeQtTemplate300_0_0/CMakeLists.txt) +- System Qt version: `6.2.4` +- Linker errors show undefined Qt6 internal symbols: + - `QMetaObject::invokeMethodImpl` + - `QFutureInterfaceBase::hasException()` + - `QMetaType::registerHelper` + - `QtPrivate::QMetaTypeInterfaceWrapper` + +### Required Resolution +To build with ArcGIS support, one of the following is needed: + +1. **Upgrade Qt to 6.8+** (recommended) + - Install Qt 6.8.2 or later + - Rebuild entire project against Qt 6.8+ + +2. **Obtain Qt 6.2-compatible ArcGIS SDK** + - Contact Esri for an ArcGIS SDK build compatible with Qt 6.2.x + - Note: ArcGIS SDK v300 may not support Qt 6.2 + +3. **Disable ArcGIS temporarily** + - Build with `-DBUILD_ARCGIS=OFF` + - Complete migration of non-ArcGIS code + - Re-enable ArcGIS when compatible SDK is available + +## Next Steps + +1. **Immediate**: Decide on ArcGIS strategy (upgrade Qt, get compatible SDK, or disable) +2. **If disabling ArcGIS**: Complete migration of remaining 27 plugin files with beginFilterChange()/endFilterChange() +3. **After ArcGIS resolution**: Run full test suite to validate migration + +## Files Modified in This Session +- Lts/Viewer/SituationMap/Symbology/sidc_codec.cpp +- Lts/Viewer/SituationMap/Symbology/sidc_builder.cpp +- Lts/Viewer/SituationMap/Symbology/sidc_builder_base.cpp +- Lts/Viewer/SituationMap/Symbology/symbol_properties.cpp +- Lts/Viewer/SituationMap/SymbologyImpl/symbol_dictionary_impl.cpp +- Lts/Viewer/SituationMap/SymbologyImpl/base_symbol_model.cpp diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.cpp new file mode 100755 index 000000000..589b3b9ed --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.cpp @@ -0,0 +1,59 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AbstractEffectSimulation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "abstract_effect_simulation_impl.h" +#include "map_impl.h" + + +// ESRI + + +// Qt + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// constants +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Utils: +//----------------------------------------------------------------------------- + + + +//----------------------------------------------------------------------------- +// class AbstractEffectSimulationImpl +//----------------------------------------------------------------------------- +AbstractEffectSimulationImpl::AbstractEffectSimulationImpl() + : BaseClass() // Interface + , m_logger(getLogger("AbstractEffectSimulationImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +AbstractEffectSimulationImpl::~AbstractEffectSimulationImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + + +QString AbstractEffectSimulationImpl::toString() const +{ + return QString(); +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.h new file mode 100755 index 000000000..ede3fd20f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_effect_simulation_impl.h @@ -0,0 +1,52 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AbstractEffectSimulation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_map_object_impl.h" +#include // Interface + + +// ESRI + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAP_EXPORT AbstractEffectSimulationImpl : public virtual AbstractEffectSimulation +{ + using BaseClass = SituationMap::AbstractEffectSimulation; + +public: + AbstractEffectSimulationImpl(); + virtual ~AbstractEffectSimulationImpl(); + + AbstractEffectSimulationImpl(const AbstractEffectSimulationImpl &other) = delete; + AbstractEffectSimulationImpl &operator=(AbstractEffectSimulationImpl &other) = delete; + + virtual QString toString() const; + +protected: + // logical + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.cpp new file mode 100755 index 000000000..31908463d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.cpp @@ -0,0 +1,1206 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AbstractMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "abstract_map_object_impl.h" +#include "map_impl.h" +#include "object_container_impl.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include + + +// ESRI +#include +#include +#include +#include + + +// Qt + + +namespace SituationMap +{ + +struct ExtraData +{ + QVector propertyNames; + QVector propertyValues; +}; + + + +AbstractMapObjectImpl::AbstractMapObjectImpl(const QString &debugInfo) + : AbstractMapObject() + , m_uniqueMapId(SituationMap::INVALID_MAP_ID) + , m_logger(getLogger("AbstractMapObjectImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " debugInfo='" << debugInfo << "'"; + m_debugInfo = debugInfo; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " debugInfo='" << debugInfo << "'"; +} // AbstractMapObjectImpl::AbstractMapObjectImpl + + +AbstractMapObjectImpl::~AbstractMapObjectImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo + << " uniqueMapId=" << m_uniqueMapId; + delete m_mapConnector; + m_mapConnector = nullptr; + + resetPin(); + + delete m_tacticalAppearance; + m_tacticalAppearance = nullptr; + + delete m_mainGraphicalObjectText; + m_mainGraphicalObjectText = nullptr; + + resetGraphicalObjectExtrusion(); + + delete m_mainGraphicalObject; + m_mainGraphicalObject = nullptr; + + delete m_extraData; + m_extraData = nullptr; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo + << " uniqueMapId=" << m_uniqueMapId; + +} // AbstractMapObjectImpl::~AbstractMapObjectImpl + + +void AbstractMapObjectImpl::removeMySelf() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo; + + // To avoid accessing already deleted variables make some local copies 2016/01/08 JT + QString debugInfo = m_debugInfo; + UniqueMapId uid = m_uniqueMapId; + SituationMap::LOGGER_T logger(m_logger); + + if (m_mapImpl) + { + TRACE_LOG(m_logger) << "removeObject, uniqueMapId = " << m_uniqueMapId; + m_uniqueMapId = SituationMap::INVALID_MAP_ID; + m_mapImpl->objectContainer().removeObject(uid); + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " m_mapImpl does not exist."; + + // Attention CRASH here if AbstractMapObject is not been referenced by a RefPointer. In this case the this-object has been deleted immediately 2016/01/08 JT + TRACE_LOG(logger) << LOGFUNCTION << " END" + << " debugInfo=" << debugInfo; +} + +bool AbstractMapObjectImpl::setProperty(const char *name, const QVariant &value) +{ + // following setProperty/property approach is take from qt4.8.5: + + if (!m_extraData) + m_extraData = new ExtraData; + + const int idx = m_extraData->propertyNames.indexOf(name); + + if (!value.isValid()) + { + if (idx == -1) + return false; + m_extraData->propertyNames.removeAt(idx); + m_extraData->propertyValues.removeAt(idx); + } + else + { + if (idx == -1) + { + m_extraData->propertyNames.append(name); + m_extraData->propertyValues.append(value); + } + else + m_extraData->propertyValues[idx] = value; + } + + return true; +} + +QVariant AbstractMapObjectImpl::property(const char *name) const +{ + if (!m_extraData) + return QVariant(); + const int i = m_extraData->propertyNames.indexOf(name); + return m_extraData->propertyValues.value(i); +} + + +UniqueMapId AbstractMapObjectImpl::uniqueMapId() const +{ + return m_uniqueMapId; +} + + +QString AbstractMapObjectImpl::debugInfo() const +{ + return m_debugInfo; +} + +void AbstractMapObjectImpl::setDebugInfo(const QString &debugInfo) +{ + m_debugInfo = debugInfo; + + if (m_mainGraphicalObject) + m_mainGraphicalObject->setDebugInfo(m_debugInfo + QLatin1String("#mainGO")); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + + if (m_mainGraphicalObjectText) + m_mainGraphicalObjectText->setDebugInfo(m_debugInfo + QLatin1String("#Text")); + + if (m_tacticalAppearance) + m_tacticalAppearance->setDebugInfo(m_debugInfo + QLatin1String("#TactApp")); + +} + +QString AbstractMapObjectImpl::toString() const +{ + QString str; + + if (!m_debugInfo.isEmpty()) + str.append(QStringLiteral("debugInfo='%1'").arg(m_debugInfo)); + + str.append(QStringLiteral("uniqueMapId='%1'").arg(m_uniqueMapId)); + + if ((m_mainGraphicalObjectText) && + (! m_mainGraphicalObjectText->text().isEmpty()) + ) + str.append(QStringLiteral(" identifier='%1'").arg(m_mainGraphicalObjectText->text())); + + if (!m_logicalLayerName.isEmpty()) + str.append(QStringLiteral(" logicalLayerName='%1'").arg(m_logicalLayerName)); + + str.append(QStringLiteral(" partyId=%1").arg(m_party.partyId.value())); + + str.append(QStringLiteral(" aesState=%1").arg(SituationMap::toString(m_aesState.value()))); + + if (m_mainGraphicalObject) + str.append(QStringLiteral(" scale='%1'").arg(m_mainGraphicalObject->scale())); + + str.append(QStringLiteral(" visibility='%1'").arg(m_visibility)); + str.append(QStringLiteral(" visibilityTech='%1'").arg(m_visibilityTechnical)); + str.append(QStringLiteral(" visibilityTact='%1'").arg(m_visibilityTactical)); + str.append(QStringLiteral(" visibilityTechGeo='%1'").arg(m_visibilityTechnicalGeometry)); + str.append(QStringLiteral(" identiferVisible=%1").arg(m_identiferVisible.value())); + str.append(QStringLiteral(" selected='%1'").arg(m_selected)); + str.append(QStringLiteral(" pickable='%1'").arg(m_pickable)); + + str.append(QStringLiteral(" majorColor='%1'").arg(this->majorColor().name(QColor::HexArgb))); + + return str; +} + +void AbstractMapObjectImpl::boundingBox(SituationMap::BoundingBox &) const +{ + // leave it empty and invalid. Method has to be overloaded if required. +} + + +void AbstractMapObjectImpl::setForceMajorColor(const QColor &majorColor) +{ + m_forcedMajorColor = majorColor; +} +QColor AbstractMapObjectImpl::majorColor() const +{ + if (m_forcedMajorColor.isValid()) + return m_forcedMajorColor; + + if (m_mainGraphicalObject) + return m_mainGraphicalObject->lineStyle().lineColor(); + + return QColor(); +} + +double AbstractMapObjectImpl::extrusionHeight() const +{ + return m_extrusionHeight; +} +void AbstractMapObjectImpl::setExtrusionHeight(double extrusionHeight) +{ + if (m_mapImpl->mode() != Mode::Mode_3D) + return; + + m_extrusionHeight = extrusionHeight; + + if (extrusionHeightValid()) + createGraphicalObjectExtrusion(); + else + resetGraphicalObjectExtrusion(); +} +bool AbstractMapObjectImpl::extrusionHeightValid() const +{ + return !std::isnan(m_extrusionHeight); +} + +const QString AbstractMapObjectImpl::extrusionColorName() const +{ + return m_extrusionColorName; +} +void AbstractMapObjectImpl::setExtrusionColorName(const QString &extrusionColorName) +{ + m_extrusionColorName = extrusionColorName; +} + + +QString AbstractMapObjectImpl::identifier() const +{ + if (m_mainGraphicalObjectText) + return m_mainGraphicalObjectText->text(); + +// else +// ERROR_LOG(m_logger) << LOGFUNCTION +// << " debugInfo=" << m_debugInfo +// << " no object" +// ; + + return QString(); +} + +void AbstractMapObjectImpl::createGraphicalObjectText() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo; + + if (!m_mainGraphicalObjectText) + { + m_mainGraphicalObjectText = new GraphicalObjectText(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , m_debugInfo + QLatin1String("#Text") + , uniqueMapId()); + // Set default text style for all MapObjects: + m_mainGraphicalObjectText->setTextStyle(defaultTextStyle()); + m_mainGraphicalObjectText->setXOffset(10); // use as initial default + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo; +} + +void AbstractMapObjectImpl::createTacticalAppearance() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo; + if (!m_tacticalAppearance) + { + m_tacticalAppearance = new TacticalAppearance(m_mapImpl, + m_logicalLayerName, + m_debugInfo + QStringLiteral("#TactApp"), + m_uniqueMapId + ); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo; +} + +void AbstractMapObjectImpl::resetTacticalAppearanceDrawOrderOffset() +{ + m_tacticalAppearanceDrawOrderOffset = 0; +} + +void AbstractMapObjectImpl::createPin() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo; + if (!m_graphicalObjectPin) + { + m_graphicalObjectPin = new GraphicalObjectPath(SituationMap::GraphicalObjectPathType::Polyline, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QStringLiteral("#Pin"), + m_uniqueMapId + ); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo; +} + +void AbstractMapObjectImpl::resetPin() +{ + delete m_graphicalObjectPin; + m_graphicalObjectPin = nullptr; +} + +void AbstractMapObjectImpl::setIdentifier(const QString &identifier) +{ + if (identifier.isEmpty()) + { + delete m_mainGraphicalObjectText; + m_mainGraphicalObjectText = nullptr; + } + else + { + createGraphicalObjectText(); + if (m_mainGraphicalObjectText) + m_mainGraphicalObjectText->setText(identifier); + } +} + +bool AbstractMapObjectImpl::identifierVisible() const +{ + return m_identiferVisible.value(); +} + +void AbstractMapObjectImpl::setIdentifierVisible(bool identifierVisible) +{ + m_identiferVisible = identifierVisible; +} + +int AbstractMapObjectImpl::partyId() const +{ + return m_party.partyId.value(); +} + +bool AbstractMapObjectImpl::isPartyIdValid() const +{ + return m_party.partyId.value() >= 0; +} + +void AbstractMapObjectImpl::setPartyId(int partyId) +{ + m_party.partyId = partyId; +} + +bool AbstractMapObjectImpl::setPartyIdChanged(int partyId) +{ + if ((partyId < 0) || (m_party.partyId.value() == partyId)) + { + m_party.partyId.setChanged(); + return true; + } + return false; +} + + +const QColor &AbstractMapObjectImpl::partyColor() const +{ + if (m_party.forcedPartyColor.value().isValid()) + return m_party.forcedPartyColor.value(); + + if (m_party.partyId.changed() || m_party.useLightColor.changed() || !m_party.partyColor.isValid()) + { + m_party.partyColor = + m_mapImpl->partyIdToColorFunctor().partyIdToColor(m_party.partyId.value(), + m_party.useLightColor.value()); + } + return m_party.partyColor; +} + +QColor AbstractMapObjectImpl::partyColor(int partyId, bool useLightColor) const +{ + return m_mapImpl->partyIdToColorFunctor().partyIdToColor(partyId, useLightColor); +} + +void AbstractMapObjectImpl::forcePartyColor(const QColor &partyColorToForce) +{ + m_party.forcedPartyColor = partyColorToForce; +} + +bool AbstractMapObjectImpl::useLightColor() const +{ + return m_party.useLightColor.value(); +} +void AbstractMapObjectImpl::setUseLightColor(bool useLightColor) +{ + m_party.useLightColor = useLightColor; +} + +AdvancedSymbology::Role AbstractMapObjectImpl::partyRole(bool roleIdentityAssumed) const +{ + return m_mapImpl->partyIdToColorFunctor().partyIdToRole(m_party.partyId.value(), roleIdentityAssumed); +} + + +AesEffectState AbstractMapObjectImpl::aesState() const +{ + return m_aesState.value(); +} +void AbstractMapObjectImpl::setAesState(AesEffectState aesState) +{ + m_aesState = aesState; +} + + +TextStyle AbstractMapObjectImpl::textStyle() const +{ + if (m_mainGraphicalObjectText) + return m_mainGraphicalObjectText->textStyle(); + else + return TextStyle(); +} + +TextStyle &AbstractMapObjectImpl::rTextStyle() +{ + createGraphicalObjectText(); + return m_mainGraphicalObjectText->rTextStyle(); +} + +void AbstractMapObjectImpl::setTextStyle(const TextStyle &textStyle) +{ + createGraphicalObjectText(); + m_mainGraphicalObjectText->setTextStyle(textStyle); +} + + +LineStyle AbstractMapObjectImpl::lineStyle() const +{ + return (m_mainGraphicalObject) ? m_mainGraphicalObject->lineStyle() : LineStyle(); +} + +LineStyle &AbstractMapObjectImpl::rLineStyle() +{ + static LineStyle dummy; + return (m_mainGraphicalObject) ? m_mainGraphicalObject->rLineStyle() : dummy; +} + +void AbstractMapObjectImpl::setLineStyle(const LineStyle &lineStyle) +{ + if (m_mainGraphicalObject) m_mainGraphicalObject->setLineStyle(lineStyle); +} + +FillStyle AbstractMapObjectImpl::fillStyle() const +{ + return (m_mainGraphicalObject) ? m_mainGraphicalObject->fillStyle() : FillStyle(); +} + +FillStyle &AbstractMapObjectImpl::rFillStyle() +{ + static FillStyle dummy; + return (m_mainGraphicalObject) ? m_mainGraphicalObject->rFillStyle() : dummy; +} + +void AbstractMapObjectImpl::setFillStyle(const FillStyle &fillStyle) +{ + if (m_mainGraphicalObject) m_mainGraphicalObject->setFillStyle(fillStyle); +} + + +QString AbstractMapObjectImpl::layer() const +{ + return logicalLayerName(); +} + +void AbstractMapObjectImpl::setDrawOrder(int drawOrder) +{ + m_drawOrder = drawOrder; + m_internalDrawOrder = m_drawOrder * 10; +} +int AbstractMapObjectImpl::drawOrder() const +{ + return m_drawOrder; +} + + +bool AbstractMapObjectImpl::visibility() const +{ + return m_visibility; +} +void AbstractMapObjectImpl::setVisibility(bool visibility) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo + << " visi old=" << m_visibility; + m_visibility = visibility; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo + << " visi new=" << m_visibility; +} + +bool AbstractMapObjectImpl::visibility(ObjectAppearance appearance) const +{ + bool visi = true; + switch (appearance) + { + case SituationMap::ObjectAppearance::Technical: + visi = m_visibilityTechnical; + break; + case SituationMap::ObjectAppearance::Tactical: + visi = m_visibilityTactical; + break; + } + return visi; +} + +void AbstractMapObjectImpl::setVisibility(ObjectAppearance appearance, bool visibility) +{ + switch (appearance) + { + case SituationMap::ObjectAppearance::Technical: + m_visibilityTechnical = visibility; + break; + case SituationMap::ObjectAppearance::Tactical: + m_visibilityTactical = visibility; + break; + default: + ERROR_LOG(m_logger) << LOGFUNCTION << " ObjectAppearance not found"; + break; + } + TRACE_LOG(m_logger) << LOGFUNCTION + << " debugInfo=" << m_debugInfo + << " m_visibilityTechnical=" << m_visibilityTechnical + << " m_visibilityTactical=" << m_visibilityTactical; +} + +bool AbstractMapObjectImpl::visibilityOfGeometry() const +{ + return m_visibilityTechnicalGeometry; +} + +void AbstractMapObjectImpl::setVisibilityOfGeometry(bool visiGeometry) +{ + m_visibilityTechnicalGeometry = visiGeometry; +} + +bool AbstractMapObjectImpl::selected() const +{ + return m_selected; +} +void AbstractMapObjectImpl::setSelected(bool selected) +{ + m_selected = selected; +} + +bool AbstractMapObjectImpl::pickable() const +{ + return m_pickable; +} + +void AbstractMapObjectImpl::setPickable(bool pickable) +{ + m_pickable = pickable; +} + + +float AbstractMapObjectImpl::scale() const +{ + return (m_mainGraphicalObject) ? m_mainGraphicalObject->scale() : 0.0f; +} + +void AbstractMapObjectImpl::setScale(float scale) +{ + if (!m_enableScale) + return; + + // typically the mainGraphicalObject does NOT need any scale values, + // but keep it similar to identifier, textStyle, fillStyle etc. + // mainGraphicalObjectText will receive the value within ::updateView() + if (m_mainGraphicalObject) m_mainGraphicalObject->setScale(scale); +} + +bool AbstractMapObjectImpl::isScaleEnabled() const +{ + return m_enableScale; +} + +void AbstractMapObjectImpl::setScaleEnable(bool enabled) +{ + m_enableScale = enabled; +} + + +SymbolData AbstractMapObjectImpl::symbol() const +{ + return m_symbol.value(); +} +void AbstractMapObjectImpl::setSymbol(const SymbolData &symbol) +{ + m_symbol = symbol; +} + +void AbstractMapObjectImpl::reRenderSymbol() +{ + m_symbol.setChanged(); +} + +SymbolDataToRender AbstractMapObjectImpl::symbolDataToRender() const +{ + return m_symbol.value().getDefaultRenderData(); +} + +SymbolImage AbstractMapObjectImpl::symbolImage() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return m_symbolImage; +} + +QImage AbstractMapObjectImpl::previewImage() const +{ + return previewImageTypical(); +} + +QImage AbstractMapObjectImpl::previewImageTypical() const +{ + return m_symbolImage.image(); +} + +QImage AbstractMapObjectImpl::previewImageSpecific() const +{ + return m_symbolImage.image(); +} + +TextStyle AbstractMapObjectImpl::defaultTextStyle() +{ + TextStyle ts; // Define default text style for all MapObjects + ts.setTextOutlineColor(Qt::white); // Define default text style for all MapObjects + ts.setTextOutlineWidth(0.5); // Define default text style for all MapObjects + return ts; +} + +bool AbstractMapObjectImpl::updateTacticalAppearance() +{ + bool successTactAppearance = true; + + if (m_tacticalAppearance) + { + m_tacticalAppearance->setUniqueDesignation(identifier()); + + m_tacticalAppearance->setDrawOrder(m_internalDrawOrder + m_tacticalAppearanceDrawOrderOffset); // m_tacticalAppearanceDrawOrderOffset == 2 + m_tacticalAppearance->setVisibility(m_visibility && m_visibilityTactical); + m_tacticalAppearance->setSelected(m_selected); + m_tacticalAppearance->setPickable(m_pickable); + + if (m_symbol.changed()) + m_tacticalAppearance->setSymbol(m_symbol.value()); + + if (m_mainGraphicalObject) + m_tacticalAppearance->setScale(m_mainGraphicalObject->scale()); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " m_mainGraphicalObject missing!!!"; + + successTactAppearance = m_tacticalAppearance->updateView(); + } + return successTactAppearance; +} + +void AbstractMapObjectImpl::createGraphicalObjectExtrusion() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo; + + if (m_mapImpl->mode() != Mode::Mode_3D) + return; + + if (!m_graphicalObjectExtrusion) + { + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = m_mapImpl->getGraphicsLayer3DExtrusion(m_logicalLayerName); + + if (!graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " getGraphicsLayer3DExtrusion failed"; + return; + } + + m_graphicalObjectExtrusion = new GraphicalObjectCube(m_mapImpl->getEsriGeoView(), + graphicsLayer, // ESRI-Layer, not logical + m_debugInfo + QStringLiteral("#GO3D"), + m_uniqueMapId + ); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo; +} + +void AbstractMapObjectImpl::resetGraphicalObjectExtrusion() +{ + delete m_graphicalObjectExtrusion; + m_graphicalObjectExtrusion = nullptr; +} + +bool AbstractMapObjectImpl::updateGraphicalObjectExtrusion() +{ + bool successGo3D = true; + + if (m_graphicalObjectExtrusion) + { + m_graphicalObjectExtrusion->setUniqueMapId(m_uniqueMapId); + + // geometry: + if (m_mainGraphicalObject) + m_graphicalObjectExtrusion->setPositions(m_mainGraphicalObject->positions()); // translated / maybe they are densified? + + m_graphicalObjectExtrusion->setDrawOrder(m_internalDrawOrder); + m_graphicalObjectExtrusion->setVisibility(m_visibility && m_visibilityTechnical); + m_graphicalObjectExtrusion->setSelected(m_selected); + m_graphicalObjectExtrusion->setPickable(m_pickable); + + if (m_extrusionColorName.isEmpty()) + { + // Fallback + const QColor &partyColor = m_mapImpl->partyIdToColorFunctor().partyIdToColor(m_party.partyId.value()); + m_graphicalObjectExtrusion->setExtrusionColorName(partyColor.name()); + } else { + m_graphicalObjectExtrusion->setExtrusionColorName(m_extrusionColorName); + } + m_graphicalObjectExtrusion->setHeight(m_extrusionHeight); + + // scale....?? + + successGo3D = m_graphicalObjectExtrusion->updateView(); + } + + return successGo3D; +} + +bool AbstractMapObjectImpl::updatePin() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, m_debugInfo=" << m_debugInfo; + + if (m_graphicalObjectPin) + { + m_graphicalObjectPin->setDrawOrder(m_internalDrawOrder + 5); // m_tacticalAppearanceDrawOrderOffset == 2 + m_graphicalObjectPin->setVisibility(m_visibility); + m_graphicalObjectPin->setSelected(m_selected); + m_graphicalObjectPin->setPickable(m_pickable); + + if (m_mainGraphicalObject) + { + m_graphicalObjectPin->setLineStyle(LineStyle(Qt::black, 3, LinePattern::DashDotDot)); + m_graphicalObjectPin->setScale(m_mainGraphicalObject->scale()); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " m_mainGraphicalObject missing!!!"; + + // + // m_graphicalObjectPin->setPositions and updateView is called at GeoImageImpl::onMapExtentChanged() + // + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, m_debugInfo=" << m_debugInfo; + return true; +} + +void AbstractMapObjectImpl::updateIdentifierPosition() +{ + m_identifierPositionCandidateTranslated = m_mainGraphicalObject->origin(); +} + +bool AbstractMapObjectImpl::updateViewImplPre() +{ + // + // createTacticalAppearance onDemand + // + if (!m_symbol.value().sidc.isEmpty()) + createTacticalAppearance(); + + // + // partyId (and its resulting color) has priority to lineColor, fillColor: + // + if (isPartyIdValid() && partyChanged()) + { + const QColor &partyCol = partyColor(); + + if (m_mainGraphicalObject) + { + if (m_lineColorFromParty) + m_mainGraphicalObject->rLineStyle().setLineColor(partyCol); + QColor fillColor(partyCol); + fillColor.setAlpha(m_fillColorAlpha); + m_mainGraphicalObject->rFillStyle().setFillColor(fillColor); + } + + if (m_mainGraphicalObjectText) + m_mainGraphicalObjectText->rTextStyle().setTextColor(partyCol); + + // maybe continued for TacticalAppearance + + } + + // + // if state!=Unknown -> its resulting pattern has priority to fillPattern: + // + if ((m_aesState.value() != AesEffectState::Unknown) && m_aesState.changed()) + { + if (m_mainGraphicalObject) + { + FillPattern pattern = m_mapImpl->partyIdToColorFunctor().aesStateToPattern(m_aesState.value()); + m_mainGraphicalObject->rFillStyle().setFillPattern(pattern); + } + } + + + return true; +} + +inline bool isSurfacePlacementRelevant(Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer) +{ + if (!graphicsLayer) + return false; + + // maybe also SurfacePlacement::RelativeToScene ??? + return (graphicsLayer->sceneProperties().surfacePlacement() == Esri::ArcGISRuntime::SurfacePlacement::Relative); +} + + +bool AbstractMapObjectImpl::updateViewImpl() +{ + bool success = true; + bool successTactAppearance = true; + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo =" << m_debugInfo + << " uniqueMapId =" << m_uniqueMapId + << " m_pickable=" << m_pickable; + + // drawOrder : + + // The LayerOrder could be added if required like... + // finalDrawOrder = m_mapImpl->layerFactory()-> indexOf(m_logicalLayerName); + + if (m_mainGraphicalObjectText) + { + m_mainGraphicalObjectText->setDrawOrder(m_internalDrawOrder + 2); // +2 to provide space for indicationCont (e.g. mines of MineField) + m_mainGraphicalObjectText->setScale(m_mainGraphicalObject->scale()); + } + + // : drawOrder + + if (m_mainGraphicalObject) + { + m_mainGraphicalObject->setDrawOrder(m_internalDrawOrder); + m_mainGraphicalObject->setVisibility(m_visibility && m_visibilityTechnical && m_visibilityTechnicalGeometry); + m_mainGraphicalObject->setSelected(m_selected); + m_mainGraphicalObject->setPickable(m_pickable); + + success = m_mainGraphicalObject->updateView(); + + if (m_mainGraphicalObjectText) + { + this->updateIdentifierPosition(); + + m_mainGraphicalObjectText->setScale(m_mainGraphicalObject->scale()); + // m_mainGraphicalObjectText->setSelected(m_selected); // Text should NEVER be selected + m_mainGraphicalObjectText->setPickable(m_pickable); + + // mainGraphicalObjectText->updateView() will be called within following 'onMapExtentChanged()': + // successGoText = m_mainGraphicalObjectText->updateView(); + } + } + + successTactAppearance = updateTacticalAppearance(); + + bool successGoExtrusion = true; + if (m_graphicalObjectExtrusion && (m_mapImpl->mode() == Mode::Mode_3D)) + { + successGoExtrusion = updateGraphicalObjectExtrusion(); + if (!successGoExtrusion) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure updateGraphicalObjectExtrusion " << " debugInfo =" << m_debugInfo << " uniqueMapId =" << m_uniqueMapId; + } + + bool successUpdatePin = true; + if (m_graphicalObjectPin && (this->mapImpl()->mode() == Mode::Mode_3D) && isSurfacePlacementRelevant(m_graphicsLayer)) + { + successUpdatePin = updatePin(); + if (!successUpdatePin) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure0 (updatePin) " << " debugInfo =" << m_debugInfo << " uniqueMapId =" << m_uniqueMapId; + } + + bool successMapExtendChanged = this->onMapExtentChanged(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure1 " << " debugInfo =" << m_debugInfo << " uniqueMapId =" << m_uniqueMapId; + if (!successTactAppearance) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure2 " << " debugInfo =" << m_debugInfo << " uniqueMapId =" << m_uniqueMapId; + if (!successGoExtrusion) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure3 " << " debugInfo =" << m_debugInfo << " uniqueMapId =" << m_uniqueMapId; + if (!successMapExtendChanged) + ERROR_LOG(m_logger) << LOGFUNCTION << " failure4 " << " debugInfo =" << m_debugInfo << " uniqueMapId =" << m_uniqueMapId; + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo =" << m_debugInfo + << " uniqueMapId =" << m_uniqueMapId; + return success && successTactAppearance && successGoExtrusion && successUpdatePin && successMapExtendChanged; +} + +bool AbstractMapObjectImpl::updateViewImplPost() +{ + partyReset(); + m_aesState.reset(); + m_symbol.reset(); + m_identiferVisible.reset(); + + if (m_updateObjectIndicator) + { + for (auto *ptr : std::as_const(m_originPositionChangedCb)) + ptr->onOriginPositionChanged(); + } + m_updateObjectIndicator = false; + + return true; +} + +int AbstractMapObjectImpl::numberOfValuesChanged() +{ + int count = 0; + if (m_party.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; ++count; } + if (m_aesState.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; ++count; } + + if (m_symbol.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; ++count; } + + if (m_identiferVisible.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; ++count; } + + return count; +} + +void AbstractMapObjectImpl::addChangedCallback(OriginPositionChangedCallback *cb) +{ + m_originPositionChangedCb.push_back(cb); +} +void AbstractMapObjectImpl::removeChangedCallback(OriginPositionChangedCallback *cb) +{ + m_originPositionChangedCb.removeAll(cb); +} + + +bool AbstractMapObjectImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo; + bool success = true; + + if (mapImpl) + m_mapImpl = mapImpl; + else + { + success = false; + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo=" << m_debugInfo + << " no mapImpl"; + } + + m_mapConnector = new SituationMap::MapConnector(); + success = success && m_mapConnector->connectMapObject(m_mapImpl, this); + if (success) + { + success = this->setLogicalLayerName(logicalLayerName); + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo=" << m_debugInfo + << " setLogicalLayerName failed" + << ", logicalLayerName='" << logicalLayerName << "'"; + } + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo=" << m_debugInfo + << " MapConnector->connectObject failed"; + } + + // take static scaleHasVisibilityValue; + m_visibilityScaleRange.setInitial(s_scaleHasVisibilityValue); // static mamber of interface class. + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo + << " success=" << success; + return success; +} + +void AbstractMapObjectImpl::setUniqueId(const UniqueMapId &uniqueId) +{ + m_uniqueMapId = uniqueId; + + if (m_mainGraphicalObject) + m_mainGraphicalObject->setUniqueMapId(m_uniqueMapId); + + if (m_mainGraphicalObjectText) + m_mainGraphicalObjectText->setUniqueMapId(m_uniqueMapId); + + if (m_tacticalAppearance) + m_tacticalAppearance->setUniqueMapId(m_uniqueMapId); +} + +bool AbstractMapObjectImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " debugInfo=" << m_debugInfo; + + bool success = true; + + // Some MapObjects like ParticipantImpl do NOT use m_mainGraphicalObjectText: + if (m_mainGraphicalObjectText && m_mapImpl) + { + // hide identifier on zoom out??? + + // old approach: + // double meterPerPixel = -1.0; + // if (m_mapImpl) + // meterPerPixel = m_mapImpl->pixelSize(); + // bool textVisi = meterPerPixel < 3000; + + // new approach: + // double curScale = m_mapImpl->scale(); + // bool textVisi = m_visibilityScaleRange.scaleHasVisibility(curScale); + // m_mainGraphicalObjectText->setVisibility(m_visibility && m_visibilityTechnical && textVisi); + + // TRACE_LOG(m_logger) << LOGFUNCTION << " START" + // << " debugInfo=" << m_debugInfo << "'" + // << " meterPerPixel=" << m_mapImpl->pixelSize() + // << " newScale=" << m_mapImpl->scale() + // << " newExtent = " << SituationMap::toString(m_mapImpl->extent()); + // m_mainGraphicalObjectText->setVisibility(m_visibility && m_visibilityTechnical && textVisi); + + // do NOT hide identifier on zoom out: + m_mainGraphicalObjectText->setVisibility(m_visibility && m_visibilityTechnical && m_identiferVisible.value()); + + m_mainGraphicalObjectText->setOrigin(m_identifierPositionCandidateTranslated); + + success = m_mainGraphicalObjectText->updateView(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo << "'" + << " success=" << success; + return true; +} + + +QString AbstractMapObjectImpl::logicalLayerName() const +{ + return m_logicalLayerName; +} + +bool AbstractMapObjectImpl::setLogicalLayerName(const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << m_debugInfo << "'" + << " logicalLayerName='" << logicalLayerName << "'"; + + m_logicalLayerName = logicalLayerName; + + bool success = false; + + m_graphicsLayer = m_mapImpl->getGraphicsLayer(logicalLayerName); + + if (m_graphicsLayer) + success = true; + else + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo=" << m_debugInfo << "'" + << ", no layer found"; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << m_debugInfo << "'" + << " logicalLayerName='" << logicalLayerName << "'" + << " success=" << success; + return success; +} + + +bool AbstractMapObjectImpl::geometryIntersect(const MathUtils::GeometryEngine::Geometry &intersector_polyline) const +{ + if (intersector_polyline.isEmpty()) + return false; + + if (m_mainGraphicalObject) + { + Esri::ArcGISRuntime::GraphicsOverlay *esriLayer = m_mainGraphicalObject->graphicsLayer(); + if ((esriLayer) && (esriLayer->isVisible())) + { + // former: if (m_mainGraphicalObject->visibility()) + // MineField stores the ountline/boundary in m_mainGraphicalObject to transfer this to m_tacticalAppearance + // That m_mainGraphicalObject is NOT visible, because of that fillPattern. + // to 'find' that MineField, anaylze just m_visibility && m_visibilityTechnical, and not m_visibilityTechnicalGeometry + if (m_visibility && m_visibilityTechnical) + { + if (MathUtils::GeometryEngine::intersect(m_mainGraphicalObject->getGraphicGeometry(), intersector_polyline)) + return true; + } + } + } + // m_mainGraphicalObjectText + if (m_tacticalAppearance) + { + Esri::ArcGISRuntime::GraphicsOverlay *esriLayer = m_tacticalAppearance->graphicsLayer(); + if ((esriLayer) && (esriLayer->isVisible())) + { + if (m_tacticalAppearance->visibility()) + { + if (MathUtils::GeometryEngine::intersect(m_tacticalAppearance->getGraphicGeometry(), intersector_polyline)) + return true; + } + } + } + return false; +} // bool AbstractMapObjectImpl::geometryIntersect( const MathUtils::GeometryEngine::Geometry & geom ) + + +bool AbstractMapObjectImpl::geometryIsInHull(const MathUtils::GeometryEngine::Geometry &hull_polygon) const +{ + if (hull_polygon.isEmpty()) + return false; + + if (m_mainGraphicalObject) + { + Esri::ArcGISRuntime::GraphicsOverlay *esriLayer = m_mainGraphicalObject->graphicsLayer(); + if ((esriLayer) && (esriLayer->isVisible())) + { + // former: if (m_mainGraphicalObject->visibility()) + // MineField stores the ountline/boundary in m_mainGraphicalObject to transfer this to m_tacticalAppearance + // That m_mainGraphicalObject is NOT visible, because of that fillPattern. + // to 'find' that MineField, anaylze just m_visibility && m_visibilityTechnical, and not m_visibilityTechnicalGeometry + if (m_visibility && m_visibilityTechnical) + { + if (MathUtils::GeometryEngine::contains(m_mainGraphicalObject->getGraphicGeometry(), hull_polygon)) + return true; + } + } + } + // m_mainGraphicalObjectText + if (m_tacticalAppearance) + { + Esri::ArcGISRuntime::GraphicsOverlay *esriLayer = m_tacticalAppearance->graphicsLayer(); + if ((esriLayer) && (esriLayer->isVisible())) + { + if (m_tacticalAppearance->visibility()) + { + if (MathUtils::GeometryEngine::contains(m_tacticalAppearance->getGraphicGeometry(), hull_polygon)) + return true; + } + } + } + return false; +} + +Esri::ArcGISRuntime::Geometry AbstractMapObjectImpl::getEsriGeometry() const +{ + if (!m_mainGraphicalObject) + return Esri::ArcGISRuntime::Geometry(); + return m_mainGraphicalObject->getGraphicGeometry(); +} + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.h new file mode 100755 index 000000000..2310e940c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/abstract_map_object_impl.h @@ -0,0 +1,443 @@ +/** + * @RDE_HEADER@ + * Implementation of AbstractMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include // for AesEffectState +#include +#include + +#include +#include +#include + + +// Qt +#include +#include + + +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Graphic; +class GraphicsOverlay; +class Geometry; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +struct ExtraData; +class MapImpl; +class MapConnector; +class AbstractGraphicalObject; +class GraphicalObjectText; +class GraphicalObjectPath; +class TacticalAppearance; +class GraphicalObjectCube; + + +//---------------------------------------------------------------------------- +class OriginPositionChangedCallback +{ +public: + virtual ~OriginPositionChangedCallback() = default; + virtual void onOriginPositionChanged() = 0; +}; +//---------------------------------------------------------------------------- + + + + + +/** + * @brief The AbstractMapObjectImpl class is the main base class of all MapObject instances. + * Therefore it supports typical parameters/functionality and avoids redundant code. + * As consequence AbstractMapObjectImpl supports functionality, which is not used in every subclass. + * The typical parameters/functionality is: + * - UniqueMapId + * - debugInfo + * - partyId, partyColor + * - aesState (aes = AreaEffectSimulation) + * - drawOrder + * - (tact.) symbol + * - visibility (major|techn|tact.) + * - pickability + * - selected + + * - mainGraphicalObject (displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + * - m_graphicalObjectExtrusion (displaying the extrusion (3D)) + * - mainGraphicalObjectPin (displaying a pin/needle to the ground) + * + * After an parameter changes, the method updateView() hat to be called. + */ +// inherent 'public virtual' to get that interface +class SITUATIONMAP_EXPORT AbstractMapObjectImpl : public virtual AbstractMapObject +{ +public: + explicit AbstractMapObjectImpl(const QString &debugInfo = QString()); + virtual ~AbstractMapObjectImpl(); + + void removeMySelf() override; + + AbstractMapObjectImpl(const AbstractMapObjectImpl &other) = delete; + AbstractMapObjectImpl &operator=(AbstractMapObjectImpl &other) = delete; + + + // emulate QObject::property : + bool setProperty(const char *name, const QVariant &value) override; + QVariant property(const char *name) const override; + // : emulate QObject::property + + + UniqueMapId uniqueMapId() const override; + + QString debugInfo() const override; + void setDebugInfo(const QString &debugInfo) override; + + QString toString() const override; + + // Data access: + void boundingBox(BoundingBox &bbox) const override; + QColor majorColor() const override; + + + QString identifier() const override; + void setIdentifier(const QString &identifier) override; + + bool identifierVisible() const override; + void setIdentifierVisible(bool identifierVisible) override; + + + int partyId() const override; + bool isPartyIdValid() const override; + void setPartyId(int partyId) override; + bool setPartyIdChanged(int partyId = -1) override; + void forcePartyColor(const QColor &partyColorToForce) override; + + bool useLightColor() const override; ///< Only for Participants + void setUseLightColor(bool useLightColor) override; ///< Only for Participants + + virtual AesEffectState aesState() const; + virtual void setAesState(AesEffectState aesState); + + + TextStyle textStyle() const override; + TextStyle &rTextStyle() override; + void setTextStyle(const TextStyle &textStyle) override; + + virtual LineStyle lineStyle() const; + virtual LineStyle &rLineStyle(); + virtual void setLineStyle(const LineStyle &lineStyle); + + virtual FillStyle fillStyle() const; + virtual FillStyle &rFillStyle(); + virtual void setFillStyle(const FillStyle &fillStyle); + + + // Scale : + float scale() const override; + void setScale(float scale) override; + + bool isScaleEnabled() const override; + void setScaleEnable(bool enabled) override; + // : Scale + + + // Layer + QString layer() const override; + + int drawOrder() const override; + /** + * @brief Set drawOrder. + * Caused by internal dependencies, the value MUST within -3000...+3000. (INT_MAX=+32767 / 10 = 3276 -> ~3000). + * \n Otherwise the internal graphics will be disordered. + * \internal Background: Due to missing relative drawOrder an absolute drawOrder has to be used.\n + * To create an absolute drawOrder the specified drawOrder is multiplied with 10. + * The components (usually GraphicalObjects) receive that multiplied valued added by s small offset. + * drawOrder() is not influenced by this and will return the value you set before. + * @param drawOrder new value. + */ + void setDrawOrder(int drawOrder) override; + + + + // Visibility : + // Prio 1: + bool visibility() const override; + void setVisibility(bool visibility) override; + + // Prio 2 + bool visibility(ObjectAppearance appearance) const override; + void setVisibility(ObjectAppearance appearance, bool visibility) override; + + bool visibilityOfGeometry() const; + void setVisibilityOfGeometry(bool visiGeometry); + + // : Visibility + + + // Selection, Picking : + bool selected() const override; + void setSelected(bool selected) override; + + bool pickable() const override; + void setPickable(bool pickable) override; + + // : Selection, Picking + + + // Symbol : + SymbolData symbol() const override; + void setSymbol(const SymbolData &symbol) override; + void reRenderSymbol() override; + SymbolDataToRender symbolDataToRender() const override; + SymbolImage symbolImage() const override; + + QImage previewImage() const override; + QImage previewImageTypical() const override; + QImage previewImageSpecific() const override; + + void setForceMajorColor(const QColor &majorColor) override; + + // : Symbol + + + /*! \name Some methods regarding extrusion (renderer approach) */ + //@{ + double extrusionHeight() const override; + void setExtrusionHeight(double extrusionHeight = std::nan("")) override; + bool extrusionHeightValid() const; + + const QString extrusionColorName() const override; + void setExtrusionColorName(const QString &extrusionColorName) override; + //@} + + // Geometry : + bool geometryIntersect(const MathUtils::GeometryEngine::Geometry &intersector_polyline) const override; + bool geometryIsInHull(const MathUtils::GeometryEngine::Geometry &hull_polygon) const override; + + Esri::ArcGISRuntime::Geometry getEsriGeometry() const; + // : Geometry + + + // Define default text style for all MapObjects: + static TextStyle defaultTextStyle(); + + + /** + * @brief updateView + * transfer values to view + * @return true on success + */ + bool updateView() override + { + return updateViewImplPre() && updateViewImpl() && updateViewImplPost(); + } + + + virtual MapImpl *mapImpl() { return m_mapImpl; } + + + int numberOfValuesChanged(); ///< For Unit/Module Test + + + +protected: + virtual bool updateViewImplPre(); + virtual bool updateViewImpl() = 0; + virtual bool updateViewImplPost(); + + + friend class ObjectFactory; + + // deriving classes MUST overload init and create an GraphicalObject!!! + // (to use whose identifier, textStyle) + virtual bool init(MapImpl *mapImpl, const QString &logicalLayerName = QStringLiteral("GraphicsLayer")); + virtual void setUniqueId(const UniqueMapId &uniqueId); + + QString logicalLayerName() const; + bool setLogicalLayerName(const QString &logicalLayerName); + + // internal/admin + void createTacticalAppearance(); + void resetTacticalAppearanceDrawOrderOffset(); + virtual bool updateTacticalAppearance(); + + // Extrusion + void createGraphicalObjectExtrusion(); + void resetGraphicalObjectExtrusion(); + bool updateGraphicalObjectExtrusion(); + + // pin/needle + void createPin(); + void resetPin(); + bool updatePin(); + + virtual void updateIdentifierPosition(); + + friend class MapConnector; + virtual bool onMapExtentChanged(); + MapConnector *m_mapConnector = nullptr; + + class VisibilityScale + { + public: + VisibilityScale() + { + resetToInitial(); + } + + void resetToInitial() + { + scale_zoomOutCurrent = scale_zoomOutInitialized; + } + + void resetToMax() + { + scale_zoomOutCurrent = std::numeric_limits::max(); + } + + void setInitial(double newValue) + { + scale_zoomOutInitialized = newValue; + resetToInitial(); + } + + bool scaleHasVisibility(double curScale) const + { + if (scale_zoomOutCurrent <= 0) + return true; + return (curScale < scale_zoomOutCurrent); + } + private: + double scale_zoomOutCurrent = 0; + double scale_zoomOutInitialized = 70000; + }; + VisibilityScale m_visibilityScaleRange; // static value 's_scaleHasVisibilityValue' is located in the interface + + + UniqueMapId m_uniqueMapId = SituationMap::INVALID_MAP_ID; ///< for identification + + QString m_debugInfo; ///< for debugging + + MapImpl *m_mapImpl = nullptr; + QString m_logicalLayerName; + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + // party : + struct party + { + SimpleValue partyId = -1; + SimpleValue useLightColor = false; // useful for Participants + + mutable QColor partyColor; + + SimpleValue forcedPartyColor; + bool changed() const { return partyId.changed() || useLightColor.changed() || forcedPartyColor.changed(); } + void reset() { partyId.reset(); useLightColor.reset(); forcedPartyColor.reset(); } + }; + party m_party; + + bool partyChanged() const { return m_party.changed(); } + void partyReset() { m_party.reset(); } + virtual const QColor &partyColor() const; + virtual QColor partyColor(int partyId, bool useLightColor) const; + virtual AdvancedSymbology::Role partyRole(bool roleIdentityAssumed = false) const; + + // : party + + // state : + SimpleValue m_aesState{AesEffectState::Unknown}; + // : state + +private: + int m_drawOrder{0}; // logical drawOrder +protected: + int m_internalDrawOrder{0}; + + // m_mainGraphicalObject will store LineStyle (color/width/pattern), FillStyle (color/pattern): + AbstractGraphicalObject *m_mainGraphicalObject{nullptr}; + bool m_enableScale{true}; + + // m_mainGraphicalText will store TextStyle (color/width/size...) + GraphicalObjectText *m_mainGraphicalObjectText{nullptr}; + Point3D m_identifierPositionCandidateTranslated; + SimpleValue m_identiferVisible{true}; + + TacticalAppearance *m_tacticalAppearance{nullptr}; + int m_tacticalAppearanceDrawOrderOffset{2}; + + // for 3D Extrusion: + GraphicalObjectCube *m_graphicalObjectExtrusion{nullptr}; + double m_extrusionHeight{std::nan("")}; + QString m_extrusionColorName; + + // For Pin + GraphicalObjectPath *m_graphicalObjectPin{nullptr}; + double m_defaultZValue{0}; + + SimpleValue m_symbol; + SymbolImage m_symbolImage; + + QColor m_forcedMajorColor; + + // Update ObjectIndicator : +protected: + bool m_updateObjectIndicator{false}; + QVector m_originPositionChangedCb; +public: + void addChangedCallback(OriginPositionChangedCallback *cb); + void removeChangedCallback(OriginPositionChangedCallback *cb); + // : Update ObjectIndicator + + +protected: + + bool m_visibility = true; + bool m_visibilityTechnical = true; + bool m_visibilityTechnicalGeometry = true; // Special case + bool m_visibilityTactical = true; + + bool m_selected = false; + bool m_pickable = true; + + bool m_editorMode = false; + bool m_lineColorFromParty = true; + int m_fillColorAlpha = 255; + +private: + ExtraData *m_extraData = nullptr; + + void createGraphicalObjectText(); + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + +QString toString(const AesEffectState value); + +std::ostream &operator<< (std::ostream &stream, + const AesEffectState &value); + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coord_translation_functor_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coord_translation_functor_impl.h new file mode 100755 index 000000000..ff0c644e0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coord_translation_functor_impl.h @@ -0,0 +1,207 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Map + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +// Own +#include +#include +#include "map_impl.h" + + +// ESRI +#include +#include +//#include + + +// Qt +#include +#include +#include + +#include + + +namespace SituationMap +{ + + +class CoordTranslationFunctorImpl final : public CoordTranslationFunctor +{ +public: + + explicit CoordTranslationFunctorImpl(const MapImpl *mapImpl) + : m_mapImpl(mapImpl) + , m_logger(getLogger("CoordTranslationFunctorImpl")) + {} + + virtual ~CoordTranslationFunctorImpl() = default; + + QPointF geographicToMap(const QPointF &coord) const override + { + return geographicToMap(coord.x(), coord.y()); + } + + QPointF mapToGeographic(const QPointF &coord) const override + { + return mapToGeographic(coord.x(), coord.y()); + } + + QPointF geographicToMap(const Esri::ArcGISRuntime::Point &coord) const override + { + return geographicToMap(coord.x(), coord.y()); + } + + QPointF mapToGeographic(const Esri::ArcGISRuntime::Point &coord) const override + { + return mapToGeographic(coord.x(), coord.y()); + } + + QPointF geographicToMap(const double lon, const double lat) const override + { + return convert(lon, lat, false); + } + + QPointF mapToGeographic(const double x, const double y) const override + { + return convert(x, y, true); + } + + + Point3D geographicToMap(const Point3D &coord) const override + { + const QPointF &p = geographicToMap(coord.x(), coord.y()); + return Point3D(p.x(), p.y(), coord.z()); + } + + Point3D mapToGeographic(const Point3D &coord) const override + { + const QPointF &p = mapToGeographic(coord.x(), coord.y()); + return Point3D(p.x(), p.y(), coord.z()); + } + + + bool mapIsGeographic() const override + { + return (m_mapImpl->getEsriSpatialReference().isGeographic()); + } // bool mapIsGeographic() const override + + + void geographicToUtm(const double referenceLon, QList &points) const override + { + int wkid(buildWkid(referenceLon)); + const Esri::ArcGISRuntime::SpatialReference utmSR(wkid); + + QMutexLocker lock(&m_mutex); + + for(QList::iterator it = points.begin(); it != points.end(); ++it) + { + QPointF &pt = *it; + const Esri::ArcGISRuntime::Point utmPoint( Esri::ArcGISRuntime::GeometryEngine::project( Esri::ArcGISRuntime::Point(pt.x(), pt.y(), 0.0, m_wkSR), utmSR )); + + pt.setX(utmPoint.x()); + pt.setY(utmPoint.y()); + } + } // void geographicToUtm(const double referenceLon, QList &points) const override + + + void utmToGeographic(const double referenceLon, QList &points) const override + { + int wkid(buildWkid(referenceLon)); + const Esri::ArcGISRuntime::SpatialReference utmSR(wkid); + + QMutexLocker lock(&m_mutex); + + for (QList::iterator it = points.begin(); it != points.end(); ++it) + { + QPointF &pt = *it; + const Esri::ArcGISRuntime::Point geoPoint(Esri::ArcGISRuntime::GeometryEngine::project(Esri::ArcGISRuntime::Point(pt.x(), pt.y(), 0.0, utmSR), m_wkSR)); + + pt.setX(geoPoint.x()); + pt.setY(geoPoint.y()); + } + } // void utmToGeographic(const double referenceLon, QList &points) const override + + + int projectionCounter() const override { return m_projectionCounter; } + void resetProjectionCounter() override { m_projectionCounter = 0; } + + +private: + + static int buildWkid(const double referenceLon) + { + int utmZone = referenceLon / 6; + if (utmZone < 0) utmZone = 60 + utmZone; + utmZone += 31; + if (utmZone > 60) utmZone -= 60; + return 32600 + utmZone; + } // static int buildWkid(const double referenceLon) + + + QPointF convert(const double x, const double y, bool toGeographic) const + { + // if x,y are geographic (x=lon/y=lat), use radian + const char *directionString = toGeographic ? "mapToGeographic" : "geographicToMap"; + if (!m_mapImpl) + { + ERROR_LOG(m_logger) << "Error converting " << directionString + << " - no map!"; + return QPointF(); + } + const CoordTranslationFunctor *ctF = m_mapImpl->coordTranslationFunctor(); + if (!ctF) + { + ERROR_LOG(m_logger) << "Error converting " << directionString + << " - no coordinate translation function!"; + return QPointF(); + } + + const Esri::ArcGISRuntime::SpatialReference sourceSR = toGeographic ? m_mapImpl->getEsriSpatialReference() : m_wkSR; + const Esri::ArcGISRuntime::SpatialReference destSR = toGeographic ? m_wkSR : m_mapImpl->getEsriSpatialReference(); + if (sourceSR.isEmpty() || destSR.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " SpatialReference empty" + << " sourceSR.isEmpty()=" << sourceSR.isEmpty() + << " destSR.isEmpty()=" << destSR.isEmpty(); + return QPointF(); + } + // toGeographic: deg -> rad + // !toGeographic: rad -> deg (== from geographic) + const double factorSrc = toGeographic ? 1 : 180 / M_PI; + const double factorDst = toGeographic ? M_PI / 180 : 1; + QMutexLocker lock(&m_mutex); +// const double z = 12.34567; + const Esri::ArcGISRuntime::Point destPoint(Esri::ArcGISRuntime::GeometryEngine::project(Esri::ArcGISRuntime::Point(x * factorSrc, + y * factorSrc, +// z, + sourceSR), + destSR)); + +// qCritical() << " zOut= " << destPoint.z(); + + const QPointF ret(destPoint.x() * factorDst, destPoint.y() * factorDst); + ++m_projectionCounter; + return ret; + } + + const MapImpl *m_mapImpl = nullptr; + + const Esri::ArcGISRuntime::SpatialReference m_wkSR = Esri::ArcGISRuntime::SpatialReference(4326); // EPSG:4236 = WGS84 + + mutable QRecursiveMutex m_mutex; + + mutable int m_projectionCounter = 0; // hmmm - really needed? + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.cpp new file mode 100755 index 000000000..084d99fb0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.cpp @@ -0,0 +1,358 @@ +/** + * @RDE_HEADER@ + * CoordinateTranslation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "coordinate_translation.h" +#include +#include +#include +#include + + +// ESRI +#include +#include + + +// Qt +#include + + +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +void TranslatedBase::setCoordTransFunctor(CoordTranslationFunctor *functor) +{ + m_coordTransFunctor = functor; +} + +void TranslatedBase::setMapObjectImpl(AbstractMapObjectImpl *mapObjectImpl) +{ + m_mapObjectImpl = mapObjectImpl; +} + + + + +OriginTranslated::OriginTranslated() +#ifdef WITH_LOGGING + : m_logger(getLogger("OriginTranslated")) +#endif +{} + +Point3D OriginTranslated::origin() const +{ + return m_origin; +} + +bool OriginTranslated::setOrigin(const Point3D &origin) +{ + bool changed = m_origin != origin; + if (changed) + { + m_origin = origin; + m_translated = false; + } + return changed; +} + + +const Point3D &OriginTranslated::originTranslated() const +{ +#ifdef WITH_LOGGING + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_origin=" << m_origin + ; +#endif + + if (!m_translated) + { + // m_coordTransFunctor has priority, otherwise use functor of mapImpl: + + if (!m_coordTransFunctor) + { + if (m_mapObjectImpl && m_mapObjectImpl->mapImpl()) + { + // if useful, a MapObjectType specific functor can be prefered here: + // like: if (m_mapObjectImpl->coordTranslationFunctor()) ctFunctor=m_mapObjectImpl->coordTranslationFunctor(); + m_coordTransFunctor = m_mapObjectImpl->mapImpl()->coordTranslationFunctor(); + } + } + + if (m_coordTransFunctor) + m_originTranslated = m_coordTransFunctor->geographicToMap(m_origin); + else + m_originTranslated = Point3D(); + m_translated = true; + } + +#ifdef WITH_LOGGING + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_originTranslated=" << m_originTranslated + ; +#endif + return m_originTranslated; +} + + +PositionsTranslated::PositionsTranslated() +#ifdef WITH_LOGGING + : m_logger(getLogger("PositionsTranslated")) +#endif +{} + +PositionCont PositionsTranslated::positions() const +{ + QMutexLocker lock(&m_mutex); + return m_positions + m_singlePositions; +} + +bool PositionsTranslated::setPositions(const PositionCont &posCont) +{ + QMutexLocker lock(&m_mutex); + bool changed = m_positions != posCont; + if (changed) + { + m_positions = posCont; + m_translated = false; + } + m_singlePositions.clear(); + return changed; +} + +void PositionsTranslated::addSinglePosition(const Point3D &singlePositions) +{ + QMutexLocker lock(&m_mutex); + m_singlePositions.append(singlePositions); +} + + +bool PositionsTranslated::setPositionsTranslated(const PositionCont &posCont) +{ + QMutexLocker lock(&m_mutex); + + if ((m_translated) && (m_positionsTranslated == posCont)) + return true; + + m_positionsTranslated = posCont; + m_positions.clear(); + + if (!m_coordTransFunctor) + { + if (m_mapObjectImpl && m_mapObjectImpl->mapImpl()) + { + // if useful, a MapObjectType specific functor can be prefered here: + // like: if (m_mapObjectImpl->coordTranslationFunctor()) ctFunctor=m_mapObjectImpl->coordTranslationFunctor(); + m_coordTransFunctor = m_mapObjectImpl->mapImpl()->coordTranslationFunctor(); + } + } + + if (m_coordTransFunctor) + { + m_positions.reserve(m_positionsTranslated.size()); + for (int i = 0; i < m_positionsTranslated.count(); ++i) + m_positions.push_back(m_coordTransFunctor->mapToGeographic(m_positionsTranslated.at(i))); // for + } + m_singlePositions.clear(); + m_translated = true; + return false; +} // PositionsTranslated::setPositionsTranslated( const PositionCont & posCont ) + + +int PositionsTranslated::size() const +{ + QMutexLocker lock(&m_mutex); + return m_positions.size() + m_singlePositions.size(); +} + +bool PositionsTranslated::isEmpty() const +{ + QMutexLocker lock(&m_mutex); + return m_positions.isEmpty() && m_singlePositions.isEmpty(); +} + +void PositionsTranslated::clear() +{ + QMutexLocker lock(&m_mutex); + m_positions.clear(); + m_positionsTranslated.clear(); + m_singlePositions.clear(); + m_translated = false; +} + +Point3D PositionsTranslated::first() +{ + if (isEmpty()) + return Point3D(); + + if (! m_positions.isEmpty()) + return m_positions.first(); + + return m_singlePositions.first(); +} + +Point3D PositionsTranslated::last() +{ + if (isEmpty()) + return Point3D(); + + if (! m_singlePositions.isEmpty()) + return m_singlePositions.last(); + + return m_positions.last(); +} + +const PositionCont &PositionsTranslated::positionsTranslated() const +{ +#ifdef WITH_LOGGING + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_positions=" << m_positions; +#endif + + QMutexLocker lock(&m_mutex); + + // !!! Important: consolidate !!! + consolidate(); + + if (!m_translated) + { + // translate: + m_positionsTranslated = geographicToMap(m_positions); + + m_translated = true; + } + +#ifdef WITH_LOGGING + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_positionsTranslated=" << m_positionsTranslated; +#endif + return m_positionsTranslated; +} + +PositionCont PositionsTranslated::geographicToMap(const PositionCont &positions) const +{ + PositionCont ret; + + // m_coordTransFunctor has priority, otherwise use functor of mapImpl: + if (!m_coordTransFunctor) + { + if (m_mapObjectImpl && m_mapObjectImpl->mapImpl()) + { + // if useful, a MapObjectType specific functor can be prefered here: + // like: if (m_mapObjectImpl->coordTranslationFunctor()) ctFunctor=m_mapObjectImpl->coordTranslationFunctor(); + m_coordTransFunctor = m_mapObjectImpl->mapImpl()->coordTranslationFunctor(); + } + } + + if (!m_coordTransFunctor) + return ret; + + ret.reserve(positions.size()); + for (int i = 0; i < positions.count(); ++i) + { + const Point3D &p = positions.at(i); + ret.push_back(m_coordTransFunctor->geographicToMap(p)); + } // for + return ret; +} + + +const Point3D &PositionsTranslated::startPointTranslated() const +{ + QMutexLocker lock(&m_mutex); + + // make sure that positions are translated: + positionsTranslated(); + + return m_positionsTranslated.first(); +} + +const Point3D &PositionsTranslated::endPointTranslated() const +{ + QMutexLocker lock(&m_mutex); + + // make sure that positions are translated: + positionsTranslated(); + + return m_positionsTranslated.back(); +} + + + +double PositionsTranslated::angleOfStartSegment() const +{ + QMutexLocker lock(&m_mutex); + + positionsTranslated(); + const int size = m_positionsTranslated.size(); + if (size < 2) + return 0; + // rechtwinkliges Koordinatensystem benutzen + return MathUtils::getAngleDegreeNorth(m_positionsTranslated.at(0).toPointF(), m_positionsTranslated.at(1).toPointF()); +} + +double PositionsTranslated::angleOfEndSegment() const +{ + QMutexLocker lock(&m_mutex); + + positionsTranslated(); + const int size = m_positionsTranslated.size(); + if (size < 2) + return 0; + // rechtwinkliges Koordinatensystem benutzen + return MathUtils::getAngleDegreeNorth(m_positionsTranslated.at(size - 2).toPointF(), m_positionsTranslated.at(size - 1).toPointF()); +} + +double PositionsTranslated::angleOfClosingSegment() const +{ + QMutexLocker lock(&m_mutex); + + positionsTranslated(); + const int size = m_positionsTranslated.size(); + if (size < 2) + return 0; + // rechtwinkliges Koordinatensystem benutzen + return MathUtils::getAngleDegreeNorth(m_positionsTranslated.last().toPointF(), m_positionsTranslated.first().toPointF()); +} + + +void PositionsTranslated::consolidate() const +{ + QMutexLocker lock(&m_mutex); + + if (m_translated) + { + // ok, m_positions is already translated to m_positionsTranslated + // translate just the remaining m_singlePositions: + + // To do so + // 1. add the m_singlePositions to m_positions + m_positions += m_singlePositions; + + // 2. translate just the remaining m_singlePositions + // 3. add the now translated singlePositions to + m_positionsTranslated += geographicToMap(m_singlePositions); + + // 4. cleanup m_singlePositions: + m_singlePositions.clear(); + } + else + { + // move m_singlePositions to m_positions + m_positions += m_singlePositions; + m_singlePositions.clear(); + } + +} + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.h new file mode 100755 index 000000000..bd6a9da98 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation.h @@ -0,0 +1,191 @@ +/** + * @RDE_HEADER@ + * CoordinateTranslation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include + +// #define WITH_LOGGING +#ifdef WITH_LOGGING +# include +#endif // WITH_LOGGING + +// ESRI + + +// Qt +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//forward: +class CoordTranslationFunctor; +class AbstractMapObjectImpl; + + +class SITUATIONMAP_EXPORT TranslatedBase +{ +public: + explicit TranslatedBase() = default; + virtual ~TranslatedBase() = default; + + // use ONE of the following methods to set functor: + void setCoordTransFunctor(CoordTranslationFunctor *functor); // set functor direct + void setMapObjectImpl(AbstractMapObjectImpl *mapObjectImpl); // MapObject->Map->functor + + bool isTranslated() const + { + return m_translated; + } + + virtual bool isEmpty() const = 0; + + +protected: + mutable CoordTranslationFunctor *m_coordTransFunctor = nullptr; + AbstractMapObjectImpl *m_mapObjectImpl = nullptr; ///< parent / host + + mutable bool m_translated = false; +}; + + + + +class SITUATIONMAP_EXPORT OriginTranslated final : public TranslatedBase +{ +public: + OriginTranslated(); + + Point3D origin() const; + bool setOrigin(const Point3D &origin); + + const Point3D &originTranslated() const; + + void clear() + { + m_origin = Point3D(); + } + + bool isEmpty() const + { + return m_origin.isNull(); + } + + /** + * @brief operator== Compare two objects + * @param other to be compared with + * @return True if identical, otherwise false. + */ + bool operator==(const OriginTranslated &other) const + { + // m_positionsTranslated must be skipped! + return ((this->m_origin == other.m_origin)); + } + + /** + * @brief operator!= Compare two objects + * @param other to be compared with + * @return True if NOT identical, otherwise false. + */ + bool operator!=(const OriginTranslated &other) const + { + return !(*this == other); + } + + +private: +#ifdef WITH_LOGGING + LOGGER_T m_logger; ///< Interface of the Logging-Facility +#endif + Point3D m_origin; + mutable Point3D m_originTranslated; +}; + + +/** + * @brief The PositionsTranslated class to translate positions of simulation world to map coordinate. + * A the translation is done using the CoordTranslationFunctor. + * (A copy of this object is not allowed since inner mutex can´t be copied.) + */ +class SITUATIONMAP_EXPORT PositionsTranslated : public TranslatedBase +{ +public: + PositionsTranslated(); + // disable copy + PositionsTranslated(const PositionsTranslated &) = delete; + PositionsTranslated &operator=(const PositionsTranslated &) = delete; + + PositionCont positions() const; + bool setPositions(const PositionCont &posCont); + void addSinglePosition(const Point3D &singlePositions); + + bool setPositionsTranslated(const PositionCont &posCont); + + int size() const; + bool isEmpty() const; + void clear(); + Point3D first(); + Point3D last(); + bool isTranslated() const { return m_translated && m_singlePositions.isEmpty(); } + + const PositionCont &positionsTranslated() const; + + + const Point3D &startPointTranslated() const; + const Point3D &endPointTranslated() const; + double angleOfStartSegment() const; + double angleOfEndSegment() const; + + double angleOfClosingSegment() const; ///< Angle between end and start + + + /** + * @brief operator== Compare two objects + * @param other to be compared with + * @return True if identical, otherwise false. + */ + bool operator==(const PositionsTranslated &other) const + { + // m_positionsTranslated must be skipped! + return ((this->m_positions == other.m_positions)); + } + + /** + * @brief operator!= Compare two objects + * @param other to be compared with + * @return True if NOT identical, otherwise false. + */ + bool operator!=(const PositionsTranslated &other) const + { + return !(*this == other); + } + +protected: + void consolidate() const; // method is const + +private: +#ifdef WITH_LOGGING + LOGGER_T m_logger; ///< Interface of the Logging-Facility +#endif + + PositionCont geographicToMap(const PositionCont &positions) const; + + mutable PositionCont m_positions; // mutable to enable consolidate + mutable PositionCont m_singlePositions; // mutable to enable consolidate + mutable PositionCont m_positionsTranslated; // mutable to enable positionsTranslated + + mutable QRecursiveMutex m_mutex; +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.cpp new file mode 100755 index 000000000..ce5fd6329 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.cpp @@ -0,0 +1,82 @@ +/** + * @RDE_HEADER@ + * CoordinateTranslation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "coordinate_translation_thread.h" +#include +#include +#include + + +// ESRI +#include +#include + + +// Qt + +long SituationMap::CoordTranslationThread::m_counter = 0; + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +CoordTranslationThread::CoordTranslationThread(const QString &debugInfo) + : BaseClass() + , m_logger(getLogger("CoordTranslationThread")) + , m_debugInfo(debugInfo) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + " counter=" << m_counter; + ++m_counter; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " counter=" << m_counter; +} + +CoordTranslationThread::~CoordTranslationThread() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + " counter=" << m_counter; + --m_counter; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " counter=" << m_counter; +} + +void CoordTranslationThread::run() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" + << " userData=" << m_userData.toInt() + << " counter=" << m_counter + << " threadId: " << QThread::currentThreadId() ; + + if (m_positions) + m_positions->positionsTranslated(); + + emit resultReady(m_userData); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" + << " userData=" << m_userData.toInt() + << " counter=" << m_counter + << " threadId: " << QThread::currentThreadId(); +} + +QVariant CoordTranslationThread::userData() const +{ + return m_userData; +} + +void CoordTranslationThread::setUserData(const QVariant &userData) +{ + m_userData = userData; +} + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.h new file mode 100755 index 000000000..03214d06c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/coordinate_translation_thread.h @@ -0,0 +1,79 @@ +/** + * @RDE_HEADER@ + * CoordinateTranslation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "coordinate_translation.h" +#include +#include + + +// Qt +#include +#include +#include + + +// ESRI + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + + + + +class SITUATIONMAP_EXPORT CoordTranslationThread : public QThread +{ + Q_OBJECT + + using BaseClass = QThread; + +public: + explicit CoordTranslationThread(const QString &debugInfo); + virtual ~CoordTranslationThread(); + + void setPositions(PositionsTranslated *positions) + { + m_positions = positions; + } + + PositionsTranslated *positions() const + { + return m_positions; + } + + + QVariant userData() const; + void setUserData(const QVariant &userData); + +signals: + void resultReady(const QVariant &userData); + +protected: + void run() override; + + +private : + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + PositionsTranslated *m_positions = nullptr; + + QString m_debugInfo; + QVariant m_userData; + + static long m_counter; + +}; + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.cpp new file mode 100755 index 000000000..1ca93f81a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.cpp @@ -0,0 +1,1197 @@ +/** + * @RDE_HEADER@ + * PlanModel - Renderer + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +#include "geometry_model_renderer_esri.h" + +#include + +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace SituationMap +{ + + GeometryModelRendererEsri::GeometryModelRendererEsri(Map *map, const QString &layerName, GeometryModel &geometryModel, QObject *parent) + : GeometryModelRenderer(map, layerName, geometryModel, parent) + , m_editorMode(false) + , m_delayedRenderTimer(new QTimer(this)) + , m_delayedRenderIds() + { + m_delayedRenderTimer->setInterval(10); + m_delayedRenderTimer->setSingleShot(true); + + connect(m_delayedRenderTimer, &QTimer::timeout, this, &GeometryModelRendererEsri::onDelayedRenderNodes); + + connect(&geometryModel, &GeometryModel::nodeCleanup , this, &GeometryModelRendererEsri::onCleanupMapObjects); + connect(&geometryModel, &GeometryModel::userDataCleanup, this, &GeometryModelRendererEsri::onRemoveMapObject); + connect(&geometryModel, &GeometryModel::changed , this, &GeometryModelRendererEsri::onUpdate); + + } // GeometryModelRendererEsri::GeometryModelRendererEsri + + + bool GeometryModelRendererEsri::editorMode() const { return m_editorMode; } + void GeometryModelRendererEsri::setEditorMode(bool on) { m_editorMode = on; } + + + class CleanUpMapObjectsVisitor : public GeometryNodeVisitor + { + SituationMap::Map *m_map; + + public: + + CleanUpMapObjectsVisitor(SituationMap::Map *map, SituationMap::GeometryModel *geoModel) : GeometryNodeVisitor(geoModel), m_map(map) { ; } + virtual ~CleanUpMapObjectsVisitor() = default; + + bool apply(SituationMap::GeometryModel *geoModel, const SituationMap::GeometryModel::Node &node, SituationMap::GeometryModel::PrimitiveType primitiveType) override + { + switch (primitiveType) + { + case GeometryModel::PrimitiveType::RootNode: + case GeometryModel::PrimitiveType::Node: + break; + + default: + { + if (geoModel->hasUserData(node)) + { + bool ok = false; + int uid = geoModel->getUserData(node, &ok); + + if (ok) + { + SituationMap::AbstractMapObject *mapObj = m_map->objectFactory().idToMapObject(uid); + if (mapObj) + mapObj->removeMySelf(); + } + } + } break; + } + return true; + } // bool apply(SituationMap::GeometryModel * geoModel, const SituationMap::GeometryModel::Node & node, SituationMap::GeometryModel::PrimitiveType primitiveType ) + + }; // CleanUpMapObjectsVisitor + + + void GeometryModelRendererEsri::onCleanupMapObjects(const SituationMap::GeometryModel::Node &node) + { + if (m_map) + { + GeometryModel::Node p = node; + if (!p.isValid()) + { + p = m_geometryModel.getRootNode(); + } + CleanUpMapObjectsVisitor visitor(m_map, &m_geometryModel); + + visitor.traverse(p); + } + } // GeometryModelRendererEsri::onCleanupMapObjects + + + void GeometryModelRendererEsri::onRemoveMapObject(int uid) + { + if (m_map) + { + SituationMap::AbstractMapObject *mapObj = m_map->objectFactory().idToMapObject(uid); + + // crash see comment in abstract_map_object_impl.cpp 2016/01/08 JT + // SituationMap::RefPointer mapObj( m_map->objectFactory().idToMapObject( uid ) ); + + if (mapObj != nullptr) + { + mapObj->removeMySelf(); + } + } + } // GeometryModelRendererEsri::onRemoveMapObject + + + void GeometryModelRendererEsri::onUpdate(const SituationMap::GeometryModel::Node &node, SituationMap::GeometryModel::Attribute /*att*/, bool /*coordsChanged*/) + { + if (m_geometryModel.hasUserData(node)) + { + SituationMap::AbstractMapObject *mapObj = nullptr; + bool ok = false; + int uid = m_geometryModel.getUserData(node, &ok); + + if (ok) + mapObj = m_map->objectFactory().idToMapObject(uid); + + if (mapObj) + { + // render node later + if (!m_delayedRenderIds.contains(uid)) + m_delayedRenderIds += uid; + m_delayedRenderTimer->start(); + + return; + } + } + renderNode(node); + } // GeometryModelRendererEsri::onUpdate(const SituationMap::GeometryModel::Node & node, SituationMap::GeometryModel::Attribute att, bool coordsChanged) + + + void GeometryModelRendererEsri::onDelayedRenderNodes() + { + for (auto uid : std::as_const(m_delayedRenderIds)) + { + SituationMap::AbstractMapObject *mapObj = m_map->objectFactory().idToMapObject(uid); + renderNode(getAssociatedNode(mapObj)); + } + m_delayedRenderIds.clear(); + } // GeometryModelRendererEsri::onDelayedRenderNodes() + + + bool GeometryModelRendererEsri::render(QPixmap &drawable) + { + Q_UNUSED(drawable); + return false; + } // GeometryModelRendererEsri::render( QPixmap & drawable ) + + + bool GeometryModelRendererEsri::render() { return renderNode(m_geometryModel.getRootNode()); } + + + bool GeometryModelRendererEsri::renderNode(const GeometryModel::Node &node) + { + bool ok = false; + if (!node.isValid()) + { + const GeometryModel::Node &root = m_geometryModel.getRootNode(); + ok = drawNodesRecursive(root); + } + else + ok = drawNodesRecursive(node); + + return ok; + } // GeometryModelRendererEsri::renderNode( Map * map, const GeometryModel::Node & node, const QString & layername ) + + + template uint GeometryModelRendererEsri::setLineStyle(const GeometryModel::Node &p, MapObjectClass *objPtr) const + + { + SituationMap::LineStyle &&objLineStyle = objPtr->lineStyle(); + + const QColor &lineColor = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Color).value(); + uint lineWidth = m_geometryModel.getAttribute(p, GeometryModel::Attribute::LineWidth).toUInt(); + GeometryModel::LineStyle lineStyle = GeometryModel::LineStyle((m_geometryModel.getAttribute(p, GeometryModel::Attribute::LineStyle).toInt())); + + objLineStyle.setLineColor(lineColor); + objLineStyle.setLineWidth((lineWidth > 0) ? lineWidth : 1); + + switch (lineStyle) + { + case GeometryModel::LineStyle::Dashed : objLineStyle.setLinePattern(LinePattern::Dash); break; + case GeometryModel::LineStyle::DashedDotted : objLineStyle.setLinePattern(LinePattern::DashDot); break; + case GeometryModel::LineStyle::Dotted : objLineStyle.setLinePattern(LinePattern::Dot); break; + case GeometryModel::LineStyle::LineSolid : objLineStyle.setLinePattern(LinePattern::Solid); break; + default : objLineStyle.setLinePattern(LinePattern::Solid); break; + } + + objPtr->setLineStyle(objLineStyle); + + return lineWidth; + } // template uint GeometryModelRendererEsri::setLineStyle( const GeometryModel::Node & p, MapObjectClass * objPtr ) const + + + template void GeometryModelRendererEsri::setFillStyle(const GeometryModel::Node &p, MapObjectClass *objPtr) const + { + SituationMap::FillStyle &&objFillStyle = objPtr->fillStyle(); + GeometryModel::FillPattern fillPattern = GeometryModel::FillPattern((m_geometryModel.getAttribute(p, GeometryModel::Attribute::FillPattern).toInt())); + const QColor &fillColor = m_geometryModel.getAttribute(p, GeometryModel::Attribute::FillColor).value(); + + objFillStyle.setFillColor(fillColor); + switch (fillPattern) + { + case GeometryModel::FillPattern::Diag1 : objFillStyle.setFillPattern(FillPattern::BackwardDiagonal); break; + case GeometryModel::FillPattern::Diag2 : objFillStyle.setFillPattern(FillPattern::ForwardDiagonal); break; + case GeometryModel::FillPattern::Hor1 : objFillStyle.setFillPattern(FillPattern::BackwardDiagonal); break; + case GeometryModel::FillPattern::Hor2 : objFillStyle.setFillPattern(FillPattern::Horizontal); break; + case GeometryModel::FillPattern::Solid : objFillStyle.setFillPattern(FillPattern::Solid); break; + case GeometryModel::FillPattern::Ver1 : + case GeometryModel::FillPattern::Ver2 : objFillStyle.setFillPattern(FillPattern::Vertical); break; + case GeometryModel::FillPattern::Dots : + default : objFillStyle.setFillPattern(FillPattern::Null); break; + } + + objPtr->setFillStyle(objFillStyle); + } // template void GeometryModelRendererEsri::setFillStyle( const GeometryModel::Node & p, MapObjectClass * objPtr ) const + + + class RenderMapObjectsVisitor : public GeometryNodeVisitor + { + GeometryModelRendererEsri *m_gmr; + + public: + + RenderMapObjectsVisitor(GeometryModelRendererEsri *gmr, SituationMap::GeometryModel *geoModel) : GeometryNodeVisitor(geoModel), m_gmr(gmr) { ; } + virtual ~RenderMapObjectsVisitor() = default; + + bool apply(SituationMap::GeometryModel *geoModel, const SituationMap::GeometryModel::Node &node, SituationMap::GeometryModel::PrimitiveType primitiveType) override + { + SituationMap::AbstractMapObject *mapObj = nullptr; + + if (geoModel->hasUserData(node)) + { + bool ok = false; + int uid = geoModel->getUserData(node, &ok); + + if (ok) mapObj = m_gmr->m_map->objectFactory().idToMapObject(uid); + } + + switch (primitiveType) + { + case GeometryModel::PrimitiveType::ParticipantSymbol : m_gmr->drawParticipantSymbol( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::DictionarySymbol : m_gmr->drawDictionarySymbol ( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::Ellipse : m_gmr->drawEllipse ( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::Line : m_gmr->drawLine ( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::Polyline : m_gmr->drawPolyline ( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::Rectangle : m_gmr->drawRectangle ( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::Spline : m_gmr->drawSpline ( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::Symbol : m_gmr->drawSymbol ( node, m_gmr->m_layerName, mapObj); break; + case GeometryModel::PrimitiveType::Text : m_gmr->drawText ( node, m_gmr->m_layerName, mapObj); break; + + case GeometryModel::PrimitiveType::RootNode : break; + case GeometryModel::PrimitiveType::Node : break; + case GeometryModel::PrimitiveType::Unknown : break; + default : break; + } + return true; + } // bool apply(SituationMap::GeometryModel * geoModel, const SituationMap::GeometryModel::Node & node, SituationMap::GeometryModel::PrimitiveType primitiveType ) + + }; // RenderMapObjectsVisitor + + + bool GeometryModelRendererEsri::drawNodesRecursive(const GeometryModel::Node &p) + { + if (!p.isValid()) return false; + + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + ++m_stats.m_drawElementsRecursiveCount; + + RenderMapObjectsVisitor visitor(this, &m_geometryModel); + visitor.traverse(p); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + + return true; + } // void GeometryModelRendererEsri::drawNodesRecursive + + + UniqueMapId GeometryModelRendererEsri::getIdFromNode(const GeometryModel::Node &node) const + { + SituationMap::UniqueMapId mapId = SituationMap::INVALID_MAP_ID; + + if (m_geometryModel.hasUserData(node)) + { + bool ok; + int userData = m_geometryModel.getUserData(node, &ok); + if (!ok) + DEBUG_LOG(m_logger) << LOGFUNCTION << " getUserData failed"; + else mapId = userData; + } + return mapId; + + } // UniqueMapId GeometryModelRendererEsri::getIdFromNode() const + + + GeometryModel::Node GeometryModelRendererEsri::getAssociatedNode(const AbstractMapObject *mapObject) + { + if (mapObject == nullptr) return GeometryModel::Node(); + SituationMap::UniqueMapId mapId = mapObject->uniqueMapId(); + + if (mapId == SituationMap::INVALID_MAP_ID) return GeometryModel::Node(); + + return m_geometryModel.findPrimitive(mapId); + } // getAssociatedNode( const AbstractMapObject * mapObject ) + + + bool GeometryModelRendererEsri::getNodesPickable() const { return m_nodesPickable; } + + + void GeometryModelRendererEsri::setNodesPickable(bool nodesPickable) { m_nodesPickable = nodesPickable; } + + + void GeometryModelRendererEsri::drawParticipantSymbol(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + QList points = m_geometryModel.getPrimitivePoints(p); + SituationMap::UniqueMapId mapId = getIdFromNode(p); + + if (points.size() == 0) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Primitive Dictionary Symbol don't have any coordinates!" + << " (" << mapId << ")"; + return; + } + + + bool ok; + + const QString & sidc = m_geometryModel.getAttribute(p, GeometryModel::Attribute::SIDC).toString(); + const QString & sidcAttributes = m_geometryModel.getAttribute(p, GeometryModel::Attribute::SIDC_Attributes).toString(); + + int symbolForceId = m_geometryModel.getAttribute(p, GeometryModel::Attribute::ForceId).toInt(&ok); if (!ok) symbolForceId = -1; + int symbolInjuryId = m_geometryModel.getAttribute(p, GeometryModel::Attribute::DamageForceId).toInt(&ok); if (!ok) symbolInjuryId = -1; + int symbolInjury = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Injury).toInt(&ok); if (!ok) symbolInjury = static_cast(AdvancedSymbology::Injury::NoInjury); + + const QVector &partyIds = m_map->partyIdToColorFunctor().partyIds(); + + if (!partyIds.contains(symbolForceId)) symbolForceId = -1; + if (!partyIds.contains(symbolInjuryId)) symbolInjuryId = -1; + + SituationMap::ObjectFactory & objFac = m_map->objectFactory(); + SituationMap::Participant * objPtr = dynamic_cast(mapObj); + + + if (objPtr == nullptr) + { + if (mapObj) + mapObj -> removeMySelf(); + objPtr = objFac.create (layer, QStringLiteral("Plan-ParticipantSymbol:sidc=%1").arg(sidc)).get(); + + if (objPtr == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " objFac.create failed"; + return; + } + } + + ++m_stats.m_drawDictionarySymbolCount; + m_stats.m_timer.restart(); + + SituationMap::SymbolData symbolData; + + symbolData.sidc = sidc; + + objPtr->setVisibility(true); + objPtr->setPickable(m_nodesPickable); + objPtr->setOrigin(SituationMap::Point3D::fromQPointF(points.first())); + objPtr->setSymbol(symbolData); + objPtr->setPartyId(symbolForceId); + objPtr->setInjury(static_cast(symbolInjury), symbolInjuryId ); + + + objPtr->setTextModifier( SituationMap::Participant::TextModifier::W_DateTimeGroup , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_W).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::T_UniqueDesignation , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_T).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::M_HigherFormation , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_M).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::EF_ReinforcedReduced , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_EF).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::XY_AltitudeLocation , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_XY).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::V_Type , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_V).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::Z_Speed , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_Z).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::G_StaffComments , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_G).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::H_AdditionalInformation , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_H).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::JKLNP , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_KLNPJ).toString()); + + QMap attrMap; + + m_geometryModel.parseSymbolAttributeString(sidcAttributes, attrMap); + + const auto tm = Participant::textModifiers(); + for (auto textModifier : tm) + objPtr->setTextModifierVisibility(textModifier, true); + + + bool success = objPtr->updateView(); + + if (!success) ERROR_LOG(m_logger) << LOGFUNCTION << " failed"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueMapId=" << objPtr->uniqueMapId(); + + // Store id in primitive + m_geometryModel.setUserData(p, objPtr->uniqueMapId()); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msDictionarySymbol += ms; + m_stats.m_msDictionarySymbolMin = std::min(ms, m_stats.m_msDictionarySymbolMin); + m_stats.m_msDictionarySymbolMax = std::max(ms, m_stats.m_msDictionarySymbolMax); + + } // void GeometryModelRendererEsri::drawParticipantSymbol(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + + + void GeometryModelRendererEsri::drawDictionarySymbol(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + QList points = m_geometryModel.getPrimitivePoints(p); + SituationMap::UniqueMapId mapId = getIdFromNode(p); + + if (points.size() == 0) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Primitive Dictionary Symbol don't have any coordinates!" + << " (" << mapId << ")"; + return; + } + + const QString & sidc = m_geometryModel.getAttribute(p, GeometryModel::Attribute::SIDC).toString(); + const QString & sidcAttributes = m_geometryModel.getAttribute(p, GeometryModel::Attribute::SIDC_Attributes).toString(); + SituationMap::ObjectFactory & objFac = m_map->objectFactory(); + SituationMap::SidcSymbol * objPtr = dynamic_cast(mapObj); + + + if (objPtr == nullptr) + { + if (mapObj) mapObj -> removeMySelf(); + + objPtr = objFac.create (layer, QStringLiteral("Plan-TacticalSymbol:sidc=%1").arg(sidc)).get(); + + if (objPtr == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " objFac.create failed"; + return; + } + } + + ++m_stats.m_drawDictionarySymbolCount; + m_stats.m_timer.restart(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << "TacticalAppearanceAttrList: " << sidcAttributes; + + QMap attrMap; + + m_geometryModel.parseSymbolAttributeString(sidcAttributes, attrMap); + + objPtr->setVisibility(true); + objPtr->setPickable(m_nodesPickable); + objPtr->defineSymbol(sidc, SituationMap::convert2D3D(points), attrMap ); + + bool success = objPtr->updateView(); + + if (!success) ERROR_LOG(m_logger) << LOGFUNCTION << " failed"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueMapId=" << objPtr->uniqueMapId(); + + // Store id in primitive + m_geometryModel.setUserData(p, objPtr->uniqueMapId()); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msDictionarySymbol += ms; + m_stats.m_msDictionarySymbolMin = std::min(ms, m_stats.m_msDictionarySymbolMin); + m_stats.m_msDictionarySymbolMax = std::max(ms, m_stats.m_msDictionarySymbolMax); + + } // GeometryModelRendererEsri::drawDictionarySymbol + + + void GeometryModelRendererEsri::drawEllipse(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + SituationMap::CoordTranslationFunctor *ctf = m_map->coordTranslationFunctor(); + + if (nullptr == ctf) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Map don't have defined any map projection! Can't render ellipse ..."; + return; + } + + QList points = m_geometryModel.getPrimitivePoints(p); + + if (points.size() != 4) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Primitive Ellipse has wrong count of geo coordinates!"; + return; + } + + ++m_stats.m_drawEllipseCount; + m_stats.m_timer.restart(); + + double radiusX = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Width).toDouble() * 0.5; + double radiusY = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Height).toDouble() * 0.5; + double phi = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Rotate).toDouble() * (M_PI / 180.0); + double start = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Start).toDouble(); + double extent = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Extent).toDouble(); + + bool fullCircle = (fabs(extent) > 359.5); + + if (fullCircle) + { + start = 0.0; + extent = 360.0; + } + + start *= (M_PI / 180.0); + extent *= (M_PI / 180.0); + + QPointF centerPoint(points.first()); + for (int i = 1; i < 4; ++i) centerPoint += points.at(i); + centerPoint /= 4; + centerPoint = ctf->geographicToMap(centerPoint); + + points.clear(); + + qreal referenceLon = 9999; + if (ctf->mapIsGeographic()) + { + referenceLon = centerPoint.x(); + points << centerPoint; + ctf->geographicToUtm( referenceLon, points); + centerPoint = points.first(); + points.clear(); + } + + SituationMap::MathUtils::Ellipse ellipseCurve(centerPoint, radiusX, radiusY, phi, start, start + extent); + std::unique_ptr pathRasterer(ellipseCurve.createPathRaster()); + + pathRasterer->epsilon(0.25 * m_map->pixelSizeMeter()); + pathRasterer->maxVertexCount(1000); + + pathRasterer->rasterize(ellipseCurve.minimumCurveParameter(), ellipseCurve.maximumCurveParameter(), points); + + if (!fullCircle) + { + GeometryModel::FillStyle fillStyle = (GeometryModel::FillStyle)(m_geometryModel.getAttribute(p, GeometryModel::Attribute::FillStyle).toInt()); + + switch (fillStyle) + { + case GeometryModel::FillStyle::Pieslice: points.append(centerPoint); break; + case GeometryModel::FillStyle::Chord: break; + case GeometryModel::FillStyle::Open: break; + } + } + + if (ctf->mapIsGeographic()) + { + ctf->utmToGeographic(referenceLon, points); + } + + drawPolyline(p, layer, points, mapObj, false); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msEllipse += ms; + m_stats.m_msEllipseMin = std::min(ms, m_stats.m_msEllipseMin); + m_stats.m_msEllipseMax = std::max(ms, m_stats.m_msEllipseMax); + + } // void GeometryModelRendererEsri::drawEllipse + + + void GeometryModelRendererEsri::drawLine(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + ++m_stats.m_drawLineCount; + m_stats.m_timer.restart(); + drawPolyline(p, layer, mapObj); + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msLine += ms; + m_stats.m_msLineMin = std::min(ms, m_stats.m_msLineMin); + m_stats.m_msLineMax = std::max(ms, m_stats.m_msLineMax); + } // void GeometryModelRendererEsri::drawLine + + + void GeometryModelRendererEsri::drawPolyline(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + ++m_stats.m_drawPolylineCount; + m_stats.m_timer.restart(); + + drawPolyline(p, layer, m_geometryModel.getPrimitivePoints(p), mapObj); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msPolyline += ms; + m_stats.m_msPolylineMin = std::min(ms, m_stats.m_msPolylineMin); + m_stats.m_msPolylineMax = std::max(ms, m_stats.m_msPolylineMax); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + + } /* GeometryModelRendererEsri::drawPolyline */ + + + void GeometryModelRendererEsri::drawPolyline( const GeometryModel::Node & p + , const QString & layer + , const QList & points + , SituationMap::AbstractMapObject * mapObj + , bool geoCoordinates ) + { + bool isRectangle = (m_geometryModel.getType(p) == GeometryModel::PrimitiveType::Rectangle); + bool isEllipse = (m_geometryModel.getType(p) == GeometryModel::PrimitiveType::Ellipse); + bool isFilled = (points.size() > 2) && m_geometryModel.getAttribute(p, GeometryModel::Attribute::Fill).toBool(); + bool isClosed = ((isEllipse) || (isRectangle) || ((points.size() > 2) && m_geometryModel.getAttribute(p, GeometryModel::Attribute::Close).toBool())); + + + if ((points.size() > 2) || (isRectangle) || (isEllipse)) + { + GeometryModel::FillPattern fillPattern = GeometryModel::FillPattern((m_geometryModel.getAttribute(p, GeometryModel::Attribute::FillPattern).toInt())); + + if (isEllipse) + { + double extent = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Extent).toDouble(); + + if (fabs(extent) <= 359.5) + { + GeometryModel::FillStyle fillStyle = GeometryModel::FillStyle((m_geometryModel.getAttribute(p, GeometryModel::Attribute::FillStyle).toInt())); + switch (fillStyle) + { + case GeometryModel::FillStyle::Chord : isClosed = true; break; + case GeometryModel::FillStyle::Pieslice : isClosed = true; break; + case GeometryModel::FillStyle::Open : isClosed = false; break; + } + } + else isClosed = true; + } + + if (fillPattern == GeometryModel::FillPattern::None) isFilled = false; + } + + SituationMap::RefPointer objPtr(mapObj); + SituationMap::ObjectFactory &objFac = m_map->objectFactory(); + bool createMapObj = false; + bool selectMapObj = false; + SituationMap::UniqueMapId removedUidMapObj = SituationMap::INVALID_MAP_ID; + + if (mapObj) + { + objPtr = (SituationMap::AbstractMultiPointMapObject *) dynamic_cast(mapObj); + if (objPtr.valid()) + { + if (isClosed) + { + createMapObj = true; + selectMapObj = objPtr->selected(); + removedUidMapObj = objPtr->uniqueMapId(); + + objPtr->removeMySelf(); + objPtr = nullptr; + } + } + else + { + objPtr = (SituationMap::AbstractMultiPointMapObject *) dynamic_cast(mapObj); + if (objPtr.valid()) + { + if (!isClosed) + { + createMapObj = true; + selectMapObj = objPtr->selected(); + removedUidMapObj = objPtr->uniqueMapId(); + + objPtr->removeMySelf(); + objPtr = nullptr; + } + } + else + { + createMapObj = true; + selectMapObj = objPtr->selected(); + removedUidMapObj = objPtr->uniqueMapId(); + + mapObj->removeMySelf(); + } + } + } + else createMapObj = true; + + if (createMapObj) + { + // re use the uid if the map object was removed before ... + if (isClosed) objPtr = (SituationMap::AbstractMultiPointMapObject *) objFac.create ( removedUidMapObj, layer, QLatin1String("PlanElement")).get(); + else objPtr = (SituationMap::AbstractMultiPointMapObject *) objFac.create( removedUidMapObj, layer, QLatin1String("PlanElement")).get(); + + if (!objPtr.valid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " SituationMap::ObjectFactory.create() failed"; + return; + } + + objPtr->setSelected(selectMapObj); + objPtr->setScaleEnable(false); // disable scaling (setScale, most of the time done by user) + } + + objPtr->setPickable(m_nodesPickable); + + uint lineWidth = setLineStyle(p, objPtr.get()); + + Q_UNUSED(lineWidth); + + if (!isFilled) + { + SituationMap::FillStyle &&objFillStyle = objPtr->fillStyle(); + objFillStyle.setFillPattern(SituationMap::FillPattern::Null); + objPtr->setFillStyle(objFillStyle); + } + else setFillStyle(p, objPtr.get()); + + if ((!isClosed) && (!isEllipse)) + { + SituationMap::Polyline *lineObj = dynamic_cast(objPtr.get()); + + if (lineObj) + { + // adding arrows if defined + + lineObj -> setArrowHeadStart(false); + lineObj -> setArrowHeadEnd(false); + + GeometryModel::Arrows arrows = (GeometryModel::Arrows) m_geometryModel.getAttribute(p, GeometryModel::Attribute::Arrows).toInt(); + + switch (arrows) + { + case GeometryModel::Arrows::Both : lineObj -> setArrowHeadEnd(true); + Q_FALLTHROUGH(); + case GeometryModel::Arrows::Start : lineObj -> setArrowHeadStart(true); break; + case GeometryModel::Arrows::End : lineObj -> setArrowHeadEnd(true); break; + case GeometryModel::Arrows::NoArrows : + default : arrows = GeometryModel::Arrows::NoArrows; break; + } + + if (arrows != GeometryModel::Arrows::NoArrows) + { + #ifdef NEVER + double scale = (lineWidth < 2) ? 1.0 : double(lineWidth) * 0.5; + + double arrowWidth = scale * (m_geometryModel.getAttribute(p, GeometryModel::Attribute::ArrowWidth).toDouble()); + double arrowHeight = scale * (m_geometryModel.getAttribute(p, GeometryModel::Attribute::ArrowHeight).toDouble()); + double arrowClampHeight = scale * (m_geometryModel.getAttribute(p, GeometryModel::Attribute::ArrowClampHeight).toDouble()); + + Q_UNUSED(arrowWidth); + Q_UNUSED(arrowHeight); + Q_UNUSED(arrowClampHeight); + #endif + } + } + } + + (geoCoordinates) ? objPtr->setPositions(SituationMap::convert2D3D(points)) : objPtr->setPositionsTranslated(SituationMap::convert2D3D(points)); + + bool success = objPtr->updateView(); + + if (!success) ERROR_LOG(m_logger) << LOGFUNCTION << " failed"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueMapId=" << objPtr->uniqueMapId(); + + // Store id in primitive + m_geometryModel.setUserData(p, objPtr->uniqueMapId()); + + } // void GeometryModelRendererEsri::drawPolyline + + + void GeometryModelRendererEsri::drawRectangle(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + QList points = m_geometryModel.getPrimitivePoints(p); + if (points.size() != 4) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Primitive Rectangle has wrong count of points!"; + return; + } + ++m_stats.m_drawRectangleCount; + m_stats.m_timer.restart(); + + GeometryModelRendererEsri::drawPolyline(p, layer, points, mapObj); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msRectangle += ms; + m_stats.m_msRectangleMin = std::min(ms, m_stats.m_msRectangleMin); + m_stats.m_msRectangleMax = std::max(ms, m_stats.m_msRectangleMax); + } // void GeometryModelRendererEsri::drawRectangle + + + void GeometryModelRendererEsri::drawSpline(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + SituationMap::CoordTranslationFunctor *ctf = m_map->coordTranslationFunctor(); + + if (nullptr == ctf) + { + ERROR_LOG(m_logger) << LOGFUNCTION << "Map doesn't have defined any map projection! Can't render spline ..."; +#if LTS_DEBUG + abort(); +#else + return; +#endif // LTS_DEBUG + } + + ++m_stats.m_drawSplineCount; + m_stats.m_timer.restart(); + + SituationMap::MathUtils::SmoothLine splineCurve; + std::unique_ptr pathRasterer(splineCurve.createPathRaster()); + bool isClosed(m_geometryModel.getAttribute(p, GeometryModel::Attribute::Close).toBool()); + + const auto & pointsMap = m_geometryModel.getPrimitivePoints(p); + + if (pointsMap.size() < 2) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Too little count of occordiantes. Can't render spline ..."; + return; + } + + QList points; + points.reserve(pointsMap.size()); + + for (int i = 0; i < pointsMap.size(); ++i) + { + points.push_back(ctf->geographicToMap(pointsMap.at(i))); + } + + qreal referenceLon = 9999; + if (ctf->mapIsGeographic()) + { + // In case of geographic coordinate system do rasterization in UTM coordinate system to avoid + // trouble with termination threshold. + referenceLon = points.first().x(); + ctf->geographicToUtm( referenceLon, points ); + } + + splineCurve.vertexVector(points.toVector(), isClosed); + + pathRasterer -> epsilon(0.25 * m_map->pixelSizeMeter()); + pathRasterer -> maxVertexCount(1000); + + QList rasterizedPoints; + pathRasterer -> rasterize(splineCurve.minimumCurveParameter(), splineCurve.maximumCurveParameter(), rasterizedPoints); + + if (ctf->mapIsGeographic()) + ctf->utmToGeographic( referenceLon, rasterizedPoints ); + + if (rasterizedPoints.size() <= 2) + { + NOTICE_LOG(m_logger) << LOGFUNCTION + << "< pointsMap.size() " << pointsMap.size() + << "< points.size() " << points.size() + << "< splineCurve.minimumCurveParameter() " << splineCurve.minimumCurveParameter() + << "< splineCurve.maximumCurveParameter() " << splineCurve.maximumCurveParameter() + << "< rasterizedPoints.size() === " << rasterizedPoints.size() + << "< m_map->pixelSizeMeter()=" << m_map->pixelSizeMeter(); + } + drawPolyline(p, layer, rasterizedPoints, mapObj, false); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msSpline += ms; + m_stats.m_msSplineMin = std::min(ms, m_stats.m_msSplineMin); + m_stats.m_msSplineMax = std::max(ms, m_stats.m_msSplineMax); + + } // void GeometryModelRendererEsri::drawSpline + + + + void GeometryModelRendererEsri::drawSymbol(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject *mapObj) + { + if (nullptr == m_map->coordTranslationFunctor()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << "Map doesn't have defined any map projection! Can't render symbol ..."; + return; + } + + const QVector &partyIds = m_map->partyIdToColorFunctor().partyIds(); + if (partyIds.size() < 1) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Party colors are not defined. Can't render symbol ..."; + return; + } + + ++m_stats.m_drawSymbolCount; + + const auto & pointsMap = m_geometryModel.getPrimitivePoints(p); + + if (pointsMap.size() == 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Primitive Symbol has no points!"; + return; + } + + const QPointF & point = pointsMap.at(0); + + SituationMap::RefPointer objPtr; + SituationMap::ObjectFactory &objFac = m_map->objectFactory(); + bool createMapObj = false; + + if (mapObj) + { + objPtr = (SituationMap::Participant *) dynamic_cast(mapObj); + if (!objPtr.valid()) + { + createMapObj = true; + mapObj->removeMySelf(); + } + } + else createMapObj = true; + + if (createMapObj) + { + objPtr = (SituationMap::Participant *) objFac.create (layer, QLatin1String("PlanElement")).get(); + + if (!objPtr.valid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " SituationMap::ObjectFactory.create() failed"; + return; + } + objPtr->setScaleEnable(false); // disable scaling (setScale, most of the time done by user) + } + + bool ok; + + SituationMap::GeometryModel::t_vbg_et vbg = SituationMap::GeometryModel::t_vbg_et(m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::att_vbg_et).toInt()); + SymbologyXML::SymbolTypes::t_state state = SymbologyXML::SymbolTypes::t_state( m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::SymbolState).toInt() ); + SymbologyXML::SymbolTypes::t_uncertainty uncertainty = SymbologyXML::SymbolTypes::t_uncertainty( m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::SymbolUncertainty).toInt() ); + SymbologyXML::SymbolTypes::t_affil affil = SymbologyXML::SymbolTypes::t_affil(m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::Affiliation).toInt()); + SymbologyXML::SymbolTypes::t_mob mob = SymbologyXML::SymbolTypes::t_mob(m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::Mobility).toInt()); + // bool abcProtect = m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::AbcProtection).toBool(); + + SymbolData symData; + + int symbolID = m_geometryModel.getAttribute(p, GeometryModel::Attribute::SymbolId).toInt(); + if (symbolID < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << "Primitive Symbol has undefinded symbol!"; + return; + } + + symData.sidc = AdvancedSymbology::SidcCodec::encodeCivilXmlSymbolId(uint(symbolID),state,uncertainty,affil,mob); + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + int symbolOffsetX = m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::SymbolOffsetX).toInt(&ok); if (!ok) symbolOffsetX = 0; + int symbolOffsetY = m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::SymbolOffsetY).toInt(&ok); if (!ok) symbolOffsetY = 0; + int symbolDirection = m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::SymbolDirection).toInt(&ok); if (!ok) symbolDirection = 0; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + float symbolScale = m_geometryModel.getAttribute(p,SituationMap::GeometryModel::Attribute::SymbolExplicitScale).toFloat(&ok); if (!ok) symbolScale = 1.0f; + int symbolForceId = m_geometryModel.getAttribute(p, GeometryModel::Attribute::ForceId).toInt(&ok); if (!ok) symbolForceId = -1; + int symbolInjuryId = m_geometryModel.getAttribute(p, GeometryModel::Attribute::DamageForceId).toInt(&ok); if (!ok) symbolInjuryId = -1; + + if (!partyIds.contains(symbolForceId)) symbolForceId = partyIds.first(); + if (!partyIds.contains(symbolInjuryId)) symbolInjuryId = partyIds.first(); + + symData.size = (symbolScale > 0) ? symbolScale : 1.0; + + objPtr->setOrigin( Point3D(point.x(),point.y(),0.0) ); + objPtr->setPickable(m_nodesPickable); + + objPtr->setSymbol(symData); + objPtr->setPartyId( symbolForceId ); + + AdvancedSymbology::Injury injury = AdvancedSymbology::Injury::NoInjury; + + switch (vbg) + { + case SituationMap::GeometryModel::t_vbg_et::ANNAEHENDER_TREFFER : + case SituationMap::GeometryModel::t_vbg_et::TREFFER_OHNE_WIRKUNG : + case SituationMap::GeometryModel::t_vbg_et::EINSATZBEREIT : injury = AdvancedSymbology::Injury::NoInjury; break; + + case SituationMap::GeometryModel::t_vbg_et::AUSFALL_WAFFENANLAGE : + case SituationMap::GeometryModel::t_vbg_et::AUSFALL_WAFFENANLAGE_SICHTB : injury = AdvancedSymbology::Injury::WeaponKill; break; + + case SituationMap::GeometryModel::t_vbg_et::BEWEGUNGSUNFAEHIG : + case SituationMap::GeometryModel::t_vbg_et::BEWEGUNGSUNFAEHIG_SICHTB : injury = AdvancedSymbology::Injury::MobilityKill; break; + + case SituationMap::GeometryModel::t_vbg_et::LEICHTER_TREFFER : + case SituationMap::GeometryModel::t_vbg_et::MITTLERER_TREFFER : + case SituationMap::GeometryModel::t_vbg_et::SCHWERER_TREFFER : injury = AdvancedSymbology::Injury::Hit; break; + case SituationMap::GeometryModel::t_vbg_et::TOTALAUSFALL : injury = AdvancedSymbology::Injury::TotalKill; break; + case SituationMap::GeometryModel::t_vbg_et::ZEITLICHER_AUSFALL : injury = AdvancedSymbology::Injury::TotalKill; break; + } + + objPtr->setInjury( injury , symbolInjuryId ); + + objPtr->setTextModifier( SituationMap::Participant::TextModifier::W_DateTimeGroup , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_W).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::T_UniqueDesignation , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_T).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::M_HigherFormation , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_M).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::EF_ReinforcedReduced , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_EF).toString()); + + objPtr->setTextModifier( SituationMap::Participant::TextModifier::XY_AltitudeLocation , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_XY).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::V_Type , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_V).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::Z_Speed , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_Z).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::G_StaffComments , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_G).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::H_AdditionalInformation, m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_H).toString()); + objPtr->setTextModifier( SituationMap::Participant::TextModifier::JKLNP , m_geometryModel.getAttribute(p, GeometryModel::Attribute::Text_KLNPJ).toString()); + + + /* + AdditionalSymbolType additionalSymbol; + + AdditionalSymbolType::NbcProtection, ///< Line at top left. + AdditionalSymbolType::Manipulation, ///< Black and white dashed lines at bottom (1st of three) + AdditionalSymbolType::NoPosition, ///< Red line at bottom (2nd of three) + AdditionalSymbolType::BiteError ///< Yellow line at bottom. (3rd of three) + + objPtr->setAdditionalSymbol(additionalSymbol); + */ + + bool success = objPtr->updateView(); + + if (!success) ERROR_LOG(m_logger) << LOGFUNCTION << " failed"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueMapId=" << objPtr->uniqueMapId(); + + // Store id in primitive + m_geometryModel.setUserData(p, objPtr->uniqueMapId()); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msSymbol += ms; + m_stats.m_msSymbolMin = std::min(ms, m_stats.m_msDictionarySymbolMin); + m_stats.m_msSymbolMax = std::max(ms, m_stats.m_msDictionarySymbolMax); + + } // void GeometryModelRendererEsri::drawSymbol + + + void GeometryModelRendererEsri::drawText(const GeometryModel::Node &p, const QString &layer, SituationMap::AbstractMapObject * /*mapObj*/) + { + QList points = m_geometryModel.getPrimitivePoints(p); + + if (points.size() != 1) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Primitive TextBox doesn't have origin!"; + return; + } + + SituationMap::UniqueMapId mapId = getIdFromNode(p); + SituationMap::ObjectFactory &objFac = m_map->objectFactory(); + SituationMap::RefPointer objPtr; + + if (SituationMap::isUniqueMapIdValid(mapId)) + objPtr = objFac.get(mapId).get(); + + if (!objPtr.valid()) + { + objPtr = objFac.create (layer, QLatin1String("PlanElement")).get(); + + if (!objPtr.valid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " objFac.create failed"; + return; + } + objPtr->setScaleEnable(false); // disable scaling (setScale, most of the time done by user) + } + + ++m_stats.m_drawTextCount; + m_stats.m_timer.restart(); + + double phi = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Rotate).toDouble(); // *(M_PI/180.0); + const QString & text = m_geometryModel.getTextData(p); + double fontSize = m_geometryModel.getAttribute(p, GeometryModel::Attribute::FontSize).toDouble(); + const QColor & textColor = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Color).value(); + const QColor & lineColor = m_geometryModel.getAttribute(p, GeometryModel::Attribute::LineColor).value(); + QColor fillColor = m_geometryModel.getAttribute(p, GeometryModel::Attribute::FillColor).value(); + int lineWidth = m_geometryModel.getAttribute(p, GeometryModel::Attribute::LineWidth).toInt(); + bool fill = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Fill).toBool(); + bool bold = m_geometryModel.getAttribute(p, GeometryModel::Attribute::FontBold).toBool(); + bool italic = m_geometryModel.getAttribute(p, GeometryModel::Attribute::FontItalic).toBool(); + bool useBox = m_geometryModel.getAttribute(p, GeometryModel::Attribute::Box).toBool(); + GeometryModel::Font pmFont = (GeometryModel::Font)(m_geometryModel.getAttribute(p, GeometryModel::Attribute::Font).toInt()); + const GeometryModel::Node & td = p.firstChildNode(QLatin1String("text_data")); + GeometryModel::HorizontalAlign pmAlign = (GeometryModel::HorizontalAlign)(m_geometryModel.getAttribute(td, GeometryModel::Attribute::Alignment).toInt()); + GeometryModel::HorizontalAlign anchorH = (GeometryModel::HorizontalAlign)(m_geometryModel.getAttribute(p, GeometryModel::Attribute::AnchorHorizontal).toInt()); + GeometryModel::VerticalAlign anchorV = (GeometryModel::VerticalAlign)(m_geometryModel.getAttribute(p, GeometryModel::Attribute::AnchorVertical).toInt()); + int marginH = m_geometryModel.getAttribute(p, GeometryModel::Attribute::MarginHorizontal).toInt(); + int marginV = m_geometryModel.getAttribute(p, GeometryModel::Attribute::MarginVertical).toInt(); + + if (fontSize <= 0) fontSize = 14.0; + + Q_UNUSED(useBox); + Q_UNUSED(marginH); + Q_UNUSED(marginV); + + QString fontName; + + switch (pmFont) + { + case GeometryModel::Font::SansSerif : fontName = QLatin1String("Arial"); break; + case GeometryModel::Font::Serif : fontName = QLatin1String("Times New Roman"); break; + case GeometryModel::Font::Fixed : fontName = QLatin1String("Courier New"); break; + case GeometryModel::Font::Decorative : + { + QStringList fontFamilies = QFontDatabase().families(); + + fontName.clear(); + for (const QLatin1String &fontFamilyName : { QLatin1String("Freestyle Script"), QLatin1String("MV Boli"), QLatin1String("URW Chancery L")}) + { + if (fontFamilies.contains(fontFamilyName)) + { + fontName = fontFamilyName; + break; + } + } + } break; + default : fontName = QLatin1String("Wingdings"); break; + } + + SituationMap::HorizontalAlignment fontAlignm = SituationMap::HorizontalAlignment::Left; + SituationMap::HorizontalAlignment hAlignm = SituationMap::HorizontalAlignment::Center; + SituationMap::VerticalAlignment vAlignm = SituationMap::VerticalAlignment::Middle; + + switch (pmAlign) + { + case GeometryModel::HorizontalAlign::Left : fontAlignm = SituationMap::HorizontalAlignment::Left; break; + case GeometryModel::HorizontalAlign::HCenter : fontAlignm = SituationMap::HorizontalAlignment::Center; break; + case GeometryModel::HorizontalAlign::Right : fontAlignm = SituationMap::HorizontalAlignment::Right; break; + } + + Q_UNUSED(fontAlignm); + + switch (anchorV) + { + case GeometryModel::VerticalAlign::Bottom : vAlignm = SituationMap::VerticalAlignment::Bottom; break; + case GeometryModel::VerticalAlign::VCenter : vAlignm = SituationMap::VerticalAlignment::Middle; break; + case GeometryModel::VerticalAlign::Top : vAlignm = SituationMap::VerticalAlignment::Top; break; + } + + switch (anchorH) + { + case GeometryModel::HorizontalAlign::Left : hAlignm = SituationMap::HorizontalAlignment::Left; break; + case GeometryModel::HorizontalAlign::HCenter : hAlignm = SituationMap::HorizontalAlignment::Center; break; + case GeometryModel::HorizontalAlign::Right : hAlignm = SituationMap::HorizontalAlignment::Right; break; + } + + // uint lineWidth = setLineStyle( p, objPtr.get() ); + + setFillStyle(p, objPtr.get()); + + SituationMap::TextStyle &textStyle = objPtr->rTextStyle(); + + if (!fill) fillColor.setAlpha(0); + + textStyle.setTextColor(textColor); + textStyle.setTextOutlineColor(lineColor); + textStyle.setBackgroundColor(fillColor); + textStyle.setTextOutlineWidth(lineWidth-1); + // textStyle.setTextOutlineWidth(0.0f); + + textStyle.setFontFamily(fontName); + textStyle.setSize(fontSize); + textStyle.setAngle(phi); + textStyle.setBold(bold); + textStyle.setItalic(italic); + textStyle.setHorizontalAlignment(hAlignm); + textStyle.setVerticalAlignment(vAlignm); + + + objPtr -> setIdentifier(text.isEmpty() ? QLatin1String("????????") : text); + objPtr -> setPickable(m_nodesPickable); + objPtr -> setOrigin(SituationMap::Point3D::fromQPointF(points.first())); + objPtr -> setShowAnchorMarker(m_editorMode); + + bool success = objPtr->updateView(); + + if (!success) ERROR_LOG(m_logger) << LOGFUNCTION << " failed"; + + // Store id in primitive + m_geometryModel.setUserData(p, objPtr->uniqueMapId()); + + int ms = m_stats.m_timer.elapsed(); + m_stats.m_msText += ms; + m_stats.m_msTextMin = std::min(ms, m_stats.m_msTextMin); + m_stats.m_msTextMax = std::max(ms, m_stats.m_msTextMax); + + } // void GeometryModelRendererEsri::drawText + +} // namepace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.h new file mode 100755 index 000000000..80b118677 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/geometry_model_renderer_esri.h @@ -0,0 +1,95 @@ +/** + * @RDE_HEADER@ + * GeometryModel - Renderer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include +#include "situation_map_export.h" + +// Qt +#include + + +namespace SituationMap +{ + +class Map; +class AbstractMultiPointMapObject; +class RenderMapObjectsVisitor; + + +class SITUATIONMAP_EXPORT GeometryModelRendererEsri : public GeometryModelRenderer +{ + Q_OBJECT + +private: + + friend class MapImpl; + + explicit GeometryModelRendererEsri(Map *map, const QString &layerName, GeometryModel &geometryModel, QObject *parent = nullptr); + ~GeometryModelRendererEsri() = default; + +public: + + bool render() override; + + bool renderNode(const GeometryModel::Node &node) override; + + bool render(QPixmap &drawable) override; + + GeometryModel::Node getAssociatedNode(const AbstractMapObject *mapObject) override; + + bool getNodesPickable() const override; + void setNodesPickable(bool nodesPickable) override; + + bool editorMode() const override; + void setEditorMode(bool on) override; + + UniqueMapId getIdFromNode(const GeometryModel::Node &node) const override; + +protected: + + friend class RenderMapObjectsVisitor; + + bool drawNodesRecursive(const GeometryModel::Node &); + + void drawPolyline(const GeometryModel::Node &, const QString &layer, const QList &points, SituationMap::AbstractMapObject *mapObj, bool geoCoordinates = true); + + void drawParticipantSymbol( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawDictionarySymbol ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawEllipse ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawLine ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawPolyline ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawRectangle ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawSpline ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawSymbol ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + void drawText ( const GeometryModel::Node &, const QString &layer, SituationMap::AbstractMapObject *mapObj ); + + template uint setLineStyle(const GeometryModel::Node &p, MapObjectClass *objPtr) const; + template void setFillStyle(const GeometryModel::Node &p, MapObjectClass *objPtr) const; + + bool m_editorMode; + QTimer *m_delayedRenderTimer; + QSet m_delayedRenderIds; + +protected slots: + + void onDelayedRenderNodes(); + + void onCleanupMapObjects(const SituationMap::GeometryModel::Node &node); + + void onRemoveMapObject(int uid); + void onUpdate(const SituationMap::GeometryModel::Node &node, SituationMap::GeometryModel::Attribute att, bool coordsChanged); + +}; // GeometryModelRendererEsri +} + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.cpp new file mode 100755 index 000000000..759293cd2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.cpp @@ -0,0 +1,220 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Grid + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "grid_impl.h" +#include "map_impl.h" + + +// ESRI +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Qt + + +namespace SituationMap +{ + + +class GridImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + Esri::ArcGISRuntime::Grid *m_esriGrid = nullptr; + + // Own data: + SituationMap::MapImpl *m_mapImpl = nullptr; + + +}; // class GridImpl::Private +//----------------------------------------------------------------------------- + + +GridImpl::GridImpl(SituationMap::MapImpl *mapImpl) + : m_logger(getLogger("GridImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_mapImpl = mapImpl; + if (d->m_mapImpl && d->m_mapImpl->getEsriMapGraphicsView()) + d->m_esriGrid = d->m_mapImpl->getEsriMapGraphicsView()->grid(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +GridImpl::~GridImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +GridType GridImpl::gridType() +{ + GridType curType = GridType::NoType; + + if (d->m_esriGrid) + { + Esri::ArcGISRuntime::GridType esriGridType = d->m_esriGrid->gridType(); + switch (esriGridType) + { + case Esri::ArcGISRuntime::GridType::Unknown: + curType = GridType::NoType; + break; + case Esri::ArcGISRuntime::GridType::LatitudeLongitudeGrid: + curType = GridType::LatLon; + break; + case Esri::ArcGISRuntime::GridType::MGRSGrid: + curType = GridType::MGRS; + break; + case Esri::ArcGISRuntime::GridType::UTMGrid: + curType = GridType::UTM; + break; + case Esri::ArcGISRuntime::GridType::USNGGrid: + curType = GridType::USGN; + break; + } // switch + + } + else + DEBUG_LOG(m_logger) << LOGFUNCTION << " no object"; + + return curType; +} + +void GridImpl::setGridType(GridType gridType) +{ + // change? + if (this->gridType() == gridType) + return; + + if (d->m_esriGrid) + d->m_esriGrid->deleteLater(); + + if (! d->m_mapImpl->getEsriMapGraphicsView()) + return; + + QObject *parent = d->m_mapImpl->getEsriMapGraphicsView(); + + switch (gridType) + { + case GridType::NoType: + d->m_esriGrid = nullptr; + break; + case GridType::LatLon: + d->m_esriGrid = new Esri::ArcGISRuntime::LatitudeLongitudeGrid(parent); + break; + case GridType::MGRS: + d->m_esriGrid = new Esri::ArcGISRuntime::MGRSGrid(parent); + break; + case GridType::UTM: + d->m_esriGrid = new Esri::ArcGISRuntime::UTMGrid(parent); + break; + case GridType::USGN: + d->m_esriGrid = new Esri::ArcGISRuntime::USNGGrid(parent); + break; + } // switch + + if (d->m_esriGrid) + { + d->m_esriGrid->setLabelPosition(Esri::ArcGISRuntime::GridLabelPosition::AllSides); + + int levelCount = d->m_esriGrid->levelCount(); + for (int i = 0; i < levelCount; ++i) + { + Esri::ArcGISRuntime::TextSymbol *gridLabel = static_cast(d->m_esriGrid->textSymbol(i)); + if (gridLabel) + { + gridLabel->setSize(10); + gridLabel->setHaloWidth(1.0); + } + const QColor col(0, 0, 0, 200); + Esri::ArcGISRuntime::SimpleLineSymbol sls(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, + col, + 1); + d->m_esriGrid->setLineSymbol(i, &sls); + } // for + } + + // call setGrid even if d->m_esriGrid is nullptr !!! + d->m_mapImpl->getEsriMapGraphicsView()->setGrid(d->m_esriGrid); + + d->m_mapImpl->emitVisualComponentChanged(VisualComponent::Grid); +} + + +void GridImpl::setVisible(bool visible) +{ + if (d->m_esriGrid) + { + if (d->m_esriGrid->isVisible() != visible) + { + d->m_esriGrid->setVisible(visible); + d->m_mapImpl->emitVisualComponentChanged(VisualComponent::Grid); + } + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; +} + +bool GridImpl::isVisible() +{ + if (d->m_esriGrid) + return d->m_esriGrid->isVisible(); + else + DEBUG_LOG(m_logger) << LOGFUNCTION << " no object"; + return false; +} + +int GridImpl::levelCount() +{ + if (d->m_esriGrid) + return d->m_esriGrid->levelCount(); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return -1; +} + +bool GridImpl::isLabelVisible() +{ + if (d->m_esriGrid) + return d->m_esriGrid->isLabelsVisible(); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return false; +} + +void GridImpl::setLabelVisible(bool visible) +{ + if (d->m_esriGrid) + { + if (d->m_esriGrid->isLabelsVisible() != visible) + { + d->m_esriGrid->setLabelsVisible(visible); + d->m_mapImpl->emitVisualComponentChanged(VisualComponent::Grid); + } + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.h new file mode 100755 index 000000000..900a907d5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/grid_impl.h @@ -0,0 +1,61 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Grid + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MapImpl; + +class GridImpl : public SituationMap::Grid +{ + using BaseClass = SituationMap::Grid; + +public: + + explicit GridImpl(SituationMap::MapImpl *mapImpl); + virtual ~GridImpl(); + + GridImpl(const GridImpl &other) = delete; + GridImpl &operator=(GridImpl &other) = delete; + + + virtual GridType gridType() override; + virtual void setGridType(GridType gridType) override; + + virtual void setVisible(bool visible) override; + virtual bool isVisible() override; + + virtual int levelCount() override; + + virtual bool isLabelVisible() override; + virtual void setLabelVisible(bool visible) override; + +private : + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; + + +} // namespace SituationMap + +//---------------------------------------------------------------------------- + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.cpp new file mode 100755 index 000000000..32c7b9384 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.cpp @@ -0,0 +1,76 @@ +/** + * @RDE_HEADER@ + * CustomView for MapObjects + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2017-04-11 + */ + +// Own +#include "jump_point_custom_view.h" +#include "ui_jump_point_custom_view.h" +#include "jump_point_model.h" +#include + + +// Ot +#include +#include +#include + + +namespace SituationMap +{ + + +JumpPointCustomView::JumpPointCustomView(QWidget *parent) + : BaseClass(parent) + , m_ui(new Ui::JumpPointCustomView) + , m_mapper(new QDataWidgetMapper(this)) +{ + m_ui->setupUi(this); + m_ui->image->setMenuEnabled(false); // no menu ! + +// // to keep toolTip up-to-date: +// connect(m_ui->image, &LtsWidgets::LtsImageEditor::imageChanged, +// this, &JumpPointCustomView::initToolTip); +} + +JumpPointCustomView::~JumpPointCustomView() +{ + delete m_ui; +} + +bool JumpPointCustomView::init(const QModelIndex &idx) +{ + if (!idx.isValid()) + return false; + + if (idx.model() != m_mapper->model()) + { + // create mapping; + m_mapper->setModel(const_cast(idx.model())); + m_mapper->addMapping(m_ui->lbDescription, JumpPointModel::Columns::NAME, "text"); + // m_mapper->addMapping(m_ui->image, JumpPointModel::Columns::ICON, "pixmap"); + } + + setRow(idx.row()); + return true; +} + +void JumpPointCustomView::setRow(int row) +{ + m_mapper->setCurrentIndex(row); + + // assume for now, that the relation between row and uniqueMapId will not change: + if (m_mapper && m_mapper->model()) + { + QAbstractItemModel *modelPtr = m_mapper->model(); + m_name = modelPtr->data(modelPtr->index(row, JumpPointModel::Columns::NAME)).toString(); + QImage img = modelPtr->data(modelPtr->index(row, JumpPointModel::Columns::ICON)).value(); + m_ui->image->setPixmap(QPixmap::fromImage(img)); + } +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.h new file mode 100755 index 000000000..37dd791b0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.h @@ -0,0 +1,48 @@ +/** + * @RDE_HEADER@ + * CustomView for MapObjects + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2017-04-11 + */ +#pragma once + +// Own +#include +#include + + +class QDataWidgetMapper; +namespace Ui +{ +class JumpPointCustomView; +} + + +namespace SituationMap +{ +class Map; + +class JumpPointCustomView final : public LtsDataView::AbstractDisplayWidget +{ + Q_OBJECT + + using BaseClass = AbstractDisplayWidget; + +public: + explicit JumpPointCustomView(QWidget *parent = nullptr); + ~JumpPointCustomView(); + bool init(const QModelIndex &idx) override; + +protected: + void setRow(int row); + +private: + Ui::JumpPointCustomView *m_ui; + QDataWidgetMapper *m_mapper = nullptr; + + QString m_name; +}; + +} // SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.ui b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.ui new file mode 100755 index 000000000..67db1b88a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_custom_view.ui @@ -0,0 +1,80 @@ + + + JumpPointCustomView + + + + 0 + 0 + 62 + 82 + + + + + 0 + 0 + + + + + + + + + 6 + 6 + 50 + 50 + + + + + 0 + 0 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + + + 6 + 57 + 50 + 19 + + + + <Desc> + + + Qt::AlignCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + LtsWidgets::LtsImageEditor + QWidget +
LtsWidgets/lts_image_editor.h
+ 1 +
+
+ + +
diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.cpp new file mode 100755 index 000000000..4f309de05 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.cpp @@ -0,0 +1,166 @@ +/** + * @RDE_HEADER@ + * Interface for JumpPointWidget + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +// Own +#include "jump_point_model.h" +#include +#include +#include +#include +#include + + +// Qt +#include + + +namespace SituationMap +{ + +//---------------------------------------------------------------------------- +// JumpPointModel +//---------------------------------------------------------------------------- + +JumpPointModel::JumpPointModel(QObject *parent) + : BaseClass( {tr("Name"), tr("OriginGeo"), tr("Origin"), tr("Scale"), tr("Icon")}, parent) +{ +} + +void JumpPointModel::setMap(Map *map) +{ + m_map = map; +} + +QVariant JumpPointModel::data(const QModelIndex &index, int role) const +{ + if ((role != Qt::DisplayRole && role != Qt::ToolTipRole && role != Qt::EditRole) || !indexIsValid(index)) + return QVariant(); + + const JPData &data = m_vecData.at(index.row()); + + if (role==Qt::ToolTipRole) + { + QString tooltip; + tooltip.append(QStringLiteral("")); + + const QImage &img = data.m_image; + + if (! img.isNull()) + { + // without a temporary file: + QByteArray baData; + QBuffer buffer(&baData); + img.save(&buffer, "PNG", 100); + tooltip.append(QStringLiteral("
").arg(QString::fromLatin1(baData.toBase64()))); // how can i center the image? + tooltip.append(QLatin1String("
")); + } + tooltip.append(QStringLiteral("

name: %1

").arg(data.m_name)); + tooltip.append(QStringLiteral("

origin: %1

").arg(m_map->geoToMgrsString(data.m_originGeo.toPointF()))); + tooltip.append(QStringLiteral("

scale: %1

").arg(data.m_mapScale)); + tooltip.append(QStringLiteral("")); + return tooltip; + } + + switch (index.column()) + { + case NAME: + return data.m_name; + case ORIGIN_GEO: + return data.m_originGeo.toPointF(); + case ORIGIN_MGRS: + { + if (m_map) + return m_map->geoToMgrsString(data.m_originGeo.toPointF()); + return QString(); + } + case SCALE: + return data.m_mapScale; + case ICON: + return data.m_image; + } + + return QVariant(); +} + +void JumpPointModel::addUpdateItem(const JPData &jpData) +{ + int row = rowOfItem(jpData.m_name); + if (row >= 0) + setRowData(row, jpData); // UPDATE + else + insertRow(jpData); // APPEND +} + +bool JumpPointModel::removeItem(const QString &name) +{ + int row = rowOfItem(name); + if (row < 0) + return false; + removeRow(row); + return true; +} + +bool JumpPointModel::contains(const QString &name) +{ + return rowOfItem(name) >= 0; +} + +int JumpPointModel::rowOfItem(const QString &name) +{ + int row = -1; + for (const JPData &item : std::as_const(m_vecData)) + { + ++row; + if (item.m_name.compare(name) == 0) + return row; + } + return -1; +} + +JPData *JumpPointModel::getItemPtr(const QModelIndex &index) +{ + if (!indexIsValid(index)) + return nullptr; + + int row = index.row(); + if (row < 0 || row > m_vecData.size()) + return nullptr; + + return &m_vecData[row]; +} + +JPData *JumpPointModel::getItemPtr(const QString &name) +{ + int row = rowOfItem(name); + if (row < 0 || row > m_vecData.size()) + return nullptr; + return &m_vecData[row]; + +} + +QVector JumpPointModel::jumpPoints() +{ + return m_vecData; +} + +QStringList JumpPointModel::names() +{ + QStringList sl; + sl.reserve(m_vecData.size()); + + for (const JPData &item : std::as_const(m_vecData)) + sl.append(item.m_name); + return sl; +} + + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.h new file mode 100755 index 000000000..2f3d502dc --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_model.h @@ -0,0 +1,105 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "situation_map_export.h" +#include +#include +#include + +#include + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//---------------------------------------------------------------------------- +// JPData +//---------------------------------------------------------------------------- +class JPData +{ +public: + JPData() = default; + + explicit JPData(const QString &name, const Point3D &originGeo, double mapScale = 0.0) + : m_name(name), m_originGeo(originGeo), m_mapScale(mapScale) + {} + + ~JPData() + { + if (m_marker) + m_marker->removeMySelf(); + } + + bool hasMarker() const + { + return m_marker.valid(); + } + + QString m_name; + Point3D m_originGeo; + double m_mapScale = 0.0; + + RefPointer m_marker; + QImage m_image; +}; + + +//---------------------------------------------------------------------------- +// JumpPointModel +//---------------------------------------------------------------------------- + +class JumpPointModel: public LtsWidgets::LtsAbstractTableModel +{ + Q_OBJECT + using BaseClass = LtsWidgets::LtsAbstractTableModel; + +public: + enum Columns + { + NAME, + ORIGIN_GEO, + ORIGIN_MGRS, + SCALE, + ICON, + }; + + explicit JumpPointModel(QObject *parent = nullptr); + void setMap(Map *map); + + + + QVariant data(const QModelIndex &index, int role) const override final; + + void addUpdateItem(const JPData& jpData); + bool removeItem(const QString &name); + + bool contains(const QString &name); + int rowOfItem(const QString &name); + + JPData* getItemPtr(const QModelIndex &index); + JPData* getItemPtr(const QString &name); + + QVector jumpPoints(); + + QStringList names(); + +private: + Map *m_map = nullptr; + +}; + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.cpp new file mode 100755 index 000000000..012554eca --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.cpp @@ -0,0 +1,41 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ +#include "jump_point_namer.h" +#include "ui_jump_point_namer.h" + +namespace SituationMap +{ + +JumpPointNamer::JumpPointNamer(int count, const QPointF &coordGeo, QWidget *parent) + : LtsWidgets::LtsFramelessDialog(parent, LtsWidgets::Behaviour::Modal) + , m_ui(new Ui::JumpPointNamer) + , m_coord(coordGeo) +{ + m_ui->setupUi(this); + m_ui->leName->setText(QStringLiteral("JP_%1").arg(count)); +#ifdef TABLET_MODE + m_ui->leName->setFocusPolicy(Qt::ClickFocus); +#endif + connect(m_ui->ltsDialogButtonBox->getButton(LtsWidgets::DialogButton::Close), &QPushButton::clicked, this, &JumpPointNamer::close); + connect(m_ui->ltsDialogButtonBox->getButton(LtsWidgets::DialogButton::Ok), &QPushButton::clicked, this, &JumpPointNamer::onOk); +} + +JumpPointNamer::~JumpPointNamer() +{ + delete m_ui; +} + +void JumpPointNamer::onOk() +{ + if (m_ui->leName->text().size() > 0) + { + emit newName(m_ui->leName->text(), m_coord); + close(); + } +} +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.h new file mode 100755 index 000000000..120377b7a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.h @@ -0,0 +1,39 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + + +#include + +namespace Ui { +class JumpPointNamer; +} + +namespace SituationMap +{ + +class JumpPointNamer : public LtsWidgets::LtsFramelessDialog +{ + Q_OBJECT + +public: + explicit JumpPointNamer(int count, const QPointF &coordGeo, QWidget *parent = nullptr); + ~JumpPointNamer(); + +signals: + void newName(const QString &name, const QPointF &coordGeo); + +private: + void onOk(); + +private: + Ui::JumpPointNamer *m_ui; + QPointF m_coord; +}; + +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.ui b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.ui new file mode 100755 index 000000000..036153e12 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_namer.ui @@ -0,0 +1,46 @@ + + + JumpPointNamer + + + + 0 + 0 + 224 + 82 + + + + Set Jump Point Name + + + + + + + + Name: + + + + + + + + + + + + + + + + LtsWidgets::LtsDialogButtonBox + QWidget +
LtsWidgets/lts_dialog_button_box.h
+ 1 +
+
+ + +
diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.cpp new file mode 100755 index 000000000..5cbc2547d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.cpp @@ -0,0 +1,445 @@ +/** + * @RDE_HEADER@ + * Interface for JumpPointWidget + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +// Own +#include "jump_point_widget.h" +#include "jump_point_custom_view.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +// Qt +#include +#include +#include +#include +#include +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class JumpPointWidget::Private +//----------------------------------------------------------------------------- +class JumpPointWidget::Private +{ +public: + Private() = default; + + // OWN data: + SituationMap::Map *m_map = nullptr; // Use INTERFACE + QString m_layername; + QString m_debugInfo; + + RefPointer m_uniqueMarker; + + + QGridLayout *m_layout = nullptr; + + JumpPointModel *m_jumpPointModel = nullptr; + LtsDataView::LtsDataView *m_jumpPointView = nullptr; + + QMenu *m_actionMenu = nullptr; + QAction *m_removeAction = nullptr; + QModelIndex m_contextModelIndex; +}; // class JumpPointWidget::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class JumpPointWidget +//----------------------------------------------------------------------------- +JumpPointWidget::JumpPointWidget(const QString &layername, + Map *map, + const QString &debugInfo) + : BaseClass() + , d(new Private) + , m_logger(getLogger("JumpPointWidget")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << debugInfo << "'"; + + d->m_jumpPointModel = new JumpPointModel(this); + + + + if (map) + { + d->m_map = map; + d->m_layername = layername; + d->m_debugInfo = debugInfo; + + connect(d->m_map, &Map::beginDestroy, this, &JumpPointWidget::cleanup); + + d->m_jumpPointModel->setMap(d->m_map); + + + QVBoxLayout *vBox = new QVBoxLayout(); + this->setLayout(vBox); + + + QHBoxLayout *hBox = new QHBoxLayout(); + vBox->addLayout(hBox); + hBox->addWidget(new QLabel(tr("Jump Points:"))); + QPushButton *pbPosition = new QPushButton(QIcon(QLatin1String("icon:position.svg")), tr("Position")); + hBox->addWidget(pbPosition); + connect(pbPosition, &QPushButton::clicked, this, &JumpPointWidget::onGetPosition); + + pbPosition->setEnabled(d->m_map->mode() == Mode::Mode_2D); + + + d->m_jumpPointView = new LtsDataView::LtsDataView(this); + vBox->addWidget(d->m_jumpPointView); + d->m_jumpPointView->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + + d->m_jumpPointView->selectCustomView(); + d->m_jumpPointView->seamless(); + d->m_jumpPointView->setModel(d->m_jumpPointModel, [this]() + { + return new JumpPointCustomView(this); + }); + + // connect signals of TableView: + connect(d->m_jumpPointView, &LtsDataView::LtsDataView::signalEntered, this, &JumpPointWidget::onTableViewClicked); + connect(d->m_jumpPointView, &LtsDataView::LtsDataView::clicked, this, &JumpPointWidget::onTableViewSelectionChanged); + + d->m_jumpPointView->hideColumn(JumpPointModel::Columns::ORIGIN_GEO); // do not "print" + d->m_jumpPointView->hideColumn(JumpPointModel::Columns::ICON); // do not "print" + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no map"; + + + // context menue: + d->m_jumpPointView->setContextMenuPolicyPerView(LtsDataView::LtsDataView::CUSTOM, Qt::CustomContextMenu); + connect(d->m_jumpPointView, &LtsDataView::LtsDataView::signalContextMenuRequested, + this, &JumpPointWidget::onCustomContextMenuRequested); + + d->m_actionMenu = new QMenu(this); + d->m_removeAction = new QAction(tr("Remove Jump Point"), this); + d->m_actionMenu->addAction(d->m_removeAction); + connect(d->m_removeAction, &QAction::triggered, this, &JumpPointWidget::onRemoveAction); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +JumpPointWidget::~JumpPointWidget() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + this->cleanup(); + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + delete d; +} + +QStringList JumpPointWidget::names() +{ + if (!d->m_jumpPointModel) + return QStringList(); + return d->m_jumpPointModel->names(); +} + +QVector JumpPointWidget::jumpPoints() +{ + if (!d->m_jumpPointModel) + return QVector(); + return d->m_jumpPointModel->jumpPoints(); +} + +int JumpPointWidget::count() const +{ + if (!d->m_jumpPointModel) + return 0; + return d->m_jumpPointModel->rowCount(); +} + +bool JumpPointWidget::contains(const QString &name) +{ + if (!d->m_jumpPointModel) + return false; + return d->m_jumpPointModel->contains(name); +} + +bool JumpPointWidget::removeJumpPoint(const QString &name) +{ + return d->m_jumpPointModel->removeItem(name); +} + + +void JumpPointWidget::onGetPosition() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + if (!d->m_map) + return; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Workaround to make sure that cursor setting of rubberband will work. Don't know why this + * is necessary. ToDo find the reason why changing cursor failed and fix the underlying cause. + * + * JT 2017/05/04 + */ + d->m_map->sideBar()->hide(); + // QCoreApplication::process Events(); // 2017.11.03, N.Thoden, not necessary + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + // remove any pending/active rubberbands of our map: + SituationMap::RB::Rubberband::deleteAllActiveRubberbands(d->m_map); + + // create rb + RB::RubberbandPointFeature *rb = d->m_map->rubberbandFactory().create(this); + if (!rb || !rb->rubberbandIsAlive()) + { + if (rb) + rb->deleteMySelf(); + FATAL_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " create Rubberband failed!"; + return; + } + rb->setAutoDelete(true); + connect(rb, &RB::Rubberband::apply, this, &JumpPointWidget::onRubberbandApply); +} + +void JumpPointWidget::onRubberbandApply(RB::RubberbandBase *rb) +{ + QPolygonF coordGeo = rb->coordinateVector(); + if (coordGeo.size() != 1) + return; + connect(this, &JumpPointWidget::newName, this, &JumpPointWidget::onName); + + askUserForName(coordGeo.front()); +} + +void JumpPointWidget::askUserForName(const QPointF &coordGeo) +{ + JumpPointNamer *namer = new JumpPointNamer(this->count(), coordGeo, this); + namer->setAttribute(Qt::WA_DeleteOnClose); + namer->connect(namer, &JumpPointNamer::newName, this, &JumpPointWidget::newName); + namer->show(); +} + +void JumpPointWidget::onName(const QString &name, const QPointF & coordGeo) +{ + disconnect(this, &JumpPointWidget::newName, this, &JumpPointWidget::onName); + const double scale = d->m_map->scale(); + addJumpPoint(name, coordGeo, scale); +} + +void JumpPointWidget::onJumpPointHovered(const QString &name, bool enter) +{ + setJumpPointSelected(name, enter); +} + +void JumpPointWidget::onTableViewClicked(const QModelIndex &index) +{ + if (!d->m_jumpPointModel) + return; + + JPData *jumpPoint = d->m_jumpPointModel->getItemPtr(index); + if (!jumpPoint) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not found"; + return; + } + + removeJumpPointSelections(); + onJumpPointHovered(jumpPoint->m_name, true); +} + +void JumpPointWidget::onTableViewSelectionChanged(const QModelIndex &index) +{ + if (!d->m_jumpPointModel) + return; + + JPData *jumpPoint = d->m_jumpPointModel->getItemPtr(index); + if (!jumpPoint) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not found"; + return; + } + + centerMapToJumpPoint(jumpPoint->m_name); +} + +bool JumpPointWidget::addJumpPoint(const QString &name, const QPointF &originGeo, double mapScale) +{ + return addJumpPoint(name, Point3D::fromQPointF(originGeo), mapScale); +} + +bool JumpPointWidget::addJumpPoint(const QString &name, const Point3D &originGeo, double mapScale) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " name='" << name << "'" + << " originGeo=" << originGeo + << " mapScale=" << mapScale; + + if (d->m_layername.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no layername"; + return false; + } + + { + JPData jpData(name, originGeo, mapScale); + jpData.m_image = QImage(QLatin1String(":/situationMap/pushPinRedClipArt.png")).scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation); + d->m_jumpPointModel->addUpdateItem(jpData); + } + + + JPData *jumpPoint = d->m_jumpPointModel->getItemPtr(name); + if (!jumpPoint) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not found"; + return false; + } + + if (! jumpPoint->hasMarker()) + { + QString debugInfo = QStringLiteral("%1_JumpPoint(%2)").arg(d->m_debugInfo, name); + + jumpPoint->m_marker = d->m_map->objectFactory().create(d->m_layername, debugInfo); + if (! jumpPoint->m_marker.valid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " create GeoImage failed, debugInfo='" << d->m_debugInfo << "'"; + return false; + } + jumpPoint->m_marker->setProxyMarker(MarkerSymbolType::PushPin2D); + jumpPoint->m_marker->setPickable(false); + } + jumpPoint->m_mapScale = mapScale; + jumpPoint->m_originGeo = originGeo; + jumpPoint->m_marker->setOrigin(originGeo); + jumpPoint->m_marker->setIdentifier(name); + this->setJumpPointSelected(name, false); + + bool success = jumpPoint->m_marker->updateView(); + if (! success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed, debugInfo='" << d->m_debugInfo << "'"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" + << " END" + << " name='" << name << "'" + << " originGeo=" << originGeo + << " mapScale=" << mapScale; + return success; +} + +bool JumpPointWidget::centerMapToJumpPoint(const QString &name) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " name='" << name << "'"; + + JPData *jumpPoint = d->m_jumpPointModel->getItemPtr(name); + if (!jumpPoint) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not found"; + return false; + } + + const Point3D &originGeo = jumpPoint->m_originGeo; + const double mapScale = jumpPoint->m_mapScale; + + d->m_map->setCenterPoint(originGeo.toPointF(), mapScale); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return true; +} + +void JumpPointWidget::removeJumpPointSelections() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + if (!d->m_jumpPointModel) + return; + + const QStringList &sl = d->m_jumpPointModel->names(); + for (const auto &name : sl) + setJumpPointSelected(name, false); + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + + +bool JumpPointWidget::setJumpPointSelected(const QString &name, bool selected) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " name='" << name << "'" + << " selected='" << selected; + + JPData *jumpPoint = d->m_jumpPointModel->getItemPtr(name); + if (!jumpPoint) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not found"; + return false; + } + if (! jumpPoint->hasMarker()) + return false; + + + // jumpPoint->m_marker->setSelected(selected); + // jumpPoint->m_marker->setScale((selected) ? 1.5 : 1.0); + jumpPoint->m_marker->setProxyMarkerSize((selected) ? 30 : 20); + jumpPoint->m_marker->setDrawOrder((selected) ? 2 : 1); // selected should be above the other + jumpPoint->m_marker->rTextStyle().setSize((selected) ? 20 : 10); + bool success = jumpPoint->m_marker->updateView(); + if (! success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed, debugInfo='" << d->m_debugInfo << "'"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END" + << " name='" << name << "'" + << " selected='" << selected; + return success; +} + +void JumpPointWidget::onCustomContextMenuRequested(const QPoint &globalPos, const QModelIndex &indexSrc) +{ + d->m_contextModelIndex = indexSrc; + if (!d->m_contextModelIndex.isValid()) + return; + d->m_actionMenu->exec(globalPos); +} + +void JumpPointWidget::onRemoveAction() +{ + if (!d->m_contextModelIndex.isValid()) + return; + d->m_jumpPointModel->removeRow(d->m_contextModelIndex.row()); +} + +void JumpPointWidget::cleanup() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + d->m_jumpPointModel->clear(); + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +bool JumpPointWidget::event(QEvent *event) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " type " << event->type(); + + if (event->type() == QEvent::Type::Hide) + { + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" + << " hide Event -> removeJumpPointSelections..."; + this->removeJumpPointSelections(); + } + return BaseClass::event(event); +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.h new file mode 100755 index 000000000..9918d612c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/jump_point_widget.h @@ -0,0 +1,103 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "jump_point_model.h" +#include "situation_map_export.h" +#include +#include +#include + +// +#include + +#include + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//Forward: +class Map; + +namespace RB +{ +class RubberbandBase; +} + +//---------------------------------------------------------------------------- +// JumpPointWidget +//---------------------------------------------------------------------------- +class SITUATIONMAP_EXPORT JumpPointWidget : public QWidget +{ + Q_OBJECT + + using BaseClass = QWidget; + +public: + explicit JumpPointWidget(const QString &layername, + Map *map, + const QString &debugInfo); + virtual ~JumpPointWidget(); + + QStringList names(); + QVector jumpPoints(); + + int count() const; + bool contains(const QString &name); +signals: + void newName(const QString &name, const QPointF &coordGeo); + +public Q_SLOTS: + void onGetPosition(); + void onRubberbandApply(RB::RubberbandBase *rb); + void askUserForName(const QPointF &coordGeo); + + void onName(const QString &name, const QPointF & coordGeo); + void onJumpPointHovered(const QString &name, bool enter); + + void onTableViewClicked(const QModelIndex &index); + void onTableViewSelectionChanged(const QModelIndex &index); + + bool addJumpPoint(const QString &name, const Point3D &originGeo, double mapScale = -1); + bool addJumpPoint(const QString &name, const QPointF &originGeo, double mapScale = -1); + bool centerMapToJumpPoint(const QString &name); + bool removeJumpPoint(const QString &name); + + void removeJumpPointSelections(); + bool setJumpPointSelected(const QString &name, bool selected = true); + + void onCustomContextMenuRequested(const QPoint &globalPos, const QModelIndex &indexSrc); + void onRemoveAction(); + + void cleanup(); + +protected: + bool event(QEvent *event) override; + + +private: + + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.cpp new file mode 100755 index 000000000..826b69ece --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.cpp @@ -0,0 +1,1134 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +// Own +#include "layer_control_widget.h" +#include +#include +#include + + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +namespace SituationMap +{ + +const QLatin1String MIMETYPE("application/x-dndEsriLayerItemData"); + +//---------------------------------------------------------------------------- +const char LayernameProperty[] = "layername"; +//---------------------------------------------------------------------------- + + + +class DragableCheckBox : public QCheckBox +{ + Q_OBJECT + using BaseClass = QCheckBox; + +public: + DragableCheckBox() = default; + explicit DragableCheckBox(const QString &text) + : QCheckBox(text) + {} + + ~DragableCheckBox() = default; + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + +private: + QPoint m_dragStartPos; +}; // class LayerControlWidget::Private +//----------------------------------------------------------------------------- + +void DragableCheckBox::mousePressEvent(QMouseEvent *event) +{ +// qWarning() << LOGFUNCTION << " START"; + + if (event->button() == Qt::LeftButton) + m_dragStartPos = event->pos(); + + BaseClass::mousePressEvent(event); + +// qWarning() << LOGFUNCTION << " END"; +} + +void DragableCheckBox::mouseMoveEvent(QMouseEvent *event) +{ +// qWarning() << LOGFUNCTION << " START"; + + if (!(event->buttons() & Qt::LeftButton)) + return; + if ((event->pos() - m_dragStartPos).manhattanLength() + < QApplication::startDragDistance()) + return; + + QByteArray itemData; + QDataStream dataStream(&itemData, QIODevice::WriteOnly); + dataStream << this->property(LayernameProperty).toString(); // == layername + + QMimeData *mimeData = new QMimeData; + mimeData->setData(MIMETYPE, itemData); + + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + + drag->exec(Qt::MoveAction); + +// qWarning() << LOGFUNCTION << " END"; +} +//----------------------------------------------------------------------------- + +struct SliderStruct +{ + QCheckBox *cbOnOff = nullptr; + QSlider *sl_opacity = nullptr; + QSlider *sl_brightness = nullptr; + QSlider *sl_contrast = nullptr; + QSlider *sl_gamma = nullptr; +}; + +class LayerControlWidget::Private +{ + +public: + Private() = default; + + // OWN data: + SituationMap::Map *m_map = nullptr; // Use INTERFACE + QString m_debugInfo; + + bool m_zoomRotScaleEnabled = true; + bool m_layerEnabled = true; + bool m_basemapEnabled = true; + + + QVBoxLayout *m_layout = nullptr; + + // MapLayer: + QGridLayout *m_mapLayerLayout = nullptr; + + // BaseMapSelection: + QHBoxLayout *m_baseMapSelectLayout = nullptr; + QList m_baseMapSelectIcons; + QLabel *m_lbBasemap = nullptr; + + + + QMap m_sliderMap; + + // Zoom and rotation + QHBoxLayout *m_zoomLayout = nullptr; // will contain zoom and rotation elements + QSlider *m_zoomSlider = nullptr; + QSlider *m_rotationSlider = nullptr; + QSlider *m_symbolSizeSlider = nullptr; // 10 ... 200 + QTimer *m_symbolSizeTimer = nullptr; + +}; // class LayerControlWidget::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class LayerControlWidget +//----------------------------------------------------------------------------- +LayerControlWidget::LayerControlWidget(QWidget *parent, + Map *map, + bool enableLayer, + bool enableBasemap, + const QString &debugInfo) + : QWidget(parent) + , d(new Private) + , m_logger(getLogger("LayerControlWidget")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << debugInfo << "'"; + + d->m_debugInfo = debugInfo; + d->m_map = map; + d->m_layerEnabled = enableLayer; + d->m_basemapEnabled = enableBasemap; + + + d->m_layout = new QVBoxLayout(); + this->setLayout(d->m_layout); + setAcceptDrops(true); + + // use QueuedConnect to let dropEvent (and destroying of QDrag object) finish + // BEFORE the panel is updated (and DragableCheckBox - parent of QDrag object - are deleted.) + connect(d->m_map, &Map::layerOrderChanged, this, &LayerControlWidget::updatePanelLayer, Qt::ConnectionType::QueuedConnection); + connect(d->m_map, &Map::layerParameterChanged, this, &LayerControlWidget::onLayerParamterChanged); + connect(d->m_map, &Map::mapReady, this, &LayerControlWidget::onMapReady, Qt::ConnectionType::QueuedConnection); + + + updatePanel(); + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +LayerControlWidget::~LayerControlWidget() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + delete d; +} + + +bool LayerControlWidget::updatePanel() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + updatePanelZoomRotationScales(); + updatePanelLayer(); +// updatePanelBasemap(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return true; +} + +bool LayerControlWidget::updatePanelZoomRotationScales() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + if (! d->m_zoomRotScaleEnabled) + return false; + + if (d->m_zoomLayout) + return true; + + d->m_zoomLayout = new QHBoxLayout(); + d->m_layout->addLayout(d->m_zoomLayout); + + d->m_zoomSlider = generateZoomSlider(); + d->m_zoomLayout->addWidget(d->m_zoomSlider); + + // problems around scale in 3D: + d->m_zoomSlider->setEnabled(d->m_map->mode() == Mode::Mode_2D); + + + QComboBox *cmbScale = new QComboBox(); + d->m_zoomLayout->addWidget(cmbScale); + + cmbScale->addItem(tr("Presets")); + cmbScale->insertSeparator(1); + + using scaleDataTyp = QPair; + const auto scaleData = { + scaleDataTyp(QLatin1String(" 1:25000"), 400000), + scaleDataTyp(QLatin1String(" 1:50000"), 200000), + scaleDataTyp(QLatin1String("1:100000"), 100000) + }; + + for (const scaleDataTyp &item : scaleData) + cmbScale->addItem(item.first, QVariant(item.second)); // stored in Qt::UserRole + + // c++11 -> lambda :-) + connect(cmbScale, static_cast(&QComboBox::activated), this, [this, cmbScale](int index) + { + if (index <= 1) return; // 1 Damit 'Presets' + Separator ohne Auswirkung bleibt + + int selectedScale = cmbScale->currentData().toInt(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " cmbScale selectedScale=" << selectedScale; + d->m_map->setScale(selectedScale); + + cmbScale->setCurrentIndex(0); // Textausgabe auf 'Presets' setzen + }); + + + // + // Rotation: + // + if (! d->m_rotationSlider) + { + d->m_rotationSlider = generateRotationSlider(); + d->m_layout->addWidget(d->m_rotationSlider); + } + + // + // SymbolSize: + // + if (! d->m_symbolSizeSlider) + { + d->m_symbolSizeSlider = generateSymbolSizeSlider(); + d->m_layout->addWidget(d->m_symbolSizeSlider); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return true; +} + +bool LayerControlWidget::updatePanelLayer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + + // + // Layer: + // + + // reNew the part of mapLayer : + if (d->m_mapLayerLayout) + { + for (int row = 0; row < d->m_mapLayerLayout->rowCount(); ++row) + { + for (int col = 0; col < d->m_mapLayerLayout->columnCount(); ++col) + { + QLayoutItem *item = d->m_mapLayerLayout->itemAtPosition(row, col); + if (item) + { + QWidget *w = item->widget(); + delete w; + // w->deleteLater() --> QCoreApplication::postEvent: Unexpected null receiver (ication/ngl_qt_msg_handler.inc:25) + } + } + } + } + delete d->m_mapLayerLayout; + d->m_mapLayerLayout = nullptr; + d->m_sliderMap.clear(); + + + + if (! d->m_layerEnabled) + return false; + + + d->m_mapLayerLayout = new QGridLayout(); + d->m_layout->addLayout(d->m_mapLayerLayout); + + int row = 0; + + QCheckBox *cbExpand = new QCheckBox(tr("Expand"), this); + d->m_mapLayerLayout->addWidget(cbExpand, row++, 0); + + cbExpand->setChecked(false); + connect(cbExpand, &QCheckBox::clicked, this, &LayerControlWidget::expandCollapse); + + + // + // Fill MapLayerLayout : + // + if (d->m_map) + { + int col = 0; + + const auto header = { + tr("Layer"), + tr("Opacity"), + tr("Brightness"), + tr("Contrast"), + tr("Gamma") + }; + + for (const QString &name : header) + d->m_mapLayerLayout->addWidget(new QLabel(name), row, col++); + + const QStringList sl = d->m_map->layerFactory().layernames(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " layers=" << sl.join(QLatin1Char(';')); + + row += sl.count(); + + for (const QString &layername : sl) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " layerName=" << layername; + int col = 0; + + const LayerParameter &lp = d->m_map->layerFactory().layerParameter(layername); + const LayerType &layerType = d->m_map->layerFactory().layerType(layername); + + DragableCheckBox *cbOnOff = new DragableCheckBox(); + if (lp.displayName.isEmpty()) + cbOnOff->setText(layername); + else + cbOnOff->setText(lp.displayName); + cbOnOff->setProperty(LayernameProperty, layername); + d->m_sliderMap[layername].cbOnOff = cbOnOff; + cbOnOff->setObjectName(QLatin1String("DragableCheckBox_") + layername); + cbOnOff->setChecked(lp.visible); + + + const QString &tooltip = generateToolTip(layername, lp); + if (!tooltip.isEmpty()) + cbOnOff->setToolTip(tooltip); + + connect(cbOnOff, &QCheckBox::stateChanged, this, &LayerControlWidget::layerOnOff); + d->m_mapLayerLayout->addWidget(cbOnOff, row, col++); + + + mySlider *sl_opac = new mySlider(layername, 0, 100); + d->m_sliderMap[layername].sl_opacity = sl_opac; + sl_opac->setObjectName(QLatin1String("sl_opac")); + sl_opac->setValue(lp.opacity); + connect(sl_opac, &mySlider::valueChanged, this, &LayerControlWidget::opacity); + d->m_mapLayerLayout->addWidget(sl_opac, row, col++); + + if (layerType == SituationMap::LayerType::Basemap) + { + mySlider *sl_brightness = new mySlider(layername, -100, 100); + d->m_sliderMap[layername].sl_brightness = sl_brightness; + sl_brightness->setObjectName(QLatin1String("sl_brightness")); + sl_brightness->setValue(lp.brightness); + connect(sl_brightness, &mySlider::valueChanged, this, &LayerControlWidget::brightness); + d->m_mapLayerLayout->addWidget(sl_brightness, row, col++); + + mySlider *sl_contrast = new mySlider(layername, -100, 100); + d->m_sliderMap[layername].sl_contrast = sl_contrast; + sl_contrast->setObjectName(QLatin1String("sl_contrast")); + sl_contrast->setValue(lp.contrast); + connect(sl_contrast, &mySlider::valueChanged, this, &LayerControlWidget::contrast); + d->m_mapLayerLayout->addWidget(sl_contrast, row, col++); + + mySlider *sl_gamma = new mySlider(layername, -100, 100); + d->m_sliderMap[layername].sl_gamma = sl_gamma; + sl_gamma->setObjectName(QLatin1String("sl_gamma")); + sl_gamma->setValue(lp.gamma); + connect(sl_gamma, &mySlider::valueChanged, this, &LayerControlWidget::gamma); + d->m_mapLayerLayout->addWidget(sl_gamma, row, col++); + } + + --row; + } + +#ifdef ONLY_FOR_TESTS + QPushButton *pbAddBasemapLayer = new QPushButton(QLatin1String("add BasemapLayer")); + d->m_mapLayerLayout->addWidget(pbAddBasemapLayer); + + QLatin1String newBasemapName("basemapAddedByBtn"); + QLatin1String newBasemapUrl("http://192.168.133.88:6080/arcgis/rest/services/BremenGeoTiff/MapServer"); + // QLatin1String newBasemap("http://192.168.133.88:6080/arcgis/rest/services/BremenGeoTiffUTM/MapServer"); + pbAddBasemapLayer->setToolTip(newBasemapUrl); + + connect(pbAddBasemapLayer, &QPushButton::clicked, this, [this]() + { + bool success = d->m_map->layerFactory().addBasemap(newBasemapName, newBasemapUrl); + INFO_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " adding basemapLayer, success=" << success; + }); + + QPushButton *pbRemoveBasemapLayer = new QPushButton(QLatin1String("remove BasemapLayer")); + d->m_mapLayerLayout->addWidget(pbRemoveBasemapLayer); + + pbRemoveBasemapLayer->setToolTip(newBasemapUrl); + + connect(pbRemoveBasemapLayer, &QPushButton::clicked, this, [this]() + { + bool success = d->m_map->layerFactory().removeBasemap(newBasemapName); + INFO_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " removing basemapLayer, success=" << success; + }); +#endif + + expandCollapse(cbExpand->isChecked()); + + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " no map !!!"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return true; +} + +bool LayerControlWidget::updatePanelBasemap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + // clear old widgets: + d->m_baseMapSelectIcons.clear(); + if (d->m_baseMapSelectLayout) + { + QLayoutItem *item; + while ((item = d->m_baseMapSelectLayout->takeAt(0))) + { + QWidget *w = item->widget(); + delete w; + delete item; + } + } + delete d->m_baseMapSelectLayout; + d->m_baseMapSelectLayout = nullptr; + + + if (! d->m_basemapEnabled) + return false; + + + + // create from scratch: + if (! d->m_baseMapSelectLayout) + { + QVBoxLayout *vBox = new QVBoxLayout(); + + if (! d->m_lbBasemap) + d->m_lbBasemap = new QLabel(tr("Basemaps:")); + + vBox->addWidget(d->m_lbBasemap); + + d->m_baseMapSelectLayout = new QHBoxLayout(); + vBox->addLayout(d->m_baseMapSelectLayout); + + d->m_layout->addLayout(vBox); + + LayerFactory &layFac = d->m_map->layerFactory(); + const QStringList &basemapLayernames = layFac.layernames(LayerType::Basemap); + + for (const QString &layername : basemapLayernames) + { + // baseMapProxy->setStyleSheet(QLatin1String("QPushButton {background-color: transparent; border: 1px solid transparent;}")); + const LayerParameter &lp = d->m_map->layerFactory().layerParameter(layername); + + QToolButton *baseMapProxy = new QToolButton(this); + + baseMapProxy->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); // eTextUnderIcon? + baseMapProxy->setText(lp.displayName); + + + baseMapProxy->setObjectName(d->m_debugInfo + layername); + baseMapProxy->setProperty(LayernameProperty, layername); + + baseMapProxy->setIcon(QIcon(QPixmap::fromImage(lp.previewImage))); + baseMapProxy->setIconSize(QSize(40,40)); + + baseMapProxy->setCheckable(true); + baseMapProxy->setChecked(true); // Assume that all are enable by default + + const QString &tooltip = generateToolTip(layername, lp); + if (!tooltip.isEmpty()) + baseMapProxy->setToolTip(tooltip); + + d->m_baseMapSelectLayout->addWidget(baseMapProxy); + d->m_baseMapSelectIcons.append(baseMapProxy); + connect(baseMapProxy, &QToolButton::clicked, this, &LayerControlWidget::onBaseMapSelection); + } // Q_FOREACH + + d->m_baseMapSelectLayout->addStretch(42); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return true; +} + +void LayerControlWidget::expandCollapse(bool expanded) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + for (int row = 1; row < d->m_mapLayerLayout->rowCount(); ++row) + { + for (int col = 2; col < d->m_mapLayerLayout->columnCount(); ++col) + { + QLayoutItem *item = d->m_mapLayerLayout->itemAtPosition(row, col); + if (item) + { + QWidget *w = item->widget(); + if (w) + w->setVisible(expanded); + } + } + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +QString LayerControlWidget::generateToolTip(const QString &layername, + const LayerParameter &lp) +{ + QString tooltip; + + tooltip.append(QStringLiteral("")); + + tooltip.append(QStringLiteral("

displayName=%1

").arg(lp.displayName)); + + if (! lp.previewImage.isNull()) + { + // without a temporary file: + QByteArray data; + QBuffer buffer(&data); + lp.previewImage.save(&buffer, "PNG", 100); + QString html(QLatin1String("
")); + html.append(QStringLiteral("").arg(QString::fromLatin1(data.toBase64()))); + tooltip.append(html); + } + + if (lp.basemapType != BasemapType::Undefined) + tooltip.append(QStringLiteral("

BasemapType=%1

").arg(toDisplayString(lp.basemapType))); + + + tooltip.append(QStringLiteral("

layername=%1

").arg(layername)); + tooltip.append(QStringLiteral("

description=%1

").arg(lp.description)); + tooltip.append(QStringLiteral("

svcDescription=%1

").arg(lp.svcDescription)); + tooltip.append(QStringLiteral("

url=%1

").arg(lp.url.toString())); + tooltip.append(QStringLiteral("

minScale=%1 - maxScale=%2

").arg((int)lp.minScale).arg((int)lp.maxScale)); + tooltip.append(QStringLiteral("")); + + return tooltip; +} + +QSlider *LayerControlWidget::generateZoomSlider() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + QSlider *slider = new QSlider(Qt::Orientation::Horizontal); + + // 1. Init + if (d->m_map->isReady()) + { + // This branch is taken in case of FunctionDialog: + onZoomSliderInit(slider); + } + + // 2. extentChanged + // Attention: use identical signal at any other zoom/scale-Slider!!! + connect(d->m_map, &Map::extentChangedDelayed, this, &LayerControlWidget::onMapExtentChanged); + + // 3. valueChanged + connect(slider, &QSlider::valueChanged, this, &LayerControlWidget::onZoomSliderValueChanged); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return slider; +} + + + +QSlider *LayerControlWidget::generateRotationSlider() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + QSlider *slider = new QSlider(); + slider->setToolTip(tr("Rotation")); + slider->setOrientation(Qt::Orientation::Horizontal); + slider->setTickPosition(QSlider::TicksBelow); + slider->setTickInterval(10); + slider->setSingleStep(10); + slider->setMinimum(0); + slider->setMaximum(359); + slider->setValue(0); + + if (d->m_map->isReady()) + slider->setValue(d->m_map->rotation()); + + connect(slider, &QSlider::valueChanged, this, &LayerControlWidget::onRotationSliderValueChanged); + connect(d->m_map, &Map::rotationChanged, this, &LayerControlWidget::onRotationOfMapChanged); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return slider; +} + +QSlider *LayerControlWidget::generateSymbolSizeSlider() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + QSlider *slider = new QSlider(); + slider->setToolTip(tr("Symbol Size")); + slider->setOrientation(Qt::Orientation::Horizontal); + slider->setTickPosition(QSlider::TicksBelow); + slider->setTickInterval(10); // 10% + slider->setMinimum(SituationMap::symbolScaleMinimum * 100); // 10% + slider->setMaximum(SituationMap::symbolScaleMaximum * 100); // 200% + slider->setSingleStep(10); // 10% + + if (d->m_map->isReady()) + slider->setValue(d->m_map->symbolScale() * 100); + + connect(slider, &QSlider::valueChanged, this, &LayerControlWidget::onSymbolSizeSliderValueChanged); + connect(d->m_map, &Map::symbolScaleChanged, this, &LayerControlWidget::onSymbolSizeOfMapChanged); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return slider; +} + + + +void LayerControlWidget::onZoomSliderInit(QSlider *slider) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + if (! slider) + return; + + const QSignalBlocker blocker(slider); + + + slider->setToolTip(tr("Zoom")); + slider->setMinimum(sqrt(d->m_map->maxScale())); + slider->setMaximum(sqrt(d->m_map->minScale())); + onMapExtentChanged(); + + if (slider->minimum() == slider->maximum()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " min/max value are identical, debugInfo='" << d->m_debugInfo << "'" + << " minimum=" << slider->minimum() + << " maximum=" << slider->maximum() + << " value=" << slider->value(); + + slider->setMinimum(sqrt(500)); + slider->setMaximum(sqrt(100000)); + + WARNING_LOG(m_logger) << LOGFUNCTION << " min/max value are identical, debugInfo='" << d->m_debugInfo << "'" + << " minimum=" << slider->minimum() + << " maximum=" << slider->maximum() + << " value=" << slider->value(); + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" + << " minimum=" << slider->minimum() + << " maximum=" << slider->maximum() + << " value=" << slider->value(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::onZoomSliderValueChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + QSlider *slider = qobject_cast(sender()); + if (! slider) + return; + + if (d->m_map->isReady()) + d->m_map->setScale(slider->value() * slider->value()); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" + << " new target value: " << slider->value() * slider->value(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::onRotationSliderValueChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " rawValue = " << d->m_rotationSlider->value(); + + if (d->m_map->isReady() +// && slider->isSliderDown() + ) + { +// ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " push new value " +// << " slider= " << slider +// << slider->value(); + d->m_map->setRotation(d->m_rotationSlider->value()); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::onRotationOfMapChanged(double rotation) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" << " rotation " << rotation; + + if (! d->m_rotationSlider) + return; + + if (d->m_map->isReady() && !d->m_rotationSlider->isSliderDown()) + { + const QSignalBlocker blocker(d->m_rotationSlider); + d->m_rotationSlider->setValue(rotation); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + + + + + +void LayerControlWidget::onSymbolSizeSliderValueChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " rawValue = " << d->m_symbolSizeSlider->value(); + + if (!d->m_symbolSizeTimer) + { + d->m_symbolSizeTimer = new QTimer(this); + d->m_symbolSizeTimer->setSingleShot(true); + d->m_symbolSizeTimer->setInterval(500); // msec + connect(d->m_symbolSizeTimer, &QTimer::timeout, this, &LayerControlWidget::onSymbolSizeSliderValueChangedTimeout); + } + + d->m_symbolSizeTimer->start(); // start -> restart + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::onSymbolSizeSliderValueChangedTimeout() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " rawValue = " << d->m_symbolSizeSlider->value(); + + double symSize = d->m_symbolSizeSlider->value() / 100.0; + d->m_map->setSymbolScale(symSize); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::onSymbolSizeOfMapChanged(double symbolSize) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" << " symbolSize " << symbolSize; + + if (! d->m_symbolSizeSlider) + return; + + int symSizeInt = symbolSize * 100; + const QSignalBlocker blocker(d->m_symbolSizeSlider); + d->m_symbolSizeSlider->setValue(symSizeInt); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + + + +void LayerControlWidget::onBaseMapSelection() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + QObject *pb = qobject_cast(sender()); + if (! pb) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " cast failed"; + return; + } + + // check/uncheck all: + for (auto widget : std::as_const(d->m_baseMapSelectIcons)) + widget->setChecked(widget == pb); + + + LayerFactory &layFac = d->m_map->layerFactory(); + const QStringList &basemapLayernames = layFac.layernames(LayerType::Basemap); + + const QString &curName = pb->property(LayernameProperty).toString(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " sender=" << curName; + for (const QString &name : basemapLayernames) + { + bool isCur = name.compare(curName) == 0; + LayerParameter lp = layFac.layerParameter(name); + if (isCur) + lp.resetView(); + lp.visible = isCur; + layFac.setLayerParameter(name, lp); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::onMapReady() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + onZoomSliderInit(d->m_zoomSlider); + onRotationOfMapChanged(d->m_map->rotation()); + onSymbolSizeOfMapChanged(d->m_map->symbolScale()); + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + if (d->m_zoomSlider && d->m_zoomSlider->isEnabled() && d->m_map->isReady() && (!d->m_zoomSlider->isSliderDown())) + { + const QSignalBlocker blocker(d->m_zoomSlider); + + float newVal = sqrt(d->m_map->scale()); + d->m_zoomSlider->setValue(newVal); + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" + << " d->m_map->scale(): " << d->m_map->scale() + << "; newVal: " << newVal + << "; value set to: " << d->m_zoomSlider->value(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + + + + +void LayerControlWidget::layerOnOff() +{ + QCheckBox *cb = qobject_cast(sender()); + if (!cb) + return; + + QString layername = cb->property(LayernameProperty).toString(); + + // get LayerParameter, update data and set: + LayerParameter lp = d->m_map->layerFactory().layerParameter(layername); + lp.visible = cb->isChecked(); + d->m_map->layerFactory().setLayerParameter(layername, lp); +} + +void LayerControlWidget::opacity(int) +{ + mySlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + + LayerParameter lp = d->m_map->layerFactory().layerParameter(layername); + lp.opacity = sl->value(); + d->m_map->layerFactory().setLayerParameter(layername, lp); + +} + +void LayerControlWidget::brightness(int) +{ + mySlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + + LayerParameter lp = d->m_map->layerFactory().layerParameter(layername); + lp.brightness = sl->value(); + d->m_map->layerFactory().setLayerParameter(layername, lp); +} + +void LayerControlWidget::contrast(int) +{ + mySlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + + LayerParameter lp = d->m_map->layerFactory().layerParameter(layername); + lp.contrast = sl->value(); + d->m_map->layerFactory().setLayerParameter(layername, lp); +} + +void LayerControlWidget::gamma(int) +{ + mySlider *sl = qobject_cast(sender()); + if (!sl) + return; + + QString layername = sl->m_layername; + + LayerParameter lp = d->m_map->layerFactory().layerParameter(layername); + lp.gamma = sl->value(); + d->m_map->layerFactory().setLayerParameter(layername, lp); +} + +void LayerControlWidget::onLayerParamterChanged(const QString &layername, const LayerParameter &newLayerParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START" + << " layername= '" << layername << "'"; + +// INFO_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" +// << " layername=" << layername +// << " lp=" << newLayerParameter; + + if (! d->m_sliderMap.contains(layername)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" + << " layername='" << layername << "' is new -> updatePanel"; + updatePanelLayer(); + return; + } + + // refresh GUI: + if (d->m_sliderMap.contains(layername)) + { + SliderStruct &data = d->m_sliderMap[layername]; + + if (data.cbOnOff) data.cbOnOff ->setChecked(newLayerParameter.visible); + if (data.sl_opacity) data.sl_opacity ->setValue(newLayerParameter.opacity); + if (data.sl_brightness) data.sl_brightness->setValue(newLayerParameter.brightness); + if (data.sl_contrast) data.sl_contrast ->setValue(newLayerParameter.contrast); + if (data.sl_gamma) data.sl_gamma ->setValue(newLayerParameter.gamma); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + + +QString LayerControlWidget::layernameAt(const QPoint &pos) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + QWidget *child = childAt(pos); + if (!child) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " no child"; + return QString(); + } + + DragableCheckBox *cb = dynamic_cast(child); + if (!cb) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " dynacast failed"; + return QString(); + } + + const QString &childLayername = cb->property(LayernameProperty).toString(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " childLayername=" << childLayername; + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return childLayername; +} + +QStringList LayerControlWidget::newLayerOrder(const QString &draggedLayername, const QString &childLayername) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + // get current order: + QStringList sl = d->m_map->layerFactory().layernames(); + + if (draggedLayername == childLayername) + return sl; + + // remove + sl.removeOne(draggedLayername); + + // find new position: + int idx = sl.indexOf(childLayername); + + // insert AFTER; + sl.insert(++idx, draggedLayername); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; + return sl; +} + + + + + + +void LayerControlWidget::dragMoveEvent(QDragMoveEvent *event) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + if (event->mimeData()->hasFormat(MIMETYPE)) + { + if (event->source() == this) + { + event->setDropAction(Qt::MoveAction); + event->accept(); + } + else + { + + QByteArray itemData = event->mimeData()->data(MIMETYPE); + QDataStream dataStream(&itemData, QIODevice::ReadOnly); + + QString draggedLayername; + dataStream >> draggedLayername; + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " draggedLayername=" << draggedLayername; + + const QString &childLayername = layernameAt(event->pos()); + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " childLayername=" << childLayername; + + if (childLayername.isEmpty() || + (draggedLayername == childLayername)) + event->ignore(); + else + + { + const QStringList &newOrder = newLayerOrder(draggedLayername, childLayername); + + bool ok = d->m_map->layerFactory().checkLayerOrder(newOrder); + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " layerOrder ok: " << ok; + + if (ok) + event->acceptProposedAction(); + else + event->ignore(); + } + + } + } + else + event->ignore(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + + + +void LayerControlWidget::dragEnterEvent(QDragEnterEvent *event) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + if (event->mimeData()->hasFormat(MIMETYPE)) + { + + event->acceptProposedAction(); + +// if (event->source() != this) { +//// event->setDropAction(Qt::MoveAction); +// event->accept(); +// } else { +// event->acceptProposedAction(); +// } + } + else + event->ignore(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + +void LayerControlWidget::dropEvent(QDropEvent *event) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " START"; + + if (event->mimeData()->hasFormat(MIMETYPE)) + { + QByteArray itemData = event->mimeData()->data(MIMETYPE); + QDataStream dataStream(&itemData, QIODevice::ReadOnly); + + QString draggedLayername; + dataStream >> draggedLayername; + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " draggedLayername=" << draggedLayername; + + + if (event->source() == this) + { + event->setDropAction(Qt::MoveAction); + event->accept(); + } + else + { + event->acceptProposedAction(); + + const QString &childLayername = layernameAt(event->pos()); + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " childLayername=" << childLayername; + + if (draggedLayername == childLayername) + return; + + const QStringList &newOrder = newLayerOrder(draggedLayername, childLayername); + + bool ok = d->m_map->layerFactory().orderLayerTo(newOrder); + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " layerOrder ok: " << ok; + } + } + else + event->ignore(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << d->m_debugInfo << "'" << " END"; +} + + + +} // namespace SituationMap + +#include "layer_control_widget.moc" diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.h new file mode 100755 index 000000000..e7974d96d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_control_widget.h @@ -0,0 +1,125 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "situation_map_export.h" +#include + + +// Qt +#include + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//Forward: +class Map; +class LayerParameter; + + +class mySlider : public QSlider +{ + Q_OBJECT + +public: + mySlider(const QString &layername, int min, int max) + : QSlider(), m_layername(layername) + { + this->setOrientation(Qt::Horizontal); + setMinimum(min); + setMaximum(max); + } + +public: + QString m_layername; +}; + + + +class SITUATIONMAP_EXPORT LayerControlWidget : public QWidget +{ + Q_OBJECT + +public: + explicit LayerControlWidget(QWidget *parent, + Map *map, + bool enableLayer, + bool enableBasemap, + const QString &debugInfo); + ~LayerControlWidget(); + + bool updatePanel(); +private: + bool updatePanelZoomRotationScales(); + bool updatePanelLayer(); + bool updatePanelBasemap(); + QString generateToolTip(const QString &layername, const LayerParameter &lp); + QSlider *generateZoomSlider(); + QSlider *generateRotationSlider(); + QSlider *generateSymbolSizeSlider(); + +private slots: + +protected: + void dragMoveEvent(QDragMoveEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + + +private slots: + void expandCollapse(bool expanded); + void layerOnOff(); + void opacity(int); + void brightness(int); + void contrast(int); + void gamma(int); + + void onLayerParamterChanged(const QString &layername, + const LayerParameter &newLayerParameter); + + void onZoomSliderInit(QSlider *slider); + void onZoomSliderValueChanged(); + + void onRotationSliderValueChanged(); + void onRotationOfMapChanged(double rotation); + + void onSymbolSizeSliderValueChanged(); + void onSymbolSizeSliderValueChangedTimeout(); + void onSymbolSizeOfMapChanged(double symbolSize); + + void onBaseMapSelection(); + + + // slots for external signals: + void onMapReady(); + void onMapExtentChanged(); + +private: + QString layernameAt(const QPoint &pos) const; + QStringList newLayerOrder(const QString &draggedLayername, + const QString &childLayername); + + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility + +}; + + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.cpp new file mode 100755 index 000000000..d33dbe2ca --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.cpp @@ -0,0 +1,3049 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of LayerFactory + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "layer_factory_impl.h" +#include "map_impl.h" +#include "abstract_map_object_impl.h" +#include +#include "logical_layer.h" +#include "location_to_elevation_helper.h" +//#include "tms_layer.h" +#include // for getResourcePath +#include + +#include + +// ESRI +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Qt +#include +#include +#include +#include +#include +#include +#include + +#include + +// FORCE_CREATION_GRAPHICSOVERLAY : +// N.Thoden, 10.02.2017 +// Esri Case #01887547 - [BOS] Dictionary Symbol crasches +// deals with order of creation of GraphicalOverlay and adding them to MapView. +// Although the reordering of the GraphicalOverlay is not included in the case, +// it seems to be relevant in this application. +// To find a quick solution, GraphicalOverlays are created as soon as possible +// (EsriDict must be loaded). +// +static bool s_forceCreationOfGraphicsOverlay = true; +// : FORCE_CREATION_GRAPHICSOVERLAY + + +namespace SituationMap +{ + +static const QLatin1String LayerNameJumpPoints("JumpPoints"); +static const QLatin1String LayerNameRubberband("RubberbandLayer"); +static const QLatin1String LayerNameTools("Tools"); +static const QLatin1String LayerNameSnapshotOverlay("SnaphotOverlay"); +static const QLatin1String LayerNameEmergencyCall("EmergencyCall"); + +// #define USE_ARCGIS_SCENE_LAYER_CACHE +#ifdef USE_ARCGIS_SCENE_LAYER_CACHE + +class ArcGISSceneLayerCache; +static ArcGISSceneLayerCache *s_arcGisSceneLayerCache = nullptr; + +/*! + * \brief The ArcGISSceneLayerCache class reduces the number of loading processes + * We are caching here to avoid numerous loading and unloading. + * At least at linux the ArcGISSceneLayerCache tends to alloc a hugh amount of data. + * Additionally allocation is influenced by the viewpoint choosen by the operator. + * And with other allocations (dds data, GBViewer of similar) at the same time + * the heap memory gets fragemented. + * So we try to reduce the number of loading processes. + */ +class ArcGISSceneLayerCache +{ +public: + static ArcGISSceneLayerCache *getInstance() + { + if (!s_arcGisSceneLayerCache) + { + s_arcGisSceneLayerCache = new ArcGISSceneLayerCache; + qAddPostRoutine(&ArcGISSceneLayerCache::cleanup); + } + return s_arcGisSceneLayerCache; + } + + struct Item + { + QPointer m_sceneLayer; + QPointer m_hostingScene; + }; + + Item& getFreeItemRef(const QString &itemName) + { + //Qt: If the map contains no item with key key, the function inserts a default-constructed value into the map with key key, and returns a reference to it. + QVector &vec = m_arcGISSceneLayerCache[itemName]; + + // search free item: + QMutableVectorIterator it(vec); + while (it.hasNext()) + { + Item& itemRef = it.next(); + if (itemRef.m_hostingScene.isNull()) + return itemRef; + } + + // Not found -> create: + vec.append(Item()); + return vec.last(); + } + + static void cleanup() + { + delete s_arcGisSceneLayerCache; + } + + ~ArcGISSceneLayerCache() + { + for (auto vec : m_arcGISSceneLayerCache) + { + for (auto item : vec) + { + std::cerr << " m_arcGISSceneLayerCache: delete " << item.m_sceneLayer << std::endl; + delete item.m_sceneLayer; // delete nullptr is ok + } + } + } + + void dump() + { + std::cerr << " ArcGISSceneLayer CacheSize = " << m_arcGISSceneLayerCache.size() << std::endl; + + for (auto vec : m_arcGISSceneLayerCache) + { + std::cerr << " ArcGISSceneLayer VectorSize: " << vec.size() << std::endl; + for (auto item : vec) + { + std::cerr << " ArcGISSceneLayer ITEM = " + << " m_hostingScene=" << item.m_hostingScene + << " m_sceneLayer=" << item.m_sceneLayer + << std::endl; + } // for + } // for + } + +private: + // slpk-name, .... + QMap> m_arcGISSceneLayerCache; +}; // class ArcGISSceneLayerCache + +#endif // USE_ARCGIS_SCENE_LAYER_CACHE + + +class LayerFactoryImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + + // ESRI data: + Esri::ArcGISRuntime::Basemap *m_basemap = nullptr; + + Esri::ArcGISRuntime::GraphicsOverlayListModel *getEsriGraphicsOverlayListModel() + { + if (m_mapImpl && m_mapImpl->getEsriGeoView()) + return m_mapImpl->getEsriGeoView()->graphicsOverlays(); + else + return nullptr; + } + + + Esri::ArcGISRuntime::GraphicsOverlay *m_functionDialogGraphicsLayer = nullptr; + + + // Own data: + SituationMap::MapImpl *m_mapImpl = nullptr; + QStringList m_mapServiceUrlList; + QVector m_sceneObjectUrlCont; + Esri::ArcGISRuntime::Surface *m_baseSurface = nullptr; + bool m_isSceneObjectLayerVisible = true; + + QTimer *reCalculateMinMaxScaleTimer = nullptr; + + MapLayerOrderedList m_orderedMapLayer; // store order of basemap and logical Layernames + + bool hasLogicalLayer(const QString &logicalName) + { + return m_logicalLayerCont.contains(logicalName); + } + + // create and initalize LogicalLayer: + void createLogicalLayer(const QString &logicalName) + { + // LayerListModel + Esri::ArcGISRuntime::LayerListModel *layerListModel = nullptr; + if (m_basemap) + layerListModel = m_basemap->baseLayers(); + + // GraphicsOverlayListModel + Esri::ArcGISRuntime::GraphicsOverlayListModel *graphicsOverlayListModel = this->getEsriGraphicsOverlayListModel(); + +#ifdef LTS_DEBUG + if (!graphicsOverlayListModel) + qCritical() << " graphicsOverlayListModel" + << " logicalName='" << logicalName << "'" + << " m_mapImpl=" << m_mapImpl + << " m_mapImpl->getEsriGeoView()=" << m_mapImpl->getEsriGeoView() + << " m_mapImpl->isReady=" << m_mapImpl->isReady() + << " graphicsOverlayListModel=" << graphicsOverlayListModel; + Q_ASSERT(graphicsOverlayListModel); +#endif // LTS_DEBUG + + QSharedPointer ll = QSharedPointer(new LogicalLayer(logicalName)); + ll->init(m_mapImpl, layerListModel, graphicsOverlayListModel); + m_logicalLayerCont[logicalName] = ll; + } + + QSharedPointer getLogicalLayer(const QString &logicalName) const + { + if (m_logicalLayerCont.contains(logicalName)) + return m_logicalLayerCont[logicalName]; + return QSharedPointer(); + } + void removeLogicalLayer(const QString &logicalName) + { + m_logicalLayerCont.remove(logicalName); + } + + QMap> m_logicalLayerCont; // store logical layer without order + + QString m_basemapLayernamePrefix = QLatin1String("ArcGISTiledLayer_URL_"); + + QString m_participantLayername = QLatin1String("Participants"); + RenderingMode m_participantLayerRenderingMode = RenderingMode::Dynamic; + SurfacePlacement m_participantLayerSurfacePlacement = SurfacePlacement::Relative; + + + QString m_logicalLayerNameForCubes; + +#if ACRGIS_VERSION < 0x640500 // 100.5.0 / Update 5 + QColor m_selectionColor = Qt::magenta; +#endif // ACRGIS_VERSION + + +}; // class LayerFactoryImpl::Private +//----------------------------------------------------------------------------- + +// +// class LayerFactoryImpl; +// +LayerFactoryImpl::LayerFactoryImpl(QObject *parent, SituationMap::MapImpl *mapImpl) + : LayerFactory() + , BaseClass(parent) + , m_logger(getLogger("LayerFactoryImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_mapImpl = mapImpl; + if (d->m_mapImpl) + d->m_basemap = d->m_mapImpl->getEsriBasemap(); + + d->reCalculateMinMaxScaleTimer = new QTimer(this); + d->reCalculateMinMaxScaleTimer->setInterval(500); + d->reCalculateMinMaxScaleTimer->setSingleShot(true); + connect(d->reCalculateMinMaxScaleTimer, &QTimer::timeout, + this, &LayerFactoryImpl::reCalculateMinMaxScaleBasedOnLayers); + + INFO_LOG(m_logger) << LOGFUNCTION << " Resource Path = '" << SituationMap::getResourcePath() << "'"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +LayerFactoryImpl::~LayerFactoryImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool LayerFactoryImpl::initializeBasemapBuiltIn(const QString &tpkFile) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " tpkFile='" << tpkFile << "'"; + + QString tiledBaseMapLayer; + if (tpkFile.isEmpty()) + tiledBaseMapLayer = SituationMap::getResourcePath() + QLatin1String("Topographic.tpk"); + else + { + QFileInfo fi(tpkFile); + if (fi.isAbsolute()) + tiledBaseMapLayer = tpkFile; + else + { + tiledBaseMapLayer = SituationMap::getResourcePath() + tpkFile; + if (fi.suffix().isEmpty()) + tiledBaseMapLayer += QLatin1String(".tpk"); + } + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " tiledBaseMapLayer='" << tiledBaseMapLayer << "'"; + + //----------------------------------- + // Local Tiled Basemap Layer using: sdk/samples/data/tpks/Topographic.tpk + //----------------------------------- + Esri::ArcGISRuntime::TileCache *tpkCache = new Esri::ArcGISRuntime::TileCache(tiledBaseMapLayer, this); + tpkCache->setObjectName(QLatin1String("TPK-tpkCache")); + + Esri::ArcGISRuntime::ArcGISTiledLayer *tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(tpkCache, this); + tiledLayer->setName(QLatin1String("TPK-tiledLayer")); + + + // loadStatusChanged -> + connect(tpkCache, &Esri::ArcGISRuntime::TileCache::loadStatusChanged, this, [this](Esri::ArcGISRuntime::LoadStatus loadStatus) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " TileCache: " << "loadStatus changed to: " << loadStatus; + }); + connect(tiledLayer, &Esri::ArcGISRuntime::ArcGISTiledLayer::loadStatusChanged, this, [this](Esri::ArcGISRuntime::LoadStatus loadStatus) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " ArcGISTiledLayer: " << "loadStatus changed to: " << loadStatus; + }); + + + QString logicalLayerName = QStringLiteral("%1%2").arg(d->m_basemapLayernamePrefix).arg(0); + tiledLayer->setName(logicalLayerName); + tiledLayer->setObjectName(tiledLayer->name()); + + QSharedPointer logicalLayer = addLayerToMgnt(logicalLayerName, LayerType::Basemap); + if (logicalLayer.isNull()) + ERROR_LOG(m_logger) << LOGFUNCTION << " getLogicalLayer invalid'" << logicalLayerName << "'"; + else + { + // update LayerParameter first: + LayerParameter lp = logicalLayer->layerParameter(); + lp.url = QUrl(tiledBaseMapLayer); + lp.displayName = logicalLayerName; + logicalLayer->setLayerParameter(lp); + + logicalLayer->setLayer(tiledLayer); + logicalLayer->setLayerType(LayerType::Basemap); + logicalLayer->setBasemapType(BasemapType::Topographic); // basemapType -> aerial, topo... + } + + + int basemapCount = -1; + if (d->m_basemap->baseLayers()) + basemapCount = d->m_basemap->baseLayers()->size(); + + INFO_LOG(m_logger) << LOGFUNCTION << " basemapCount = " << basemapCount; + + d->m_basemap->baseLayers()->append(tiledLayer); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool LayerFactoryImpl::isUsingBaseMapBuiltIn() const +{ + return d->m_mapServiceUrlList.isEmpty(); +} + +bool LayerFactoryImpl::addOperationalLayer(const QString &logicalLayerName, + const QString &displayName, + RenderingMode renderMode, + SurfacePlacement surfacePlacement) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " logicalLayerName='" << logicalLayerName << "'" + << " displayName='" << displayName << "'" + << " renderMode=" << renderMode + << " surfacePlacement=" << surfacePlacement; + + if (s_forceCreationOfGraphicsOverlay && !logicalLayerName.startsWith(QLatin1String("Participant"), Qt::CaseInsensitive)) + { + // First, wait until esriDict is load, second force creation of esriLayer: + + // wait until esriDict is load: + AdvancedSymbology::SymbolDictionary &dict = d->m_mapImpl->symbolDictionary(); + AdvancedSymbology::SymbolDictionaryImpl &dictImpl = static_cast(dict); + + // keep SymbologyType::None in mind: + Esri::ArcGISRuntime::DictionarySymbolStyle *esriDict = const_cast(dictImpl.getEsriSymbolDictionary()); + + if (esriDict) + { + if (esriDict->loadStatus() != Esri::ArcGISRuntime::LoadStatus::Loaded) + { + QEventLoop loop; + connect(esriDict, &Esri::ArcGISRuntime::DictionarySymbolStyle::doneLoading, + &loop, &QEventLoop::quit); + QTimer::singleShot(2000, &loop, &QEventLoop::quit); + loop.exec(); + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " FORCE_CREATION_GRAPHICSOVERLAY: " + << " esriDict: loadStatus=" << esriDict->loadStatus(); + } + } // if (s_forceCreationOfGraphicsOverlay) + + const LayerType layerType = LayerType::Operational; + + if (! layerExistsInMngt(logicalLayerName, layerType)) + { + QSharedPointer logicalLayer = addLayerToMgnt(logicalLayerName, layerType); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addLayer failed: '" + << logicalLayerName << "'"; + return false; + } + LayerParameter lp = logicalLayer->layerParameter(); + lp.displayName = displayName; + logicalLayer->setLayerParameter(lp); + logicalLayer->setRenderingMode(renderMode); + logicalLayer->setSurfacePlacement(surfacePlacement); + } + + + // + // the real Esri-Layers will be create later on demand :-) + // + + //#ifdef FORCE_CREATION_GRAPHICSOVERLAY + if (s_forceCreationOfGraphicsOverlay && !logicalLayerName.startsWith(QLatin1String("Participant"), Qt::CaseInsensitive)) + { + QSharedPointer logicalLayer = d->getLogicalLayer(logicalLayerName); + if (logicalLayer) + { + logicalLayer->getCreateGraphicalOverlay(TechnicalLayerType::GraphicsLayer); + if (d->m_mapImpl->symbologyType() != AdvancedSymbology::SymbologyType::None) + logicalLayer->getCreateGraphicalOverlay(TechnicalLayerType::GraphicsLayer_Dict); + DEBUG_LOG(m_logger) << LOGFUNCTION << " FORCE_CREATION_GRAPHICSOVERLAY"; + } + } // if (s_forceCreationOfGraphicsOverlay) + //#endif // FORCE_CREATION_GRAPHICSOVERLAY + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " logicalLayerName='" << logicalLayerName << "'" + << " success=" << true; + return true; +} + + +bool LayerFactoryImpl::removeOperationalLayer(const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " logicalLayerName='" << logicalLayerName << "'"; + + if (logicalLayerName.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName empty" + << logicalLayerName; + return false; + } + + const LayerType layerType = LayerType::Operational; + + if (! d->m_orderedMapLayer.contains(logicalLayerName, layerType)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName does not exist: '" + << logicalLayerName << "'"; + return false; + } + + if (removeLayerInternal(layerType, logicalLayerName)) + d->m_mapImpl->emitLayerOrderChanged(); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " logicalLayerName='" << logicalLayerName << "'"; + return true; +} + +bool LayerFactoryImpl::hasOperationalLayer(const QString &logicalLayerName) const +{ + if (logicalLayerName.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName empty" + << logicalLayerName; + return false; + } + return layerExistsInMngt(logicalLayerName, LayerType::Operational); +} + + +bool LayerFactoryImpl::addOverlayLayer(const QString &logicalLayerName, const QString &displayName, bool isSnaphot) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " logicalLayerName='" << logicalLayerName << "'" + << " displayName='" << displayName << "'" + << " isSnaphot='" << isSnaphot; + + const LayerType layerType = (isSnaphot) ? LayerType::SnapshotOverlay : LayerType::Overlay; + + if (! layerExistsInMngt(logicalLayerName, layerType)) + { + QSharedPointer logicalLayer = addLayerToMgnt(logicalLayerName, layerType); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addLayer failed: '" + << logicalLayerName << "'"; + return false; + } + // logicalLayer->setRenderingMode(RenderingMode::Dynamic); + } + + // update displayName: + QSharedPointer logicalLayer = d->getLogicalLayer(logicalLayerName); + if (logicalLayer.isNull()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " getLogicalLayer failed: '" + << logicalLayerName << "'"; + } else { + LayerParameter lp = logicalLayer->layerParameter(); + lp.displayName = displayName; + logicalLayer->setLayerParameter(lp); + } + + + // + // the real Esri-Layers will be create later on demand :-) + // + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +QString LayerFactoryImpl::technicalNameOfOverlayLayer(const QString &logicalLayerName) +{ + // By default, our overlays are displayed on just one GraphicsLayer: + return createInternalLayerName(TechnicalLayerType::GraphicsLayer, logicalLayerName); +} + + +QSharedPointer LayerFactoryImpl::addLayerToMgnt(const QString &logicalLayerName, const LayerType &layerType) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layerType=" << layerType + << " logicalLayerName='" << logicalLayerName << "'"; + + QSharedPointer logicalLayer; + + if (logicalLayerName.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName empty" + << logicalLayerName; + return logicalLayer; + } + if (d->m_orderedMapLayer.contains(logicalLayerName, LayerType::Undefined)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName already exists: '" + << logicalLayerName << "'"; + return logicalLayer; + } + + // 1. add to orderedList: + + if (! d->m_orderedMapLayer.addLayer(logicalLayerName, layerType)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addLayer failed: '" + << logicalLayerName << "'" + << " layerType=" << layerType; + return logicalLayer; + } + + // getLogicalLayer will + // - initialize + // - do errorHandling + // - update LayerParameter on doneLoading + // - and add layer to LayerlistModel of basemap: + logicalLayer = this->getCreateLogicalLayer(logicalLayerName); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " getLogicalLayer invalid'" << logicalLayerName << "'"; + return logicalLayer; + } + logicalLayer->setLayerType(layerType); + + + DEBUG_LOG(m_logger) << LOGFUNCTION << " handleEmergencySpecials ..."; + handleEmergencyCallSpecials(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " handleEmergencySpecials ...DONE"; + + // trigger gui update: + d->m_mapImpl->emitLayerOrderChanged(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " layerType=" << layerType + << " logicalLayerName='" << logicalLayerName << "'"; + return logicalLayer; +} + +bool LayerFactoryImpl::layerExistsInMngt(const QString &logicalLayerName, const LayerType &layerType) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layerType=" << layerType + << " logicalLayerName='" << logicalLayerName << "'"; + + bool exists = d->m_orderedMapLayer.contains(logicalLayerName, layerType); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " layerType=" << layerType + << " logicalLayerName='" << logicalLayerName << "'" + << " exists=" << exists; + return exists; +} + +bool LayerFactoryImpl::removeOverlayLayer(const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layername='" << logicalLayerName << "'"; + + if (logicalLayerName.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName empty" + << logicalLayerName; + return false; + } + const MapLayer &mapLayer = d->m_orderedMapLayer.getLayer(logicalLayerName); + if (!mapLayer.isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName does not exist: '" + << logicalLayerName << "'"; + return false; + } + + const LayerType type = mapLayer.type(); + const bool isOverlayLayer = (type == LayerType::Overlay) || (type == LayerType::SnapshotOverlay); + if (!isOverlayLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName has wrong type: '" + << logicalLayerName << "'" + << " type = " << type; + return false; + } + + if (removeLayerInternal(type, logicalLayerName)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " remove..." + << " layername='" << logicalLayerName << "'"; + d->m_mapImpl->emitLayerOrderChanged(); + } + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " logicalLayerName='" << logicalLayerName << "'"; + return true; +} + +QString LayerFactoryImpl::snapshotOverlayLayername() const +{ + return LayerNameSnapshotOverlay; +} + +QString LayerFactoryImpl::layernameForCubes() const +{ + return d->m_logicalLayerNameForCubes; +} + +void LayerFactoryImpl::setLayernameForCubes(const QString &logicalName) +{ + d->m_logicalLayerNameForCubes = logicalName; +} + +bool LayerFactoryImpl::hasLayernameForCubes() const +{ + return !d->m_logicalLayerNameForCubes.isEmpty(); +} + + +bool LayerFactoryImpl::assignUniqueValueRendererForCubes() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalLayerNameForCubes='" << d->m_logicalLayerNameForCubes << "'"; + + if (d->m_mapImpl->mode() != Mode::Mode_3D) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not in 3Dmode"; + return false; + } + + if (d->m_logicalLayerNameForCubes.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_logicalLayerNameForCubes configured"; + return false; + } + + + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsOverlay = this->getGraphicsLayer(d->m_logicalLayerNameForCubes); + if (!graphicsOverlay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " graphicsOverlay not found, d->m_logicalLayerNameForCubes='" << d->m_logicalLayerNameForCubes << "'"; + return false; + } + + + const PartyIdToColorFunctor::ColorMap &colorMap = d->m_mapImpl->partyIdToColorFunctor().colorMap(); + if (colorMap.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " colorMap is empty"; + return false; + } + + + + // prepare UniqueValues + Esri::ArcGISRuntime::SimpleFillSymbol *sls; + QList uvList; + + QObject *parent = graphicsOverlay; + + for (const PartyIdToColorFunctor::PartyData &partyData : colorMap) + { + const QString &partyName = partyData.m_name; + const QColor &partyColor = partyData.m_color; + + INFO_LOG(m_logger) << LOGFUNCTION + << " partyName=" << partyName << "'" + << " partyColor=" << partyColor.name() << "'"; + + sls = new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, partyColor, parent); + + Esri::ArcGISRuntime::UniqueValue *uv = + new Esri::ArcGISRuntime::UniqueValue(partyName, // more or less relevant + QStringLiteral("Party '%1'").arg(partyName), + QVariantList() << partyColor.name(), // (was in former times: partyName) + sls, parent); + uvList.append(uv); + } // for + + //qt build in + // static QVector addColors = {Qt::darkGray, Qt::gray, Qt::lightGray, + // Qt::red, Qt::green, Qt::blue, + // Qt::cyan, Qt::magenta, Qt::yellow, + // Qt::darkRed, Qt::darkGreen, Qt::darkBlue, + // Qt::darkCyan, Qt::darkMagenta, Qt::darkYellow, Qt::white}; + static QVector addColors = { + Qt::yellow + }; + uvList.reserve(uvList.size() + addColors.size()); + + for (const QColor &addColor : std::as_const(addColors)) + { + sls = new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, addColor.name(), parent); + + Esri::ArcGISRuntime::UniqueValue *uv = + new Esri::ArcGISRuntime::UniqueValue(addColor.name(), // more or less relevant + QStringLiteral("Party '%1'").arg(addColor.name()), + QVariantList() << addColor.name(), // (was in former times: partyName) + sls, parent); + uvList.append(uv); + } //for + + // setup UniqueValueRenderer: + + // setup default: + sls = new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, Qt::white, this); + + + Esri::ArcGISRuntime::UniqueValueRenderer *uniqueRenderer = + new Esri::ArcGISRuntime::UniqueValueRenderer(QStringLiteral("UnknownParty"), + sls, // default + QStringList() << QStringLiteral("Extrusion_Color"), // fieldNames (was in former times: "PartyName") + uvList, parent); + static long s_counter = 0; + uniqueRenderer->setObjectName(QStringLiteral("UniqueValueRenderer_ForCubes_%1").arg(s_counter++)); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- assignUniqueValueRendererForCubes occured: "; + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; + }); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::destroyed, this, [this, uniqueRenderer] () { + INFO_LOG(m_logger) << " UniqueValueRenderer is destroyed , objectName='" << uniqueRenderer->objectName() << "'"; + }); + + + // set sceneProperties: + Esri::ArcGISRuntime::RendererSceneProperties props = uniqueRenderer->sceneProperties(); + props.setExtrusionMode(Esri::ArcGISRuntime::ExtrusionMode::AbsoluteHeight); // see BuildingOwnership::retrieveBuildingAbsHeight + props.setExtrusionExpression(QStringLiteral("[height]")); // <-- keyword + uniqueRenderer->setSceneProperties(props); + + + // take care that the old one is deleted: + Esri::ArcGISRuntime::Renderer *oldRenderer = graphicsOverlay->renderer(); + delete oldRenderer; // delete nullptr is safe! + + // assign new: + graphicsOverlay->setRenderer(uniqueRenderer); + + return true; +} + +bool LayerFactoryImpl::assignUniqueValueRenderer(const QString &layername, + const QString &fieldName, + const QVector &uvDataCont) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, layername='" << layername << "'" + << ", fieldName='" << fieldName << "'" + << ", uvDataCont.size()=" << uvDataCont.size(); + + // ensure existence: + getCreateLogicalLayer(layername); + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsOverlay = this->getGraphicsLayer(layername); + if (!graphicsOverlay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " graphicsOverlay not found, layername='" << layername << "'"; + return false; + } + + + // prepare UniqueValues + Esri::ArcGISRuntime::PictureMarkerSymbol *pms; + QList uvList; + QObject *parent = graphicsOverlay; + + for (const UniqueValueData &uvData : std::as_const(uvDataCont)) + { + INFO_LOG(m_logger) << LOGFUNCTION + << " uvData.value=" << uvData.value << "'"; + + pms = new Esri::ArcGISRuntime::PictureMarkerSymbol(uvData.img, parent); + + Esri::ArcGISRuntime::UniqueValue *uv = + new Esri::ArcGISRuntime::UniqueValue(uvData.value, + QStringLiteral("GenericUniqueValue '%1'").arg(uvData.value), + QVariantList() << uvData.value, pms, parent); + uvList.append(uv); + } // for + + // setup default: + Esri::ArcGISRuntime::SimpleMarkerSymbol *sms; + sms = new Esri::ArcGISRuntime::SimpleMarkerSymbol(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::X, Qt::white, 5, parent); + + // Finally, setup Renderer: + Esri::ArcGISRuntime::UniqueValueRenderer* uniqueRenderer = + new Esri::ArcGISRuntime::UniqueValueRenderer(QStringLiteral("GenericUniqueValueRenderer"), + sms, // default + QStringList() << fieldName, + uvList, parent); + static long s_counter = 0; + uniqueRenderer->setObjectName(QStringLiteral("UniqueValueRenderer_assignUniqueValueRenderer_%1").arg(s_counter++)); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- assignUniqueValueRenderer occured: "; + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; + }); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::destroyed, this, [this, uniqueRenderer] () { + INFO_LOG(m_logger) << " UniqueValueRenderer is destroyed , objectName='" << uniqueRenderer->objectName() << "'"; + }); + + // take care that the old one is deleted: + Esri::ArcGISRuntime::Renderer *oldRenderer = graphicsOverlay->renderer(); + delete oldRenderer; // delete nullptr is safe! + + // assign new: + graphicsOverlay->setRenderer(uniqueRenderer); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, layername='" << layername << "'" + << ", fieldName='" << fieldName << "'" + << ", uvDataCont.size()=" << uvDataCont.size(); + return true; +} + +bool LayerFactoryImpl::assignUniqueValueRendererAesCentral(const QString &layername, + const QStringList &fieldNames, + const QVector &uvDataCont) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, layername='" << layername << "'" + << ", fieldNames='" << fieldNames.join(QStringLiteral(";")) << "'" + << ", uvDataCont.size()=" << uvDataCont.size(); + + // ensure existence: + getCreateLogicalLayer(layername); + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsOverlay = this->getGraphicsLayer(layername); + if (!graphicsOverlay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " graphicsOverlay not found, layername='" << layername << "'"; + return false; + } + + // prepare UniqueValues + QList uvList; + QObject *parent = graphicsOverlay; + for (const UniqueValueDataAesCentral &uvData : std::as_const(uvDataCont)) + { + INFO_LOG(m_logger) << LOGFUNCTION +// << " uvData.value=" << uvData.values << "'" + << ", type " << toString(uvData.type) + << ", col " << uvData.col + << ", col.name " << uvData.col.name() + << ", uvData.radius " << uvData.radius; + Esri::ArcGISRuntime::SimpleMarkerSceneSymbol *symbol = nullptr; + + switch (uvData.type) { + case UniqueValueDataAesCentral::TYPE::CONE: + symbol = Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::cone(uvData.col, + // extent should be similar to sphere + uvData.radius, // V100.8: cone: diameter is interpreted as radius + uvData.height, + parent); + symbol->setAnchorPosition(Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Top); + break; + case UniqueValueDataAesCentral::TYPE::SPHERE: + symbol = Esri::ArcGISRuntime::SimpleMarkerSceneSymbol::sphere(uvData.col, + uvData.radius*2.0, // diameter + parent); + symbol->setAnchorPosition(Esri::ArcGISRuntime::SceneSymbolAnchorPosition::Center); + default: + break; + } + + Esri::ArcGISRuntime::UniqueValue *uv = + new Esri::ArcGISRuntime::UniqueValue(QStringLiteral("uvData.value"), + QStringLiteral("AesCentralUniqueValue '%%%1'"), + uvData.values, symbol, parent); + uvList.append(uv); + } // for + + // setup default: + Esri::ArcGISRuntime::SimpleMarkerSymbol *sms; + sms = new Esri::ArcGISRuntime::SimpleMarkerSymbol(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::X, Qt::white, 25, parent); + + // Finally, setup Renderer: + Esri::ArcGISRuntime::UniqueValueRenderer* uniqueRenderer = + new Esri::ArcGISRuntime::UniqueValueRenderer(QStringLiteral("AesCentralUniqueValueRenderer"), + sms, // default + fieldNames, + uvList, parent); + static long s_counter = 0; + uniqueRenderer->setObjectName(QStringLiteral("UniqueValueRenderer_assignUniqueValueRendererAesCentral_%1").arg(s_counter++)); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- UniqueValueRendererAesCentral occured: "; + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; + }); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::destroyed, this, [this, uniqueRenderer] () { + INFO_LOG(m_logger) << " UniqueValueRenderer is destroyed , objectName='" << uniqueRenderer->objectName() << "'"; + }); + + // setup SceneProperties + Esri::ArcGISRuntime::RendererSceneProperties sceneProps; + sceneProps.setHeadingExpression(QStringLiteral("[HEADING]")); // Expression with "[]", later on without "[]" + sceneProps.setPitchExpression(QStringLiteral("[PITCH]")); // Expression with "[]", later on without "[]" + sceneProps.setRollExpression(QStringLiteral("[ROLL]")); // Expression with "[]", later on without "[]" + uniqueRenderer->setSceneProperties(sceneProps); + + // take care that the old one is deleted: + Esri::ArcGISRuntime::Renderer *oldRenderer = graphicsOverlay->renderer(); + delete oldRenderer; // delete nullptr is safe! + + // assign new: + graphicsOverlay->setRenderer(uniqueRenderer); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, layername='" << layername << "'" + << ", fieldNames='" << fieldNames.join(QStringLiteral(";")) << "'" + << ", uvDataCont.size()=" << uvDataCont.size(); + return true; +} + +bool LayerFactoryImpl::assignUniqueValueRendererAesCentral3DExtrusion(const QString &layername, + const QStringList &fieldNames, + const QVector &uvDataCont) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, layername='" << layername << "'" + << ", fieldNames='" << fieldNames.join(QStringLiteral(";")) << "'" + << ", uvDataCont.size()=" << uvDataCont.size(); + + // ensure existence: + getCreateLogicalLayer(layername); + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsOverlay = this->getGraphicsLayer3DExtrusion(layername); + if (!graphicsOverlay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " graphicsOverlay not found, layername='" << layername << "'"; + return false; + } + + // prepare UniqueValues + QList uvList; + QObject *parent = graphicsOverlay; + for (const UniqueValueDataAesCentral &uvData : std::as_const(uvDataCont)) + { + INFO_LOG(m_logger) << LOGFUNCTION +// << " uvData.value=" << uvData.values << "'" + << ", type " << toString(uvData.type) + << ", col " << uvData.col + << ", col.name " << uvData.col.name() + << ", uvData.radius " << uvData.radius; + + Esri::ArcGISRuntime::SimpleFillSymbol *symbol = + new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, uvData.col, parent); + + Esri::ArcGISRuntime::UniqueValue *uv = + new Esri::ArcGISRuntime::UniqueValue(uvData.label, + QString(QStringLiteral("Description '%1'")).arg(uvData.col.name()), + uvData.values, symbol, parent); + uvList.append(uv); + } // for + + // setup default: + Esri::ArcGISRuntime::SimpleFillSymbol *defaultSymbol = + new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, Qt::white, parent); + + // Finally, setup Renderer: + Esri::ArcGISRuntime::UniqueValueRenderer* uniqueRenderer = + new Esri::ArcGISRuntime::UniqueValueRenderer(QStringLiteral("assignUniqueValueRendererAesCentral3DUNKNOWN"), + defaultSymbol, // default + fieldNames, + uvList, parent); + static long s_counter = 0; + uniqueRenderer->setObjectName(QStringLiteral("UniqueValueRenderer_assignUniqueValueRendererAesCentral3D_%1").arg(s_counter++)); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- UniqueValueRendererAesCentral3DExtrusion occured: "; + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; + }); + connect(uniqueRenderer, &Esri::ArcGISRuntime::UniqueValueRenderer::destroyed, this, [this, uniqueRenderer] () { + INFO_LOG(m_logger) << " UniqueValueRenderer is destroyed , objectName='" << uniqueRenderer->objectName() << "'"; + }); + + Esri::ArcGISRuntime::RendererSceneProperties props = uniqueRenderer->sceneProperties(); + props.setExtrusionMode(Esri::ArcGISRuntime::ExtrusionMode::BaseHeight); + props.setExtrusionExpression(QStringLiteral("[height]")); // <-- keyword + uniqueRenderer->setSceneProperties(props); + + // take care that the old one is deleted: + Esri::ArcGISRuntime::Renderer *oldRenderer = graphicsOverlay->renderer(); + delete oldRenderer; // delete nullptr is safe! + + // assign new: + graphicsOverlay->setRenderer(uniqueRenderer); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, layername='" << layername << "'" + << ", fieldNames='" << fieldNames.join(QStringLiteral(";")) << "'" + << ", uvDataCont.size()=" << uvDataCont.size(); + return true; +} + +bool LayerFactoryImpl::initializeUnitTestLayer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QUrl mapUrl(QLatin1String("http://DOES_NOT_EXISTS")); // wkid = 102100/3857 + + this->addBasemap(QLatin1String("DummyLayerName"), mapUrl, BasemapType::Aerial); + + //----------------------------------- + // Layer: + //----------------------------------- + + QStringList layerNames; + layerNames << QLatin1String("AES"); + layerNames << QLatin1String("Participants"); + bool success = true; + for (const QString &layername : std::as_const(layerNames)) + if (!addOperationalLayer(layername, layername)) + success = false; + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " initializeOperationalLayers failed"; + + // enableFunctionDialog(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return success; + +} + +bool LayerFactoryImpl::addBasemap(const QString &layername, + const QUrl &mapServiceUrl, + const BasemapType basemapType, + const double minScale, + const double maxScale, + const QString &displayName, + const QString &description, + const QImage &previewImage) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layername='" << layername << "'" + << " mapServiceUrl='" << mapServiceUrl.toString() << "'" + << " basemapType='" << basemapType << "'" + << " minScale='" << minScale << "'" + << " maxScale='" << maxScale << "'" + << " displayName='" << displayName << "'" + << " description='" << description << "'" + << " previewImage.isNull()=" << previewImage.isNull() + << " wkid=" << d->m_mapImpl->getEsriSpatialReference().wkid(); + + bool success = addBasemapInternal(layername, mapServiceUrl, basemapType, + minScale, maxScale, + displayName, description); + + // reOrder + success = success && reOrderLayer(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " layername='" << layername << "'" + << " mapServiceUrl='" << mapServiceUrl.toString() << "'" + << " displayName='" << displayName << "'" + << " success =" << success; + return success; +} + +bool LayerFactoryImpl::removeBasemap(const QString &layername) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layername='" << layername << "'"; + if (!d->m_orderedMapLayer.contains(layername, LayerType::Basemap)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " layername='" << layername << "' not found"; + return false; + } + + bool success = false; + + if (d->m_orderedMapLayer.removeLayer(layername) && d->m_basemap) + { + Esri::ArcGISRuntime::Layer *layer = this->getEsriBasemapLayer(layername); + if (layer && d->m_basemap->baseLayers()) + { + d->m_basemap->baseLayers()->removeOne(layer); + success = true; + INFO_LOG(m_logger) << LOGFUNCTION << " layer" + << " layername='" << layername << "' removed"; + + d->m_mapImpl->emitLayerOrderChanged(); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " layername='" << layername << "' can`t be found"; + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " layername='" << layername << "' can`t be removed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " layername='" << layername << "'" + << " success='" << success << "'"; + return success; +} + +Esri::ArcGISRuntime::Envelope LayerFactoryImpl::fullExtentOfBasemaps(double margin_zeroToOne) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + Esri::ArcGISRuntime::LayerListModel *model = d->m_basemap->baseLayers(); + if (!model || model->isEmpty()) + return Esri::ArcGISRuntime::Envelope(); + + double xmin = std::numeric_limits::max(); + double ymin = std::numeric_limits::max(); + double xmax = std::numeric_limits::lowest(); + double ymax = std::numeric_limits::lowest(); + + Esri::ArcGISRuntime::SpatialReference sr; + + for (int idx = 0; idx < model->size(); ++idx) + { + Esri::ArcGISRuntime::Layer *layer = model->at(idx); + // 12.12.2018, N. Thoden, V100.4: + // Do not call fullExtent if layer is not loaded. + // Envelope contains nan and will not changed on further calls... + if (!layer || (layer->loadStatus() != Esri::ArcGISRuntime::LoadStatus::Loaded)) + continue; + + const Esri::ArcGISRuntime::Envelope singleEnv = layer->fullExtent(); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " layer->loadStatus()" << layer->loadStatus() + << ", singleEnv.toJson()" << singleEnv.toJson(); + + sr = singleEnv.spatialReference(); + + if (!std::isnan(singleEnv.xMin())) + xmin = std::min(singleEnv.xMin(), xmin); + if (!std::isnan(singleEnv.yMin())) + ymin = std::min(singleEnv.yMin(), ymin); + + if (!std::isnan(singleEnv.xMax())) + xmax = std::max(singleEnv.xMax(), xmax); + if (!std::isnan(singleEnv.yMax())) + ymax = std::max(singleEnv.yMax(), ymax); + } // for + + if (qFuzzyCompare(xmin, std::numeric_limits::max()) || sr.isEmpty()) + return Esri::ArcGISRuntime::Envelope(); + + if (margin_zeroToOne < 0.0) + margin_zeroToOne = 0.0; + + if (margin_zeroToOne > 1.0) + margin_zeroToOne = 1.0; + + if (margin_zeroToOne > 0) + { + double width = xmax - xmin; + double height = ymax - ymin; + + xmin -= width * margin_zeroToOne; + xmax += width * margin_zeroToOne; + + ymin -= height * margin_zeroToOne; + ymax += height * margin_zeroToOne; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " return.x " << xmin << " - " << xmax; + DEBUG_LOG(m_logger) << LOGFUNCTION << " return.y " << ymin << " - " << ymax; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return Esri::ArcGISRuntime::Envelope(xmin, ymin, xmax, ymax, sr); +} + +bool LayerFactoryImpl::addSceneObjectLayer(const QString &layername, const QUrl &sceneLayerServiceUrl, const double altitudeOffset, const QString &displayName, const QString &description, const QImage &previewImage) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layername='" << layername << "'" + << " sceneLayerServiceUrl='" << sceneLayerServiceUrl.toString() << "'" + << " altitudeOffset='" << altitudeOffset << "'" + << " displayName='" << displayName << "'" + << " description='" << description << "'" + << " previewImage.isNull()=" << previewImage.isNull(); + + if (d->m_mapImpl->mode() != Mode::Mode_3D) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not allowd in Mode3D"; + return false; + } + + if (sceneLayerServiceUrl.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " sceneLayerServiceUrl empty, can not add layer"; + return false; + } + + if (d->m_orderedMapLayer.contains(layername, LayerType::Undefined)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " layername '" << layername << "' already in use"; + return false; + } + + //----------------------------------- + // ArcGIS ArcGISSceneLayer + //----------------------------------- + //----------------------------------------------------------------- + // Attention: + // local URLS -> do NOT specify any userCredentials (main) + // external URLS -> DO specify userCredentials (main) + //----------------------------------------------------------------- + + QUrl finalUrl = this->completeUrl(QLatin1String("SCENE_OBJ"), + sceneLayerServiceUrl, + QLatin1String("slpk/")); + INFO_LOG(m_logger) << LOGFUNCTION << " SCENE_OBJ finalUrl '" << finalUrl.toString() << "'"; + + Esri::ArcGISRuntime::Scene *esriScene = d->m_mapImpl->getEsriScene(); + +#ifdef USE_ARCGIS_SCENE_LAYER_CACHE + ArcGISSceneLayerCache::Item& aslCacheItem = ArcGISSceneLayerCache::getInstance()->getFreeItemRef(sceneLayerServiceUrl.toString()); + + if (m_logger.isInfoEnabled()) + ArcGISSceneLayerCache::getInstance()->dump(); + + + // const bool reUse = aslCacheItem.m_hostingScene.isNull() && aslCacheItem.m_sceneLayer; + // aslCacheItem.m_hostingScene is always null. See getItemRef + const bool reUse = aslCacheItem.m_sceneLayer; + + ERROR_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> reUse = "<< reUse + << " itemName = '" << sceneLayerServiceUrl.toString() << "'" + << " layername='" << layername << "'" + << " aslCacheItem.m_hostingScene.data() = " << aslCacheItem.m_hostingScene.data() + << " aslCacheItem.m_sceneLayer.data() = " << aslCacheItem.m_sceneLayer.data(); + + Esri::ArcGISRuntime::ArcGISSceneLayer *sceneLayer = nullptr; + + if (!reUse) + { + INFO_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> CREATE" + << " (itemName = '" << sceneLayerServiceUrl.toString() << "', layername='" << layername << "')"; + + sceneLayer = new Esri::ArcGISRuntime::ArcGISSceneLayer(finalUrl); // ArcGISSceneLayer should live longer than the scene: -> no parent here :-) + sceneLayer->setName(layername); + sceneLayer->setObjectName(sceneLayer->name()); + + connect(sceneLayer, &Esri::ArcGISRuntime::ArcGISSceneLayer::errorOccurred, this, [this](const Esri::ArcGISRuntime::Error &error) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> EsriError occured: " << error; + }); + connect(sceneLayer, &Esri::ArcGISRuntime::ArcGISSceneLayer::doneLoading, this, [this]() + { + Esri::ArcGISRuntime::ArcGISSceneLayer *sceneLayer = qobject_cast(sender()); + if (!sceneLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " qobject_cast failed to get sender!?"; + return; + } + + QString senderName = sceneLayer->objectName(); + INFO_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> doneLoading" + << " ,sendername= '" << senderName << "'" + << " ,URL= '" << sceneLayer->url().toString() << "'"; + }); + + connect(sceneLayer, &Esri::ArcGISRuntime::ArcGISSceneLayer::destroyed, this, [this]() + { + ERROR_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> destroyed"; + }); + + aslCacheItem.m_hostingScene = esriScene; // update cache + aslCacheItem.m_sceneLayer = sceneLayer; // update cache + + } else { + INFO_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> TAKE FROM STATIC" + << " (itemName = '" << sceneLayerServiceUrl.toString() << "', layername='" << layername << "')"; + + aslCacheItem.m_hostingScene = esriScene; // update cache + sceneLayer = aslCacheItem.m_sceneLayer; + } +#else + Esri::ArcGISRuntime::ArcGISSceneLayer *sceneLayer = new Esri::ArcGISRuntime::ArcGISSceneLayer(finalUrl, d->m_mapImpl); + sceneLayer->setName(layername); + sceneLayer->setObjectName(sceneLayer->name()); + sceneLayer->setAltitudeOffset(altitudeOffset); + + connect(sceneLayer, &Esri::ArcGISRuntime::ArcGISSceneLayer::errorOccurred, this, [this](const Esri::ArcGISRuntime::Error &error) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> EsriError occured: " << error; + }); + connect(sceneLayer, &Esri::ArcGISRuntime::ArcGISSceneLayer::doneLoading, this, [this]() + { + Esri::ArcGISRuntime::ArcGISSceneLayer *sceneLayer = qobject_cast(sender()); + if (!sceneLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " qobject_cast failed to get sender!?"; + return; + } + + QString senderName = sceneLayer->objectName(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " ArcGISSceneLayer -> doneLoading" + << " ,sendername= '" << senderName << "'" + << " ,URL= '" << sceneLayer->url().toString() << "'"; + }); +#endif // USE_ARCGIS_SCENE_LAYER_CACHE + + + if (esriScene) + { + esriScene->operationalLayers()->append(sceneLayer); + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no scene"; + return false; + } + +#ifdef USE_ARCGIS_SCENE_LAYER_CACHE + if (m_logger.isInfoEnabled()) + ArcGISSceneLayerCache::getInstance()->dump(); +#endif // USE_ARCGIS_SCENE_LAYER_CACHE + + d->m_sceneObjectUrlCont += sceneLayerServiceUrl; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " sceneLayerServiceUrl='" << sceneLayerServiceUrl.toString() << "'"; + return true; +} + +bool LayerFactoryImpl::isSceneObjectLayerVisible() const +{ + return d->m_isSceneObjectLayerVisible; +} + +void LayerFactoryImpl::setSceneObjectLayerVisible(bool visible) +{ + d->m_isSceneObjectLayerVisible = visible; + + if ( (!d->m_mapImpl) || d->m_mapImpl->mode() != Mode::Mode_3D) + return; + + Esri::ArcGISRuntime::Scene *esriScene = d->m_mapImpl->getEsriScene(); + if (!esriScene) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no scene"; + return; + } + + Esri::ArcGISRuntime::LayerListModel *layerListModel = esriScene->operationalLayers(); + if (!layerListModel) + return; + + const int size = layerListModel->size(); + for (int idx=0; idxat(idx); + if (!objLayer) + continue; + objLayer->setVisible(d->m_isSceneObjectLayerVisible); + } // for + + + +} + +bool LayerFactoryImpl::initializeElevationSources(const QStringList &sceneElevationServiceUrls) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " sceneElevationServiceUrls='" << sceneElevationServiceUrls.join(QLatin1String(";")) << "'"; + + const Mode mode = d->m_mapImpl->mode(); + + switch (mode) + { + case Mode::Mode_2D: + this->initializeElevationSources2D(sceneElevationServiceUrls); + break; + case Mode::Mode_3D: + this->initializeElevationSources3D(sceneElevationServiceUrls); + break; + default: + ERROR_LOG(m_logger) << LOGFUNCTION << " unknown mode"; + return false; + } // switch + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool LayerFactoryImpl::initializeElevationSources2D(const QStringList &sceneElevationServiceUrls) +{ + if (sceneElevationServiceUrls.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " sceneObjectServiceUrls is Empty"; + return false; + } + + if (d->m_mapImpl->mode() != Mode::Mode_2D) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " wrong mode"; + return false; + } + + // + // append to 'own' esri (base) surface: + // + delete d->m_baseSurface; + d->m_baseSurface = new Esri::ArcGISRuntime::Surface(this); + d->m_baseSurface->setName(QLatin1String("BaseSurface2D")); + d->m_baseSurface->setObjectName(d->m_baseSurface->name()); + + connect(d->m_baseSurface, &Esri::ArcGISRuntime::Surface::errorOccurred, this, &LayerFactoryImpl::onSurfaceErrorOccured2D, Qt::UniqueConnection); + connect(d->m_baseSurface, &Esri::ArcGISRuntime::Surface::doneLoading, this, &LayerFactoryImpl::onSurfaceDoneLoading2D, Qt::UniqueConnection); + + const QList &elevationSources = + initializeElevationSourcesInternal(sceneElevationServiceUrls); + + for (auto elevationSource : std::as_const(elevationSources)) + d->m_baseSurface->elevationSources()->append(elevationSource); + + + // trigger loading: + // As Luke suggested: load surface if first elevation entry is loaded: + // https://community.esri.com/thread/206757-elevation-in-2d + if (!elevationSources.isEmpty()) + { + Esri::ArcGISRuntime::ElevationSource* elevationSourcesFirst = elevationSources.first(); + if (elevationSourcesFirst) + { + connect(elevationSourcesFirst, &Esri::ArcGISRuntime::ElevationSource::doneLoading, this, [this](const Esri::ArcGISRuntime::Error&) + { + d->m_baseSurface->load(); + }); + elevationSourcesFirst->load(); + } + } + + return true; +} + +bool LayerFactoryImpl::initializeElevationSources3D(const QStringList &sceneElevationServiceUrls) +{ + if (sceneElevationServiceUrls.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " sceneObjectServiceUrls is Empty"; + return false; + } + + if (d->m_mapImpl->mode() != Mode::Mode_3D) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " wrong mode"; + return false; + } + + Esri::ArcGISRuntime::Scene *esriScene = d->m_mapImpl->getEsriScene(); + if (!esriScene) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no scene"; + return false; + } + + if (!esriScene->baseSurface() || !esriScene->baseSurface()->elevationSources()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no ElevationSourceListModel"; + return false; + } + + // + // append to esri scene: + // + const QList elevationSources = + initializeElevationSourcesInternal(sceneElevationServiceUrls); + + for (auto item : elevationSources) + esriScene->baseSurface()->elevationSources()->append(item); + + return true; +} + +QList LayerFactoryImpl::initializeElevationSourcesInternal(const QStringList &sceneElevationServiceUrls) +{ + if (sceneElevationServiceUrls.isEmpty()) + return {}; + + Esri::ArcGISRuntime::RasterElevationSource* rasterElevationSource = + initializeRasterElevationSourceInternal(sceneElevationServiceUrls); + + QList elevationSources; + elevationSources.append(rasterElevationSource); + + return elevationSources; +} + +Esri::ArcGISRuntime::RasterElevationSource *LayerFactoryImpl::initializeRasterElevationSourceInternal(const QStringList &filePaths) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " filePaths = " << filePaths.join(QStringLiteral(";")) << "'"; + + QStringList absFilePaths_rough_to_fine; + absFilePaths_rough_to_fine.reserve(filePaths.size()); + + for (const QString &filePath : filePaths) + { + QUrl finalUrl = this->completeUrl(QLatin1String("ELEVATION"), + QUrl(filePath), + QLatin1String("elevation/")); + + INFO_LOG(m_logger) << LOGFUNCTION << " ELEVATION finalUrl '" << finalUrl.toString() << "'"; + absFilePaths_rough_to_fine.append(finalUrl.toLocalFile()); + } // for + + if (absFilePaths_rough_to_fine.isEmpty()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " filePaths are empty!!!"; + return nullptr; + } + DEBUG_LOG(m_logger) << LOGFUNCTION << " absFilePaths_rough_to_fine = " << absFilePaths_rough_to_fine.join(QStringLiteral(";")) << "'"; + + // + // init ElevationMapManager + // + if (!d->m_mapImpl->getElevationMapManager()) + { + if (!d->m_mapImpl->initElevationMapManager(absFilePaths_rough_to_fine)) + ERROR_LOG(m_logger) << LOGFUNCTION << " initElevationMapManager failed"; + } + + // + // Esri::ArcGISRuntime::RasterElevationSource with several files + // + Esri::ArcGISRuntime::RasterElevationSource *rasterElevationSource = + new Esri::ArcGISRuntime::RasterElevationSource(absFilePaths_rough_to_fine, this); + + const QString &elevationSourceName = QStringLiteral("RasterElevationSources_((%1))").arg(absFilePaths_rough_to_fine.size()); + rasterElevationSource->setName(elevationSourceName); + rasterElevationSource->setObjectName(rasterElevationSource->name()); + + // 2017-09-18; N.Thoden, V100.1: + // Following message occures very often. Therefore don`t connect to errorOccurred + // 'code=404 domain=0 extendedErrorType=0, message='Error transferring http://excgeo21.sl .. - server replied: Not Found' + connect(rasterElevationSource, &Esri::ArcGISRuntime::ElevationSource::errorOccurred, this, &LayerFactoryImpl::onElevationSourceErrorOccured); + connect(rasterElevationSource, &Esri::ArcGISRuntime::ElevationSource::doneLoading, + this, &LayerFactoryImpl::onElevationSourceDoneLoading); + return rasterElevationSource; +} + + +void LayerFactoryImpl::setForceCreationOfGraphicsOverlay(bool onOff) +{ + s_forceCreationOfGraphicsOverlay = onOff; +} + + +QString LayerFactoryImpl::basemapLayernamePrefix() const +{ + return d->m_basemapLayernamePrefix; +} +void LayerFactoryImpl::setBasemapLayernamePrefix(const QString &basemapLayernamePrefix) +{ + d->m_basemapLayernamePrefix = basemapLayernamePrefix; +} + +QString LayerFactoryImpl::participantLayername() const +{ + return d->m_participantLayername; +} +RenderingMode LayerFactoryImpl::participantLayerRenderingMode() const +{ + return d->m_participantLayerRenderingMode; +} +SurfacePlacement LayerFactoryImpl::participantLayerSurfacePlacement() const +{ + return d->m_participantLayerSurfacePlacement; +} + +void LayerFactoryImpl::setParticipantLayer(const QString &participantLayername, + RenderingMode renderingMode, + SurfacePlacement surfacePlacement) +{ + d->m_participantLayername = participantLayername; + d->m_participantLayerRenderingMode = renderingMode; + d->m_participantLayerSurfacePlacement = surfacePlacement; +} + +QString LayerFactoryImpl::jumpPointLayer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QString &logicalLayerName = LayerNameJumpPoints; + const LayerType layerType = LayerType::JumpPoint; + + if (! layerExistsInMngt(logicalLayerName, layerType)) + { + QSharedPointer logicalLayer = addLayerToMgnt(logicalLayerName, layerType); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addLayer failed: '" + << logicalLayerName << "'"; + return QString(); + } + LayerParameter lp = logicalLayer->layerParameter(); + lp.displayName = tr("JumpPoints"); // dt. Sprungmarken + logicalLayer->setLayerParameter(lp); + } + + + // + // the real Esri-Layers will be create later on demand :-) + // + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return logicalLayerName; // == LayerNameJumpPoints; +} + + +bool LayerFactoryImpl::jumpPointLayerExists() const +{ + const QString &logicalLayerName = LayerNameJumpPoints; + return (d->m_orderedMapLayer.contains(logicalLayerName, SituationMap::LayerType::JumpPoint)); +} + + + +QString LayerFactoryImpl::toolLayer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QString &logicalLayerName = LayerNameTools; + const LayerType layerType = LayerType::Tools; + + if (! layerExistsInMngt(logicalLayerName, layerType)) + { + QSharedPointer logicalLayer = addLayerToMgnt(logicalLayerName, layerType); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addLayer failed: '" + << logicalLayerName << "'"; + return QString(); + } + LayerParameter lp = logicalLayer->layerParameter(); + lp.displayName = tr("Tools"); // dt. Werkzeuge (?) + logicalLayer->setLayerParameter(lp); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + + // + // the real Esri-Layers will be create later on demand :-) + // + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return logicalLayerName; // == LayerNameTools; +} + +bool LayerFactoryImpl::toolLayerExists() const +{ + const QString &logicalLayerName = LayerNameTools; + return (d->m_orderedMapLayer.contains(logicalLayerName, SituationMap::LayerType::Tools)); +} + + +QString LayerFactoryImpl::rubberbandLayer() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QString &logicalLayerName = LayerNameRubberband; + const LayerType layerType = LayerType::Rubberband; + + if (! layerExistsInMngt(logicalLayerName, layerType)) + { + LayerFactoryImpl *THIS = const_cast(this); + QSharedPointer logicalLayer = THIS->addLayerToMgnt(logicalLayerName, layerType); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addLayer failed: '" + << logicalLayerName << "'"; + return QString(); + } + } + + // + // the real Esri-Layers will be create later on demand :-) + // + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return logicalLayerName; // == LayerNameRubberband; +} +bool LayerFactoryImpl::rubberbandLayerExists() const +{ + const QString &logicalLayerName = LayerNameRubberband; + return (d->m_orderedMapLayer.contains(logicalLayerName, SituationMap::LayerType::Rubberband)); +} + +QString LayerFactoryImpl::emergencyCallLayer(bool getJustTheName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + const QString &logicalLayerName = LayerNameEmergencyCall; + + if (getJustTheName) + { + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return logicalLayerName; + } + + const LayerType layerType = LayerType::EmergencyCall; + + if (! layerExistsInMngt(logicalLayerName, layerType)) + { + QSharedPointer logicalLayer = addLayerToMgnt(logicalLayerName, layerType); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addLayer failed: '" + << logicalLayerName << "'"; + return QString(); + } + LayerParameter lp = logicalLayer->layerParameter(); + lp.displayName = tr("EmergencyCalls"); // dt. Notrufe/SOS (?) + logicalLayer->setLayerParameter(lp); + } + + { + Esri::ArcGISRuntime::GraphicsOverlay *graphicsOverlay = this->getGraphicsLayer(logicalLayerName); + if (!graphicsOverlay) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " graphicsOverlay not found, logicalLayerName'" << logicalLayerName<< "'"; + return QString(); + } + // set SurfacePlacement // EmergencyCalls have a pin/needle + Esri::ArcGISRuntime::LayerSceneProperties lsp = graphicsOverlay->sceneProperties(); + lsp.setSurfacePlacement(static_cast(Esri::ArcGISRuntime::SurfacePlacement::Relative)); + graphicsOverlay->setSceneProperties(lsp); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return logicalLayerName; // == LayerNameEmergencyCall; +} + +bool LayerFactoryImpl::emergencyCallLayerExists() const +{ + const QString &emergencyCallLayername = LayerNameEmergencyCall; + return (d->m_orderedMapLayer.contains(emergencyCallLayername, SituationMap::LayerType::EmergencyCall)); +} + +bool LayerFactoryImpl::switchEmergencyCallLayerOn() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + const QString &emergencyCallLayername = LayerNameEmergencyCall; + + if (!(d->m_orderedMapLayer.contains(emergencyCallLayername, SituationMap::LayerType::EmergencyCall))) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " EmergencyCallLayer does not exists, can´t be switched on."; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return false; + } + + LayerParameter lp = this->layerParameter(emergencyCallLayername); + lp.visible = true; + lp.opacity = 100; + lp.minScale = -1; + lp.maxScale = -1; + + const bool success = this->setLayerParameter(emergencyCallLayername, lp); + if (!success) { + ERROR_LOG(m_logger) << LOGFUNCTION << " setLayerParameter failed for '" << emergencyCallLayername << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool LayerFactoryImpl::switchEmergencyCallLayerOff() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + const QString &emergencyCallLayername = LayerNameEmergencyCall; + + if (!(d->m_orderedMapLayer.contains(emergencyCallLayername, SituationMap::LayerType::EmergencyCall))) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " EmergencyCallLayer does not exists, can´t be switched off."; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return false; + } + + LayerParameter lp = this->layerParameter(emergencyCallLayername); + lp.visible = false; + lp.opacity = 100; + lp.minScale = -1; + lp.maxScale = -1; + + const bool success = this->setLayerParameter(emergencyCallLayername, lp); + if (!success) { + ERROR_LOG(m_logger) << LOGFUNCTION << " setLayerParameter failed for '" << emergencyCallLayername << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; + +} + +void LayerFactoryImpl::handleEmergencyCallSpecials() +{ + const QString &emergencyCallLayername = LayerNameEmergencyCall; + const bool bEmergencyCallLayerExists = emergencyCallLayerExists(); + TRACE_LOG(m_logger) << LOGFUNCTION << " START, emergencyCallLayerExists() = " << bEmergencyCallLayerExists; + + if (!bEmergencyCallLayerExists) + return; + + // ensure to be on top: + QStringList slLayerNamesOrdered = d->m_orderedMapLayer.layerNamesOrdered(); + if (slLayerNamesOrdered.isEmpty()) + return; + + const QString &lastNameTOP = slLayerNamesOrdered.last(); + + if (lastNameTOP == emergencyCallLayer()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " emergencyCallLayer already on top"; + return; + } + + slLayerNamesOrdered.removeAll(emergencyCallLayername); + slLayerNamesOrdered.push_back(emergencyCallLayername); + orderLayerTo(slLayerNamesOrdered); + + { + QStringList slLayerNamesOrderedLocal = d->m_orderedMapLayer.layerNamesOrdered(); + if (slLayerNamesOrderedLocal.isEmpty()) + return; + + const QString &lastNameTOPLocal = slLayerNamesOrderedLocal.last(); + if (lastNameTOPLocal == emergencyCallLayer()) + DEBUG_LOG(m_logger) << LOGFUNCTION << " emergencyCallLayer is now on top"; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +Map *LayerFactoryImpl::map() const +{ + return d->m_mapImpl; +} + +int LayerFactoryImpl::count() const +{ + return d->m_orderedMapLayer.count(); +} + +QStringList LayerFactoryImpl::layernames() const +{ + return d->m_orderedMapLayer.layerNamesOrdered(); +} + +QStringList LayerFactoryImpl::layernames(const LayerType &layerType) const +{ + return d->m_orderedMapLayer.layerNamesOrdered(layerType); +} + +bool SituationMap::LayerFactoryImpl::checkLayerOrder(const QStringList &orderedLayernames) +{ + bool requestedOrderIsValid = false; + bool success = d->m_orderedMapLayer.orderLayerTo(orderedLayernames, &requestedOrderIsValid); + + if (success) + return requestedOrderIsValid; + else + return false; +} + +bool LayerFactoryImpl::orderLayerTo(const QStringList &orderedLayernames) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << ", orderedLayername= " << orderedLayernames.join(QLatin1String(";")); + + if (orderedLayernames.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " orderedLayername is empty"; + return false; + } + + bool success = true; + + const QStringList &orderedLayernamesCurrent = d->m_orderedMapLayer.layerNamesOrdered(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << ", orderedLayernamesCurrent=" << orderedLayernamesCurrent.join(QLatin1String(";")); + + if (orderedLayernamesCurrent != orderedLayernames) + { + if ((success = d->m_orderedMapLayer.orderLayerTo(orderedLayernames))) + reOrderLayer(); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " orderLayerTo failed"; + ERROR_LOG(m_logger) << LOGFUNCTION << " orderLayerTo failed, cur =" << d->m_orderedMapLayer.layerNamesOrdered().join(QLatin1String(";")); + ERROR_LOG(m_logger) << LOGFUNCTION << " orderLayerTo failed, should=" << orderedLayernames.join(QLatin1String(";")); + } + d->m_mapImpl->emitLayerOrderChanged(); + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " END, success=" << success + << ", orderedLayernamesCurrent=" << orderedLayernamesCurrent.join(QLatin1String(";")); + return success; +} + +LayerType LayerFactoryImpl::layerType(const QString &layername) const +{ + LayerType layerType = LayerType::Undefined; + + MapLayer mapLayer = d->m_orderedMapLayer.getLayer(layername); + if (mapLayer.isValid()) + layerType = mapLayer.type(); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " layer not found" + << " layername='" << layername << "'"; + } + + return layerType; +} + +LayerParameter LayerFactoryImpl::layerParameter(const QString &layername) const +{ + LayerParameter lp; + + QSharedPointer logicalLayer = this->getLogicalLayer(layername); + if (logicalLayer) + lp = logicalLayer->layerParameter(); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not found" + << " layername=" << layername; + } + + return lp; +} + +bool LayerFactoryImpl::setLayerParameter(const QString &layername, const LayerParameter &layerParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layername=" << layername; + + bool success = false; + + QSharedPointer logicalLayer = this->getLogicalLayer(layername); + if (logicalLayer) + { + logicalLayer->setLayerParameter(layerParameter); + success = true; + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not found" + << " layername=" << layername; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " success=" << success; + return success; +} + + +bool LayerFactoryImpl::setLayerParameter(LayerType layerType, int value, int LayerParameter::*memberPtr) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layer type=" << int(layerType) << " layer parameter member=" << memberPtr << " value=" << value; + + const QStringList &layerNameList = d->m_orderedMapLayer.layerNamesOrdered(layerType); + bool success = true; + + for (const QString &layername : layerNameList) + { + QSharedPointer logicalLayer = this->getLogicalLayer(layername); + if (logicalLayer) + { + LayerParameter layerParamTmp = logicalLayer->layerParameter(); + layerParamTmp.*memberPtr = value; + logicalLayer->setLayerParameter(layerParamTmp); + + } + else + { + success = false; + ERROR_LOG(m_logger) << LOGFUNCTION << " not found" + << ", layername='" << layername << "'"; + } + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " success=" << success; + return success; +} + +bool LayerFactoryImpl::setLayerOpacity(LayerType layerType, int opacity) { return setLayerParameter(layerType, opacity, &LayerParameter::opacity); } +bool LayerFactoryImpl::setLayerBrightness(LayerType layerType, int brightness) { return setLayerParameter(layerType, brightness, &LayerParameter::brightness); } +bool LayerFactoryImpl::setLayerContrast(LayerType layerType, int contrast) { return setLayerParameter(layerType, contrast, &LayerParameter::contrast); } +bool LayerFactoryImpl::setLayerGamma(LayerType layerType, int gamma) { return setLayerParameter(layerType, gamma, &LayerParameter::gamma); } + +void LayerFactoryImpl::setLayersVisible(const QVector &layerTypes, bool visible) +{ + for (auto &logicalLayer : d->m_logicalLayerCont) + { + if (!layerTypes.contains(logicalLayer->layerType())) + continue; + LayerParameter layerParamTmp = logicalLayer->layerParameter(); + layerParamTmp.visible = visible; + logicalLayer->setLayerParameter(layerParamTmp); + } +} + +#if ACRGIS_VERSION < 0x640500 // 100.5.0 / Update 5 +// deprecated in V100.5, replaced by GeoView::setSelectionProperties() +QColor LayerFactoryImpl::selectionColor() const +{ + return d->m_selectionColor; +} +void LayerFactoryImpl::setSelectionColor(const QColor &selectionColor) +{ + d->m_selectionColor = selectionColor; +} +#endif // ACRGIS_VERSION + +bool LayerFactoryImpl::elevationAvailable() const +{ + if (!d->m_mapImpl) + return false; + return this->elevationSourcesCount() > 0; +} + +Esri::ArcGISRuntime::Surface *LayerFactoryImpl::baseSurface() const +{ + if (!elevationAvailable()) + return nullptr; + + Esri::ArcGISRuntime::Surface *baseSurface = nullptr; + + switch (d->m_mapImpl->mode()) + { + case Mode::Mode_2D: + baseSurface = d->m_baseSurface; + break; + case Mode::Mode_3D: + { + Esri::ArcGISRuntime::Scene *esriScene = d->m_mapImpl->getEsriScene(); + if (esriScene && + esriScene->baseSurface() + ) + baseSurface = esriScene->baseSurface(); + } + break; + default: + break; + } // switch + + + // Verify: + if (!baseSurface) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no baseSurface"; + return nullptr; + } + + if (baseSurface->loadStatus() != Esri::ArcGISRuntime::LoadStatus::Loaded) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " baseSurface not loaded yet"; + return nullptr; + } + + int elevationSourcesCount = -1; + Esri::ArcGISRuntime::ElevationSourceListModel* eslm = baseSurface->elevationSources(); + if (eslm) + elevationSourcesCount = eslm->size(); + + if (elevationSourcesCount <= 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no elevationSourcesCount"; + return nullptr; + } + + return baseSurface; +} + +int LayerFactoryImpl::elevationSourcesCount(bool checkIfLoaded) const +{ + if (!d->m_mapImpl) + return -1; + + Esri::ArcGISRuntime::ElevationSourceListModel* elevationSourceListModel = nullptr; + + switch (d->m_mapImpl->mode()) + { + case Mode::Mode_2D: + { + if (d->m_baseSurface && d->m_baseSurface->elevationSources()) + elevationSourceListModel = d->m_baseSurface->elevationSources(); + } + + break; + case Mode::Mode_3D: + { + if (d->m_mapImpl->getEsriScene() && + d->m_mapImpl->getEsriScene()->baseSurface() && + d->m_mapImpl->getEsriScene()->baseSurface()->elevationSources() + ) + elevationSourceListModel = d->m_mapImpl->getEsriScene()->baseSurface()->elevationSources(); + } + break; + default: + return false; + } // switch + + if (!elevationSourceListModel) + return -1; + + int elevationSourcesCount = -1; + + if (checkIfLoaded) + { + elevationSourcesCount = 0; + + int idx = 0; + const int esmSize = elevationSourceListModel->size(); + for (; idx < esmSize; ++idx) + { + Esri::ArcGISRuntime::ElevationSource *es = elevationSourceListModel->at(idx); + + if (es && (es->loadStatus() == Esri::ArcGISRuntime::LoadStatus::Loaded)) + ++elevationSourcesCount; + } // for + + INFO_LOG(m_logger) << LOGFUNCTION << " elevationSourcesCount = " << esmSize + << ", loaded = " << elevationSourcesCount; + } + else + { + elevationSourcesCount = elevationSourceListModel->size(); + } + + return elevationSourcesCount; +} + +bool LayerFactoryImpl::elevationSourcesLoaded() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + const int elevationSourcesCount = this->elevationSourcesCount(); + const int elevationSourcesCountLoaded = this->elevationSourcesCount(true); + return (elevationSourcesCount == elevationSourcesCountLoaded); +} + +QPointer LayerFactoryImpl::locationToElevation() const +{ + QPointer locationToElevationHelper; + + if (!elevationAvailable()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no elevation available"; + return locationToElevationHelper; + } + + Esri::ArcGISRuntime::Surface *baseSurface = this->baseSurface(); + + if (!baseSurface) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no baseSurface"; + return locationToElevationHelper; + } + + LayerFactoryImpl *THIS = const_cast(this); + locationToElevationHelper = new LocationToElevationHelper(THIS); // QPointer -> parent is ok! + + if (!locationToElevationHelper->init(d->m_mapImpl, + baseSurface, + d->m_mapImpl->getEsriSpatialReference())) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " locationToElevationHelper->init failed"; + delete locationToElevationHelper; + return nullptr; + } + + return locationToElevationHelper; +} + +Esri::ArcGISRuntime::GraphicsOverlay *LayerFactoryImpl::getGraphicsLayer(const QString &logicalName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, logicalName='" << logicalName << "'"; + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = nullptr; + + QSharedPointer logicalLayer = d->getLogicalLayer(logicalName); + if (d->m_orderedMapLayer.contains(logicalName, LayerType::Undefined) && logicalLayer) + graphicsLayer = logicalLayer->getCreateGraphicalOverlay(TechnicalLayerType::GraphicsLayer); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, logicalName='" << logicalName << "'" + << " graphicsLayer=" << graphicsLayer; + return graphicsLayer; +} + +Esri::ArcGISRuntime::GraphicsOverlay *LayerFactoryImpl::getGraphicsLayerDict(const QString &logicalName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, logicalName='" << logicalName << "'"; + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = nullptr; + + QSharedPointer logicalLayer = d->getLogicalLayer(logicalName); + if (d->m_orderedMapLayer.contains(logicalName, LayerType::Undefined) && logicalLayer) + graphicsLayer = logicalLayer->getCreateGraphicalOverlay(TechnicalLayerType::GraphicsLayer_Dict); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, logicalName='" << logicalName << "'" + << " graphicsLayer=" << graphicsLayer; + return graphicsLayer; +} + +Esri::ArcGISRuntime::GraphicsOverlay *LayerFactoryImpl::getGraphicsLayer3DExtrusion(const QString &logicalName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, logicalName='" << logicalName << "'"; + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = nullptr; + + // getGraphicsLayer3DExtrusion -> SurfacePlacement::Relative + + QSharedPointer logicalLayer = d->getLogicalLayer(logicalName); + if (d->m_orderedMapLayer.contains(logicalName, LayerType::Undefined) && logicalLayer) + graphicsLayer = logicalLayer->getCreateGraphicalOverlay(TechnicalLayerType::GraphicsLayer_3D_Extrusion); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, logicalName='" << logicalName << "'" + << " graphicsLayer=" << graphicsLayer; + return graphicsLayer; +} + +Esri::ArcGISRuntime::GraphicsOverlay *LayerFactoryImpl::getGraphicsLayer3D_SpAbsolute(const QString &logicalName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, logicalName='" << logicalName << "'"; + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = nullptr; + + // getGraphicsLayer3D_SpAbsolute -> SurfacePlacement::Absolute + + QSharedPointer logicalLayer = d->getLogicalLayer(logicalName); + if (d->m_orderedMapLayer.contains(logicalName, LayerType::Undefined) && logicalLayer) + graphicsLayer = logicalLayer->getCreateGraphicalOverlay(TechnicalLayerType::GraphicsLayer_3D_Absolute); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, logicalName='" << logicalName << "'" + << " graphicsLayer=" << graphicsLayer; + return graphicsLayer; +} + + + +QString LayerFactoryImpl::createInternalLayerName(const TechnicalLayerType &technicalLayerType, + const QString &logicalLayerName) const +{ + return LogicalLayer::createInternalLayerName(logicalLayerName, technicalLayerType); +} + +QSharedPointer LayerFactoryImpl::getLogicalLayer(const QString &logicalName) const +{ + return d->getLogicalLayer(logicalName); +} + +QSharedPointer LayerFactoryImpl::getCreateLogicalLayer(const QString &logicalName) +{ + if (! d->hasLogicalLayer(logicalName)) + d->createLogicalLayer(logicalName); + + QSharedPointer logicalLayer = d->getLogicalLayer(logicalName); + if (logicalLayer) + { + connect(logicalLayer.data(), &LogicalLayer::layerParameterChanged, + this, &LayerFactoryImpl::onLayerParameterChanged); + + connect(logicalLayer.data(), &LogicalLayer::layerCreatedOnDemand, + this, &LayerFactoryImpl::onLayerCreatedOnDemand); + + connect(logicalLayer.data(), &LogicalLayer::layerDoneLoading, + this, &LayerFactoryImpl::onLayerDoneLoading); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " getLogicalLayer failed"; + return logicalLayer; +} + +bool LayerFactoryImpl::addBasemapInternal(const QString &layername, + const QUrl &mapServiceUrl, + const BasemapType basemapType, + const double minScale, + const double maxScale, + const QString &displayName, + const QString &description, + const QImage &previewImage) +{ + if (mapServiceUrl.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " mapServiceUrl empty, can not add layer " + << layername << " basemap type: " << basemapType; + return false; + } + + if (d->m_orderedMapLayer.contains(layername, LayerType::Undefined)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " layername '" << layername << "' already in use"; + return false; + } + + //----------------------------------- + // ArcGIS Tiled Basemap Layer + //----------------------------------- + //----------------------------------------------------------------- + // Attention: + // local URLS -> do NOT specify any userCredentials (main) + // external URLS -> DO specify userCredentials (main) + //----------------------------------------------------------------- + + Esri::ArcGISRuntime::Layer *layer = nullptr; + + + if (mapServiceUrl.isLocalFile()) + { + QUrl finalUrl = this->completeUrl(QLatin1String("BASEMAP"), + mapServiceUrl, + QLatin1String("/")); + INFO_LOG(m_logger) << LOGFUNCTION + << " TileCache: finalUrl '" << finalUrl.toString() << "'" + << ", finalUrltoLocalFile '" << finalUrl.toLocalFile() << "'"; + QString tcFileAbs = finalUrl.toLocalFile(); + + QFileInfo fi(tcFileAbs); + + if (!fi.exists(tcFileAbs)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " file '" << tcFileAbs << "' doesn't exists"; + return false; + } + + if (fi.isDir() || // TileCacheFolder + fi.suffix().compare(QLatin1String("tpk"))==0) // TilePackage + { + Esri::ArcGISRuntime::TileCache *tileCache = new Esri::ArcGISRuntime::TileCache(tcFileAbs, d->m_mapImpl); + tileCache->setObjectName(QLatin1String("BasemapTileCache-")+layername); + layer = new Esri::ArcGISRuntime::ArcGISTiledLayer(tileCache, d->m_mapImpl); + } + else if (fi.suffix().compare(QLatin1String("vtpk"))==0) // VectorTilePackage + { + Esri::ArcGISRuntime::VectorTileCache *vectorTileCache = new Esri::ArcGISRuntime::VectorTileCache(tcFileAbs, d->m_mapImpl); + vectorTileCache->setObjectName(QLatin1String("BasemapVectorTileCache-")+layername); + layer = new Esri::ArcGISRuntime::ArcGISVectorTiledLayer(vectorTileCache, d->m_mapImpl); + } + else + { + // geotiff (often '.tif') + Esri::ArcGISRuntime::Raster* raster = new Esri::ArcGISRuntime::Raster(tcFileAbs, d->m_mapImpl); + layer = new Esri::ArcGISRuntime::RasterLayer(raster, d->m_mapImpl); + + // loading of the geotiff may take a while (tile by tile) + } + } + else + { + INFO_LOG(m_logger) << LOGFUNCTION << " loading url '" << mapServiceUrl.toString() << "'"; + layer = new Esri::ArcGISRuntime::ArcGISTiledLayer(mapServiceUrl, d->m_mapImpl); + } + + layer->setName(layername); + layer->setObjectName(layer->name()); + + if (!d->m_orderedMapLayer.addLayer(layer->name(), SituationMap::LayerType::Basemap)) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " addLayer failed'" << layer->name() << "'"; + } + + QImage previewImageFinal = previewImage; + if (previewImageFinal.isNull()) + { + switch (basemapType) + { + case BasemapType::Undefined: + case BasemapType::Aerial: + previewImageFinal = QImage(QLatin1String(":/situationMap/preview_aerial.png")); + break; + case BasemapType::Topographic: + previewImageFinal = QImage(QLatin1String(":/situationMap/preview_topographic.png")); + break; + } // switch + } + + if (! d->m_basemap) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no basemap!!!"; + return false; + } + + + int basemapCount = -1; + if (d->m_basemap->baseLayers()) + basemapCount = d->m_basemap->baseLayers()->size(); + + INFO_LOG(m_logger) << LOGFUNCTION << " basemapCount = " << basemapCount; + + d->m_basemap->baseLayers()->append(layer); + + + // getLogicalLayer will + // - initialize + // - do errorHandling + // - update Layerparamter on doneLoading + // - and add layer to LayerListModel of basemap: + QSharedPointer logicalLayer = this->getCreateLogicalLayer(layername); + if (logicalLayer.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " getLogicalLayer invalid'" << layername << "'"; + } + else + { + // update LayerParameter first: + LayerParameter lp = logicalLayer->layerParameter(); + lp.url = mapServiceUrl; + lp.minScale = minScale; + lp.maxScale = maxScale; + lp.basemapType = basemapType; + lp.displayName = displayName; + lp.description = description; + lp.previewImage = previewImageFinal; + logicalLayer->setLayerParameter(lp); + + logicalLayer->setLayer(layer); + logicalLayer->setLayerType(LayerType::Basemap); + logicalLayer->setBasemapType(basemapType); // basemapType -> aerial, topo... + } + + + // // load / retryLoad: + // if (d->m_esriMap->loadStatus() == Esri::ArcGISRuntime::LoadStatus::FailedToLoad) + // d->m_esriMap->retryLoad(); + // else + // d->m_esriMap->load(); + + return true; +} + +bool LayerFactoryImpl::removeLayerInternal(const LayerType &layerType, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layerType=" << layerType + << " logicalLayerName=" << logicalLayerName; + + if (logicalLayerName.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " logicalLayerName empty" + << logicalLayerName; + return false; + } + if (! d->m_orderedMapLayer.contains(logicalLayerName, layerType)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " does not exist: '" + << " layerType=" << layerType + << " logicalLayerName='" << logicalLayerName << "'"; + return false; + } + + if (! d->hasLogicalLayer(logicalLayerName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " does not exist: '" + << " layerType=" << layerType + << " logicalLayerName='" << logicalLayerName << "'"; + } + + if (! d->m_orderedMapLayer.removeLayer(logicalLayerName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " remove failed: '" + << " layerType=" << layerType; + return false; + } + + d->removeLogicalLayer(logicalLayerName); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " logicalLayerName='" << logicalLayerName << "'"; + return true; +} + +bool LayerFactoryImpl::reOrderLayer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " orderedList 1: " << d->m_orderedMapLayer.layerNamesOrdered().join(QLatin1String(";")); + + reOrderLayerBasemap(); + reOrderLayerGraphicOverlays(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " orderedList 2: " << d->m_orderedMapLayer.layerNamesOrdered().join(QLatin1String(";")); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool LayerFactoryImpl::reOrderLayerBasemap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!(d->m_basemap && + d->m_basemap->baseLayers() && + ! d->m_basemap->baseLayers()->isEmpty()) + ) + return false; + + const QStringList &allLogicalLayerNames = d->m_orderedMapLayer.layerNamesOrdered(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " orderedListAll: " << allLogicalLayerNames.join(QLatin1String(";")); + + const QStringList &basemapLogicalNames = d->m_orderedMapLayer.layerNamesOrdered(LayerType::Basemap); + DEBUG_LOG(m_logger) << LOGFUNCTION << " orderedListBasemap: " << basemapLogicalNames.join(QLatin1String(";")); + + int newIdx = 0; + Esri::ArcGISRuntime::LayerListModel *model = d->m_basemap->baseLayers(); + for (const QString &basemapName : basemapLogicalNames) + { + int curIdx = getEsriBasemapLayerIndex(basemapName); + + if (curIdx != newIdx) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " move" + << " basemapName ='" << basemapName << "'" + << " from " << curIdx + << " to " << newIdx << "..."; + model->move(curIdx, newIdx); + } + ++newIdx; + } // for + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " orderedList: " << d->m_orderedMapLayer.layerNamesOrdered().join(QLatin1String(";")); + return true; +} + +bool LayerFactoryImpl::reOrderLayerGraphicOverlays() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " orderedList: " << d->m_orderedMapLayer.layerNamesOrdered().join(QLatin1String(";")); + + if (! d->getEsriGraphicsOverlayListModel()) + return false; + + if (d->getEsriGraphicsOverlayListModel()->isEmpty()) + return false; + + const QStringList &layernamesOrdered = d->m_orderedMapLayer.layerNamesOrdered(); + + // + // reOrder all GraphicOverlays: + // + DEBUG_LOG(m_logger) << LOGFUNCTION << " layernamesOrdered: " << layernamesOrdered.join(QLatin1String(";")); + + Esri::ArcGISRuntime::GraphicsOverlayListModel *model = d->getEsriGraphicsOverlayListModel(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " model.size() 1= " << model->size(); + + + // N. Thoden, 28.03.2018, V100.2.1 - Consulting with Luke Smallwood: + // Using the testFix for the crashOnStartup, the approach of + // model->clear(); + // // re-append in required new order: + // for ( auto l, newOrderedGraphicsOverlayList) + // model->append(l) + // fails. -> go back to the initial (move) approach + // ==> use MOVE_APPROACH +#define MOVE_APPROACH + + // N. Thoden, 06.04.2018, V100.2.1 - AFTER Consulting with Luke Smallwood: + // Scenario: + // - start opApp, + // - add simple plan/overlay (one ETS point symbol) via Overlay Dialag + // (participants can be picked and MapObjectSelection is opened) + // - remove plan/overlay via Overlay Dialag + // (participants can no longer be picked and MapObjectSelection is opened) + // -> EventMouseNavigator::onGetPickingResult gets empty list from ESRI... + // ==> toogle back to NOT using MOVE_APPROACH +#undef MOVE_APPROACH + + // N. Thoden, 30.08.2018, V100.3.0 - behaviour is now: + // Using the MOVE_APPROACH the picking is working +#if ACRGIS_VERSION >= 0x640300 // 100.3.0 / Update 3 +# define MOVE_APPROACH +#endif + + +#ifdef MOVE_APPROACH + // + // model->move(curIdx, newIdx); seems to be buggy + // (N. Thoden, 29.09.2016, QuartzBeta1) + // + int newIdx = 0; + for (const QString &logicalname : layernamesOrdered) + { + QSharedPointer ll = d->getLogicalLayer(logicalname); + if (ll && ll->basemapType() == BasemapType::Undefined) //BasemapType::Undefined -> GraphicsOverlay + { + QList > pairs = + ll->getCurrentGraphicalOverlaysOrderedWithName(); + + QListIterator< QPair > it(pairs); + + while (it.hasNext()) + { + const QPair &pair = it.next(); + + const QString &esriLayername = pair.first; + Esri::ArcGISRuntime::GraphicsOverlay *graphicalOverlay = pair.second; + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " handle" + << " esriLayername='" << esriLayername << "':"; + + int curIdx = model->indexOf(graphicalOverlay); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " handle" + << " esriLayername='" << esriLayername << "':" + << " curIdx=" << curIdx << " -> newIdx="<< newIdx; + + if (curIdx < 0) + { + FATAL_LOG(m_logger) << LOGFUNCTION + << " esriLayername='" << esriLayername << "':" + << " not found"; + abort(); + // continue; + } + + if (curIdx != newIdx) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " move" + << " GraphicOverlays =" + << " from " << curIdx + << " to " << newIdx << "..." ; + DEBUG_LOG(m_logger) << LOGFUNCTION << " model.size() 2= " << model->size(); + model->move(curIdx, newIdx); + DEBUG_LOG(m_logger) << LOGFUNCTION << " model.size() 3= " << model->size(); + + int retIdx = model->indexOf(graphicalOverlay); + + if (retIdx != newIdx) + { + FATAL_LOG(m_logger) << LOGFUNCTION + << " esriLayername='" << esriLayername << "':" + << " move failed" + << " curIdx='" << curIdx + << " newIdx='" << newIdx + << " retIdx='" << retIdx; + abort(); + } + } + ++newIdx; + } // while + } // if + } // for + +#else + + // build new order of GraphicsOverlays : + QList newOrderedGraphicsOverlayList; + + for (const QString &logicalname : layernamesOrdered) + { + QSharedPointer ll = d->getLogicalLayer(logicalname); + if (ll && ll->basemapType() == BasemapType::Undefined) //BasemapType::Undefined -> GraphicsOverlay + { + QList > pairs = + ll->getCurrentGraphicalOverlaysOrderedWithName(); + + QListIterator< QPair > it(pairs); + + while (it.hasNext()) + { + const QPair &pair = it.next(); + + QString esriLayername = pair.first; + Esri::ArcGISRuntime::GraphicsOverlay *graphicalOverlay = pair.second; + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " handle" + << " esriLayername='" << esriLayername << "'"; + newOrderedGraphicsOverlayList.append(graphicalOverlay); + } // while + } // if + } // for + // : build new order of GraphicsOverlays + + // clear model: + model->clear(); + + // re-append in required new order: + for (auto l : newOrderedGraphicsOverlayList) + model->append(l); + +#endif // MOVE_APPROACH + + DEBUG_LOG(m_logger) << LOGFUNCTION << " model.size() 4= " << model->size(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " orderedList: " << d->m_orderedMapLayer.layerNamesOrdered().join(QLatin1String(";")); + return true; +} + +int LayerFactoryImpl::getEsriBasemapLayerIndex(const QString &esriLayername) const +{ + if (!(d->m_basemap && d->m_basemap->baseLayers())) + return -1; + + const Esri::ArcGISRuntime::LayerListModel *model = d->m_basemap->baseLayers(); + for (int i = 0; i < model->size(); ++i) + { + const Esri::ArcGISRuntime::Layer *curLayer = model->at(i); + const QString &curName = curLayer->name(); + if (QString::compare(esriLayername, curName) == 0) + return i; + } + return -1; +} + +Esri::ArcGISRuntime::Layer *LayerFactoryImpl::getEsriBasemapLayer(const QString &esriLayername) const +{ + int idx = getEsriBasemapLayerIndex(esriLayername); + if (idx < 0) + return nullptr; + // d->m_basemap already checked within 'getEsriBasemapLayerIndex' + Esri::ArcGISRuntime::Layer *layerFound = d->m_basemap->baseLayers()->at(idx); + return layerFound; +} + +QUrl LayerFactoryImpl::completeUrl(const QString &context, + const QUrl &urlIn, + const QString &resourceSubdir) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << " context='" << context << "'" + << ", urlIn='" << urlIn.toString() + << " resourceSubdir='" << resourceSubdir << "'"; + + if (!urlIn.isLocalFile()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " http -> return" + << ", url ='" << urlIn.toString(); + return urlIn; + } + + // assume it´s a file url: + // make absolute path according resourcePath: + QString filePathAbs = urlIn.toLocalFile(); + if (QDir::isRelativePath(filePathAbs)) + filePathAbs = SituationMap::getResourcePath() + resourceSubdir + filePathAbs; + + if (!QFileInfo::exists(filePathAbs)) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " context='" << context << "'" + << " file '" << filePathAbs << "' doesn't exists"; + return urlIn; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " END" + << " context='" << context << "'" + << ", filePathAbs='" << filePathAbs << "'"; + return QUrl::fromLocalFile(filePathAbs); +} + + +void LayerFactoryImpl::onLayerParameterChanged(const QString &layername, const LayerParameter &newLayerParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " layername='" << layername << "'" + << ", newLayerParameter='" << newLayerParameter; + d->m_mapImpl->emitLayerParameterChanged(layername, newLayerParameter); +} + +void LayerFactoryImpl::onLayerCreatedOnDemand(const QString &logicalName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " logicalName='" << logicalName << "'"; + this->reOrderLayer(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " logicalName='" << logicalName << "'"; +} + +void LayerFactoryImpl::onLayerDoneLoading(const QString &logicalName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " logicalName='" << logicalName << "'"; + + MapLayer mapLayer = d->m_orderedMapLayer.getLayer(logicalName); + if (!mapLayer.isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " mapLayer not found" + << " layername='" << logicalName << "'"; + } + + if (mapLayer.type() != LayerType::Basemap) + return; + + QSharedPointer layer = d->getLogicalLayer(logicalName); + if (!layer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no layer, " + << " layername='" << logicalName << "'"; + return; + } + + // Bugfix : + // REGG-5350 - mit dem normalen Zoom ( Mausrad ) kann man 6 Stufen ansteuern. + // Erst nach der Ums... + // REGG-5338 - In der Live-Lage ist die Zoomstufe nicht + // vollständig nutzbar, das letzte Viertel... + // REGG-5317 - Die Lagekarte der 1. Lageanwendung lässt sich nicht bis + // zur untersten Begrenzung... + + // (Hinein)Zoomen nur bis Scale 4000 moeglich... + // Tritt mit TA Letzlingen und User OHNE Settings sgelgentlich auf. + // Loesungsansatz: Slot-Methoden von synchronen Aufruf zu + // reCalculateMinMaxScaleBasedOnLayers befreien und per Timer aufrufen. + // // reCalculate minMaxScale even if there is just one Basemap: + // // calulcate and set values direct: + // reCalculateMinMaxScaleBasedOnLayers(); + // : Bugfix + + // do via timer: + d->reCalculateMinMaxScaleTimer->start(); // start == restart + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " logicalName='" << logicalName << "'"; +} + +void LayerFactoryImpl::onElevationSourceDoneLoading() +{ + Esri::ArcGISRuntime::ElevationSource *elevationSource = qobject_cast(sender()); + if (elevationSource) + { + INFO_LOG(m_logger) << LOGFUNCTION << " ElevationSource -> doneLoading: " + << " sender= '" << elevationSource->objectName() << "'" + << " loadStatus= '" << elevationSource->loadStatus() << "'" + << " elevationSourceType= " << elevationSource->elevationSourceType(); + + INFO_LOG(m_logger) << LOGFUNCTION << " ElevationSources " << elevationSourcesLoaded(); + + if (elevationSourcesLoaded()) + { + INFO_LOG(m_logger) << LOGFUNCTION << " ElevationSources completed"; + d->m_mapImpl->emitAllElevationSourcesLoaded(); + } + } + else + INFO_LOG(m_logger) << LOGFUNCTION << " ElevationSource -> doneLoading"; + +} + +void LayerFactoryImpl::onElevationSourceErrorOccured(const Esri::ArcGISRuntime::Error &error) +{ + QString senderName; + Esri::ArcGISRuntime::ElevationSource *elevationSource = qobject_cast(sender()); + if (elevationSource) + senderName = elevationSource->objectName(); + + ERROR_LOG(m_logger) << LOGFUNCTION << " ElevationSource -> EsriError occured: " << error + << ", sender= '" << senderName << "'"; +} + +void LayerFactoryImpl::onSurfaceDoneLoading2D() +{ + QString senderName; + Esri::ArcGISRuntime::Surface *surface = qobject_cast(sender()); + if (surface) + senderName = surface->objectName(); + + INFO_LOG(m_logger) << LOGFUNCTION << " Surface -> doneLoading: " + << " sender= '" << senderName << "'"; +} + +void LayerFactoryImpl::onSurfaceErrorOccured2D(const Esri::ArcGISRuntime::Error &error) +{ + QString senderName; + Esri::ArcGISRuntime::Surface *surface = qobject_cast(sender()); + if (surface) + senderName = surface->objectName(); + + ERROR_LOG(m_logger) << LOGFUNCTION << " Surface -> EsriError occured: " << error + << ", sender= '" << senderName << "'"; +} + +void LayerFactoryImpl::reCalculateMinMaxScaleBasedOnLayers() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QStringList &layernames = d->m_orderedMapLayer.layerNamesOrdered(LayerType::Basemap); + + double minScale = 0; + double maxScale = 0; + + QList minScales; + QList maxScales; + + + bool initial = true; + + for (const QString &layername : layernames) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " layername" << layername; + + QSharedPointer layer = d->getLogicalLayer(layername); + if (!layer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no layer, " + << " layername='" << layername << "'"; + continue; + } + + const LayerParameter &layerParameter = layer->layerParameter(); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " layername='" << layername << "'" + << " minScale=" << layerParameter.minScale + << " maxScale=" << layerParameter.maxScale; + + + // already loaded? ( + if (!layerParameter.minMaxScaleValid()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " layername" << layername + << " not valid"; + continue; + } + + double localMinScale = layerParameter.minScale; + double localMaxScale = layerParameter.maxScale; + + minScales.append(static_cast(localMinScale)); + maxScales.append(static_cast(localMaxScale)); + + // first time: set initial values: + if (initial) + { + initial = false; + minScale = localMinScale; + maxScale = localMaxScale; + } + else + { + minScale = std::max(minScale, localMinScale); // use std::max for minScale!!! + maxScale = std::min(maxScale, localMaxScale); // use std::min for maxScale!!! + } + } // for + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " a minScale=" << minScale + << " maxScale=" << maxScale; + + // OGC: scales are optional. In this case use '0': + // For MinScale: + if (minScales.contains(0)) + minScale = 0; + + // For MaxScale: + if (maxScales.contains(0)) + maxScale = 0; + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " apply " + << " minScale=" << minScale + << " maxScale=" << maxScale; +// d->m_mapImpl->setMinScale(minScale+1); // why did i wrote this? +// d->m_mapImpl->setMaxScale(maxScale+1); // why did i wrote this? + d->m_mapImpl->setMinScale(minScale); + d->m_mapImpl->setMaxScale(maxScale); +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.h new file mode 100755 index 000000000..3363a10a0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/layer_factory_impl.h @@ -0,0 +1,329 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of LayerFactory + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "map_layer.h" +#include "logical_layer.h" +#include +#include +#include +#include + + +// ESRI +#include +#include +#include +#include +#include +#include +#include + + +// Qt +#include + + +// Forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Layer; +class GraphicsOverlay; +class RasterElevationSource; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MapImpl; + + + +/** + * @brief The LayerFactoryImpl + * + * + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class LayerFactoryImpl : public QObject, public virtual LayerFactory +{ + Q_OBJECT + + using BaseClass = QObject; + +public: + /*! \name Construction, Destruction */ + //@{ + explicit LayerFactoryImpl(QObject *parent, SituationMap::MapImpl *mapImpl); + virtual ~LayerFactoryImpl(); + + LayerFactoryImpl(const LayerFactoryImpl &other) = delete; + LayerFactoryImpl &operator=(const LayerFactoryImpl &other) = delete; + //@} + + /*! \name Basemap Layer */ + //@{ + bool initializeBasemapBuiltIn(const QString &tpkFile) override; + bool isUsingBaseMapBuiltIn() const override; + + QString basemapLayernamePrefix() const override; + void setBasemapLayernamePrefix(const QString &basemapLayernamePrefix) override; + + QString participantLayername() const override; + RenderingMode participantLayerRenderingMode() const override; + SurfacePlacement participantLayerSurfacePlacement() const override; + void setParticipantLayer(const QString &participantLayername, + RenderingMode renderingMode, + SurfacePlacement surfacePlacement) override; + + + bool addBasemap(const QString &layername, + const QUrl &mapServiceUrl, + const BasemapType basemapType, + const double minScale = -1, + const double maxScale = -1, + const QString &displayName = QString(), + const QString &description = QString(), + const QImage &previewImage = QImage()) override; + bool removeBasemap(const QString &layername) override; + + Esri::ArcGISRuntime::Envelope fullExtentOfBasemaps(double margin_zeroToOne = 0.0) const; + + //@} + + /*! \name Scene Object Layer */ + //@{ + bool addSceneObjectLayer(const QString &layername, + const QUrl &sceneLayerServiceUrl, + const double altitudeOffset = 0, + const QString &displayName = QString(), + const QString &description = QString(), + const QImage &previewImage = QImage()) override; + + bool isSceneObjectLayerVisible() const override; + void setSceneObjectLayerVisible(bool visible) override; + //@} + + + /*! \name Elevation */ + //@{ + bool initializeElevationSources(const QStringList &sceneElevationServiceUrls) override; + //@} + + /*! \name Operational Layer */ + //@{ + + static void setForceCreationOfGraphicsOverlay(bool onOff); // to deal with esri bug + + bool addOperationalLayer(const QString &logicalLayerName, + const QString &displayName, + RenderingMode renderMode = RenderingMode::Dynamic, + SurfacePlacement surfacePlacement = SurfacePlacement::Relative) override; + bool removeOperationalLayer(const QString &logicalLayerName) override; + bool hasOperationalLayer(const QString &logicalLayerName) const override; + + + QString layernameForCubes() const override; + void setLayernameForCubes(const QString &logicalName) override; + bool hasLayernameForCubes() const override; + + bool assignUniqueValueRendererForCubes() override; + + bool assignUniqueValueRenderer(const QString &layername, + const QString &fieldName, + const QVector &uvDataCont) override; + + // For BoobyTraps + bool assignUniqueValueRendererAesCentral(const QString &layername, + const QStringList &fieldNames, + const QVector &uvDataCont) override; + + // For AES / 3D Extrusion + bool assignUniqueValueRendererAesCentral3DExtrusion(const QString &layername, + const QStringList &fieldNames, + const QVector &uvDataCont) override; + + + bool initializeUnitTestLayer() override; + //@} + + + /*! \name Overlay Layer */ + //@{ + bool addOverlayLayer(const QString &logicalLayerName, const QString &displayName, bool isSnaphot = false) override; + QString technicalNameOfOverlayLayer(const QString &logicalLayerName); + bool removeOverlayLayer(const QString &logicalLayerName) override; + + QString snapshotOverlayLayername() const override; + //@} + + + /*! \name Snapshot (overlay) Layer */ + //@{ + QString snapshotLayer(); + bool snapshotLayerExists() const; + //@} + + + /*! \name JumpPoint Layer */ + //@{ + QString jumpPointLayer(); + bool jumpPointLayerExists() const; + //@} + + /*! \name Tool Layer */ + //@{ + QString toolLayer() override; // Tools: Viewshed etc. + bool toolLayerExists() const; // Tools: Viewshed etc. + //@} + + /*! \name Rubberband Layer */ + //@{ + QString rubberbandLayer() const override; + bool rubberbandLayerExists() const; + //@} + + /*! \name EmergencyCall Layer (at least relevant for GUEZ) */ + //@{ + QString emergencyCallLayer(bool getJustTheName = false) override; + bool emergencyCallLayerExists() const override; + + bool switchEmergencyCallLayerOn() override; + bool switchEmergencyCallLayerOff() override; + + void handleEmergencyCallSpecials(); + //@} + + + /*! \name Common */ + //@{ + Map *map() const override; + int count() const override; + QStringList layernames() const override; + QStringList layernames(const LayerType &layerType) const override; + bool checkLayerOrder(const QStringList &orderedLayernames) override; + bool orderLayerTo(const QStringList &orderedLayernames) override; + + LayerType layerType(const QString &layername) const override; + LayerParameter layerParameter(const QString &layername) const override; + bool setLayerParameter(const QString &layername, const LayerParameter &layerParameter) override; + + bool setLayerOpacity(LayerType layerType, int opacity) override; + bool setLayerBrightness(LayerType layerType, int brightness) override; + bool setLayerContrast(LayerType layerType, int contrast) override; + bool setLayerGamma(LayerType layerType, int gamma) override; + bool setLayerParameter(LayerType layerType, int value, int LayerParameter::*memberPtr); + void setLayersVisible(const QVector &layerTypes, bool visible) override; + //@} + + +#if ACRGIS_VERSION < 0x640500 // 100.5.0 / Update 5 + /*! \name Selection */ + //@{ + QColor selectionColor() const override; + void setSelectionColor(const QColor &selectionColor) override; + //@} +#endif // ACRGIS_VERSION + + + /*! \name Elevation */ + //@{ + Esri::ArcGISRuntime::Surface *baseSurface() const; + bool elevationAvailable() const override; + int elevationSourcesCount(bool checkIfLoaded = false) const; + bool elevationSourcesLoaded(); + QPointerlocationToElevation() const override; + //@} + + // Not LayerIf, but for MapObject(Impl) + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayer(const QString &logicalName); + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayerDict(const QString &logicalName); + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayer3DExtrusion(const QString &logicalName); + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayer3D_SpAbsolute(const QString &logicalName); + +protected slots: + void onLayerParameterChanged(const QString &layername, + const LayerParameter &newLayerParameter); + + void onLayerCreatedOnDemand(const QString &logicalName); + void onLayerDoneLoading(const QString &logicalName); + + void onElevationSourceDoneLoading(); + void onElevationSourceErrorOccured(const Esri::ArcGISRuntime::Error &error); + + void onSurfaceDoneLoading2D(); + void onSurfaceErrorOccured2D(const Esri::ArcGISRuntime::Error &error); + + void reCalculateMinMaxScaleBasedOnLayers(); + +protected: + bool initializeElevationSources2D(const QStringList &sceneElevationServiceUrls); + bool initializeElevationSources3D(const QStringList &sceneElevationServiceUrls); + QList initializeElevationSourcesInternal(const QStringList &sceneElevationServiceUrls); + Esri::ArcGISRuntime::ElevationSource* initializeElevationSourceInternal(const QUrl &elevationServiceUrl, int layerCount); + Esri::ArcGISRuntime::RasterElevationSource *initializeRasterElevationSourceInternal(const QStringList &filePaths); + + + QSharedPointer addLayerToMgnt(const QString &logicalLayerName, const LayerType &layerType); + bool layerExistsInMngt(const QString &logicalLayerName, const LayerType &layerType) const; + + QString createInternalLayerName(const TechnicalLayerType &technicalLayerType, + const QString &logicalLayerName) const; + + QSharedPointer getLogicalLayer(const QString &logicalName) const; + QSharedPointer getCreateLogicalLayer(const QString &logicalName); + + + virtual bool addBasemapInternal(const QString &layername, + const QUrl &mapServiceUrl, + const BasemapType basemapType, + const double minScale = -1, + const double maxScale = -1, + const QString &displayName = QString(), + const QString &description = QString(), + const QImage &previewImage = QImage()); + + + bool removeLayerInternal(const LayerType &layerType, const QString &logicalLayerName); + + + virtual bool reOrderLayer(); + virtual bool reOrderLayerBasemap(); + virtual bool reOrderLayerGraphicOverlays(); + + int getEsriBasemapLayerIndex(const QString &esriLayername) const; + Esri::ArcGISRuntime::Layer *getEsriBasemapLayer(const QString &esriLayername) const; + + QUrl completeUrl(const QString &context, + const QUrl &urlIn, + const QString &resourceSubdir); + +private : + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; + + +} // namespace SituationMap + +//---------------------------------------------------------------------------- + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.cpp new file mode 100755 index 000000000..bbd78cd31 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.cpp @@ -0,0 +1,245 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of LocationDisplay + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "location_display_impl.h" +#include "map_impl.h" +#include +#include + + +// ESRI +#include +#include + +#if ACRGIS_VERSION >= 0x640800 // 100.8.0 / Update 8 +# define HAS_LOCATION_DISPLAY +#endif // ACRGIS_VERSION + +#ifdef HAS_LOCATION_DISPLAY +# include +# include +#endif // HAS_LOCATION_DISPLAY + +// Qt + + +namespace SituationMap +{ + +#ifdef HAS_LOCATION_DISPLAY + +class LocationDisplayImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + SituationMap::MapImpl *m_mapImpl{nullptr}; + OriginTranslated m_originTranslated; + + Esri::ArcGISRuntime::LocationDisplay *m_locationDisplay{nullptr}; + Esri::ArcGISRuntime::SimulatedLocationDataSource *m_locationDataSource{nullptr}; + +}; // class LocationDisplayImpl::Private +//----------------------------------------------------------------------------- + + +LocationDisplayImpl::LocationDisplayImpl(SituationMap::MapImpl *mapImpl) + : m_logger(getLogger("LocationDisplayImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_mapImpl = mapImpl; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +LocationDisplayImpl::~LocationDisplayImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool LocationDisplayImpl::init() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!d->m_mapImpl) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no map"; + return false; + } + if (d->m_mapImpl->mode() != Mode::Mode_2D) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " LocationDisplay only supported at Mode2D"; + return false; + } + + // setup coordTranslationFunctor + d->m_originTranslated.setCoordTransFunctor(d->m_mapImpl->coordTranslationFunctor()); + + d->m_locationDisplay = d->m_mapImpl->getEsriMapGraphicsView()->locationDisplay(); + if (!d->m_locationDisplay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no locationDisplay"; + return false; + } + + // setup LocationDisplay: + if (d->m_locationDisplay->objectName().isEmpty()) + d->m_locationDisplay->setObjectName(QStringLiteral("EsriLocationDisplay")); + + QObject::connect(d->m_locationDisplay, &Esri::ArcGISRuntime::LocationDisplay::errorOccurred, d->m_locationDisplay, [this](const Esri::ArcGISRuntime::Error &error) { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on LocationDisplay, error " << error; + }); + if (m_logger.isDebugEnabled()) + QObject::connect(d->m_locationDisplay, &Esri::ArcGISRuntime::LocationDisplay::locationChanged, d->m_locationDisplay, [this](const Esri::ArcGISRuntime::Location &location) { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Location changed: " << location.position().toJson(); + }); + + // setup DataSource: + d->m_locationDataSource = new Esri::ArcGISRuntime::SimulatedLocationDataSource(d->m_mapImpl); + d->m_locationDataSource->setObjectName(QStringLiteral("SimulatedLocationDataSource")); + QObject::connect(d->m_locationDataSource, &Esri::ArcGISRuntime::SimulatedLocationDataSource::errorOccurred, d->m_locationDisplay, [this](const Esri::ArcGISRuntime::Error &error) { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on SimulatedLocationDataSource, error " << error; + }); + + // Maybe reduce animation rate: + // d->m_locationDataSource->setIterationRate(0.25); + + d->m_locationDisplay->setDataSource(d->m_locationDataSource); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return (d->m_locationDisplay); +} + +bool LocationDisplayImpl::setCurrentLocation(const Point3D & currentLocation, + double horizontalAccuracy, + double velocity, + double course, + bool lastKnown) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, currentLocation=" << currentLocation + << ", horizontalAccuracy=" << horizontalAccuracy + << ", velocity=" << velocity + << ", course=" << course + << ", lastKnown=" << lastKnown; + if (!d->m_mapImpl) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no map"; + return false; + } + if (!d->m_locationDataSource) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no locationDataSource"; + return false; + } + + // convert using coordTranslationFunctor (wrapped by OriginTranslated) + d->m_originTranslated.setOrigin(currentLocation); + const Point3D &origionTranslated = d->m_originTranslated.originTranslated(); + + Esri::ArcGISRuntime::Point point(origionTranslated.x(), origionTranslated.y(), origionTranslated.z(), d->m_mapImpl->getEsriSpatialReference()); + DEBUG_LOG(m_logger) << LOGFUNCTION << " point=" << point.toJson(); + +// const int velocity = 0; // 0 => kein Richtungsinformation in der Darstellung + Esri::ArcGISRuntime::Location location(point, + 0, // The horizontal accuracy in meters. Positive values or NaN are supported + velocity, // The location's velocity in meters per second + course, // The location's course in degrees (clockwise), 0 being true North. + lastKnown // (false->blue ; true -> gray) + ); + + d->m_locationDataSource->setLocations({location}); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool LocationDisplayImpl::setOutdated(bool lastKnown) +{ + return setCurrentLocation(d->m_originTranslated.origin(),0.0 ,0.0 ,0.0 ,lastKnown); +} + + +bool LocationDisplayImpl::start() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + // 2023, N.Thoden: V100.15: use LocationDisplay instead of LocationDataSource + if (!d->m_locationDisplay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no locationDisplay"; + return false; + } + +// d->m_locationDisplay->setAutoPanMode(Esri::ArcGISRuntime::LocationDisplayAutoPanMode::Recenter); +// d->m_locationDisplay->setInitialZoomScale(8123); + + d->m_locationDisplay->start(); +// d->m_locationDataSource->start(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void LocationDisplayImpl::stop() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + // 2023, N.Thoden: V100.15: use LocationDisplay instead of LocationDataSource + if (!d->m_locationDisplay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no locationDisplay"; + return; + } + + d->m_locationDisplay->stop(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool LocationDisplayImpl::isStarted() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + // 2023, N.Thoden: V100.15: avoid deprecated, use LocationDisplay instead of LocationDataSource + if (!d->m_locationDisplay) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no locationDisplay"; + return false; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_locationDisplay->isStarted(); +} + +#else // HAS_LOCATION_DISPLAY + + LocationDisplayImpl::LocationDisplayImpl(SituationMap::MapImpl *mapImpl) + : m_logger(getLogger("LocationDisplayImpl")) + , d(nullptr) + { Q_UNUSED(mapImpl); } + + LocationDisplayImpl::~LocationDisplayImpl() {} + + bool LocationDisplayImpl::init() {return false; } + + bool LocationDisplayImpl::setCurrentLocation(const Point3D & /*currentLocation*/, + double /*horizontalAccuracy*/, + double /*velocity*/, + double /*course*/, + bool /*lastKnown*/) + { return false; } + + bool LocationDisplayImpl::start() { return false; } + void LocationDisplayImpl::stop() { } + bool LocationDisplayImpl::isStarted() { return false; } + bool LocationDisplayImpl::setOutdated(bool lastKnown) { return lastKnown; } + + +#endif // HAS_LOCATION_DISPLAY + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.h new file mode 100755 index 000000000..150a1d461 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_display_impl.h @@ -0,0 +1,61 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of LocationDisplay + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MapImpl; + +class LocationDisplayImpl : public SituationMap::LocationDisplay +{ + using BaseClass = SituationMap::LocationDisplay; + +public: + + explicit LocationDisplayImpl(SituationMap::MapImpl *mapImpl); + virtual ~LocationDisplayImpl(); + + LocationDisplayImpl(const LocationDisplayImpl &other) = delete; + LocationDisplayImpl &operator=(LocationDisplayImpl &other) = delete; + + bool init(); + + bool setCurrentLocation(const Point3D & currentLocation, + double horizontalAccuracy = 0, + double velocity = 0, + double course = 0, + bool lastKnown = false + ) override; + bool setOutdated(bool lastKnown) override; + + bool start() override; + void stop() override; + bool isStarted() override; + + +private : + LOGGER_T m_logger; ///< Interface of the Logging-Facility + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d{nullptr}; + +}; + +} // namespace SituationMap + +//---------------------------------------------------------------------------- diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.cpp new file mode 100755 index 000000000..a420ec4bd --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.cpp @@ -0,0 +1,218 @@ +/** + * @RDE_HEADER@ + * LocationToElevationBulk + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "location_to_elevation_bulk.h" +#include +#include "Map/situation_map_utils.h" + +#include "location_to_elevation_helper.h" // to get define USE_ELEVATION_MAP_AS_WORKAROUND + + +// ESRI +#include +#include + + +// Qt +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//---------------------------------------------------------------------------- +class LocationToElevationBulk::Private +{ +public: + Private() = default; + ~Private() = default; + + + // ESRI data: + QPointer m_surface; // for ESRI-Surface-based calculation + QPointer m_map; // for ElevationMap-based calculation + + Esri::ArcGISRuntime::Point m_location; + + Esri::ArcGISRuntime::SpatialReference m_spatialReference; + + + + QVector> m_locationDataCont; + int m_progressCount = 0; +}; + + +//---------------------------------------------------------------------------- +LocationToElevationBulk::LocationToElevationBulk(QObject *parent) + : BaseClass(parent) + , m_logger(getLogger("LocationToElevationBulk")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + reset(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +LocationToElevationBulk::~LocationToElevationBulk() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->stop(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool LocationToElevationBulk::init(Map *map, Esri::ArcGISRuntime::Surface *surface) +{ + this->reset(); + + if (!map) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no map"; + return false; + } + d->m_map = map; + + connect(d->m_map.data(), &Map::beginDestroy, this, &LocationToElevationBulk::stop); + + MapImpl *mapImpl = static_cast(d->m_map.data()); + d->m_spatialReference = mapImpl->getEsriSpatialReference(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_spatialReference.wkid = " << d->m_spatialReference.wkid(); + + // use ESRI-Surface-based OR ElevationMap-based: + +#ifdef USE_ELEVATION_MAP_AS_WORKAROUND + Q_UNUSED(surface); + d->m_surface.clear(); + + auto elevationMap = static_cast(d->m_map.data())->getElevationMapManager(); + if (!elevationMap) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no ElevationMapManager"; + return false; + } + +#else + d->m_surface = surface; + if (!d->m_surface) + return false; + + + if (d->m_surface) + { + // be prepared in case of destruction: + connect(d->m_surface.data(), &Esri::ArcGISRuntime::Surface::destroyed, this, &LocationToElevationBulk::stop); + } +#endif // USE_ELEVATION_MAP_AS_WORKAROUND + + return true; +} + +void LocationToElevationBulk::addLocation(const Esri::ArcGISRuntime::Point &esriMapLocation) +{ + d->m_locationDataCont.append(QSharedPointer(new LocationData(esriMapLocation))); +} + +void LocationToElevationBulk::start() +{ + d->m_progressCount = 0; + +#ifndef USE_ELEVATION_MAP_AS_WORKAROUND + + if (!d->m_surface || d->m_surface->loadStatus() != Esri::ArcGISRuntime::LoadStatus::Loaded) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no surface or surface not loaded yet" + << " d->m_surface = " << d->m_surface.data(); + return; + } +#endif // USE_ELEVATION_MAP_AS_WORKAROUND + + QMutableVectorIterator> vecIt(d->m_locationDataCont); + while (vecIt.hasNext()) + { + QSharedPointer ld = vecIt.next(); + if(!ld) + continue; + +#ifdef USE_ELEVATION_MAP_AS_WORKAROUND + ld->setMap(d->m_map); +#else + ld->setSurface(d->m_surface); +#endif // USE_ELEVATION_MAP_AS_WORKAROUND + + + connect(ld.data(), &LocationData::elevationCompleted, + this, &LocationToElevationBulk::onLocationDataCompleted, Qt::UniqueConnection); + + if (!ld->startLocationToElevation()) + ERROR_LOG(m_logger) << LOGFUNCTION << " startLocationToElevation failed"; + } // while +} + + +void LocationToElevationBulk::stop() +{ + QMutableVectorIterator> vecIt(d->m_locationDataCont); + while (vecIt.hasNext()) + { + QSharedPointer ld = vecIt.next(); + if(!ld) + continue; + if (!ld->cancel()) + INFO_LOG(m_logger) << LOGFUNCTION << " LocationData.cancel failed"; + } // while +} + +void LocationToElevationBulk::reset() +{ + stop(); + d->m_locationDataCont.clear(); + d->m_progressCount = 0; +} + +int LocationToElevationBulk::size() const +{ + return d->m_locationDataCont.size(); +} + +QVector > LocationToElevationBulk::results() const +{ + QVector > results; + + QVectorIterator> vecIt(d->m_locationDataCont); + while (vecIt.hasNext()) + { + QSharedPointer ld = vecIt.next(); + if(!ld) + continue; + + const Esri::ArcGISRuntime::Point &location = ld->location(); + QPair pair(QPointF(location.x(), location.y()), ld->elevation()); + results.append(pair); + } + return results; +} + +void LocationToElevationBulk::onLocationDataCompleted() +{ + ++d->m_progressCount; + + if (d->m_progressCount >= d->m_locationDataCont.size()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " emit locationsToElevationsCompleted"; + emit locationsToElevationsCompleted(); + } +} + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.h new file mode 100755 index 000000000..a508d246a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_bulk.h @@ -0,0 +1,212 @@ +/** + * @RDE_HEADER@ + * LocationToElevationBulk + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "screen_location_elevation_helper_base.h" +#include +#include + +// for ElevationMap-based calculation +#include +#include +#include + + +//ESRI +#include +#include + + +// Qt +#include +#include +#include +#include + + +namespace Esri +{ +namespace ArcGISRuntime +{ +class Surface; +class Point; +class SpatialReference; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class LocationData : public QObject +{ + Q_OBJECT + +public: + explicit LocationData(const Esri::ArcGISRuntime::Point &esriMapLocation) + : QObject() + , m_esriMapLocation(esriMapLocation) + {} + + ~LocationData() + { + cancel(); + } + + /*! \name Prepare surface (ESRI-based) OR map (own class ElevationMap */ + //@{ + void setSurface(Esri::ArcGISRuntime::Surface *surface) + { + m_map.clear(); + m_surface = QPointer(surface); + } + + void setMap(Map *map) + { + m_surface.clear(); + m_map = map; + } + //@} + + + bool startLocationToElevation() + { + // ESRI-Surface-based calculation : + if (m_surface) + { + // SDK v300: locationToElevation() replaced with elevationAsync() + m_taskFuture = m_surface->elevationAsync(m_esriMapLocation); + if (!m_taskFuture.isValid()) + { + qCritical() << " elevationAsync failed, taskFuture invalid"; + return false; + } + // Handle async result with continuation + m_taskFuture.then(this, [this](double elevation) { + m_elevation = elevation; + emit elevationCompleted(); + }); + return true; + } + // : ESRI-Surface-based calculation + + // ElevationMap-based calculation : + if (m_map) + { + auto elevationMap = static_cast(m_map.data())->getElevationMapManager(); // ToDo: remove if Esri-Surface will work without crash + if (!elevationMap) + { + qCritical() << " no ElevationMapManager"; + return false; + } + + double lon = m_esriMapLocation.x(); + double lat = m_esriMapLocation.y(); + + CoordTranslationFunctor *coordTranslationFunctor = m_map->coordTranslationFunctor(); + if ((coordTranslationFunctor) && (!coordTranslationFunctor->mapIsGeographic())) + { + const QPointF &geoLoc = coordTranslationFunctor->mapToGeographic(lon, lat); + lon = geoLoc.x(); + lat = geoLoc.y(); + } + + m_elevation = elevationMap->elevation(lon, lat); + emit elevationCompleted(); + + return true; + } + // : ElevationMap-based calculation + + return false; + } + + bool cancel() + { + // QFuture in Qt6 doesn't have cancel(), but we can check if it's still running + return !m_taskFuture.isFinished(); + } + + const Esri::ArcGISRuntime::Point &location() const { return m_esriMapLocation; } + + bool hasElevation() const { return !std::isnan(m_elevation); } + double elevation() const { return m_elevation; } + void setElevation(double elevation) { m_elevation = elevation; } + +signals: + void elevationCompleted(); + +private: + // In: + Esri::ArcGISRuntime::Point m_esriMapLocation; + + // processing: + QPointer m_surface; // for ESRI-Surface-based calculation + QPointer m_map; // for ElevationMap-based calculation + + + QFuture m_taskFuture; + + // out: + double m_elevation = std::nan(""); +}; // LocationData + + + +/** + * @brief The LocationToElevationBulk class helps calculate a couple of locations to their corresponding elevation + */ +class SITUATIONMAP_EXPORT LocationToElevationBulk : public QObject + +{ + Q_OBJECT + + using BaseClass = QObject; + +public: + /*! \name Construction, Destruction */ + //@{ + explicit LocationToElevationBulk(QObject *parent = nullptr); + ~LocationToElevationBulk(); + + bool init(Map *map, Esri::ArcGISRuntime::Surface *surface); + + LocationToElevationBulk(const LocationToElevationBulk &other) = delete; + LocationToElevationBulk &operator=(LocationToElevationBulk &other) = delete; + //@} + + void addLocation(const Esri::ArcGISRuntime::Point &esriMapLocation); + void start(); + void stop(); + void reset(); + + int size() const; + + QVector< QPair< QPointF, double > > results() const; + + +Q_SIGNALS: + void locationsToElevationsCompleted(); + +private Q_SLOTS: + void onLocationDataCompleted(); + +protected: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.cpp new file mode 100755 index 000000000..a4fef05c1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.cpp @@ -0,0 +1,198 @@ +/** + * @RDE_HEADER@ + * LocationToElevationHelper + * $Id$ + * $HeadURL$ + * $Author$ + */ + + + +// Own +#include "location_to_elevation_helper.h" +#include "Map/situation_map_utils.h" + +// +// NOTE: define should be in the headerfile to switch location_to_elevation_bulk also +// +#ifdef USE_ELEVATION_MAP_AS_WORKAROUND + #include "Map/coord_translation_functor.h" // ToDo: remove if Esri-Surface will work under windows without crash + #include "MapImpl/map_impl.h" + #include +#endif // USE_ELEVATION_MAP_AS_WORKAROUND + +// ESRI +#include +#include + + +// Qt +#include + + + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//---------------------------------------------------------------------------- +class LocationToElevationHelper::Private +{ +public: + Private() = default; + ~Private() = default; + + + // ESRI data: + QPointer m_surface; + + Esri::ArcGISRuntime::Point m_location; + + Esri::ArcGISRuntime::SpatialReference m_spatialReference; + + QFutureWatcher *m_elevationWatcher = nullptr; + + #ifdef USE_ELEVATION_MAP_AS_WORKAROUND + + CoordTranslationFunctor *m_coordTransFunctor; // ToDo: remove once Esri-Surface will work under windows without crash + QPointer m_elevationMapManager; + + #endif // USE_ELEVATION_MAP_AS_WORKAROUND + +}; + +//---------------------------------------------------------------------------- +LocationToElevationHelper::LocationToElevationHelper(QObject *parent) + : BaseClass(parent) + , m_logger(getLogger("LocationToElevationHelper")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +LocationToElevationHelper::~LocationToElevationHelper() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->stop(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool LocationToElevationHelper::init(Map *map, + Esri::ArcGISRuntime::Surface *surface, + const Esri::ArcGISRuntime::SpatialReference &spatialReference) +{ + this->stop(); + + connect(map, &Map::beginDestroy, this, &LocationToElevationHelper::stop); + + d->m_surface = surface; + d->m_spatialReference = spatialReference; + + #ifdef USE_ELEVATION_MAP_AS_WORKAROUND + d->m_elevationMapManager = static_cast(map)->getElevationMapManager().get(); // ToDo: remove (?) if Esri-Surface will work under windows without crash + d->m_coordTransFunctor = map->coordTranslationFunctor(); + + if (!d->m_elevationMapManager) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no ElevationMapManager"; + return false; + } + +#endif // USE_ELEVATION_MAP_AS_WORKAROUND + + if (!d->m_surface) + return false; + + INFO_LOG(m_logger) << LOGFUNCTION << " m_spatialReference.wkid = " << d->m_spatialReference.wkid(); + + + // be prepared in case of destruction: + connect(d->m_surface.data(), &Esri::ArcGISRuntime::Surface::destroyed, this, &LocationToElevationHelper::stop); + + // Create QFutureWatcher for elevationAsync() results + d->m_elevationWatcher = new QFutureWatcher(this); + connect(d->m_elevationWatcher, &QFutureWatcher::finished, + this, [this]() { + if (d->m_elevationWatcher->future().isValid()) + { + double elevation = d->m_elevationWatcher->future().result(); + emit elevationCompleted(elevation); + } + }); + + return true; +} + + +void LocationToElevationHelper::stop() +{ + if (d->m_surface) + d->m_surface->disconnect(this); + this->disconnect(this); + BaseClass::stop(); + d->m_surface.clear(); +} + +void LocationToElevationHelper::locationToElevation(const QPointF &mapLocation) +{ + locationToElevation(Esri::ArcGISRuntime::Point(mapLocation.x(), mapLocation.y(), d->m_spatialReference)); +} + +void LocationToElevationHelper::locationToElevation(const Esri::ArcGISRuntime::Point &esriMapLocation) +{ + #ifdef USE_ELEVATION_MAP_AS_WORKAROUND + if (d->m_elevationMapManager) // ToDo: remove (?) if Esri-Surface will work under windows without crash + { + double lon = esriMapLocation.x(); + double lat = esriMapLocation.y(); + + if ((d->m_coordTransFunctor) && (!d->m_coordTransFunctor->mapIsGeographic())) + { + const QPointF & geoLoc = d->m_coordTransFunctor->mapToGeographic(lon, lat); + lon = geoLoc.x(); + lat = geoLoc.y(); + } + + const double ele = d->m_elevationMapManager->elevation(lon,lat); + emit elevationCompleted(ele); + return; + } + #endif // USE_ELEVATION_MAP_AS_WORKAROUND + + d->m_location = esriMapLocation; + +// this->cancel(); // cancel all former: + + BaseClass::newComputationRequested(); +} + +void LocationToElevationHelper::mainComputation() +{ + if (!d->m_surface) + return; + + if (d->m_surface->loadStatus() != Esri::ArcGISRuntime::LoadStatus::Loaded) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " surface not loaded yet"; + return; + } + + QFuture future = d->m_surface->elevationAsync(Esri::ArcGISRuntime::Point(d->m_location)); + + if (future.isValid()) + { + d->m_elevationWatcher->setFuture(future); + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " elevationAsync failed"; + } +} + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.h new file mode 100755 index 000000000..a88f889aa --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/location_to_elevation_helper.h @@ -0,0 +1,81 @@ +/** + * @RDE_HEADER@ + * LocationToElevationHelper + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "screen_location_elevation_helper_base.h" +#include +#include + + +#define USE_ELEVATION_MAP_AS_WORKAROUND // ToDo: remove if Esri-Surface will work without crash + + +// Qt +#include +#include + + +namespace Esri { +namespace ArcGISRuntime +{ +class Surface; +class Point; +class SpatialReference; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +/** + * @brief The ScreenToLocationHelper class helps to assign the (global) requests location_to_elevation to the right caller + */ +class SITUATIONMAP_EXPORT LocationToElevationHelper : public ScreenLocationElevationHelperBase +{ + Q_OBJECT + + using BaseClass = ScreenLocationElevationHelperBase; + +public: + /*! \name Construction, Destruction */ + //@{ + explicit LocationToElevationHelper(QObject *parent = nullptr); + ~LocationToElevationHelper(); + + bool init(Map *map, + Esri::ArcGISRuntime::Surface *surface, + const Esri::ArcGISRuntime::SpatialReference &spatialReference); + + LocationToElevationHelper(const LocationToElevationHelper &other) = delete; + LocationToElevationHelper &operator=(LocationToElevationHelper &other) = delete; + //@} + + void stop(); + + void locationToElevation(const QPointF &mapLocation); + void locationToElevation(const Esri::ArcGISRuntime::Point &esriMapLocation); + + void mainComputation() override; + +Q_SIGNALS: + void elevationCompleted(double elevation); + +protected: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.cpp new file mode 100755 index 000000000..56b16ad83 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.cpp @@ -0,0 +1,802 @@ +/** + * @RDE_HEADER@ + * MapLayer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "logical_layer.h" +#include +#include +#include +#include + + +// ESRI +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Qt +// #include +#include + + +static_assert((int)SituationMap::SurfacePlacement::DrapedFlat == (int)Esri::ArcGISRuntime::SurfacePlacement::DrapedFlat, "SurfacePlacement mismatch"); +static_assert((int)SituationMap::SurfacePlacement::Absolute == (int)Esri::ArcGISRuntime::SurfacePlacement::Absolute, "SurfacePlacement mismatch"); +static_assert((int)SituationMap::SurfacePlacement::Relative == (int)Esri::ArcGISRuntime::SurfacePlacement::Relative, "SurfacePlacement mismatch"); + + + +namespace SituationMap +{ + +LogicalLayer::LogicalLayer(const QString &logicalName, QObject *parent) + : BaseClass(parent), + m_logicalName(logicalName), + m_logger(getLogger("LogicalLayer")) +{ + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_logicalName='" << m_logicalName << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_logicalName='" << m_logicalName << "'"; +} + +LogicalLayer::~LogicalLayer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_logicalName='" << m_logicalName << "'"; + fini(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_logicalName='" << m_logicalName << "'"; +} + +void LogicalLayer::init(SituationMap::Map *map, + Esri::ArcGISRuntime::LayerListModel *layerListModel, + Esri::ArcGISRuntime::GraphicsOverlayListModel *graphicsOverlayListModel) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'" + << " layerListModel=" << layerListModel + << " graphicsOverlayListModel=" << graphicsOverlayListModel; + m_map = map; + m_layerListModel = layerListModel; + m_graphicsOverlayListModel = graphicsOverlayListModel; + + +#ifdef LTS_DEBUG + // sometimes getCreateGraphicalOverlay() has no m_graphicsOverlayListModel - hmm + // happens for layer 'MountingfPoints' + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'" + << " m_map=" << m_map + << " m_map->isReady=" << m_map->isReady() + << " layerListModel=" << layerListModel + << " graphicsOverlayListModel=" << graphicsOverlayListModel; + + Q_ASSERT(m_graphicsOverlayListModel); +#endif // LTS_DEBUG + + // set default order of GraphicalOverlays: (from bottom to top) + setOrderOfGraphicalOverlays({ TechnicalLayerType::GraphicsLayer, + TechnicalLayerType::GraphicsLayer_Dict, + TechnicalLayerType::GraphicsLayer_3D_Extrusion, + TechnicalLayerType::GraphicsLayer_3D_Absolute }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void LogicalLayer::fini() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'" + << " layerListModel=" << m_layerListModel + << " graphicsOverlayListModel=" << m_graphicsOverlayListModel; + + // process Layer (e.g. Basemap) + // Esri::ArcGISRuntime::Layer + if (m_layer && m_layerListModel) + { + m_layerListModel->removeOne(m_layer); + delete m_layer; + m_layer = nullptr; + } + + // process GraphicsOverlay + if (m_graphicsOverlayListModel) + { + for (Esri::ArcGISRuntime::GraphicsOverlay *graphicsOverlay : std::as_const(m_graphicalOverlays)) + { + // do some checks : + { + const int numberOfCurrentObjects = graphicsOverlay->graphics()->size(); + if (numberOfCurrentObjects != 0) + { + QString objectName; + Esri::ArcGISRuntime::Graphic* g = graphicsOverlay->graphics()->first(); + if (g) + objectName = g->objectName(); + + ERROR_LOG(m_logger) << LOGFUNCTION + << " Attention, removing GraphicsOverlay still having objects!!!" + << ", graphicsOverlay='" << graphicsOverlay << "'" + << ", numberOfCurrentObjects=" << numberOfCurrentObjects + << ", m_logicalName='" << m_logicalName << "'" + << ", objectName of first='" << objectName << "'"; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " m_logicalName='" << m_logicalName << "'" + << " numberOfCurrentObjects=" << numberOfCurrentObjects; + } + // : do some checks + + m_graphicsOverlayListModel->removeOne(graphicsOverlay); + delete graphicsOverlay; + } + } + + // cleanup internal container: + m_graphicalOverlays.clear(); + m_graphicOverlayContainer.clear(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_logicalName='" << m_logicalName << "'" + << " layerListModel=" << m_layerListModel + << " graphicsOverlayListModel=" << m_graphicsOverlayListModel; +} + + +QString LogicalLayer::logicalName() const +{ + return m_logicalName; +} + +void LogicalLayer::setLogicalName(const QString &logicalName) +{ + m_logicalName = logicalName; +} + + +LayerType LogicalLayer::layerType() const +{ + return m_layerType; +} +void LogicalLayer::setLayerType(const LayerType &layerType) +{ + m_layerType = layerType; +} +BasemapType LogicalLayer::basemapType() const +{ + return m_basemapType; +} +void LogicalLayer::setBasemapType(const BasemapType &basemapType) +{ + m_basemapType = basemapType; +} + + +RenderingMode LogicalLayer::renderingMode() const +{ + return m_graphicsOverlayRenderingMode; +} +void LogicalLayer::setRenderingMode(RenderingMode renderingMode) +{ + m_graphicsOverlayRenderingMode = renderingMode; +} + +SurfacePlacement LogicalLayer::surfacePlacement() const +{ + return m_surfacePlacement; +} +void LogicalLayer::setSurfacePlacement(SurfacePlacement surfacePlacement) +{ + m_surfacePlacement = surfacePlacement; +} + +const LayerParameter &LogicalLayer::layerParameter() const +{ + return m_layerParameter; +} +void LogicalLayer::setLayerParameter(const LayerParameter &layerParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'" + << " layerParameter=" << layerParameter; + bool changed = (m_layerParameter != layerParameter); + + m_layerParameter = layerParameter; + + if (changed) + { + // apply LayerParameters (visible, opca, brightness....) + this->applyLayerParameterToEsri(); + + // trigger update of gui: + emit layerParameterChanged(m_logicalName, m_layerParameter); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_logicalName='" << m_logicalName << "'" + << " changed=" << changed; +} + +Esri::ArcGISRuntime::Layer *LogicalLayer::layer() const +{ + return m_layer; +} +void LogicalLayer::setLayer(Esri::ArcGISRuntime::Layer *layer) +{ + m_layer = layer; + + // Handle errors of layer: + connect(m_layer, &Esri::ArcGISRuntime::Layer::errorOccurred, + this, &LogicalLayer::onLayerError, Qt::UniqueConnection); + + // Handle doneLoading: + connect(m_layer, &Esri::ArcGISRuntime::Layer::doneLoading, + this, &LogicalLayer::onLayerDoneLoading, Qt::UniqueConnection); + + connect(m_layer, &Esri::ArcGISRuntime::Layer::loadStatusChanged, + this, &LogicalLayer::onLayerLoadStatusChanged, Qt::UniqueConnection); + + + if (!m_layerListModel->contains(m_layer)) + m_layerListModel->append(m_layer); +} + + +QList LogicalLayer::getCurrentGraphicalOverlaysOrdered() const +{ + QList orderedLayerList; + + for (TechnicalLayerType techType : m_orderOfGraphicalOverlays) + { + const QString &esriLayerName = LogicalLayer::createInternalLayerName(m_logicalName, techType); + if (!m_graphicOverlayContainer.contains(esriLayerName)) + continue; + Esri::ArcGISRuntime::GraphicsOverlay *layer = m_graphicOverlayContainer[esriLayerName]; + if (layer) + orderedLayerList.append(layer); + } // for + return orderedLayerList; +} + +QList > LogicalLayer::getCurrentGraphicalOverlaysOrderedWithName() const +{ + QList > ret; + + for (TechnicalLayerType techType : std::as_const(m_orderOfGraphicalOverlays)) + { + const QString &esriLayerName = LogicalLayer::createInternalLayerName(m_logicalName, techType); + if (!m_graphicOverlayContainer.contains(esriLayerName)) + continue; + Esri::ArcGISRuntime::GraphicsOverlay *layer = m_graphicOverlayContainer[esriLayerName]; + if (!layer) + continue; + + QPair foundPair(esriLayerName, layer); + ret.append(foundPair); + } // for + return ret; +} + + +int LogicalLayer::layerIndex() const +{ + if (! m_layerListModel) + return -1; + + return m_layerListModel->indexOf(m_layer); +} + +Esri::ArcGISRuntime::GraphicsOverlay *LogicalLayer::getCreateGraphicalOverlay(const TechnicalLayerType &technicalLayerType) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'" + << " technicalLayerType=" << technicalLayerType; + + // [GUEZE-496] RPL Nr 770: 3D Lagedarstellung Einfrieren der SW Appl. + // Layer renderer does not support multipoint graphics (V100.8) + // So turn back to 2 technical layers per logical layer. So The MineArea + // is on a TechnicalLayerType::GraphicsLayer and MineArea/IndicationContainer + // uses ONE GraphicalObjectPictureMarker/Esri::ArcGISRuntime::PictureMarkerSymbol + // with multiple coordinates (multipoint) + TechnicalLayerType technicalLayerTypeLocal = technicalLayerType; + + if ((m_layerType == LayerType::Rubberband) || + (m_layerType == LayerType::Overlay) || + ((m_layerType == LayerType::Operational) && (m_logicalName.compare(QStringLiteral("PlanEditorLayer")) == 0)) + ) + { + technicalLayerTypeLocal = TechnicalLayerType::GraphicsLayer_Dict; + } + + if (! m_graphicsOverlayListModel) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " m_logicalName='" << m_logicalName << "'" + << " no m_graphicsOverlayListModel"; + +#ifdef LTS_DEBUG + Q_ASSERT(m_graphicsOverlayListModel); +#endif // LTS_DEBUG + + return nullptr; + } + + if (! m_orderOfGraphicalOverlays.contains(technicalLayerTypeLocal)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " m_logicalName='" << m_logicalName << "'" + << " not allowed: technicalLayerType=" << technicalLayerTypeLocal; + return nullptr; + } + + + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = nullptr; + + const QString &esriLayerName = LogicalLayer::createInternalLayerName(m_logicalName, technicalLayerTypeLocal); + + // lookup in own container: + if (m_graphicOverlayContainer.contains(esriLayerName)) + { + graphicsLayer = m_graphicOverlayContainer[esriLayerName]; + + // make sure, that layer is still valid (within esri/model): + if (graphicsLayer) + { + if (! m_graphicsOverlayListModel->contains(graphicsLayer)) + { + m_graphicOverlayContainer.remove(esriLayerName); + graphicsLayer = nullptr; + } + } + } + + // create if not found + if (! graphicsLayer) + { + INFO_LOG(m_logger) << LOGFUNCTION + << " requested (logical) layername ='" << m_logicalName << "' not in esriMap" + << ", create on demand..." + << " (renderMode=" << m_graphicsOverlayRenderingMode << ")"; + graphicsLayer = createGraphicLayer(technicalLayerTypeLocal, m_graphicsOverlayRenderingMode); + + // + // post operations: + // + + // apply LayerParameters (visible, opca, brightness....) + this->applyLayerParameterToEsri(); + + // trigger re-ordering + emit layerCreatedOnDemand(m_logicalName); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " technicalLayerType=" << technicalLayerType; + return graphicsLayer; +} + +void LogicalLayer::onLayerError(const Esri::ArcGISRuntime::Error &error) +{ + if (error.errorType() == Esri::ArcGISRuntime::ErrorType::CommonNotFound) + DEBUG_LOG(m_logger) << LOGFUNCTION << " EsriError occured: " << error + << ", layerName=" << m_layerParameter.url.toString(); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " EsriError occured: " << error + << ", layerName=" << m_layerParameter.url.toString(); +} + +void LogicalLayer::onGraphicsOverlayLayerError(const Esri::ArcGISRuntime::Error &error) +{ + ERROR_LOG(m_logger) << LOGFUNCTION << " EsriError occured: " << error + << ", layerName=" << m_layerParameter.url.toString(); +} + +void LogicalLayer::onRendererError(const Esri::ArcGISRuntime::Error &error) +{ + ERROR_LOG(m_logger) << LOGFUNCTION << " EsriError occured: " << error + << ", layerName=" << m_layerParameter.url.toString(); +} + +void LogicalLayer::onLayerDoneLoading(const Esri::ArcGISRuntime::Error &loadError) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'"; + + if (!loadError.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " m_logicalName='" << m_logicalName << "'" + << ", layerName = '" << m_layerParameter.url.toString() << "'" + << ", loadError: " << loadError; + + Esri::ArcGISRuntime::Layer *layer = qobject_cast(sender()); + if (layer) + { + const int maxRetry = 2; + if (m_retryCounter<=maxRetry) + { + ++m_retryCounter; + QTimer::singleShot(1000 + (m_retryCounter+1000), this, std::bind(&Esri::ArcGISRuntime::Layer::retryLoad, layer)); + INFO_LOG(m_logger) << LOGFUNCTION << " schedule retryLoad for '" << m_logicalName << "'" << " (m_retryCounter=" << m_retryCounter << ")"; + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " max number of retries (" << maxRetry << ") exceeded, give up" << ", m_logicalName='" << m_logicalName << "'"; + } + return; + } + else + ERROR_LOG(m_logger) << LOGFUNCTION + << " qobject_cast failed m_logicalName='" << m_logicalName << "'"; + return; + + } + + + Esri::ArcGISRuntime::Layer *layer = qobject_cast(sender()); + if (!layer) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " qobject_cast failed m_logicalName='" << m_logicalName << "'"; + return; + } + + // Esri::ArcGISRuntime::Item *item = layer->item(); + // if (!item) + // FATAL_LOG(m_logger) << LOGFUNCTION << " no item!!!! " << m_logicalName << "'"; + + + const QString layername(layer->name()); + INFO_LOG(m_logger) << LOGFUNCTION << " esriLayer of LogicalLayer: '" << layername << "' is loaded now #1: ESRI-Layer: " + << " minScale()=" << layer->minScale() + << ", maxScale()=" << layer->maxScale() + << " (m_retryCounter=" << m_retryCounter << ")"; + + // minScale, maxScale at 3D returns always '0' for basemaps, so skip + if ((m_layerType == LayerType::Basemap) && (m_map->mode() == Mode::Mode_2D)) + { + if (!m_layerParameter.minScaleValid()) + m_layerParameter.minScale = layer->minScale(); + if (!m_layerParameter.minMaxScaleValid()) + m_layerParameter.maxScale = layer->maxScale(); + } + + INFO_LOG(m_logger) << LOGFUNCTION << " esriLayer of LogicalLayer: '" << layername << "' is loaded now #2: LogicalLayer" + << ", minScale = " << m_layerParameter.minScale + << ", maxScale = " << m_layerParameter.maxScale; + + Esri::ArcGISRuntime::ArcGISTiledLayer *arcGISTiledLayer = qobject_cast(sender()); + if (arcGISTiledLayer) + { + const Esri::ArcGISRuntime::ArcGISMapServiceInfo &mapSvcInfo = arcGISTiledLayer->mapServiceInfo(); + if (m_layerParameter.displayName.isEmpty()) // if not already set use mapName of Service + m_layerParameter.displayName = mapSvcInfo.mapName(); + m_layerParameter.description = mapSvcInfo.description(); + m_layerParameter.svcDescription = mapSvcInfo.serviceDescription(); + } // if (arcGISTiledLayer) + + DEBUG_LOG(m_logger) << LOGFUNCTION << " doneLoading: '" << layername << "'" + << " new m_layerParameter:" << m_layerParameter; + + + // maybe the para weren`t applied because not loaded.. + // so do it here (again); + applyLayerParameterToEsri(); + + // trigger reCalulation of minMaxScale: + emit layerDoneLoading(m_logicalName); + + // trigger update of gui: + emit layerParameterChanged(m_logicalName, m_layerParameter); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " doneLoading: '" << layername << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void LogicalLayer::onLayerLoadStatusChanged(const Esri::ArcGISRuntime::LoadStatus &loadStatus) +{ + INFO_LOG(m_logger) << LOGFUNCTION << " START" << " m_logicalName='" << m_logicalName << "'" + << " new loadStatus=" << loadStatus; +} + + +Esri::ArcGISRuntime::GraphicsOverlay *LogicalLayer::createGraphicLayer(const TechnicalLayerType &technicalLayerType, + RenderingMode renderingMode) +{ + Esri::ArcGISRuntime::GraphicsOverlay *graphicalOverlay = + createGraphicLayerInternal(technicalLayerType, renderingMode); + + if (technicalLayerType != TechnicalLayerType::GraphicsLayer_Dict) + return graphicalOverlay; + + // get access to esri dictionary: + const AdvancedSymbology::SymbolDictionary &dict = m_map->symbolDictionary(); + const AdvancedSymbology::SymbolDictionaryImpl &dictImpl = static_cast(dict); + Esri::ArcGISRuntime::DictionarySymbolStyle *esriDict = const_cast(dictImpl.getEsriSymbolDictionary()); + + if (!esriDict) + return graphicalOverlay; + + // make sure, that dict is already loaded: + if (esriDict->loadStatus() == Esri::ArcGISRuntime::LoadStatus::Loaded) + { + // create renderer + Esri::ArcGISRuntime::DictionaryRenderer *dictRenderer = + new Esri::ArcGISRuntime::DictionaryRenderer(esriDict, this); + + // Handle errors of DictionaryRenderer: + connect(dictRenderer, &Esri::ArcGISRuntime::DictionaryRenderer::errorOccurred, + this, &LogicalLayer::onRendererError); + + // setTextVisible removed in ArcGIS SDK v300 - text visibility is now controlled differently + + // set Renderer to layer: + + // take care that the old one is deleted: + Esri::ArcGISRuntime::Renderer *oldRenderer = graphicalOverlay->renderer(); + if (oldRenderer) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " replace/destroy/remove old renderer, is this intended???"; + oldRenderer->deleteLater(); // Qt-Doc: 'It is safe to call this function more than once;' + } + // assign new: + graphicalOverlay->setRenderer(dictRenderer); + INFO_LOG(m_logger) << LOGFUNCTION + << " requested (logical) layername ='" << m_logicalName << "' -> with Renderer" + << " of symbologyType=" << dict.symbologyType(); + } + else + { + FATAL_LOG(m_logger) << LOGFUNCTION << " SymbolDictionary not loaded!!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " SymbolDictionary not loaded!!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " SymbolDictionary not loaded!!!"; + // do not return 'nullptr' to avoid crash on further operations + } + return graphicalOverlay; +} + +QString LogicalLayer::createInternalLayerName(const QString &logicalLayerName, const TechnicalLayerType &technicalLayerType) +{ + return logicalLayerName + QLatin1Char('#') + toString(technicalLayerType); +} + +Esri::ArcGISRuntime::GraphicsOverlay *LogicalLayer::createGraphicLayerInternal(const TechnicalLayerType &technicalLayerType, + RenderingMode renderingMode) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'" + << " technicalLayerType=" << technicalLayerType + << " m_graphicalOverlays.size()=" << m_graphicalOverlays.size() + << " m_graphicsOverlayListModel.size()=" << m_graphicsOverlayListModel->size(); + + if (! m_graphicsOverlayListModel) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_graphicsOverlayListModel"; + return nullptr; + } + + const QString &esriLayername = createInternalLayerName(m_logicalName, technicalLayerType); + + Esri::ArcGISRuntime::GraphicsOverlay *graphicalOverlay = + new Esri::ArcGISRuntime::GraphicsOverlay(); + + graphicalOverlay->setOverlayId(esriLayername); + + static long s_counter = 0; + graphicalOverlay->setObjectName(QStringLiteral("GraphicsOverlay_%1_'%2'").arg(s_counter++).arg(esriLayername)); + connect(graphicalOverlay, &Esri::ArcGISRuntime::GraphicsOverlay::destroyed, this, [this, graphicalOverlay] () { + INFO_LOG(m_logger) << " GraphicsOverlay is destroyed , objectName='" << graphicalOverlay->objectName() << "'"; + }); + + + + // Extrusion requires + // a) GraphicsRenderingMode::Dynamic and + // b) SurfacePlacement::Relative + + if (technicalLayerType == TechnicalLayerType::GraphicsLayer_3D_Extrusion) + { + graphicalOverlay->setRenderingMode(Esri::ArcGISRuntime::GraphicsRenderingMode::Dynamic); + } else { + switch (renderingMode) + { + case RenderingMode::Dynamic: + graphicalOverlay->setRenderingMode(Esri::ArcGISRuntime::GraphicsRenderingMode::Dynamic); + break; + case RenderingMode::Static: + graphicalOverlay->setRenderingMode(Esri::ArcGISRuntime::GraphicsRenderingMode::Static); + break; + default: + // empty + break; + } + } + + +// setSelectionColor removed in ArcGIS SDK v300 - replaced by GeoView::setSelectionProperties() + + m_graphicalOverlays.append(graphicalOverlay); + m_graphicsOverlayListModel->append(graphicalOverlay); + + // store it for lookup in own container: + m_graphicOverlayContainer[esriLayername] = graphicalOverlay; + + + // set SurfacePlacement + Esri::ArcGISRuntime::LayerSceneProperties lsp = graphicalOverlay->sceneProperties(); + + + // Extrusion requires + // a) GraphicsRenderingMode::Dynamic and + // b) SurfacePlacement::Relative + + if (technicalLayerType == TechnicalLayerType::GraphicsLayer_3D_Extrusion) + lsp.setSurfacePlacement(Esri::ArcGISRuntime::SurfacePlacement::Relative); + else if (technicalLayerType == TechnicalLayerType::GraphicsLayer_3D_Absolute) + lsp.setSurfacePlacement(Esri::ArcGISRuntime::SurfacePlacement::Absolute); + else + lsp.setSurfacePlacement(static_cast(m_surfacePlacement)); // save due to static_assert + graphicalOverlay->setSceneProperties(lsp); + + Esri::ArcGISRuntime::GraphicsRenderingMode esriRenderingMode = graphicalOverlay->renderingMode(); + + INFO_LOG(m_logger) << LOGFUNCTION << " created GraphicsOverlay: " + << " logicalName='" << m_logicalName << "'" + << " technicalLayerType=" << technicalLayerType + << " esriLayerName='" << esriLayername << "'" + << " esriRenderModeStr=" << esriRenderingMode // Check operator + << " surfacePlacement=" << m_surfacePlacement + << " curIdx=" << m_graphicsOverlayListModel->indexOf(graphicalOverlay) + << " graphicalOverlay=" << graphicalOverlay; + + // Handle errors of layer: + connect(graphicalOverlay, &Esri::ArcGISRuntime::GraphicsOverlay::errorOccurred, + this, &LogicalLayer::onGraphicsOverlayLayerError); + + // GraphicsOverlay need no doneLoading :-) + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_logicalName='" << m_logicalName << "'" + << " technicalLayerType=" << technicalLayerType + << " m_graphicalOverlays.size()=" << m_graphicalOverlays.size() + << " m_graphicsOverlayListModel.size()=" << m_graphicsOverlayListModel->size(); + return graphicalOverlay; +} + +void LogicalLayer::applyLayerParameterToEsri() +{ + int graphicsOverlayListModelSize = -1; + + if (m_graphicsOverlayListModel) + graphicsOverlayListModelSize = m_graphicsOverlayListModel->size(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_logicalName='" << m_logicalName << "'" + << " m_layer=" << m_layer + << " m_graphicalOverlays.size()=" << m_graphicalOverlays.size() + << " m_graphicsOverlayListModel.size()=" << graphicsOverlayListModelSize + << " lp=" << m_layerParameter; + + // process Layer (e.g. Basemap) + // Esri::ArcGISRuntime::Layer + Esri::ArcGISRuntime::Layer *esriLayer = m_layer; + if (esriLayer) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_logicalName='" << m_logicalName << "'" + << " CURRENT loadStatus =" << esriLayer->loadStatus(); + if (esriLayer->loadStatus() != Esri::ArcGISRuntime::LoadStatus::Loaded) + { + INFO_LOG(m_logger) << LOGFUNCTION << " not loaded yet, skip" + << " m_logicalName='" << m_logicalName << "'" + << " m_layer=" << m_layer + << " lp=" << m_layerParameter; + return; + } + + + esriLayer->setVisible(m_layerParameter.visible); + esriLayer->setOpacity(m_layerParameter.opacity / 100.0f); + + if (m_layerParameter.minScaleValid()) + esriLayer->setMinScale(m_layerParameter.minScale); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " minScale not valid, set to '0'" + << " m_logicalName='" << m_logicalName << "'"; + esriLayer->setMinScale(0); + } + + // Sonderfall (leider) + // Für 3D wird maxScale hart auf '0' gesetzt, damit beim Neigen keine basemap + // (besser basemapkachel) ausgeblendet wird. + if ((m_layerType == LayerType::Basemap) && + (m_map->mode() == Mode::Mode_3D) + ) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " set maxScale to '0', because Basemap and Mode_3D for: " + << "m_logicalName='" << m_logicalName << "'"; + esriLayer->setMaxScale(0); + } else { + if (m_layerParameter.maxScaleValid()) + esriLayer->setMaxScale(m_layerParameter.maxScale); + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " maxScale not valid, set to '0'" + << " m_logicalName='" << m_logicalName << "'"; + esriLayer->setMaxScale(0); + } + } // : Sonderfall + + // for layer supporting bightness, contrast, gamma: + if (esriLayer->layerType() == Esri::ArcGISRuntime::LayerType::ArcGISTiledLayer) + { + Esri::ArcGISRuntime::ArcGISTiledLayer *tiledLayer = + static_cast(esriLayer); + + tiledLayer->setBrightness(m_layerParameter.brightness); + tiledLayer->setContrast(m_layerParameter.contrast); + tiledLayer->setGamma(m_layerParameter.gamma); + } + } // if (esriLayer) + + + // process GraphicsOverlay + for (Esri::ArcGISRuntime::GraphicsOverlay *graphicsOverlay : std::as_const(m_graphicalOverlays)) + { + graphicsOverlay->setVisible(m_layerParameter.visible); + graphicsOverlay->setOpacity(m_layerParameter.opacity / 100.0); + + if (m_layerParameter.minScaleValid()) + graphicsOverlay->setMinScale(m_layerParameter.minScale); + if (m_layerParameter.maxScaleValid()) + graphicsOverlay->setMaxScale(m_layerParameter.maxScale); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_logicalName='" << m_logicalName << "'"; +} + + + +void LogicalLayer::setOrderOfGraphicalOverlays(const QVector &orderOfGraphicalOverlays) +{ + m_orderOfGraphicalOverlays = orderOfGraphicalOverlays; +} + + + + + + +//------------------------------------------------------------------------------ +QLatin1String toString(const TechnicalLayerType &value) +{ + switch (value) + { + case TechnicalLayerType::GraphicsLayer: + return QLatin1String("GraphicsLayer"); + case TechnicalLayerType::GraphicsLayer_Dict: + return QLatin1String("GraphicsLayer_Dict"); + case TechnicalLayerType::GraphicsLayer_3D_Extrusion: + return QLatin1String("GraphicsLayer_3DExtrusion"); + case TechnicalLayerType::GraphicsLayer_3D_Absolute: + return QLatin1String("GraphicsLayer_3D_Absolute"); + } + + return QLatin1String("TechnicalLayerTypeERROR"); +} + + +std::ostream &operator<<(std::ostream &stream, const TechnicalLayerType &value) +{ + stream << toString(value).latin1(); + return stream; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.h new file mode 100755 index 000000000..19e9be959 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/logical_layer.h @@ -0,0 +1,190 @@ +/** + * @RDE_HEADER@ + * MapLayer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include + + +//ESRI +#include +#include +#include +#include + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MapImpl; + + +enum class TechnicalLayerType +{ + GraphicsLayer, + GraphicsLayer_Dict, + GraphicsLayer_3D_Extrusion, + GraphicsLayer_3D_Absolute +}; +SITUATIONMAP_EXPORT QLatin1String toString(const TechnicalLayerType &value); + + + +class LogicalLayer final : public QObject +{ + Q_OBJECT + using BaseClass = QObject; + +public: + /*! \name Creation, Destruction */ + //@{ + explicit LogicalLayer(const QString &logicalName, QObject *parent = nullptr); + ~LogicalLayer(); + + void init(SituationMap::Map *map, + Esri::ArcGISRuntime::LayerListModel *layerListModel, + Esri::ArcGISRuntime::GraphicsOverlayListModel *graphicsOverlayListModel); + void fini(); + + void setLayerType(const LayerType &layerType); + void setBasemapType(const BasemapType &basemapType); + + RenderingMode renderingMode() const; ///< has to be set early + void setRenderingMode(RenderingMode renderingMode); ///< has to be set early + + SurfacePlacement surfacePlacement() const; ///< has to be set early + void setSurfacePlacement(SurfacePlacement surfacePlacement); ///< has to be set early + + + void setOrderOfGraphicalOverlays(const QVector &orderOfGraphicalOverlays); + //@} + + + /*! \name Some names */ + //@{ + QString logicalName() const; + void setLogicalName(const QString &logicalName); + static QString createInternalLayerName(const QString &logicalLayerName, + const TechnicalLayerType &technicalLayerType); + //@} + + + /*! \name Access to some types */ + //@{ + LayerType layerType() const; + BasemapType basemapType() const; + //@} + + /*! \name Access to LayerParameter*/ + //@{ + const LayerParameter &layerParameter() const; + void setLayerParameter(const LayerParameter &layerParameter); + //@} + + + /*! \name Main Access without creation on demand*/ + //@{ + void setLayer(Esri::ArcGISRuntime::Layer *layer); + Esri::ArcGISRuntime::Layer *layer() const; + QList getCurrentGraphicalOverlaysOrdered() const; + + QList> getCurrentGraphicalOverlaysOrderedWithName() const; + + //@} + + /*! \name Main Access with creation on demand*/ + //@{ + Esri::ArcGISRuntime::GraphicsOverlay *getCreateGraphicalOverlay(const TechnicalLayerType &technicalLayerType); + //@} + + /*! \name Access to LayerIndex*/ + //@{ + int layerIndex() const; + //@} + + + + +protected: + QString m_logicalName; + + + // Some enums: + LayerType m_layerType = LayerType::Undefined; + BasemapType m_basemapType = BasemapType::Undefined; + RenderingMode m_graphicsOverlayRenderingMode = RenderingMode::Dynamic; + SurfacePlacement m_surfacePlacement = SurfacePlacement::DrapedFlat; + + // visi, opac, brigth, gamma, contrast... + LayerParameter m_layerParameter; + + // to enable/allow GraphicalOverlays and set their order: + QVector m_orderOfGraphicalOverlays; + + // ESRI: + Esri::ArcGISRuntime::Layer *m_layer = nullptr; // store BasemapLayer + QVector m_graphicalOverlays; // store GraphicsOverlays (unordered) + int m_retryCounter = 0; + + +signals: + void layerCreatedOnDemand(const QString &logicalName); + void layerDoneLoading(const QString &logicalName); + void layerParameterChanged(const QString &logicalName, const LayerParameter &newLayerParameter); + +protected slots: + void onLayerError(const Esri::ArcGISRuntime::Error &error); + void onGraphicsOverlayLayerError(const Esri::ArcGISRuntime::Error &error); + void onRendererError(const Esri::ArcGISRuntime::Error &error); + void onLayerDoneLoading(const Esri::ArcGISRuntime::Error &loadError); // not for GraphicOverlays + void onLayerLoadStatusChanged(const Esri::ArcGISRuntime::LoadStatus &loadStatus); // not for GraphicOverlays + + +private: + // to be able to handle with BaseLayers (got from Map) + Esri::ArcGISRuntime::LayerListModel *m_layerListModel = nullptr; + + // to be able to handle with GraphicalOverlays (got from GeoView) + Esri::ArcGISRuntime::GraphicsOverlayListModel *m_graphicsOverlayListModel = nullptr; + + // to be able to create DictionaryRenderer: + SituationMap::Map *m_map = nullptr; + + + // Since GraphicOverlays don`t support setName yet: + // (and objectname returns an empty string in any case): + mutable QMap m_graphicOverlayContainer; // workaround + + + Esri::ArcGISRuntime::GraphicsOverlay *createGraphicLayer(const TechnicalLayerType &technicalLayerType, + RenderingMode renderingMode = RenderingMode::Dynamic); + Esri::ArcGISRuntime::GraphicsOverlay *createGraphicLayerInternal(const TechnicalLayerType &technicalLayerType, + RenderingMode renderingMode = RenderingMode::Dynamic); + + void applyLayerParameterToEsri(); + + + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + +std::ostream &operator<< (std::ostream &stream, + const SituationMap::TechnicalLayerType &value); + + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.cpp new file mode 100755 index 000000000..f302232b5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.cpp @@ -0,0 +1,5811 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Map + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +// Own +#include "map_impl.h" +#include "grid_impl.h" +#include "screen_to_location_helper.h" +#include "location_to_elevation_helper.h" +#include "layer_factory_impl.h" +#include "layer_control_widget.h" +#include "coord_translation_functor_impl.h" +#include "object_indicator_impl.h" +#include +#include "jump_point_widget.h" +#include "overview_map_handler_impl.h" +#include "measurement_impl.h" +#include "terrain_profile_impl.h" +#include "viewshed_impl.h" +#include "location_display_impl.h" +#include "pixel_based_textbox_handler_impl.h" + +#include +#include + +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "overlay_handler_impl.h" + +#include +#include + +// NGL +#include + +// Lts +#include + +#include + +// ESRI +#include +#include +#include +#include +#include +#include +#include // enum NavigationConstraint +//#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if ACRGIS_VERSION >= 0x640500 // 100.5.0 +# define HAS_BELOW_GROUND +#endif + +#if ACRGIS_VERSION >= 0x640f00 // 100.15.0 +# define HAS_NETWORK_CACHE_CONFIGURATION +#endif + +#ifdef HAS_NETWORK_CACHE_CONFIGURATION +#include +#include +#endif // HAS_NETWORK_CACHE_CONFIGURATION + + +#include +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + + +#define USE_OWN_EVENTHANDLER + + +static_assert((int)SituationMap::DrawStatus::InProgress == (int)Esri::ArcGISRuntime::DrawStatus::InProgress, "DrawStatus mismatch"); +static_assert((int)SituationMap::DrawStatus::Completed == (int)Esri::ArcGISRuntime::DrawStatus::Completed, "DrawStatus mismatch"); + +#ifdef HAS_BELOW_GROUND +static_assert((int)SituationMap::NavigationConstraint::None == (int)Esri::ArcGISRuntime::NavigationConstraint::None, "NavigationConstraint mismatch"); +static_assert((int)SituationMap::NavigationConstraint::StayAbove == (int)Esri::ArcGISRuntime::NavigationConstraint::StayAbove, "NavigationConstraint mismatch"); +#endif // HAS_BELOW_GROUND + + +namespace SituationMap +{ + +class MyGraphicsPixmapItem final : public QObject, public QGraphicsPixmapItem +{ + Q_OBJECT +public: + explicit MyGraphicsPixmapItem(QGraphicsItem *parent = nullptr) + : QObject(), + QGraphicsPixmapItem(parent) + {} + +signals: + void doubleClicked(); + +protected: + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) override + { + emit doubleClicked(); + } + +}; // class MyGraphicsPixmapItem +//----------------------------------------------------------------------------- + + +class MyPushButton : public QPushButton +{ + Q_OBJECT +public: + MyPushButton() : QPushButton() {} + + void setIconPale(const QIcon &icon) + { + m_iconPale = icon; + setIcon(m_iconPale); + } + + void setIconOnHover(const QIcon &icon) + { + m_iconOnHover = icon; + } + + void enterEvent(QEnterEvent *e) override + { + setIcon(m_iconOnHover); + QPushButton::enterEvent(e); + } + void leaveEvent(QEvent *e) override + { + setIcon(m_iconPale); + QPushButton::leaveEvent(e); + } + +protected: + QIcon m_iconPale; + QIcon m_iconOnHover; +}; // class MyPushButton +//----------------------------------------------------------------------------- + + + +class MapImpl::Private +{ +public: + Private() = default; + + static int s_counter; + + // ESRI data: + Esri::ArcGISRuntime::Map *m_esriMap = nullptr; + Esri::ArcGISRuntime::Scene *m_esriScene = nullptr; + Esri::ArcGISRuntime::Basemap *m_basemap = nullptr; + + std::shared_ptr m_elevationMap; + std::shared_ptr m_elevationMapManager; + + Esri::ArcGISRuntime::MapGraphicsView *m_mapGraphicsView = nullptr; + Esri::ArcGISRuntime::SceneGraphicsView *m_sceneGraphicsView = nullptr; + ScreenToLocationHelper *m_screenToLocationHelper = nullptr; + + QFutureWatcher *m_exportImageWatcher = nullptr; + + bool m_destroying = false; + bool m_mapReadyFlag = false; + bool m_mapReadyAndSymbolDictionaryEmitted = false; + + Viewpoint2D m_initialViewpoint; + double m_animationDuration = 0.5; + double m_rocketDuration = 2.0; + QTimer *m_rocketTimer = nullptr; + bool m_rocketRunning = false; + + QPointF m_currentCenterPointBeforeRocket; + double m_currentScaleBeforeRocket = 0.0; + Camera m_currentCameraBeforRocket; + + + // PositionLabel + QLabel *m_positionLabel = nullptr; + QString m_positionStr; + QString m_positionElevationStr = QLatin1String("0"); + QColor m_positionLabelForegroundColor; + QColor m_positionLabelBackgroundColor; + QPointer m_locationToElevationHelper; + + + void updatePositionText() + { + if (!m_positionLabel) + return; + m_positionLabel->setText(QStringLiteral("%1 %2"). + arg(m_positionStr). + arg(m_positionElevationStr, 3)); + } + + // ExerciseTimeLabel + QLabel *m_exerciseTimeLabel = nullptr; + QColor m_exerciseTimeLabelForegroundColor; + QColor m_exerciseTimeLabelBackgroundColor; + bool m_isReplay = false; + + // TimeLabel + QLabel *m_timeLabel = nullptr; + QColor m_timeLabelForegroundColor; + QColor m_timeLabelBackgroundColor; + + // Compass + MyGraphicsPixmapItem *m_compass = nullptr; // scene will take ownership. + + + // OWN data: + QVector m_trainingsAreaExtent; + + // Layout, SideBar etc + QStackedLayout *m_stackedLayout = nullptr; + QPointer m_sideBar = nullptr; // optional + QPushButton *m_pbSideBar = nullptr; // optional + JumpPointWidget *m_jumpPointWidget = nullptr; // optional + + // QTextEdit / TextBoxPixel + struct { + QPointer m_label; + QPointer m_textEdit; + QGraphicsRectItem *m_handle_or_proxyControl{nullptr}; + QGraphicsProxyWidget *m_proxy{nullptr}; + QPointer m_sizeGrip; + + void reset() + { + // ToDo: check + // delete without removing from scene/view? + +// qCritical() << " 11"; + delete m_handle_or_proxyControl; + m_handle_or_proxyControl = nullptr; + + // since m_proxy has m_handle_or_proxyControl as parent... + m_proxy = nullptr; + + +// qCritical() << " 33"; + delete m_textEdit; // QPointer + delete m_sizeGrip; // QPointer + +// qCritical() << " fini"; + +// qCritical() << " m_handle_or_proxyControl= " << m_handle_or_proxyControl +// << " m_proxy= " << m_proxy +// << " m_textEdit= " << m_textEdit +// << " m_sizeGrip= " << m_sizeGrip; + + } + + } m_textBoxPixelAndCo; + + + LayerFactoryImpl *m_layerFactoryImpl = nullptr; + + ObjectFactory *m_objectFactory = nullptr; + QSharedPointer m_objectContainer; + CoordTranslationFunctor *m_coordTranslationFunctorInitialize = nullptr; + CoordTranslationFunctor *m_coordTranslationFunctor = nullptr; + + QSharedPointer m_partyIdToColorFunctor; + QTimer *m_partychangedDelayTimer = nullptr; + + QSharedPointer m_overlayHandler; + + SituationMap::GridImpl *m_gridImpl = nullptr; + + + bool m_suppressFirstViewpointChange = true; + double m_pixelsize = -1.0; // meterPerPixel + double m_absoluteScale = -1.0; + + double m_rotationAbsoluteAngleDegrees = 0; + + QTimer *m_maxExtentTimer = nullptr; + + RB::RubberbandFactory *m_rubberbandFactory = nullptr; + + + OverviewMapHandlerImpl *m_overviewMapHandler = nullptr; + + ObjectIndicatorImpl *m_objectIndicatorImpl = nullptr; + LocationDisplayImpl *m_locationDisplayImpl = nullptr; + QPointer m_pixelBasedTextBoxHandlerImpl; + + + MeasurementImpl *m_measurementImpl = nullptr; + TerrainProfileImpl *m_terrainProfileImpl = nullptr; + ViewshedImpl *m_viewshedImpl = nullptr; + + AdvancedSymbology::SymbologyType m_symbologyType = AdvancedSymbology::SymbologyType::None; + double m_symbolScale = 1.0; + + // // 'Optional' / Internal : + // replaced by EventMoseNavigator + RB::EventMouseNavigator *m_mouseEventHandler = nullptr; + + QList> m_dropEventManagerList; + + // : 'Optional' / Internal + + +}; // class MapImpl::Private +//----------------------------------------------------------------------------- + +int MapImpl::Private::s_counter = 0; + +MapImpl::MapImpl(QWidget *parent) + : BaseClass(parent) + , m_logger(getLogger("MapImpl")) + , d(new Private()) +{ + INFO_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << ++d->s_counter; + + // connect errorHandling: + connect(Esri::ArcGISRuntime::GeometryEngine::instance(), &Esri::ArcGISRuntime::GeometryEngine::errorOccurred, + this, &MapImpl::onErrorGeometryEngine); + + + d->m_basemap = new Esri::ArcGISRuntime::Basemap(this); + d->m_basemap->setObjectName(QLatin1String("Basemap-Basemap")); + //---------------------------------------------------------------------------- + // connect errorHandling: + //---------------------------------------------------------------------------- + connect(d->m_basemap, &Esri::ArcGISRuntime::Map::errorOccurred, + this, &MapImpl::onErrorBasemap); + + + d->m_maxExtentTimer = new QTimer(this); + d->m_maxExtentTimer->setSingleShot(true); + d->m_maxExtentTimer->setInterval(200); + + connect(d->m_maxExtentTimer, &QTimer::timeout, this, [this]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " ************** extentChangedDELAYED="; + + if (d->m_destroying) + return; + emit extentChangedDelayed(); // will be sent if zooming/scaling/moving/resizing has finished for a while + }); + + + //---------------------------------------------------------------------------- + // add view to widget: + // (dependency to plugin???) + //---------------------------------------------------------------------------- + // QGridLayout* layout = new QGridLayout(this); + // layout->setObjectName(QLatin1String("MapImpl-layout")); + // layout->setMargin(0); + // layout->addWidget(d->m_mapGraphicsView, 0, 0); + // this->setLayout(layout); + + // StackedLayout: + d->m_stackedLayout = new QStackedLayout(); + d->m_stackedLayout->setStackingMode(QStackedLayout::StackAll); + this->setLayout(d->m_stackedLayout); + + //---------------------------------------------------------------------------- + // set default IdGenerator / IdContainer: + // can be replaced using 'setObjectContainer' + //---------------------------------------------------------------------------- + QSharedPointer idGen(new SituationMap::IdGenerator); + QSharedPointer objectCont(new SituationMap::ObjectContainerCTC(idGen)); + setObjectContainer(objectCont); + + + //---------------------------------------------------------------------------- + // set default partyIdToColorFunctor: + // can be replaced using '' + //---------------------------------------------------------------------------- + d->m_partyIdToColorFunctor = + QSharedPointer(new PartyIdToColorFunctor()); + + d->m_partychangedDelayTimer = new QTimer(this); + + d->m_partychangedDelayTimer->setSingleShot(true); + d->m_partychangedDelayTimer->setInterval(1000); + connect(d->m_partychangedDelayTimer, &QTimer::timeout, this, &MapImpl::onAllPartysChanged); + + + //---------------------------------------------------------------------------- + // setting of further details are continued within 'init D' and 'onMapReady'... + //---------------------------------------------------------------------------- + + TRACE_LOG(m_logger) << LOGFUNCTION << " END " << this; +} + +bool MapImpl::init(const Mode mapMode_2D3D) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " mode=" << mapMode_2D3D; + bool success = false; + switch (mapMode_2D3D) + { + case Mode::Mode_2D: + success = init2D(); + break; + case Mode::Mode_3D: + success = init3D(); + break; + case Mode::Undefined: + { + ERROR_LOG(m_logger) << LOGFUNCTION << " undefined 2D/3D mode"; + return false; + } + } //switch + + this->getEsriGeoView()->setAttributionTextVisible(false); // ToDo Check + +#if ACRGIS_VERSION >= 0x640500 // 100.5.0 / Update 5 + this->getEsriGeoView()->setSelectionProperties(Esri::ArcGISRuntime::SelectionProperties(Qt::magenta)); +#endif // ACRGIS_VERSION + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " mode=" << mapMode_2D3D + << " success=" << success; + + return success; +} + + +// for debugging : +class MyMapGraphicsView : public Esri::ArcGISRuntime::MapGraphicsView +{ + using BaseClass = Esri::ArcGISRuntime::MapGraphicsView; +public: + MyMapGraphicsView(QWidget* parent = nullptr) + : BaseClass(parent) + {} + + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE + { + BaseClass::paintEvent(event); + qCritical() << " MyMapGraphicsView::paintEvent called" + << " type=" << event->type() + << " rect=" << event->rect() + << " region=" << event->region(); + } +}; // class MyMapGraphicsView + +class MySceneGraphicsView : public Esri::ArcGISRuntime::SceneGraphicsView +{ + using BaseClass = Esri::ArcGISRuntime::SceneGraphicsView; +public: + MySceneGraphicsView(QWidget* parent = nullptr) + : BaseClass(parent) + {} + + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE + { + BaseClass::paintEvent(event); + qCritical() << " MySceneGraphicsView::paintEvent called" + << " type=" << event->type() + << " rect=" << event->rect() + << " region=" << event->region(); + } +}; // class MySceneGraphicsView +// : for debugging + + + +bool MapImpl::init2D() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_mapReadyFlag = false; + +#ifdef LTS_DEBUG + //---------------------------------------------------------------------------- + // QNetworkProxy + //---------------------------------------------------------------------------- + { + QNetworkProxy proxy = QNetworkProxy::applicationProxy(); + INFO_LOG(m_logger) << LOGFUNCTION << " QNetworkProxy is NOT Empty" + << ", hostname: '" << proxy.hostName() << "'" + << ", port: '" << proxy.port() << "'" + << ", user: '" << proxy.user() << "'" + // << ", password: '" << proxy.password() << "'" + ; + } +#endif + + //---------------------------------------------------------------------------- + // Map + //---------------------------------------------------------------------------- + d->m_esriMap = new Esri::ArcGISRuntime::Map(d->m_basemap, this); + d->m_esriMap->setObjectName(QLatin1String("EsriMap")); + + // the fetch infos (like minMaxScale) of layers: + d->m_esriMap->setAutoFetchLegendInfos(true); + + //---------------------------------------------------------------------------- + // connect some signals: + //---------------------------------------------------------------------------- + connect(d->m_esriMap, &Esri::ArcGISRuntime::Map::errorOccurred, + this, &MapImpl::onErrorMap); + + connect(d->m_esriMap, &Esri::ArcGISRuntime::Map::doneLoading, + this, &MapImpl::onDoneLoading); + + connect(d->m_esriMap->operationalLayers(), &Esri::ArcGISRuntime::LayerListModel::errorOccurred, + this, &MapImpl::onErrorOperationalLayer); + + //---------------------------------------------------------------------------- + // MapGraphicsView + //---------------------------------------------------------------------------- + try + { + // d->m_mapGraphicsView = new Esri::ArcGISRuntime::MapGraphicsView(d->m_esriMap, this); + d->m_mapGraphicsView = new Esri::ArcGISRuntime::MapGraphicsView(this); + d->m_mapGraphicsView->setObjectName(QLatin1String("EsriMapGraphicsView")); + d->m_mapGraphicsView->setFrameShape(QFrame::NoFrame); // frameless +#ifdef USE_OWN_EVENTHANDLER + d->m_mapGraphicsView->viewport()->installEventFilter(this); // filter drag and drop events +#endif // USE_OWN_EVENTHANDLER + d->m_mapGraphicsView->installEventFilter(this); // filter keyboard events + + // filter keyboard events new, for 2D and 3D + connect(d->m_mapGraphicsView, &Esri::ArcGISRuntime::MapGraphicsView::keyPressed, + this, &MapImpl::onKeyPressEvent); + + } + catch (std::exception &stdEx) + { + const char *what = stdEx.what(); + if (what) + { + FATAL_LOG(m_logger) << LOGFUNCTION << "std Exception " + << " what: '" << what << "'"; + } + else + FATAL_LOG(m_logger) << LOGFUNCTION << "std Exception"; + throw; // re-throw + } + catch (...) + { + FATAL_LOG(m_logger) << LOGFUNCTION << "unknown Exception"; + throw; // re-throw + } + + //---------------------------------------------------------------------------- + // Add to StackedLayout: + //---------------------------------------------------------------------------- + d->m_stackedLayout->addWidget(d->m_mapGraphicsView); + + + //---------------------------------------------------------------------------- + // some details: + //---------------------------------------------------------------------------- + d->m_mapGraphicsView->setWrapAroundMode(Esri::ArcGISRuntime::WrapAroundMode::Disabled); + + + // initFeature(); + + // handle rotations controlled by ESRI View + connect( d->m_mapGraphicsView, &Esri::ArcGISRuntime::MapGraphicsView::mapRotationChanged, this, &MapImpl::onEsriRotate ); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + + +bool MapImpl::initFeature() +{ + QString urlStr(QLatin1String("http://sampleserver6.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer/9")); + urlStr = QLatin1String("http://10.86.144.36/arcgis/rest/services/Bremen/MountingPoints/FeatureServer/0"); + + QUrl featureUrl(urlStr); + + // create the feature table + Esri::ArcGISRuntime::ServiceFeatureTable *featureTable = new Esri::ArcGISRuntime::ServiceFeatureTable(featureUrl, this); + + // create the feature layer using the feature table + Esri::ArcGISRuntime::FeatureLayer *featureLayer = new Esri::ArcGISRuntime::FeatureLayer(featureTable, this); + + // add the feature layer to the map + d->m_esriMap->operationalLayers()->append(featureLayer); + + return true; +} + + +bool MapImpl::init3D() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_mapReadyFlag = false; + + //---------------------------------------------------------------------------- + // Map + //---------------------------------------------------------------------------- + d->m_esriScene = new Esri::ArcGISRuntime::Scene(d->m_basemap, this); + d->m_esriScene->setObjectName(QLatin1String("EsriScene")); + + + //---------------------------------------------------------------------------- + // connect some signals: + //---------------------------------------------------------------------------- + connect(d->m_esriScene, &Esri::ArcGISRuntime::Scene::errorOccurred, + this, &MapImpl::onErrorScene); + + connect(d->m_esriScene, &Esri::ArcGISRuntime::Scene::doneLoading, + this, &MapImpl::onDoneLoading); + + connect(d->m_esriScene->operationalLayers(), &Esri::ArcGISRuntime::LayerListModel::errorOccurred, + this, &MapImpl::onErrorOperationalLayer); + + // the fetch infos (like minMaxScale) of layers: + d->m_esriScene->setAutoFetchLegendInfos(true); + + //---------------------------------------------------------------------------- + // SceneGraphicsView + //---------------------------------------------------------------------------- + try + { + // 2017-11-02, V100.1 + // restoreOperatorMapSettings (restoreViewpoint will not really work, if the parent is set. + // wierd, but true... + d->m_sceneGraphicsView = new Esri::ArcGISRuntime::SceneGraphicsView(); // no parent, + d->m_sceneGraphicsView->setObjectName(QLatin1String("EsriSceneView")); + d->m_sceneGraphicsView->setFrameShape(QFrame::NoFrame); // frameless + + // filter keyboard events new, for 2D and 3D + connect(d->m_sceneGraphicsView, &Esri::ArcGISRuntime::SceneGraphicsView::keyPressed, + this, &MapImpl::onKeyPressEvent); + } + catch (std::exception &stdEx) + { + const char *what = stdEx.what(); + if (what) + { + FATAL_LOG(m_logger) << LOGFUNCTION << "std Exception " + << " what: '" << what << "'"; + } + else + FATAL_LOG(m_logger) << LOGFUNCTION << "std Exception"; + throw; // re-throw + } + catch (...) + { + FATAL_LOG(m_logger) << LOGFUNCTION << "unknown Exception"; + throw; // re-throw + } + + //---------------------------------------------------------------------------- + // ScreenToLocationHelper + // to reduce the load of screenToLocation, limit the number of calculation to 10/sec + //---------------------------------------------------------------------------- + d->m_screenToLocationHelper = new ScreenToLocationHelper(d->m_sceneGraphicsView); + + connect(d->m_screenToLocationHelper, &ScreenToLocationHelper::mouseScenePositionChanged, + this, &MapImpl::onMouseScenePositionChanged, Qt::UniqueConnection); + + + //---------------------------------------------------------------------------- + // Add to StackedLayout: + //---------------------------------------------------------------------------- + d->m_stackedLayout->addWidget(d->m_sceneGraphicsView); + + + //---------------------------------------------------------------------------- + // some details: + //---------------------------------------------------------------------------- + + // SceneView has no member 'setWrapAroundMode' + // d->m_sceneGraphicsView->setWrapAroundMode(Esri::ArcGISRuntime::WrapAroundMode::Disabled); + + // handle rotations controlled by ESRI View + connect(d->m_sceneGraphicsView, &Esri::ArcGISRuntime::SceneGraphicsView::viewpointChanged, this, &MapImpl::onEsriRotate); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void MapImpl::onPartyChanged(int changedPartyId) +{ + INFO_LOG(m_logger) << LOGFUNCTION << " START, changedPartyId=" << changedPartyId + << ", this->objectContainer().countObjects()=" << this->objectContainer().countObjects() + << ", isReady = " << this->isReady(); + + // partyIdToColorFunctor signals that colorTable has changed. + + // + // Update UniqueValueRenderer: (for cubes displaying the buildingOwnership + // + if ((this->mode() == Mode::Mode_3D) && this->layerFactory().hasLayernameForCubes()) + this->layerFactory().assignUniqueValueRendererForCubes(); + + + if (this->isReady()) + INFO_LOG(m_logger) << LOGFUNCTION << " do party-update."; + else + { + INFO_LOG(m_logger) << LOGFUNCTION << " skip party-update, because map is not ready yet."; + emit partysChangedDelayed(); + + return; + } + + + // + // clearXmlSymbolCache + // + // maybe on particpant has an XML-Symbol with NBC-Protection=1; + // to force re-creation of that addonSymbol: + symbolDictionary().clearXmlSymbolCache(); + + + // + // Update all MapObjects + // + long objectsToUpdate = 0; + QMutableMapIterator> mapObjIt = this->objectContainer().mutableIterator(); + + while (mapObjIt.hasNext()) + { + mapObjIt.next(); + RefPointer mapObj = mapObjIt.value(); + if (mapObj) + { + bool changed = mapObj->setPartyIdChanged(changedPartyId); + + if (changed) + { + ++objectsToUpdate; + if (! mapObj->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed, debugInfo='" << mapObj->debugInfo() << "'"; + } + +// if (changedPartyId >= 0) +// if (mapObj->partyId() != changedPartyId) // ToDo: honor injuryPartyId also +// continue; +// mapObj->setPartyIdChanged(changedPartyId); + +// if (! mapObj->updateView()) +// ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed, debugInfo='" << mapObj->debugInfo() << "'"; + } + } // while + + + INFO_LOG(m_logger) << LOGFUNCTION << " START, partysChangedDelayed " ; + emit partysChangedDelayed(); + + INFO_LOG(m_logger) << LOGFUNCTION << objectsToUpdate << " of " << this->objectContainer().countObjects() + << " were updated"; +} + +void MapImpl::onAllPartysChanged() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START"; + onPartyChanged(-1); +} + +void MapImpl::onCheckMapReadyAndSymbolDictionaryLoaded() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->m_mapReadyFlag && + (AdvancedSymbology::SymbolDictionary::alreadyExists(d->m_symbologyType) && symbolDictionary().isLoaded() && (symbolDictionary().symbologySpecificCount()>0)) && + d->m_layerFactoryImpl->elevationSourcesLoaded() + ) + { + if (!d->m_mapReadyAndSymbolDictionaryEmitted) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " ***** emitting mapReadyAndSymbolDictionaryLoaded" + << ": '" << mode() << "'" + << ": '" << symbolDictionary().symbologyType() << "'" + << " (" << symbolDictionary().symbologySpecificCount() + << " + " + << symbolDictionary().etsSymbolCount() + << " + " + << symbolDictionary().xmlSymbolCount() << " = " + << symbolDictionary().count() << ")"; + emit mapReadyAndSymbolDictionaryLoaded(); + d->m_mapReadyAndSymbolDictionaryEmitted = true; + } + } +} + + +bool MapImpl::fini() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // Eventhandling: + delete d->m_mouseEventHandler; + + delete d->m_measurementImpl; // ToDo: check + delete d->m_terrainProfileImpl; + delete d->m_viewshedImpl; + + if (d->m_partychangedDelayTimer) + d->m_partychangedDelayTimer->stop(); + + emit beginDestroy(); // to enable destruction of FunctionDialog and its object first + + // cleanup overlays: + if (d->m_overlayHandler) + { + INFO_LOG(m_logger) << LOGFUNCTION + << " cleanup " << d->m_overlayHandler->overlayCount() << " overlays..."; + d->m_overlayHandler->removeAllOverlays(); + } + else + { + INFO_LOG(m_logger) << LOGFUNCTION + << " no overlays to cleanup -> skip"; + } + + // cleanup overviewMap: + if (d->m_overviewMapHandler) + { + INFO_LOG(m_logger) << LOGFUNCTION + << " cleanup overviewMap"; + d->m_overviewMapHandler->removeAll(); + } + + // SideBar: + // (remove embedded widgets like JumpPointWidget and is JumpPoints from the map) + removeSideBar(); + + + // ObjectFactory: + int numberOfObjects = d->m_objectFactory->count(); + INFO_LOG(m_logger) << LOGFUNCTION + << " numberOfObjects at the moment = " << numberOfObjects; + + if (numberOfObjects > 0) + d->m_objectFactory->clearObjects(); + + // Verify: + numberOfObjects = d->m_objectFactory->count(); + INFO_LOG(m_logger) << LOGFUNCTION << " numberOfObjects remaining = " << numberOfObjects; + + // deleting the LayerFactory may cause changing to DratStatus::InProgress: + delete d->m_layerFactoryImpl; + delete d->m_gridImpl; + delete d->m_rubberbandFactory; + + // The static MapSingleton::getFactoryMap() should be cleaned up + // via 'qAddPostRoutine(cleanupFactoryMap);' + delete d->m_objectFactory; + + delete d->m_coordTranslationFunctorInitialize; + + // cleanup/detach ElevationMap + d->m_elevationMap = nullptr; + d->m_elevationMapManager = nullptr; + + INFO_LOG(m_logger) << LOGFUNCTION << " drawStatus = " << this->drawStatus(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void MapImpl::fini2D() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + fini(); + delete d->m_esriMap; + d->m_esriMap = nullptr; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} +void MapImpl::fini3D() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + fini(); + + INFO_LOG(m_logger) << LOGFUNCTION << " drawStatus = " << this->drawStatus(); + + delete d->m_sceneGraphicsView; + d->m_sceneGraphicsView = nullptr; + // delete d->m_esriScene; + INFO_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool MapImpl::load() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->m_mapGraphicsView && d->m_sceneGraphicsView) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " Upps, 2D(Map) and also 3D (Scene) -> error!!! "; + FATAL_LOG(m_logger) << LOGFUNCTION << " Upps, 2D(Map) and also 3D (Scene) -> error!!! "; + FATAL_LOG(m_logger) << LOGFUNCTION << " Upps, 2D(Map) and also 3D (Scene) -> error!!! "; + } + + // + // setMap + // + // will start esris cascading load dependecies... + + if (d->m_mapGraphicsView) + d->m_mapGraphicsView->setMap(d->m_esriMap); + + // or + + if (d->m_sceneGraphicsView) + d->m_sceneGraphicsView->setArcGISScene(d->m_esriScene); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +Mode MapImpl::mode() const +{ + Mode mode = Mode::Undefined; + if (d->m_esriMap && (!d->m_esriScene)) + mode = Mode::Mode_2D; + else if (!d->m_esriMap && d->m_esriScene) + mode = Mode::Mode_3D; + + return mode; +} + +void MapImpl::setParent(QWidget *parent, bool bBeginDestroyFini) +{ + QWidget::setParent(parent); + + if (bBeginDestroyFini) + { + // GUEZE-791 - RPL Nr 791: Arbeitsspeicherleck + // If map at 3D is cached and going to be unused, destroy + // some high level objects: + // see ::fini() + + delete d->m_measurementImpl; + d->m_measurementImpl = nullptr; + + delete d->m_terrainProfileImpl; + d->m_terrainProfileImpl = nullptr; + + delete d->m_viewshedImpl; + d->m_viewshedImpl = nullptr; + + delete d->m_objectIndicatorImpl; + d->m_objectIndicatorImpl = nullptr; + + emit beginDestroy(); + + // cleanup overlays: + if (d->m_overlayHandler) + { + INFO_LOG(m_logger) << LOGFUNCTION + << " cleanup " << d->m_overlayHandler->overlayCount() << " overlays..."; + d->m_overlayHandler->removeAllOverlays(); + } + else + { + INFO_LOG(m_logger) << LOGFUNCTION + << " no overlays to cleanup -> skip"; + } + + int numberOfObjects = d->m_objectFactory->count(); + INFO_LOG(m_logger) << LOGFUNCTION + << " numberOfObjects remaining = " << numberOfObjects; + + } +} + +void MapImpl::installViewPortEventFilter(QObject *eventFilter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (! eventFilter) + return; + d->m_mapGraphicsView->viewport()->installEventFilter(eventFilter); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} // void MapImpl::installViewPortEventFilter( QObject * eventFilter ) + + +void MapImpl::removeViewPortEventFilter(QObject *eventFilter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (! eventFilter) + return; + d->m_mapGraphicsView->viewport()->removeEventFilter(eventFilter); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QPointer MapImpl::createMapNotifier() const +{ + MapImpl *THIS = const_cast(this); + return MapNotifier::createMapNotifier(THIS, THIS); +} + + +void MapImpl::onLoadStatusChanged(Esri::ArcGISRuntime::LoadStatus loadStatus) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " loadStatus changes to '" << loadStatus << "'"; +} + +void MapImpl::onDoneLoading(const Esri::ArcGISRuntime::Error &loadError) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START " << this + << " d->m_mapGraphicsView=" << d->m_mapGraphicsView + << " d->m_sceneGraphicsView=" << d->m_sceneGraphicsView; + + if (! loadError.isEmpty()) + ERROR_LOG(m_logger) << LOGFUNCTION << " loadError: " << loadError; + + if (d->m_mouseEventHandler) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " already loaded, return"; + return; + } + + // d->m_absoluteScale = d->m_mapGraphicsView->mapScale(); // update d->m_absoluteScale + + // filter mouse events to navigate the map + +#ifdef USE_OWN_EVENTHANDLER + + { + switch (this->mode()) + { + case Mode::Mode_2D: + d->m_mouseEventHandler = new RB::EventMouseNavigator(d->m_mapGraphicsView->viewport(), this); + break; + case Mode::Mode_3D: + d->m_mouseEventHandler = new RB::EventMouseNavigator(d->m_sceneGraphicsView->viewport(), this); + break; + default: + // empty + break; + } // switch + + if (d->m_mouseEventHandler) + { + // handle mouse move events + connect(d->m_mouseEventHandler + , &RB::EventMouseNavigator::mouseMovedEvent + , this + , &MapImpl::onHandleMouseMove); + + connect(d->m_mouseEventHandler + , &RB::EventMouseNavigator::selectionRequest + , this + , &MapImpl::onClickMapObject); + + connect(d->m_mouseEventHandler + , &RB::EventMouseNavigator::contextMenuRequest + , this + , &MapImpl::onContextClick); + +#ifdef SITUATIONMAP_OWN_WHEEL_HANDLING + connect(d->m_mouseEventHandler + , &RB::EventMouseNavigator::mouseWheelEvent + , this + , &MapImpl::onWheelZoom); +#endif // SITUATIONMAP_OWN_WHEEL_HANDLING + + if (d->m_sideBar && d->m_pbSideBar) + { + connect(d->m_mouseEventHandler, &RB::EventMouseNavigator::selectionRequest, this, [this] + (const QList< SituationMap::UniqueMapId > & //idList + , const QPointF & viewPortPos) + { + // ignore event, if viewPortPos is at m_pbSideBar + // (In that case the SideBar is currently opening...) + QRect rect = d->m_pbSideBar->rect(); + rect.moveTo(d->m_pbSideBar->pos()); + + if (! rect.contains(viewPortPos.toPoint())) + d->m_sideBar->hideAnimated(); + }); + + } // if (d->m_sideBar && d->m_pbSideBar) + } // if (d->m_mouseEventHandler) + } +#endif // USE_OWN_EVENTHANDLER + + + + //---------------------------------------------------------------------------- + // set CoordTranslationFunctor to default within Combat Training Centers (CTC) + // (SLSE) + //---------------------------------------------------------------------------- + if (! d->m_coordTranslationFunctorInitialize) + d->m_coordTranslationFunctorInitialize = new CoordTranslationFunctorImpl(this); + + if (! d->m_coordTranslationFunctor) + d->m_coordTranslationFunctor = d->m_coordTranslationFunctorInitialize; + + INFO_LOG(m_logger) << LOGFUNCTION << " ***** CoordTranslationFunctor set" << " (" << d->m_coordTranslationFunctor << ")"; + INFO_LOG(m_logger) << LOGFUNCTION << " ***** wkid=" << this->getEsriSpatialReference().wkid() << " *****"; + INFO_LOG(m_logger) << LOGFUNCTION << " ***** wkt='" << this->getEsriSpatialReference().wkText() << "' *****"; + + + if (this->getEsriSpatialReference().wkid() >= 0) + { + // + // set map ready: + // + INFO_LOG(m_logger) << LOGFUNCTION << " ***** emitting mapReady" + << " (wkid = " << this->getEsriSpatialReference().wkid() + << ", mode = " << this->mode() + << ", minScale = " << this->minScale() + << ", maxScale = " << this->maxScale() + << ", this=" << this + << ", ThreadId=" << QThread::currentThreadId() + << ")"; + + d->m_mapReadyFlag = true; + emit mapReady(); + + // maybe SymbolDictionary was loaded earlier than map: + onCheckMapReadyAndSymbolDictionaryLoaded(); + + + } // if (this->getEsriSpatialReference().wkid() >= 0) + else + ERROR_LOG(m_logger) << LOGFUNCTION << " wkid failed, wkid=" << this->getEsriSpatialReference().wkid() << " *****"; + + + // Problems with order: + // mapReady and "connect viewpointChanged" -> ok + // "connect viewpointChanged" and mapReady -> NOT ok + + // connect some signals: + if (d->m_mapGraphicsView) + { + connect(d->m_mapGraphicsView, &Esri::ArcGISRuntime::MapGraphicsView::viewpointChanged, this, &MapImpl::onViewpointChanged); + connect(d->m_mapGraphicsView, &Esri::ArcGISRuntime::MapGraphicsView::rectChanged, this, &MapImpl::onRectChanged); + } + if (d->m_sceneGraphicsView) + { + connect(d->m_sceneGraphicsView, &Esri::ArcGISRuntime::SceneGraphicsView::viewpointChanged, this, &MapImpl::onViewpointChanged); + connect(d->m_sceneGraphicsView, &Esri::ArcGISRuntime::SceneGraphicsView::rectChanged, this, &MapImpl::onRectChanged); + } + + // check mapReady and SymbolDictionary::loaded + onCheckMapReadyAndSymbolDictionaryLoaded(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void MapImpl::onMouseScenePositionChanged(const Point3D &locationScene, bool isValid) +{ + if (d->m_destroying) + return; + + // 1 + emit mouseMapPositionChanged(locationScene, isValid); + + SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (mapCtFunctor) + { + if (isSignalConnected(QMetaMethod::fromSignal(&Map::mouseGeoPositionChanged))) + { + // 2 + emit mouseGeoPositionChanged(mapCtFunctor->mapToGeographic(locationScene), isValid); + } + } +} + +void MapImpl::onRubberbandPickMapObjectsApply(RB::RubberbandBase *rubberband) +{ + if (d->m_destroying) + return; + + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool mapObjectIdsPicked = isSignalConnected(QMetaMethod::fromSignal(&Map::mapObjectIdsPickedByArea)); + bool mapObjectsPicked = isSignalConnected(QMetaMethod::fromSignal(&Map::mapObjectsPickedByArea)); + + // // cehck if any signal is connected: + if (!(mapObjectIdsPicked || mapObjectsPicked)) + return; + + QList pickedMapObjectIdList; + RefPointerMapObjectList pickedMapObjects; + + const QVector &mapCoordVec = rubberband->coordinateVector(RB::CoordinateSystem::Map); + if (mapCoordVec.size() != 4) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " failure, coordVec.size()=" << mapCoordVec.size(); + return; + } + + // create esriHull for geometryIsInHull ; create esriLine for geometryIntersect + // + // if required : + // Esri::ArcGISRuntime::PolylineBuilder polylineBuilder(this->getEsriSpatialReference()); + // polylineBuilder.addPoint(mapCoord.x(), mapCoord.y()); + // mapObj->geometryIntersect(esriLine) + // : if required + + + Esri::ArcGISRuntime::PolygonBuilder polygonBuilder(this->getEsriSpatialReference()); + for (const QPointF &mapCoord : mapCoordVec) + polygonBuilder.addPoint(mapCoord.x(), mapCoord.y()); + const Esri::ArcGISRuntime::Geometry &esriHull = polygonBuilder.toGeometry(); + + // check any MapObject + const QList allMapObj = this->objectContainer().getAllObjects(); + pickedMapObjectIdList.reserve(allMapObj.size()); + pickedMapObjects.reserve(allMapObj.size()); + + for (AbstractMapObject *mapObj : allMapObj) + { + if (!mapObj) + continue; + + if (!mapObj->pickable()) + continue; + + if (mapObj->geometryIsInHull(esriHull)) + { + pickedMapObjectIdList.append(mapObj->uniqueMapId()); + pickedMapObjects.append(RefPointer(mapObj)); + } + } + + // emit + if (mapObjectIdsPicked) + emit mapObjectIdsPickedByArea(pickedMapObjectIdList); + if (mapObjectsPicked) + emit mapObjectsPickedByArea(pickedMapObjects); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +MapImpl::~MapImpl() +{ + INFO_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << d->s_counter--; + + d->m_destroying = true; + + if (d->m_maxExtentTimer) + d->m_maxExtentTimer->stop(); + delete d->m_maxExtentTimer; + d->m_maxExtentTimer = nullptr; + + delete d->m_rocketTimer; + d->m_rocketTimer = nullptr; + + if (d->m_screenToLocationHelper) + { + d->m_screenToLocationHelper->disconnect(this); + INFO_LOG(m_logger) << LOGFUNCTION << " Helper pendingOperationsCount = " << d->m_screenToLocationHelper->pendingOperationsCount(); + delete d->m_screenToLocationHelper; + d->m_screenToLocationHelper = nullptr; + } + + if (d->m_locationToElevationHelper) + { + d->m_locationToElevationHelper->disconnect(this); + INFO_LOG(m_logger) << LOGFUNCTION << " Helper pendingOperationsCount = " << d->m_locationToElevationHelper->pendingOperationsCount(); + delete d->m_locationToElevationHelper; + d->m_locationToElevationHelper = nullptr; + } + + delete d->m_objectIndicatorImpl; + d->m_objectIndicatorImpl = nullptr; + + delete d->m_locationDisplayImpl; + d->m_locationDisplayImpl = nullptr; + + switch(mode()) + { + case Mode::Mode_2D: fini2D(); break; + case Mode::Mode_3D: fini3D(); break; + case Mode::Undefined: fini(); break; + } + + delete d->m_basemap; + d->m_basemap = nullptr; + delete d; + + INFO_LOG(m_logger) << LOGFUNCTION << " END " << this; +} + + +double MapImpl::animationDuration() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_animationDuration; +} + +void SituationMap::MapImpl::setAnimationDuration(double durationSec) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " durationSec=" << durationSec; + d->m_animationDuration = durationSec; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +double SituationMap::MapImpl::rocketDuration() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_rocketDuration; +} + +void SituationMap::MapImpl::setRocketDuration(double durationSec) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " durationSec=" << durationSec; + d->m_rocketDuration = durationSec; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +//class MyLabel : public QLabel +//{ +//public: +// void hideEvent(QHideEvent* event) override +// { +// qWarning() << LOGFUNCTION << " type=" << event->type(); +// QLabel::hideEvent(event); +//// abort(); +// } + +// bool event(QEvent *e) +// { +// qWarning() << LOGFUNCTION << " type=" << e->type(); +// return QLabel::event(e); +// } + +// void updateGeometry() +// { +// qWarning() << LOGFUNCTION; +// QLabel::updateGeometry(); +// } +//}; + + + + + +void MapImpl::addMapWidget() +{ + // return addMapWidget_OwnWidget(); + // return this->addMapWidget_AddToScene(); + + if (this->isReady()) + this->addMapWidget_AddToScene(); + else + { + connect(this, &Map::mapReady, this, [this]() + { + this->addMapWidget_AddToScene(); + }); + } +} + +bool MapImpl::setTextBox(const PixelTextBoxData &ptbd, bool enableGeometryModifications) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " XXXXXXXXXXXXXXXXXX START" + << " enableGeomtryUpdate " << enableGeometryModifications + << ", ptbd " << ptbd.toDebugString(); + + if (ptbd.m_htmlText.isEmpty()) + return false; + + QGraphicsView *gView = this->getQGraphicsView(); + if (!gView) + return false; + + d->m_textBoxPixelAndCo.reset(); + + if (!d->m_textBoxPixelAndCo.m_textEdit) + { + d->m_textBoxPixelAndCo.m_textEdit = new QTextEdit(); + d->m_textBoxPixelAndCo.m_textEdit->setObjectName(QStringLiteral("WidgetOverlay_QTextEdit")); + d->m_textBoxPixelAndCo.m_textEdit->setReadOnly(false); + + d->m_textBoxPixelAndCo.m_label = new QLabel(QStringLiteral("new QLabel")); + + // Make it 'modifiable' + if (enableGeometryModifications) + { + // It looks like: + // GraphicsItem <-> ProxyWidget <-> Widget/TextEdit + // (QGraphicsItem is a baseclass of both GraphicsItem as well as ProxyWidget) + // GraphicsItem becomes parent of ProxyWidget + // Then TextEdit is added via setWidget to ProxyWidget + // Now both are connected, TextEdit in front of GraphicsItem which ist little bit bigger + // GraphicsItem is movable, GI an TE are connected to each other... + // 10.05.2023, Norbert Thoden + // (((Idea: Maybe ProxyWidget should be parent of GraphicsItem + // to put GraphicsItem on top of TextEdit???))) + d->m_textBoxPixelAndCo.m_handle_or_proxyControl = new QGraphicsRectItem(0, 0, 0, 0); // x,y == rel position, size will be set later + d->m_textBoxPixelAndCo.m_handle_or_proxyControl->setCursor(Qt::SizeAllCursor); + + d->m_textBoxPixelAndCo.m_proxy = new QGraphicsProxyWidget(d->m_textBoxPixelAndCo.m_handle_or_proxyControl); + d->m_textBoxPixelAndCo.m_proxy->setObjectName(QStringLiteral("ProxyWidgetOverlay_QTextEdit")); + d->m_textBoxPixelAndCo.m_proxy->setWidget(d->m_textBoxPixelAndCo.m_textEdit); +// d->m_textBoxPixelAndCo.m_proxy->setWidget(d->m_textBoxPixelAndCo.m_label); + + d->m_textBoxPixelAndCo.m_proxy->setPos(QPoint(0, 0)); // relative position to GraphicsItem !? + + d->m_textBoxPixelAndCo.m_proxy->setOpacity(0.99); // avoid white on white + // d->m_textBoxPixelAndCo.m_proxy->setZValue(std::numeric_limits::max()); + + d->m_textBoxPixelAndCo.m_handle_or_proxyControl->setPen(QPen(Qt::gray)); + d->m_textBoxPixelAndCo.m_handle_or_proxyControl->setBrush(QBrush(Qt::gray)); + d->m_textBoxPixelAndCo.m_handle_or_proxyControl->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + + this->getQGraphicsView()->scene()->addItem(d->m_textBoxPixelAndCo.m_handle_or_proxyControl); + + // restore geometry: + d->m_textBoxPixelAndCo.m_handle_or_proxyControl->setPos(ptbd.m_position); + + // SizeGrip : + QSizeGrip *sizeGrip = new QSizeGrip(d->m_textBoxPixelAndCo.m_textEdit); + QHBoxLayout *layout = new QHBoxLayout(d->m_textBoxPixelAndCo.m_textEdit); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom); + // : SizeGrip + + { + // Sync geometry of TextEdit and GraphicsItem/ProxyWidget: + // create fn... + std::function fnUpdateGraphicsItemGeometry = [this]() + { + d->m_textBoxPixelAndCo.m_handle_or_proxyControl->setRect( d->m_textBoxPixelAndCo.m_textEdit->geometry().adjusted(-10, -10, 10, 10) ); + +// qCritical() << " handle_or_proxyControl = " << d->m_textBoxPixelAndCo.m_handle_or_proxyControl->pos(); +// qCritical() << " d->m_textEdit->geometry() = " << d->m_textBoxPixelAndCo.m_textEdit->geometry(); + }; + + // ... to be executed initially + fnUpdateGraphicsItemGeometry(); + + // ... and on geometry changes + // QTextEdit has no geometryChanged or similar, but ProxyWidget has: + connect(d->m_textBoxPixelAndCo.m_proxy, &QGraphicsProxyWidget::geometryChanged, fnUpdateGraphicsItemGeometry); + } + + { + // get pos and geometry; + std::function fnGetPosAndGeometry = [this]() + { +// qCritical() << " 222 handle_or_proxyControl pos = " << handle_or_proxyControl->pos(); +// qCritical() << " 222 handle_or_proxyControl rect= " << handle_or_proxyControl->rect(); + +// const QPoint &pos = d->m_textBoxPixelAndCo.m_handle_or_proxyControl->pos().toPoint(); + const QSize size(d->m_textBoxPixelAndCo.m_handle_or_proxyControl->rect().width(), + d->m_textBoxPixelAndCo.m_handle_or_proxyControl->rect().height()); + + //maybe take border of 10 pixel into account!? + + +// qCritical() << " 222 handle_or_proxyControl pos= " << pos +// << ", size= " << size; + + +// ptbd.m_position = pos; +// ptbd.m_size = size; + }; + + auto *timer =new QTimer(this); + connect(timer, &QTimer::timeout, fnGetPosAndGeometry); +// timer->start(5000); + } + + } else { +// d->m_textBoxPixelAndCo.m_proxy = this->getQGraphicsView()->scene()->addWidget(d->m_textBoxPixelAndCo.m_textEdit); +// d->m_textBoxPixelAndCo.m_proxy->setOpacity(0.99); +// d->m_textBoxPixelAndCo.m_proxy->setZValue(std::numeric_limits::max()); +// d->m_textBoxPixelAndCo.m_proxy->setPos(QPoint(75, 75)); +// d->m_textBoxPixelAndCo.m_proxy->setObjectName(QStringLiteral("ProxyWidgetOverlay_QTextEdit")); + } + + } + +// qCritical() << " setText= " << ptbd.m_htmlText; + + d->m_textBoxPixelAndCo.m_textEdit->setText(ptbd.m_htmlText); + + d->m_textBoxPixelAndCo.m_proxy->setOpacity(0.99); + +// QTimer::singleShot(5000, this, [this] () { +// FATAL_LOG(m_logger) << LOGFUNCTION << " XXXX timer: reset..."; +// d->m_textBoxPixelAndCo.reset(); +// FATAL_LOG(m_logger) << LOGFUNCTION << " XXXX timer: reset... Done"; +// }); + +// QSizeF size = te->document()->size(); +// te->setFixedWidth(te->document()->idealWidth() + 100); + +// te->setFixedSize(size.toSize()); + +// QGraphicsProxyWidget *proxy = this->getQGraphicsView()->scene()->addWidget(d->m_textEdit); +// proxy->setOpacity(0.99); +// proxy->setZValue(std::numeric_limits::max()); + +// proxy->setPos(QPoint(75, 75)); + +// proxy->setObjectName(QStringLiteral("ProxyWidgetOverlay_QTextEdit")); + +/* + QTimer::singleShot(10000000, this, [this] () { + FATAL_LOG(m_logger) << LOGFUNCTION << " XXXX timer:"; + + qCritical() << " size " << d->m_textEdit->size(); + + auto *handle_or_proxyControl = new QGraphicsRectItem(0, 0, 0, 0); // x,y == rel position + handle_or_proxyControl->setCursor(Qt::SizeAllCursor); + + auto *localProxy = new QGraphicsProxyWidget(handle_or_proxyControl); + localProxy->setWidget(d->m_textEdit); + localProxy->setPos(QPoint(75, 75)); + + // SizeGrip : + QSizeGrip *sizeGrip = new QSizeGrip(d->m_textEdit); + QHBoxLayout *layout = new QHBoxLayout(d->m_textEdit); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom); + // : SizeGrip + + handle_or_proxyControl->setPen(QPen(Qt::yellow)); + handle_or_proxyControl->setBrush(QBrush(Qt::gray)); + handle_or_proxyControl->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable ); + + this->getQGraphicsView()->scene()->addItem(handle_or_proxyControl); + + handle_or_proxyControl->setRect(d->m_textEdit->geometry().adjusted(-10, -10, 10, 10) ); + + connect(d->m_textEdit.data(), &QTextEdit::cursorPositionChanged, [this, handle_or_proxyControl] () + { + handle_or_proxyControl->setRect( d->m_textEdit->geometry().adjusted(-10, -10, 10, 10) ); + }); + + +// QGraphicsWidget* parentWidget = new QGraphicsWidget(handle);//make parent of widget +// parentWidget->setCursor(Qt::SizeAllCursor); +// qCritical() << " size " << d->m_textEdit; +// parentWidget->setGeometry(100, 100, 500, 500); + +// parentWidget->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable ); +// parentWidget->setSelected(true); +// this->getQGraphicsView()->scene()->addItem(handle); + +// localProxy->setParentItem(parentWidget); + + }); +*/ + +/* + // Moving: + + QTimer::singleShot(1000, this, [this, &te] () { + + FATAL_LOG(m_logger) << LOGFUNCTION << " timer:"; + + QGraphicsWidget* parentWidget = new QGraphicsWidget();//make parent of widget + parentWidget->setCursor(Qt::SizeAllCursor); + qCritical() << " size " << te->size(); +// parentWidget->setGeometry(75, 75, te->size().width(), te->size().height()); + parentWidget->setGeometry(75, 75, 200, 200); + parentWidget->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable ); +// parentWidget->setSelected(true); + this->getQGraphicsView()->scene()->addItem(parentWidget); + + QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget(); + proxy->setPos(QPoint(75, 75)); + +// QGraphicsSimpleTextItem *gitem = new QGraphicsSimpleTextItem(); +// gitem->setText(QStringLiteral("MyMovableText")); + + proxy->setWidget(te); + proxy->setParentItem(parentWidget); + + }); +*/ + + FATAL_LOG(m_logger) << LOGFUNCTION + << " XXXXXXXXXXXXXXXXXX Done"; + + + return true; +} + +bool MapImpl::removeTextBox() +{ + FATAL_LOG(m_logger) << LOGFUNCTION << "START"; + d->m_textBoxPixelAndCo.reset(); + FATAL_LOG(m_logger) << LOGFUNCTION << "START"; + return true; +} + +void moveLabelTo(QLabel *label, int windowWidth, int borderX, int y) +{ + QPoint newPos; + newPos.rx() = windowWidth - label->width() - borderX; + newPos.ry() = y; + label->move(newPos); +} + +QWidget *MapImpl::addMapWidget_AddToScene() +{ + const QFont font(QLatin1String("Courier New"), 13, QFont::DemiBold); + const QSize labelBorderPixel(5, 2); + const QPoint distanceToFrame(25, 25); + + QFontMetrics fm(font); + QSize labelMinSize; + if (true) // (mode() == Mode::Mode_3D) && layerFactory().elevationAvailable()) + labelMinSize = fm.size(0, QLatin1String("32U MD 12345 12345 - 123")); + else + labelMinSize = fm.size(0, QLatin1String("32U MD 12345 12345")); + + QPoint labelPos = distanceToFrame; + + // PositionLabel + if (1) // Todo: unnecessary if ? + { + if (! d->m_positionLabel) + { + d->m_positionLabel = new QLabel(); + d->m_positionLabel->setObjectName(QLatin1String("WidgetOverlayLayoutPositionLabel")); + d->m_positionLabel->setText(QLatin1String("-")); + d->m_positionLabel->setFont(font); + d->m_positionLabel->setAlignment(Qt::AlignBottom | Qt::AlignCenter); + d->m_positionLabel->setMinimumSize(labelMinSize + labelBorderPixel); + + // Bug iOSQz-000005 'QGraphicsProxyWidget will crash' + // Adding a widget will let the application crash + // Workaround: 'proxy->setOpacity(0.99);' + QGraphicsProxyWidget *proxy = this->getQGraphicsView()->scene()->addWidget(d->m_positionLabel); + proxy->setOpacity(0.99); + proxy->setZValue(std::numeric_limits::max()); + + // lambda :-) + connect(this, &SituationMap::Map::rectChanged, this, [this, distanceToFrame]() + { + moveLabelTo(d->m_positionLabel, getQGraphicsView()->width(), distanceToFrame.x(), d->m_positionLabel->pos().y()); + }); + + // lambda :-) + connect(this, &SituationMap::Map::mouseMapPositionChanged, this, [this](const Point3D &mapPos, bool isValid) + { + if (!this->isReady()) + return; + + if (!isValid) + { + d->m_positionLabel->setText(QStringLiteral("-")); + return; + } + + + // Shift + Strg -> lonLat in DEG + // Shift + Alt -> lonLat in RAD + // default -> MGRS + + if (d->m_positionLabel->isVisible()) + { + QString posStr; + + const Qt::KeyboardModifiers &curModifier = QGuiApplication::queryKeyboardModifiers(); + SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (mapCtFunctor && curModifier.testFlag(Qt::KeyboardModifier::ShiftModifier)) + { + Point3D geoPos3D = mapCtFunctor->mapToGeographic(mapPos); + QPointF geoPos2D = geoPos3D.toPointF(); + if (curModifier.testFlag(Qt::ControlModifier)) + { + posStr = QStringLiteral("%1 - %2").arg(geoPos2D.x()).arg(geoPos2D.y()); + d->m_positionLabel->setToolTip(QLatin1String("lon/x - lat/y ")); + } else if (curModifier.testFlag(Qt::AltModifier)) + { + geoPos2D *= LtsCoordTrans::rad2deg(); // RAD to DEG + posStr = QStringLiteral("%1 - %2").arg(geoPos2D.x()).arg(geoPos2D.y()); + d->m_positionLabel->setToolTip(QLatin1String("lon/x - lat/y")); + } + } else { + d->m_positionLabel->setToolTip(QString()); + } + if (posStr.isEmpty()) + posStr = this->mapToMgrsString(mapPos.toPointF()); + d->m_positionStr = posStr; + + + switch(this->mode()) + { + case Mode::Mode_3D: + { + // 3D: simple -> use z value: + // based on Surface::screenToBaseSurface / Surface::screenToLocation + // + if (qIsNaN(mapPos.z())) + d->m_positionElevationStr = QLatin1String("INV"); + else + { + if (std::lround(mapPos.z()) < 10.0) + d->m_positionElevationStr.setNum(mapPos.z(), 'f', 1); + else + d->m_positionElevationStr.setNum(std::lround(mapPos.z())); + } + d->updatePositionText(); + } + break; + case Mode::Mode_2D: + { + // if elevation is available + if (this->layerFactory().elevationAvailable() && this->isReady()) + { + // 2D: use LocationToElevationHelper: + // 1. do setup: + setupLocationToElevationHelper(); + + // 2. trigger computation: + if (d->m_locationToElevationHelper) + d->m_locationToElevationHelper->locationToElevation(mapPos.toPointF()); + } // if (this->layerFactory().elevationAvailable() && this->isReady()) + else + d->updatePositionText(); // update if elevation is NOT available + } + break; + case Mode::Undefined: + // empty + break; + } //switch + + + + } // if (d->m_positionLabel->isVisible()) + }); // lambda + + } + moveLabelTo(d->m_positionLabel, getQGraphicsView()->width(), distanceToFrame.x(), labelPos.y()); + + + // Prepare next label: + if (d->m_positionLabel->isVisible()) + labelPos.ry() += d->m_positionLabel->minimumHeight(); + } + + // ExerciseTimeLabel + // if (isReplay()) + { + if (! d->m_exerciseTimeLabel) + { + d->m_exerciseTimeLabel = new QLabel(); + d->m_exerciseTimeLabel->setVisible(false); + d->m_exerciseTimeLabel->setObjectName(QLatin1String("WidgetOverlayLayoutExerciseTimeLabel")); + d->m_exerciseTimeLabel->setText(QLatin1String("-")); + d->m_exerciseTimeLabel->setFont(font); + d->m_exerciseTimeLabel->setAlignment(Qt::AlignBottom | Qt::AlignCenter); + d->m_exerciseTimeLabel->setMinimumSize(labelMinSize + labelBorderPixel); + + // Bug iOSQz-000005 'QGraphicsProxyWidget will crash' + // Adding a widget will let the application crash + // Workaround: 'proxy->setOpacity(0.99);' + QGraphicsProxyWidget *proxy = this->getQGraphicsView()->scene()->addWidget(d->m_exerciseTimeLabel); + proxy->setOpacity(0.99); + proxy->setZValue(std::numeric_limits::max()); + + connect(this, &SituationMap::Map::rectChanged, this, [this, distanceToFrame]() + { + moveLabelTo(d->m_exerciseTimeLabel, getQGraphicsView()->width(), distanceToFrame.x(), d->m_exerciseTimeLabel->pos().y()); + }); + } + moveLabelTo(d->m_exerciseTimeLabel, getQGraphicsView()->width(), distanceToFrame.x(), labelPos.y()); + // Prepare next label: + if (d->m_exerciseTimeLabel->isVisible()) + labelPos.ry() += d->m_exerciseTimeLabel->minimumHeight(); + } + + // TimeLabel: + if (0) // Todo: unnecessary if ? + { + if (! d->m_timeLabel) + { + d->m_timeLabel = new QLabel(); + d->m_timeLabel->setObjectName(QLatin1String("WidgetOverlayLayoutTimeStringLabel")); + d->m_timeLabel->setText(QLatin1String("#")); + d->m_timeLabel->setFont(font); + d->m_timeLabel->setAlignment(Qt::AlignBottom | Qt::AlignCenter); + d->m_timeLabel->setMinimumSize(labelMinSize + labelBorderPixel); + + // Bug iOSQz-000005 'QGraphicsProxyWidget will crash' + // Adding a widget will let the application crash + // Workaround: 'proxy->setOpacity(0.99);' + QGraphicsProxyWidget *proxy = this->getQGraphicsView()->scene()->addWidget(d->m_timeLabel); + proxy->setOpacity(0.99); + proxy->setZValue(std::numeric_limits::max()); + + // lambda :-) + connect(this, &SituationMap::Map::rectChanged, this, [this, distanceToFrame]() + { + moveLabelTo(d->m_timeLabel, getQGraphicsView()->width(), distanceToFrame.x(), d->m_timeLabel->pos().y()); + }); + + // by default -> not visible: + d->m_timeLabel->setVisible(false); + } + + moveLabelTo(d->m_timeLabel, getQGraphicsView()->width(), distanceToFrame.x(), labelPos.y()); + + + + // Prepare next label: + if (d->m_timeLabel->isVisible()) + labelPos.ry() += d->m_timeLabel->minimumHeight(); + + } + + // compass + if (this->mode() != Mode::Undefined) + { + if (! d->m_compass) + { + d->m_compass = new MyGraphicsPixmapItem(); // QGraphicsPixmapItem + + // get rde blue: + QColor rdeColor = this->palette().color(QPalette::WindowText); // RDE_DARK_BLUE + QColor bgColor;// = this->palette().color(QPalette::Shadow); // RDE_LIGHTER_GRAY is too pale + if (! bgColor.isValid()) + bgColor = Qt::gray; + bgColor.setAlphaF(0.5); + QImage image = renderCompass(rdeColor, Qt::white, bgColor, 8, 30, this->mode()==Mode::Mode_3D); + d->m_compass->setPixmap(QPixmap::fromImage(image)); + + // QPixmap pixmap(QLatin1String(":/compass.png")); + // d->m_compass->setPixmap(pixmap.scaledToHeight(height)); + + qreal xOffset = d->m_compass->pixmap().width() * 0.5; + qreal yOffset = d->m_compass->pixmap().height() * 0.5; + d->m_compass->setOffset(-xOffset, -yOffset); + + d->m_compass->setZValue(std::numeric_limits::max()); + d->m_compass->setVisible(true); + + d->m_compass->setFlags(QGraphicsItem::ItemIgnoresTransformations); + d->m_compass->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); + + this->getQGraphicsView()->scene()->addItem(d->m_compass); + + // lambda :-) + connect(this, &SituationMap::Map::rectChanged, this, [this, distanceToFrame]() + { + QPoint newPos = d->m_compass->pos().toPoint(); + int maxDimHalf = std::max(d->m_compass->pixmap().width(), + d->m_compass->pixmap().height()) * 0.5; + newPos.rx() = this->getQGraphicsView()->width() - maxDimHalf - distanceToFrame.x(); + d->m_compass->setPos(newPos); + }); + + + if (this->mode() == Mode::Mode_2D) + { + // lambda :-) + connect(this, &Map::rotationChanged, this, [this](double absoluteAngleDegrees) + { + + // QGraphicsItem: + // Sets the clockwise rotation angle, in degrees, around the Z axis. + // The default value is 0 (i.e., the item is not rotated). + // Assigning a negative value will rotate the item counter-clockwise. + // Normally the rotation angle is in the range (-360, 360), + // but it's also possible to assign values outside of this range + // (e.g., a rotation of 370 degrees is the same as a rotation of 10 degrees). + + // -> use values directly: + d->m_compass->setRotation(absoluteAngleDegrees); + }); + } else if (this->mode() == Mode::Mode_3D) + { + // lambda :-) + connect(this, &Map::extentChanged, this, [this]() + { + const Esri::ArcGISRuntime::Camera &cam = d->m_sceneGraphicsView->currentViewpointCamera(); + d->m_compass->setRotation(360.0 - cam.heading()); + }); + } + + // lambda :-) + connect(d->m_compass, &MyGraphicsPixmapItem::doubleClicked, this, [this]() + { + if (mode() == Mode::Mode_2D) + { + if (qApp->keyboardModifiers().testFlag(Qt::ShiftModifier)) + { + const Esri::ArcGISRuntime::Envelope &fullExtent = this->layerFactoryImpl().fullExtentOfBasemaps(); + d->m_mapGraphicsView->setViewpointAsync(Esri::ArcGISRuntime::Viewpoint(fullExtent)); + } else if (qApp->keyboardModifiers().testFlag(Qt::ControlModifier)) + { + this->zoomToArea(d->m_trainingsAreaExtent, 50); + } else { + Viewpoint2D vp = this->viewpoint2D(); + vp.setRotation(0.0); + this->setViewpoint2D(vp); + } + } + else if(mode() == Mode::Mode_3D) + { + if (qApp->keyboardModifiers().testFlag(Qt::ShiftModifier)) + { + const Esri::ArcGISRuntime::Envelope &fullExtent = this->layerFactoryImpl().fullExtentOfBasemaps(); + d->m_sceneGraphicsView->setViewpointAsync(Esri::ArcGISRuntime::Viewpoint(fullExtent)); + } else if (qApp->keyboardModifiers().testFlag(Qt::ControlModifier)) + { + this->zoomToArea(d->m_trainingsAreaExtent, 50); + } else { + Camera cam = this->camera(); + cam.heading = 0.0; + cam.pitch = 0.0; + cam.roll = 0.0; + this->setCamera(cam); + } + } + }); + + } // if (! d->m_compass) + + QPoint newPos = labelPos; + newPos.ry() += 10; // small distance + int maxDimHalf = std::max(d->m_compass->pixmap().width(), + d->m_compass->pixmap().height()) * 0.5; + newPos.ry() += maxDimHalf; + newPos.rx() = this->getQGraphicsView()->width() - maxDimHalf - distanceToFrame.x(); + d->m_compass->setPos(newPos); + } + else + DEBUG_LOG(m_logger) << LOGFUNCTION << " compass not supported for 3D yet"; + + // to trigger all 'consumer' + emitVisualComponentChanged(VisualComponent::PosititionString); + emitVisualComponentChanged(VisualComponent::ExerciseTimeString); + emitVisualComponentChanged(VisualComponent::TimeString); + emitVisualComponentChanged(VisualComponent::CompassRose); + + return nullptr; +} + +void MapImpl::setupLocationToElevationHelper() +{ + if (d->m_locationToElevationHelper) + return; + + auto elevationMap = this->getElevationMapManager(); + if (!elevationMap) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " no ElevationMapManager"; + return; + } + + d->m_locationToElevationHelper = this->layerFactory().locationToElevation(); // layerFactory has ownership + if (d->m_locationToElevationHelper) + { + // lambda :-) + connect(d->m_locationToElevationHelper.data(), &SituationMap::LocationToElevationHelper::elevationCompleted, + this, [this](double elevation) + { + // ElevationMap return std::numeric_limits::min() or similar if location is not covered: + if (qIsNaN(elevation) || elevation < -30000) + d->m_positionElevationStr = QLatin1String("-"); + else + if (std::lround(elevation) < 10.0) + d->m_positionElevationStr.setNum(elevation, 'f', 1); + else + d->m_positionElevationStr.setNum(std::lround(elevation)); + d->updatePositionText(); + }); + } +} + + +#ifdef NEVER +class MyWidget : public QWidget +{ + Q_OBJECT +public: + MyWidget() + : QWidget() + , m_targetWidget(nullptr) + , m_enable(false) + {} + + void setTargetWidget(QWidget *targetWidget) + { + m_targetWidget = targetWidget; + } + + void enable() + { + qWarning() << " target is enabled now"; + m_enable = true; + } + + bool event(QEvent *e) override + { + qWarning() << " e=" << e; + + if (!m_enable) + return QWidget::event(e); + + + if (e->type() == QEvent::MouseButtonDblClick + || e->type() == QEvent::MouseMove + || e->type() == QEvent::MouseButtonPress + || e->type() == QEvent::MouseButtonRelease) + { + // Mouse events are forwarded to the underlying widget + QMouseEvent *event = static_cast(e); + + qWarning() << " event.pos=" << event->pos(); + + QWidget *child = childAt(event->pos()); + qWarning() << " child=" << child; + + // no own child widgets found: + if (! child) + { + QMouseEvent shiftedEvent(event->type(), event->pos(), + event->globalPos(), event->button(), event->buttons(), + event->modifiers()); + + qWarning() << " forwarding event=" << e; + // -> send event to 'underlying' widget: + QApplication::sendEvent(m_targetWidget, &shiftedEvent); + } + + } + // own child widgets found -> handle event normally: + return QWidget::event(e); + } + +private: + QWidget *m_targetWidget; + bool m_enable; +}; +#endif + + +QWidget *MapImpl::addMapWidget_OwnWidget() +{ +#ifdef NEVER + const QFont font(QLatin1String("Courier New"), 13, QFont::DemiBold); + const QSize labelBorderPixel(5, 2); + // const QPoint distanceToFrame (25,25); + + QFontMetrics fm(font); + //const QString defaultPostitionLabelStr(QLatin1String("32 U MD 12345 12345")); + const QString defaultTimeStr(QLatin1String("01234567890123456789")); + QSize labelMinSize = fm.size(0, defaultTimeStr); + + + MyWidget *widget = new MyWidget(); + // widget->setStyleSheet(QLatin1String("* {background-color: yellow;}")); + QHBoxLayout *layout = new QHBoxLayout(); + widget->setLayout(layout); + + + widget->setTargetWidget(d->m_stackedLayout->currentWidget()); + + QTimer *timer = new QTimer(); + timer->setSingleShot(true); + timer->setInterval(2000); + + connect(timer, &QTimer::timeout, widget, [widget]() + { + widget->enable(); + }); + + timer->start(); + + + // add WidgetOverlayLayout and make it current: + int idx = d->m_stackedLayout->addWidget(widget); + d->m_stackedLayout->setCurrentIndex(idx); + + d->m_positionLabel = new QLabel(); + d->m_positionLabel->setObjectName(QLatin1String("WidgetOverlayLayoutPositionLabel")); + d->m_positionLabel->setStyleSheet(QLatin1String("QLabel {background-color: gray;}")); + d->m_positionLabel->setText(QLatin1String("-")); + d->m_positionLabel->setFont(font); + d->m_positionLabel->setAlignment(Qt::AlignBottom | Qt::AlignCenter); + d->m_positionLabel->setMinimumSize(labelMinSize + labelBorderPixel); + d->m_positionLabel->setMaximumSize(this->minimumSize()); + + // QSpacerItem *spacer = new QSpacerItem(10, 1, QSizePolicy::Maximum, QSizePolicy::Maximum); + // layout->addItem(spacer, 0,0); + + layout->addStretch(2); + layout->addWidget(d->m_positionLabel); + +#endif + return nullptr; +} + +QImage SituationMap::MapImpl::renderCompass(const QColor &colorNorth, + const QColor &colorSouth, + const QColor &colorBackground, + int halfWidth, int halfHeight, + bool b_3D) +{ + int width = halfWidth * 2; + int height = halfHeight * 2; + QImage image(width, height, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + QPainter painter(&image); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + { + // QPen pen(Qt::black); + // pen.setWidth(1); + painter.setPen(Qt::NoPen); + painter.setBrush(QBrush(colorNorth, Qt::BrushStyle::SolidPattern)); + + // 2 || + // | | + // 1 | | 3 + // | | + // || + + QPolygon poly; + poly.reserve(3); + poly.append(QPoint( 0, halfHeight)); + poly.append(QPoint(halfWidth, 0)); + poly.append(QPoint( width, halfHeight)); + + painter.drawPolygon(poly); + } + { + // QPen pen(Qt::black); + // pen.setWidth(1); + painter.setPen(Qt::NoPen); + painter.setBrush(QBrush(colorSouth, Qt::BrushStyle::SolidPattern)); + + // || + // | | + // 1 | | 3 + // | | + // 2 || + + QPolygon poly; + poly.reserve(3); + poly.append(QPoint( 0, halfHeight)); + poly.append(QPoint(halfWidth, height)); + poly.append(QPoint( width, halfHeight)); + + painter.drawPolygon(poly); + } + + painter.end(); + + // background: + int maxDim = std::max(width, height); + int border = 5; + QImage backgroundImage(maxDim + (2 * border), + maxDim + (2 * border), + QImage::Format_ARGB32); + { + backgroundImage.fill(Qt::transparent); + + painter.begin(&backgroundImage); + painter.setBrush(QBrush(colorBackground)); + painter.setPen(Qt::NoPen); + painter.drawEllipse(backgroundImage.rect()); + painter.end(); + } + + + // merge: + { + painter.begin(&backgroundImage); + + int offsetX = (backgroundImage.width() - image.width()) / 2; + int offsetY = (backgroundImage.height() - image.height()) / 2; + + QRect r = image.rect(); + r.moveTo(offsetX, offsetY); + painter.drawImage(r, image, image.rect()); + painter.end(); + } + + + { + painter.begin(&backgroundImage); + QPointF pos(backgroundImage.width() / 2.0 * 1.1, + backgroundImage.height() / 2.0 * 0.4); + + // 'N' + QFont font = painter.font(); + font.setBold(true); + painter.setFont(font); + painter.setPen(colorNorth); + + painter.drawText(pos, QLatin1String("N")); + + pos.rx() = backgroundImage.width() / 2.0 * 1.1; + pos.ry() = backgroundImage.height() / 2.0 * 1.9; + if (b_3D) + painter.drawText(pos, QLatin1String("3D")); + else + painter.drawText(pos, QLatin1String("2D")); + } + + return backgroundImage; +} + + +bool SituationMap::MapImpl::isPositionStringVisible() const +{ + if (! d->m_positionLabel) + return false; + d->updatePositionText(); + return d->m_positionLabel->isVisible(); +} + +void MapImpl::showPositionString(bool visible) +{ + if (! d->m_positionLabel) + return; + + bool changed = d->m_positionLabel->isVisible() != visible; + d->m_positionLabel->setVisible(visible); + if (changed) + { + addMapWidget(); + emitVisualComponentChanged(VisualComponent::PosititionString); + } +} + +void SituationMap::MapImpl::setPositionStringColor(const QColor &foregroundColor, const QColor &backgroundColor) +{ + d->m_positionLabelForegroundColor = foregroundColor; + d->m_positionLabelBackgroundColor = backgroundColor; + + if (! d->m_positionLabel) + return; + + QLabel *label = d->m_positionLabel; + if (label) + { + QPalette palette = label->palette(); + if (d->m_positionLabelForegroundColor.isValid()) + palette.setColor(label->foregroundRole(), d->m_positionLabelForegroundColor); + if (d->m_positionLabelBackgroundColor.isValid()) + palette.setColor(label->backgroundRole(), d->m_positionLabelBackgroundColor); + label->setPalette(palette); + } +} + +void MapImpl::setPartitionTime(const qint64 &time) +{ + if(!d->m_exerciseTimeLabel) + return; + d->m_exerciseTimeLabel->setText(DTG::toStringMSec(time)); +} + +void MapImpl::setReplay(bool b) +{ + d->m_isReplay = b; +} + +bool MapImpl::isReplay() const +{ + return d->m_isReplay; +} + +bool MapImpl::isExerciseTimeStringVisible() const +{ + if (! d->m_exerciseTimeLabel) + return false; + return d->m_exerciseTimeLabel->isVisible(); +} + +void MapImpl::showExerciseTimeString(bool visible) +{ + if (! d->m_exerciseTimeLabel) + return; + bool changed = d->m_exerciseTimeLabel->isVisible() != visible; + d->m_exerciseTimeLabel->setVisible(visible); + if (changed) + { + addMapWidget(); + emitVisualComponentChanged(VisualComponent::ExerciseTimeString); + } +} + +void MapImpl::setExerciseTimeStringColor(const QColor &foregroundColor, const QColor &backgroundColor) +{ + d->m_exerciseTimeLabelForegroundColor = foregroundColor; + d->m_exerciseTimeLabelBackgroundColor = backgroundColor; + + if (! d->m_exerciseTimeLabel) + return; + + QLabel *label = d->m_exerciseTimeLabel; + if (label) + { + QPalette palette = label->palette(); + if (d->m_exerciseTimeLabelForegroundColor.isValid()) + palette.setColor(label->foregroundRole(), d->m_exerciseTimeLabelForegroundColor); + if (d->m_exerciseTimeLabelBackgroundColor.isValid()) + palette.setColor(label->backgroundRole(), d->m_exerciseTimeLabelBackgroundColor); + label->setPalette(palette); + } +} + +bool SituationMap::MapImpl::isTimeStringVisible() const +{ + if (! d->m_timeLabel) + return false; + return d->m_timeLabel->isVisible(); +} + +void MapImpl::setTimeString(const QString &timeString) +{ + if (! d->m_timeLabel) + return; + + d->m_timeLabel->setText(timeString); +} + +void SituationMap::MapImpl::showTimeString(bool visible) +{ + if (d->m_destroying) + return; + + if (!d->m_timeLabel) + return; + + bool changed = d->m_timeLabel->isVisible() != visible; + d->m_timeLabel->setVisible(visible); + if (changed) + { + addMapWidget(); + emitVisualComponentChanged(VisualComponent::TimeString); + } +} + +void SituationMap::MapImpl::setTimeStringColor(const QColor &foregroundColor, const QColor &backgroundColor) +{ + if (d->m_destroying) + return; + + d->m_timeLabelForegroundColor = foregroundColor; + d->m_timeLabelBackgroundColor = backgroundColor; + + if (! d->m_timeLabel) + return; + + QLabel *label = d->m_timeLabel; + if (label) + { + QPalette palette = label->palette(); + if (d->m_timeLabelForegroundColor.isValid()) + palette.setColor(label->foregroundRole(), d->m_timeLabelForegroundColor); + if (d->m_timeLabelBackgroundColor.isValid()) + palette.setColor(label->backgroundRole(), d->m_timeLabelBackgroundColor); + label->setPalette(palette); + } +} + +bool SituationMap::MapImpl::isCompassVisible() const +{ + if (! d->m_compass) + return false; + return d->m_compass->isVisible(); +} + +void MapImpl::showCompass(bool visible) +{ + if (d->m_destroying) + return; + + if (!d->m_compass) + return; + + bool changed = d->m_compass->isVisible() != visible; + d->m_compass->setVisible(visible); + if (changed) + emitVisualComponentChanged(VisualComponent::CompassRose); +} + + +void MapImpl::addSideBar() +{ + if (d->m_destroying) + return; + + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->m_sideBar) + return; + + // Create SideBar: + d->m_sideBar = QPointer(new SideBar()); // no parent, will be embedded to StackedItem taking ownership + + d->m_sideBar->setAnimationDuration(500); + + d->m_sideBar->setMinimumWidth(350); + d->m_sideBar->setMaximumWidth(d->m_sideBar->minimumWidth()); + d->m_sideBar->setMinimumHeight(300); + + + int width = d->m_sideBar->minimumWidth(); + d->m_sideBar->setVisiblePosition(QPoint(0, 0)); + d->m_sideBar->setInvisiblePosition(QPoint(-width, 0)); + + // Embed (and take ownership) + if (d->m_stackedLayout) + { + int idx = d->m_stackedLayout->addWidget(d->m_sideBar); // takes ownership + d->m_stackedLayout->setCurrentIndex(idx); + } + + // Visibility: + d->m_sideBar->hide(); + + + //---------------------------------------------- + // add Entries: + //---------------------------------------------- + SituationMap::SideBarItem *item; + + // Show Grid + item = new SituationMap::SideBarItem(tr("Show Grid")); + item->setEnabled(this->mode() == Mode::Mode_2D); + item->setCheckState((this->grid().isVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + d->m_sideBar->addCheckableItem(item); + connect(item, &SituationMap::SideBarItem::changed, this, [this](bool onOff) + { + this->grid().setVisible(onOff); + }); + connect(this, &SituationMap::Map::visualComponentChanged, this, [this, item]() + { + item->setCheckState((this->grid().isVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + }); + + // Show Position String + item = new SituationMap::SideBarItem(tr("Show Position String")); + item->setCheckState((this->isPositionStringVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + d->m_sideBar->addCheckableItem(item); + connect(item, &SituationMap::SideBarItem::changed, this, [this](bool onOff) + { + this->showPositionString(onOff); + }); + connect(this, &SituationMap::Map::visualComponentChanged, this, [this, item]() + { + item->setCheckState((this->isPositionStringVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + }); + + // Show Exercise Time String + // // if(isReplay()) // GUEZE-2921 'Anpassen der Uhrzeit auf Nutzerbasis (Checkbox)' + // Mit GUEZE-2921 ist es immer verfügbar. + // Muss es vielleicht über ein projectSetting gesteuert werden? + // Gilt es wirklich für alle Apps? + { + item = new SituationMap::SideBarItem(tr("Show Exercise Time String")); + item->setCheckState((this->isExerciseTimeStringVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + d->m_sideBar->addCheckableItem(item); + connect(item, &SituationMap::SideBarItem::changed, this, [this](bool onOff) + { + this->showExerciseTimeString(onOff); + }); + connect(this, &SituationMap::Map::visualComponentChanged, this, [this, item]() + { + item->setCheckState((this->isExerciseTimeStringVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + }); + } + + // Show Time String + // item = new SituationMap::SideBarItem(tr("Show Time String")); + // item->setCheckState((this->isTimeStringVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + // d->m_sideBar->addCheckableItem(item); + // connect(item, &SituationMap::SideBarItem::changed, this, [this](bool onOff) + // { + // this->showTimeString(onOff); + // }); + // connect(this, &SituationMap::Map::visualComponentChanged, this, [this, item]() + // { + // item->setCheckState((this->isTimeStringVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + // }); + + // Show Compass Rose + item = new SituationMap::SideBarItem(tr("Show Compass Rose")); + item->setCheckState((this->isCompassVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + d->m_sideBar->addCheckableItem(item); + connect(item, &SituationMap::SideBarItem::changed, this, [this](bool onOff) + { + this->showCompass(onOff); + }); + connect(this, &SituationMap::Map::visualComponentChanged, this, [this, item]() + { + item->setCheckState((this->isCompassVisible()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + }); + + + // Show Overview Map + item = new SituationMap::SideBarItem(tr("Show Overview Map")); + OverviewMapHandler &omHdl = overviewMapHandler(); + item->setCheckState((omHdl.builtInOverviewMapWidget()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + d->m_sideBar->addCheckableItem(item); + connect(item, &SituationMap::SideBarItem::changed, this, [this](bool onOff) + { + OverviewMapHandler &omHdl = overviewMapHandler(); + if (onOff) + omHdl.createBuiltInOverviewMap(); + else + omHdl.removeBuiltInOverviewMap(); + }); + connect(this, &SituationMap::Map::visualComponentChanged, this, [this, item]() + { + OverviewMapHandler &omHdl = overviewMapHandler(); + item->setCheckState((omHdl.builtInOverviewMapWidget()) ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + }); + + // could be continue like: + // item = new SituationMap::SideBarItem(tr("Scale Bar (tbd)")); + // item->setCheckState(Qt::CheckState::Checked); + // d->m_sideBar->addCheckableItem(item); + + // item = new SituationMap::SideBarItem(tr("Distance Measurement (tbd)")); + // item->setCheckState(Qt::CheckState::Checked); + // d->m_sideBar->addCheckableItem(item); + // item = new SituationMap::SideBarItem(tr("Terrain Profile (tbd)")); + // item->setCheckState(Qt::CheckState::Checked); + // d->m_sideBar->addCheckableItem(item); + // item = new SituationMap::SideBarItem(tr("Viewshed (tbd)")); + // item->setCheckState(Qt::CheckState::Checked); + // d->m_sideBar->addCheckableItem(item); + + + // Rocket + QPushButton *pbHome = new QPushButton(tr("Home")); + pbHome->setIcon(QIcon(QLatin1String("icon:go-home.svg"))); + d->m_sideBar->addWidget(pbHome); + connect(pbHome, &QPushButton::clicked, this, [this]() { + this->zoomToArea(d->m_trainingsAreaExtent, 50); + }); + + // Rocket + QPushButton *pbRocket = new QPushButton(tr("Rocket")); + pbRocket->setIcon(QIcon(QLatin1String("icon:rocket.svg"))); + d->m_sideBar->addWidget(pbRocket); + connect(pbRocket, &QPushButton::clicked, this, &MapImpl::startRocket); + + + // JumpPointWidget: + const QString jpLayername = this->layerFactoryImpl().jumpPointLayer(); + d->m_jumpPointWidget = new JumpPointWidget(jpLayername, this, QLatin1String("myJumpPointWidget")); + d->m_sideBar->addWidget(d->m_jumpPointWidget); + + // temporaery : + if (0) + { + if (this->isReady()) + { + d->m_jumpPointWidget->addJumpPoint(QLatin1String("JP_BuildingDemo111"), + QPointF(0.155275, 0.925975), + 1000); + } + else + { + connect(this, &Map::mapReady, this, [this]() + { + d->m_jumpPointWidget->addJumpPoint(QLatin1String("JP_BuildingDemo222"), + QPointF(0.155275, 0.925975), + 1000); + }); + } + } + // : temporaery + + + + // Add Button for SideBar/BurgerMenue + { + if (!d->m_pbSideBar) + { + MyPushButton *pb = new MyPushButton(); // parent = 0 -> toplevelWidget + + //const QColor &base = this->palette().color(QPalette::Base); // qt::white + QColor rdeColor = palette().color(QPalette::WindowText); // RDE_DARK_BLUE + + pb->setFlat(true); + pb->setIconSize(QSize(36, 36)); + pb->setFixedSize(pb->iconSize() * 1.2); + + pb->setIconOnHover(SideBar::createIcon(rdeColor, QColor(), 100)); + rdeColor.setAlphaF(0.7); + pb->setIconPale(SideBar::createIcon(rdeColor, QColor(), 100)); + + d->m_pbSideBar = pb; + } + + QPushButton *pbSideBar = d->m_pbSideBar; + // pbSideBar->setText(QLatin1String("MainMenue")); + pbSideBar->setObjectName(QLatin1String("QPushButton-MainMenue")); + + + // add button + + // Bug iOSQz-000005 'QGraphicsProxyWidget will crash' + // Adding a widget will let the application crash + // Workaround: 'proxy->setOpacity(0.99);' + switch (mode()) + { + case Mode::Mode_2D: + { + QGraphicsProxyWidget *proxy = d->m_mapGraphicsView->scene()->addWidget(pbSideBar); + proxy->setOpacity(0.99); + } + break; + case Mode::Mode_3D: + { + QGraphicsProxyWidget *proxy = d->m_sceneGraphicsView->QGraphicsView::scene()->addWidget(pbSideBar); + proxy->setOpacity(0.99); + } + break; + case Mode::Undefined: + // empty + break; + } // switch + + pbSideBar->move(25, 25); + + // connect(pbMainMenue, &QPushButton::clicked, this, &SitMap::onShowSideBar); + connect(pbSideBar, &QPushButton::clicked, this, [&]() + { + if (QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier) + && QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) + && QGuiApplication::keyboardModifiers().testFlag(Qt::AltModifier)) + { + + // 'debug' behaviour: toDumpString + std::string localLoggerName = m_logger.getLoggerName() + ".dump"; + LOGGER_T logger = m_logger.getNGLLogger(localLoggerName); + + NOTICE_LOG(logger) << "START DUMP --- objectName: " << this->objectName(); + const QString &dumpStr = this->toDumpString(); + NOTICE_LOG(logger) << dumpStr; + NOTICE_LOG(logger) << "START END --- objectName: " << this->objectName(); + } else { + + // typical behaviour: + d->m_sideBar->showAnimated(); + + } + }); + + // QDialog * d = new QDialog; + // d->setLayout(new QHBoxLayout); + // d->layout()->addWidget(pbSideBar); + // d->setMinimumSize(QSize(500, 500)); + // d->exec(); + + + + // enable/disable button depending on mapReady + if (this->isReady()) + { + pbSideBar->setStyleSheet(QLatin1String("QPushButton {background-color: transparent; border: 1px solid transparent;}")); + pbSideBar->setEnabled(true); + } + else + { + pbSideBar->setStyleSheet(QLatin1String("QPushButton {background-color: red; border: 1px solid transparent;}")); + pbSideBar->setEnabled(false); + + connect(this, &Map::mapReady, this, [pbSideBar]() + { + pbSideBar->setStyleSheet(QLatin1String("QPushButton {background-color: transparent; border: 1px solid transparent;}")); + pbSideBar->setEnabled(true); + + // pbSideBar->setStyleSheet(QLatin1String("QPushButton {background-color: transparent; border: 1px solid transparent;} QPushButton:hover {background-color: white; border: 1px solid transparent;}")); + // setStyleSheet(QLatin1String("QTabBar::close-button{ image: url(\"icon:dialog-close.svg\"); }")); + }); + } + + + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void MapImpl::removeSideBar() +{ + delete d->m_sideBar; +} + +QPointer MapImpl::sideBar() +{ + return d->m_sideBar; +} + + +bool MapImpl::applyLicense(const QString &licenseStr, const QStringList &extensions) +{ + LOGGER_T logger = getLogger("MapImpl"); + static int licensed = -1; + + INFO_LOG(logger) << LOGFUNCTION << " licenseStr = '" << licenseStr << "'"; + for (const auto &ex : extensions) + INFO_LOG(logger) << LOGFUNCTION << " extension = '" << ex << "'"; + + if (licensed != -1) + { + INFO_LOG(logger) << LOGFUNCTION + << " already licensed" + << " (licensed=" << licensed << ")"; + return licensed == 1; + } + + connect(Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::instance(), &Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::errorOccurred, + [](const Esri::ArcGISRuntime::Error & error) + { + std::cerr << " EsriError occured: " << error << std::endl; + LOGGER_T logger = getLogger("MapImpl"); + ERROR_LOG(logger) << LOGFUNCTION << " EsriError occured: " << error; + }); + + // 1. setLicense + Esri::ArcGISRuntime::LicenseResult licResult; + + if (extensions.isEmpty()) + licResult = Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setLicense(licenseStr); + else + licResult = Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setLicense(licenseStr, extensions); + + // 2. initialize + if (! Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::initialize()) + FATAL_LOG(logger) << LOGFUNCTION << " ArcGISRuntimeEnvironment::initialize failed"; + DEBUG_LOG(logger) << LOGFUNCTION << " licResult=" << (int)licResult.licenseStatus(); + // Invalid = 0, Expired = 2, LoginRequired = 3, Valid = 4 + + std::ostringstream msg; + msg << " licenseStatus = " << licResult.licenseStatus() + << " licenseStr='" << licenseStr.toStdString() << "'"; + for (const QString &extStr : std::as_const(extensions)) + msg << " extensionsStr= '" << extStr << "'"; + + if (licResult.licenseStatus() == Esri::ArcGISRuntime::LicenseStatus::Valid) + { + licensed = 1; + INFO_LOG(logger) << LOGFUNCTION << " success: " << msg.str(); + } + else + { + licensed = 0; + FATAL_LOG(logger) << LOGFUNCTION << " Error: " << msg.str(); + } + + return licResult.licenseStatus() == Esri::ArcGISRuntime::LicenseStatus::Valid; +} + +bool MapImpl::applyNetworkCacheSettings(const EsriNetworkCacheSettings &esriNetworkCacheSettings) +{ + LOGGER_T logger = getLogger("MapImpl"); + INFO_LOG(logger) << LOGFUNCTION << " EsriNetworkCacheSettings = " << esriNetworkCacheSettings; + +#if ACRGIS_VERSION < 0x640700 // 100.7.0 / Update 7 + WARNING_LOG(logger) << LOGFUNCTION << " EsriNetworkCacheSettings not supported yet"; +#else +# ifdef HAS_NETWORK_CACHE_CONFIGURATION // 100.15 + Esri::ArcGISRuntime::NetworkCacheConfiguration *globNetCacheConfig = + Esri::ArcGISRuntime::RequestConfiguration::globalNetworkCacheConfiguration(); + if (!globNetCacheConfig) + { + WARNING_LOG(logger) << LOGFUNCTION << " globalNetworkCacheConfiguration failed"; + return false; + } + + // V100.8: + // setNetworkCachingEnabled -> NetworkCacheConfiguration + // V100.15: default CachePolicy is 'PreferNetwork' + + + INFO_LOG(logger) << LOGFUNCTION << " globalNetworkCacheConfiguration: clearCache... '" << globNetCacheConfig->cacheDirectory() << "'"; + globNetCacheConfig->clearCacheAsync(); // clearCacheAsync - since 100.15.0 + + INFO_LOG(logger) << LOGFUNCTION << " globalNetworkCacheConfiguration: setNetworkCachingEnabledAsync... to " << globNetCacheConfig->networkCachingEnabled(); + globNetCacheConfig->setNetworkCachingEnabledAsync(esriNetworkCacheSettings.enabled); // setNetworkCachingEnabledAsync - since 100.15.0 + + NOTICE_LOG(logger) << LOGFUNCTION << " globalNetworkCacheConfiguration: " << globNetCacheConfig->cachePolicy() + << ", cacheDir='" << globNetCacheConfig->cacheDirectory() << "'" + << ", cacheSize=" << globNetCacheConfig->cacheSize(); +# else + Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setNetworkCachingEnabled(esriNetworkCacheSettings.enabled); +# endif // HAS_NETWORK_CACHE_CONFIGURATION +#endif // ACRGIS_VERSION + + return true; +} +EsriNetworkCacheSettings MapImpl::networkCacheSettings() +{ + EsriNetworkCacheSettings encs; + + +#if ACRGIS_VERSION < 0x640700 // 100.7.0 / Update 7 + LOGGER_T logger = getLogger("MapImpl"); + WARNING_LOG(logger) << LOGFUNCTION << " EsriNetworkCacheSettings not supported yet"; +#else + +# ifdef HAS_NETWORK_CACHE_CONFIGURATION // 100.15 + LOGGER_T logger = getLogger("MapImpl"); + Esri::ArcGISRuntime::NetworkCacheConfiguration *globNetCacheConfig = + Esri::ArcGISRuntime::RequestConfiguration::globalNetworkCacheConfiguration(); + if (globNetCacheConfig) + { + encs.enabled = globNetCacheConfig->networkCachingEnabled(); + } else { + WARNING_LOG(logger) << LOGFUNCTION << " globalNetworkCacheConfiguration failed"; + } + +# else + encs.enabled = Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::networkCachingEnabled(); +# endif // HAS_NETWORK_CACHE_CONFIGURATION +#endif // #if ACRGIS_VERSION < 0x640700 + + return encs; +} + + + +Esri::ArcGISRuntime::Map *MapImpl::getEsriMap() const +{ + return d->m_esriMap; +} + +Esri::ArcGISRuntime::Basemap *MapImpl::getEsriBasemap() const +{ + return d->m_basemap; +} + + +bool MapImpl::initElevationMapManager(const QStringList &filePaths_rough_to_fine) +{ + INFO_LOG(m_logger) << LOGFUNCTION << " read elevation data file \'" << filePaths_rough_to_fine.join(QStringLiteral(" ; ")) << "\' ..."; + + QString fileCombinationKey; // name of TrainingArea would be helpful :-) + for (const QString &filePath : filePaths_rough_to_fine) + fileCombinationKey.append(QFileInfo(filePath).fileName() + QStringLiteral("#")); + + + static QHash> s_elevationMapManagers; + auto it = s_elevationMapManagers.find(fileCombinationKey); + if (it != s_elevationMapManagers.end()) + { + d->m_elevationMapManager = it.value().lock(); + if (d->m_elevationMapManager) + return true; + } + d->m_elevationMapManager = std::make_shared(); + d->m_elevationMapManager->init(filePaths_rough_to_fine); + + INFO_LOG(m_logger) << LOGFUNCTION << " read elevation data file \'" << filePaths_rough_to_fine.join(QStringLiteral(" ; ")) << "\' ... done"; + + return true; +} + +std::shared_ptr MapImpl::getElevationMapManager() const +{ + if (d->m_elevationMapManager && !d->m_elevationMapManager->isValid()) + return nullptr; + return d->m_elevationMapManager; +} + +Esri::ArcGISRuntime::SpatialReference MapImpl::getEsriSpatialReference() const +{ + if (d->m_esriMap) + return d->m_esriMap->spatialReference(); + if (d->m_esriScene) + return d->m_esriScene->spatialReference(); + return Esri::ArcGISRuntime::SpatialReference(); +} + +Esri::ArcGISRuntime::Scene *MapImpl::getEsriScene() const +{ + if (!d->m_esriScene) + return nullptr; + return d->m_esriScene; +} + +Esri::ArcGISRuntime::MapGraphicsView *MapImpl::getEsriMapGraphicsView() const +{ + return d->m_mapGraphicsView; +} + +Esri::ArcGISRuntime::SceneGraphicsView *MapImpl::getEsriSceneGraphicsView() const +{ + return d->m_sceneGraphicsView; +} + +QGraphicsView *MapImpl::getEsriQGraphicsView() const +{ + if (d->m_mapGraphicsView) + return d->m_mapGraphicsView; + if (d->m_sceneGraphicsView) + return d->m_sceneGraphicsView; + return nullptr; +} + +Esri::ArcGISRuntime::GeoView *MapImpl::getEsriGeoView() const +{ + if (d->m_mapGraphicsView && d->m_sceneGraphicsView) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " Upps, 2D(Map) and also 3D (Scene) -> error!!! "; + FATAL_LOG(m_logger) << LOGFUNCTION << " Upps, 2D(Map) and also 3D (Scene) -> error!!! "; + FATAL_LOG(m_logger) << LOGFUNCTION << " Upps, 2D(Map) and also 3D (Scene) -> error!!! "; + } + if (d->m_mapGraphicsView) + return d->m_mapGraphicsView; + if (d->m_sceneGraphicsView) + return d->m_sceneGraphicsView; + return nullptr; +} + +QString MapImpl::toDumpString() const +{ + std::stringstream myLine; + Esri::ArcGISRuntime::Map *esriMap = d->m_esriMap; + Esri::ArcGISRuntime::MapGraphicsView *esriGraphicsMapView = d->m_mapGraphicsView; + Esri::ArcGISRuntime::Scene *esriScene = d->m_esriScene; + // Esri::ArcGISRuntime::SceneGraphicsView *esriGraphicsSceneView = d->m_sceneGraphicsView; + + Esri::ArcGISRuntime::GeoView *geoView = this->getEsriGeoView(); + if (! geoView) + return QString(); + + int graphicsOverlayCount = -1; + if (geoView->graphicsOverlays()) + graphicsOverlayCount = geoView->graphicsOverlays()->size(); + + int basemapCount = -1; + if (this->getEsriBasemap() && this->getEsriBasemap()->baseLayers()) + basemapCount = this->getEsriBasemap()->baseLayers()->size(); + + QRectF curExtent = this->extent(); + QRectF fullExtent; // = this->fullExtent(); + + // Esri::ArcGISRuntime::Envelope env = esriMap->fullExtent(); + // QPointF fullExtentMin(env.xMin(), env.yMin()); + // QPointF fullExtentMax(env.xMax(), env.yMax()); + // QPointF fullExtentCenter(env.center().x(), env.center().y()); + + + // header + { + myLine << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" << std::endl; + + // myLine << " animationMode \t\t= " << static_cast(esriMap->animationMode()) << std::endl; + // myLine << " animationDuration \t= " << esriMap->animationDuration() << std::endl; + + myLine << " current wkid \t\t= " << this->getEsriSpatialReference().wkid() << std::endl; + myLine << " wkText \t='" << this->getEsriSpatialReference().wkText() << "'" << std::endl; + myLine << " toJson='" << this->getEsriSpatialReference().toJson() << "'" << std::endl; + + if (esriMap) + myLine << " Map::isAutoFetchLegendInfos='" << esriMap->isAutoFetchLegendInfos() << std::endl; + if (esriScene) + myLine << " Scene::isAutoFetchLegendInfos='" << esriScene->isAutoFetchLegendInfos() << std::endl; + + myLine << " dict isLoaded \t\t= " << this->symbolDictionary().isLoaded() << std::endl; + myLine << " dict extraSymbolCount \t= " << this->symbolDictionary().etsSymbolCount() << std::endl; + + myLine << " graphicsOverlayCount \t= " << graphicsOverlayCount << std::endl; + myLine << " basemapCount \t\t= " << basemapCount << std::endl; + + myLine << " curExtent \t\t= " << "center=" << curExtent.center() + << " ; w=" << curExtent.width() << " h=" << curExtent.height() << std::endl; + myLine << " fullExtent \t\t= " << "center=" << fullExtent.center() + << " ; w=" << fullExtent.width() << " h=" << fullExtent.height() << std::endl; + + // myLine << " (fullExtent[esri] \t= " << "min=" << fullExtentMin << " ; max=" << fullExtentMax << ")" << std::endl; + // myLine << " (fullExtent[esri] \t= " << "center=" << fullExtentCenter + // << " ; w=" << env.width() << " h=" << env.height() << ")" << std::endl; + + if (esriMap) + { + myLine << " minScale \t\t\t= " << esriMap->minScale() << std::endl; + myLine << " maxScale \t\t\t= " << esriMap->maxScale() << std::endl; + } + myLine << " scale(SitMap) \t\t= " << this->scale() << std::endl; + + myLine << " rotation(SitMap) \t\t= " << this->rotation() << " [deg]" << std::endl; + if (esriMap) + myLine << " MapView::rotation \t\t= " << esriGraphicsMapView->mapRotation() << " [deg]" << std::endl; + if (esriScene) + myLine << " SceneView::rotation \t\t= " << "-" << std::endl; + + + myLine << " viewpoint \t\t\t= " << this->viewpoint2D() << std::endl; + if (d->m_mapGraphicsView) + myLine << " MapView::viewpoint \t\t= " << d->m_mapGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale) << std::endl; + if (d->m_sceneGraphicsView) + { + myLine << " Camera(SitMap) \t\t= " << this->camera() << std::endl; + myLine << " SceneView::viewpoint \t\t= " << d->m_sceneGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale) << std::endl; + myLine << " SceneView::camera \t\t= " << d->m_sceneGraphicsView->currentViewpointCamera() << std::endl; + + myLine << "\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" << std::endl; + myLine << " OperationalLayers:"; + + Esri::ArcGISRuntime::LayerListModel *llm = nullptr; + if (esriScene) + llm = esriScene->operationalLayers(); + if (llm) + { + myLine << " - count=" << llm->size(); + myLine << "\nLayer \tVisi \tOpac \tSts \t\tType \t\t\t\tName\n"; + for (int i = 0; i < llm->size(); ++i) + { + Esri::ArcGISRuntime::Layer *layer = llm->at(i); + if (!layer) + continue; + myLine << " " << i + << "\t" << layer->isVisible() + << "\t" << layer->opacity() + << "\t" << layer->loadStatus() + << "\t" << layer->layerType() + << "\t" << layer->name() + << " (minScale=" << layer->minScale() << " ; maxScale=" << layer->maxScale() << ")" + << " (attribution='" << layer->attribution() << "'')" + << "\t description=" << layer->description() + << "\t isShowInLegend=" << layer->isShowInLegend(); + + { + Esri::ArcGISRuntime::ArcGISSceneLayer *sceneLayer = + dynamic_cast(layer); + + if (sceneLayer) + myLine << "\n\t\t\t\t\t url:" << sceneLayer->url().toString(); + } + + myLine << std::endl; + } // for + + } + + + } + } + + + myLine << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" << std::endl; + AdvancedSymbology::SymbolDictionary &dict = this->symbolDictionary(); + AdvancedSymbology::SymbolDictionaryImpl &dictImpl = static_cast(dict); + + myLine << " symbologyFieldNames: " << dictImpl.symbologyFieldNamesInternal().join(QLatin1String(" ; ")) << std::endl; + myLine << " textFieldNames: " << dictImpl.textFieldNamesInternal().join(QLatin1String(" ; ")) << std::endl; + + myLine << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" << std::endl; + if (basemapCount > 0) + { + myLine << "basemapCount=" << basemapCount << std::endl; + + myLine << "\nLayer \tVisi \tOpac \tSts \t\tType \t\t\t\tName" << std::endl; + + const Esri::ArcGISRuntime::LayerListModel *model = this->getEsriBasemap()->baseLayers(); + for (int i = 0; i < model->size(); ++i) + { + /*const*/ Esri::ArcGISRuntime::Layer *layer = model->at(i); + + myLine << " " << i + << "\t" << layer->isVisible() + << "\t" << layer->opacity() + << "\t" << layer->loadStatus() + << "\t" << layer->layerType() + << "\t" << layer->name() + << " (minScale=" << layer->minScale() << " ; maxScale=" << layer->maxScale() << ")" + << " (attribution='" << layer->attribution() << "'')" + + << "\t description=" << layer->description() + << "\t isShowInLegend=" << layer->isShowInLegend(); + + { + Esri::ArcGISRuntime::ImageAdjustmentLayer *iaLayer = + dynamic_cast(layer); + + if (iaLayer) + { + myLine << "\t b:" << iaLayer->brightness() + << "\t g:" << iaLayer->gamma() + << "\t c:" << iaLayer->contrast(); + } + } + + myLine << std::endl; + + QString url(QLatin1String("no url availble")); + Esri::ArcGISRuntime::ArcGISTiledLayer *tiledLayer = dynamic_cast(layer); + if (tiledLayer) + url = tiledLayer->mapServiceInfo().url().toString(); + myLine << "url= '" << url << "'"; + + + { + Esri::ArcGISRuntime::LegendInfoListModel *lilm = layer->legendInfos(); + int lSize = -1; + if (lilm) + lSize = lilm->size(); + + myLine << std::endl; + myLine << "LegendInfoListModelCount=" << lSize << std::endl; + + for (int j = 0; j < lSize; ++j) + { + Esri::ArcGISRuntime::LegendInfo *li = lilm->at(j); + + myLine << " " << j + << "\t" << li->name() + << "\t" << (int)li->symbol()->symbolType(); + } + } + + myLine << std::endl; + } // for + } // if (basemapCount > 0) + + // + // GraphicsOverlays + // + + myLine << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + int objCounter = 0; + QVector> gOverlayCounter; + if (graphicsOverlayCount) + { + myLine << std::endl; + myLine << "graphicsOverlayCount=" << graphicsOverlayCount << std::endl; + myLine << "i \tVisi \tOpac \tGRM \t\t\tSP \t\t\t#Obj \tName" << std::endl; + + Esri::ArcGISRuntime::GraphicsOverlayListModel *gOverlayListModel = getEsriGeoView()->graphicsOverlays(); + const int gOverlayCount = gOverlayListModel->size(); + for (int esriIdx=0; esriIdxat(esriIdx); + if (!gOverlay) + continue; + + const QString &name = gOverlay->overlayId(); // since 100.2.0 + if (gOverlay) + { + int i = 0; + + int objCountPerLayer = -1; + if (gOverlay->graphics()) + objCountPerLayer = gOverlay->graphics()->size(); + + objCounter += objCountPerLayer; + gOverlayCounter.append(QPair(name, objCountPerLayer)); + + int esriIdx = gOverlayListModel ? gOverlayListModel->indexOf(gOverlay) : -1; + + Esri::ArcGISRuntime::LayerSceneProperties sceneProperties = gOverlay->sceneProperties(); + + std::string typeName; + Esri::ArcGISRuntime::Renderer *renderer = gOverlay->renderer(); + if (renderer) + typeName = typeid(*renderer).name(); + + QString objName; + if (renderer) + objName = renderer->objectName(); + + myLine << " " << i++ + << "\t" << gOverlay->isVisible() + << "\t" << gOverlay->opacity() + << "\t" << gOverlay->renderingMode() << " " + << "\t" << sceneProperties.surfacePlacement() << " " + << "\t" << objCountPerLayer + << "\t (" << esriIdx << ")" + << "\t" << name + << " (minScale=" << gOverlay->minScale() << " ; maxScale=" << gOverlay->maxScale() << ")" + << " (Renderer=" << gOverlay->renderer() + << ", type_info=" << typeName + << ", objName=" << objName + << ")"; + + + Esri::ArcGISRuntime::GraphicListModel *gListModel = gOverlay->graphics(); + if (gListModel && !gListModel->isEmpty()) + { + myLine << std::endl; + myLine << "objIdx" << "\tUMapId \tVisi \tPick \tSel \tzIdx \tobjectName" << std::endl; + + for (int idx = 0; idx < gListModel->size(); ++idx) + { + Esri::ArcGISRuntime::Graphic *graphic = gListModel->at(idx); + if (graphic) + { + UniqueMapId uniqueMapId = INVALID_MAP_ID; + bool isPickable = false; + Esri::ArcGISRuntime::AttributeListModel *attLisModel = graphic->attributes(); + if (attLisModel) + { + bool ok; + uniqueMapId = attLisModel->attributeValue(QLatin1String(SituationMap::UniqueMapIdAttribute)).toUInt(&ok); + isPickable = attLisModel->attributeValue(QLatin1String(SituationMap::IsPickableAttribute)).toBool(); + } + + myLine << " " << idx + << "\t" << uniqueMapId + << "\t" << graphic->isVisible() + << "\t" << isPickable + << "\t" << graphic->isSelected() + << "\t" << graphic->zIndex() + << "\t objName " << graphic->objectName() + << "\t geom " << graphic->geometry().toJson().left(100) + << std::endl; + } + + } // for + + + } + + + myLine << std::endl; + + } + } // while + + } // if (graphicsOverlayCount) + + myLine << "PartyIdToColorFunctor: " << std::endl; + this->partyIdToColorFunctor().toString(myLine); + myLine << std::endl; + + myLine << "Obj per GraphicalOverlay:" << std::endl; + for (const QPair &item : gOverlayCounter) + myLine << " GraphicalOverlay: " << item.second << "\t" << " obj on '" << item.first << "'" << std::endl; + + myLine << "-------" << std::endl; + myLine << "#Total: " << objCounter << std::endl; + + myLine << std::endl; + + myLine << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" << std::endl; + myLine << "count() of objectFactory/Container: " << d->m_objectFactory->count() << std::endl; + myLine << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" << std::endl; + + + + myLine << std::endl; + return QString::fromStdString(myLine.str()); +} + + +bool MapImpl::isLoaded() const +{ + if (d->m_esriMap) + return d->m_esriMap->loadStatus() == Esri::ArcGISRuntime::LoadStatus::Loaded; + if (d->m_esriScene) + return d->m_esriScene->loadStatus() == Esri::ArcGISRuntime::LoadStatus::Loaded; + return false; +} + +QGraphicsView *MapImpl::getQGraphicsView() const +{ + if (d->m_mapGraphicsView) + return d->m_mapGraphicsView; + if (d->m_sceneGraphicsView) + return d->m_sceneGraphicsView; + return nullptr; +} + +double MapImpl::pixelSizeMeter() +{ + if (d->m_pixelsize <= 0) + { + if (d->m_mapGraphicsView) + { + Esri::ArcGISRuntime::Point p1_mapCoord = d->m_mapGraphicsView->screenToLocation(0, 0); + Esri::ArcGISRuntime::Point p2_mapCoord = d->m_mapGraphicsView->screenToLocation(0, 1); + + if (d->m_coordTranslationFunctor) + { + // const QPointF &dist = d->m_coordTranslationFunctor->mapToGeographic(p1_mapCoord) - d->m_coordTranslationFunctor->mapToGeographic(p2_mapCoord); + const QPointF &dist = QPointF(p1_mapCoord.x(), p1_mapCoord.y()) - QPointF(p2_mapCoord.x(), p2_mapCoord.y()); + + d->m_pixelsize = sqrt(dist.x() * dist.x() + dist.y() * dist.y()); + } + else + { + d->m_pixelsize = Esri::ArcGISRuntime::GeometryEngine::distance(d->m_mapGraphicsView->screenToLocation(0, 0), + d->m_mapGraphicsView->screenToLocation(0, 1)); + } + + // 2019-11-13, Norbert Thoden + // Sometimes (most of the time when starting a new map) the drawing of a + // spline results in a simple line (vertexCount=2 assumed) + // Maybe m_pixelsize is NaN? To avoid this: + if (std::isnan(d->m_pixelsize)) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " d->m_pixelsize is Nan, set to default"; +#ifdef LTS_DEBUG + QMessageBox::critical(nullptr, QLatin1String("pixelSizeMeter"), QLatin1String("pixelSizeMeter is NaN - abort.")); + abort(); +#endif // LTS_DEBUG + d->m_pixelsize = 1.234567; + } + + if (d->m_sceneGraphicsView) + d->m_pixelsize = 1.2345; + } + } + return d->m_pixelsize; +} + +double MapImpl::pixelSizeGeographic() +{ + if (d->m_mapGraphicsView) + { + Esri::ArcGISRuntime::Point p1_mapCoord = d->m_mapGraphicsView->screenToLocation(0, 0); + Esri::ArcGISRuntime::Point p2_mapCoord = d->m_mapGraphicsView->screenToLocation(0, 1); + + if (d->m_coordTranslationFunctor) + { + const QPointF &dist = d->m_coordTranslationFunctor->mapToGeographic(p1_mapCoord) - d->m_coordTranslationFunctor->mapToGeographic(p2_mapCoord); + return sqrt(dist.x() * dist.x() + dist.y() * dist.y()); + } + } + return -1; +} + +//void MapImpl::moveEvent(QMoveEvent *e) +//{ +// qWarning() << LOGFUNCTION << "moveEvent" << e->type(); +// QWidget::moveEvent(e); +//} + +//bool MapImpl::event(QEvent *e) +//{ +// qWarning() << LOGFUNCTION << "EVENT" << e->type(); +// if (e->type() == QEvent::Move) +// qWarning() << "MOVEE"; + +// return QWidget::event(e); +//} + + +//Esri::ArcGISRuntime::Layer* MapImpl::getEsriLayerByName(const QString &layerName) +//{ +// Esri::ArcGISRuntime::Layer* foundLayer = nullptr; + +// Esri::ArcGISRuntime::Map* esriMap = this->getEsriMap(); +// QList layerList = esriMap->layers(); + +// for ( Esri::ArcGISRuntime::Layer* layer, layerList) { +// if (layer) +// { +// if ( layer->name() == layerName) +// { +// foundLayer = layer; +// break; +// } +// } +// } + +// return foundLayer; +//} + + +QPointF MapImpl::mapCoordinateToViewPortWindowPoint(const QPointF &mapPos) const +{ + // don NOT delegate to mapCoordinateToViewPortWindowPoint3D(Point3D::fromQPointF(mapPos)); + // In that case, the z-value is set to zero. That is different to this method. + // Here the z-value is not taken into account: + + if (!isReady()) + return QPointF(); + + if (d->m_mapGraphicsView) + { + Esri::ArcGISRuntime::Point esriCoordinate(mapPos.x(), mapPos.y(), d->m_mapGraphicsView->spatialReference()); + const QPointF &screenCoordinate = d->m_mapGraphicsView->locationToScreen(esriCoordinate); + return screenCoordinate; + } + else if (d->m_sceneGraphicsView) + { + Esri::ArcGISRuntime::Point esriCoordinate(mapPos.x(), mapPos.y(), d->m_sceneGraphicsView->spatialReference()); + const Esri::ArcGISRuntime::LocationToScreenResult &locToSrnRes = d->m_sceneGraphicsView->locationToScreen(esriCoordinate); + switch (locToSrnRes.visibility()) + { + case Esri::ArcGISRuntime::SceneLocationVisibility::Visible: + case Esri::ArcGISRuntime::SceneLocationVisibility::HiddenByBaseSurface: + case Esri::ArcGISRuntime::SceneLocationVisibility::HiddenByEarth: + case Esri::ArcGISRuntime::SceneLocationVisibility::HiddenByElevation: + { + const QPointF &screenCoordinate = locToSrnRes.screenPoint(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " screenCoordinate=" << screenCoordinate << " locToSrnRes.visibility()=" << locToSrnRes.visibility(); + return screenCoordinate; + } + break; + case Esri::ArcGISRuntime::SceneLocationVisibility::NotOnScreen: + DEBUG_LOG(m_logger) << LOGFUNCTION << " locToSrnRes.visibility()=" << locToSrnRes.visibility() + << ", locToSrnRes.screenPoint()=" << locToSrnRes.screenPoint(); + + } // switch + } + return QPointF(); +} + +QPointF MapImpl::mapCoordinateToViewPortWindowPoint3D(const Point3D &mapPos) const +{ + if (!isReady()) + return QPointF(); + + if (d->m_mapGraphicsView) + { + Esri::ArcGISRuntime::Point esriCoordinate(mapPos.x(), mapPos.y(), mapPos.z(), d->m_mapGraphicsView->spatialReference()); + const QPointF &screenCoordinate = d->m_mapGraphicsView->locationToScreen(esriCoordinate); + return screenCoordinate; + } + else if (d->m_sceneGraphicsView) + { + Esri::ArcGISRuntime::Point esriCoordinate(mapPos.x(), mapPos.y(), mapPos.z(), d->m_sceneGraphicsView->spatialReference()); + const Esri::ArcGISRuntime::LocationToScreenResult &locToSrnRes = d->m_sceneGraphicsView->locationToScreen(esriCoordinate); + switch (locToSrnRes.visibility()) + { + case Esri::ArcGISRuntime::SceneLocationVisibility::Visible: + case Esri::ArcGISRuntime::SceneLocationVisibility::HiddenByBaseSurface: + case Esri::ArcGISRuntime::SceneLocationVisibility::HiddenByEarth: + case Esri::ArcGISRuntime::SceneLocationVisibility::HiddenByElevation: + { + const QPointF &screenCoordinate = locToSrnRes.screenPoint(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " screenCoordinate=" << screenCoordinate << " locToSrnRes.visibility()=" << locToSrnRes.visibility(); + return screenCoordinate; + } + break; + case Esri::ArcGISRuntime::SceneLocationVisibility::NotOnScreen: + DEBUG_LOG(m_logger) << LOGFUNCTION << " locToSrnRes.visibility()=" << locToSrnRes.visibility() + << ", locToSrnRes.screenPoint()=" << locToSrnRes.screenPoint(); + break; + } // switch + } + return QPointF(); +} + + +QPointF MapImpl::viewPortWindowPointToMapCoordinate(const QPointF &pixelCoord) const +{ + if (!isReady()) + return QPointF(); + + if (d->m_mapGraphicsView) + { + const Esri::ArcGISRuntime::Point &mapCoordinate = d->m_mapGraphicsView->screenToLocation(pixelCoord.x(), pixelCoord.y()); + return QPointF(mapCoordinate.x(), mapCoordinate.y()); + } + else if (d->m_sceneGraphicsView) + { + const Esri::ArcGISRuntime::Point &baseCoordinate = d->m_sceneGraphicsView->screenToBaseSurface(pixelCoord.x(), pixelCoord.y()); + return QPointF(baseCoordinate.x(), baseCoordinate.y()); + } + return QPointF(); +} + +Point3D MapImpl::viewPortWindowPointToMapCoordinate3D(const QPointF &pixelCoord) const +{ + if (!isReady()) + return Point3D(); + + if (d->m_mapGraphicsView) + { + const Esri::ArcGISRuntime::Point &mapCoordinate = d->m_mapGraphicsView->screenToLocation(pixelCoord.x(), pixelCoord.y()); + return Point3D(mapCoordinate.x(), mapCoordinate.y(), mapCoordinate.z()); + } + else if (d->m_sceneGraphicsView) + { + const Esri::ArcGISRuntime::Point &baseCoordinate = d->m_sceneGraphicsView->screenToBaseSurface(pixelCoord.x(), pixelCoord.y()); + return Point3D(baseCoordinate.x(), baseCoordinate.y(), baseCoordinate.z()); + } + return Point3D(); +} + +QPointF MapImpl::geoCoordinateToViewPortWindowPoint(const QPointF &geoPos) const +{ + if (!isReady()) + return QPointF(); + const SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (!mapCtFunctor) + return QPointF(); + + return mapCoordinateToViewPortWindowPoint(mapCtFunctor->geographicToMap(geoPos)); +} + +QPointF MapImpl::geoCoordinateToViewPortWindowPoint3D(const Point3D &geoPos) const +{ + if (!isReady()) + return QPointF(); + const SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (!mapCtFunctor) + return QPointF(); + + Point3D mapPos = mapCtFunctor->geographicToMap(geoPos); + + return mapCoordinateToViewPortWindowPoint3D(mapPos); +} + +QPointF MapImpl::viewPortWindowPointToGeoCoordinate(const QPointF &pixelCoord) const +{ + if (!isReady()) + return QPointF(); + const SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (!mapCtFunctor) + return QPointF(); + const QPointF mapCoordinate = viewPortWindowPointToMapCoordinate(pixelCoord); + return mapCtFunctor->mapToGeographic(mapCoordinate); +} + +Point3D MapImpl::viewPortWindowPointToGeoCoordinate3D(const QPointF &pixelCoord) const +{ + if (!isReady()) + return Point3D(); + const SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (!mapCtFunctor) + return Point3D(); + const Point3D mapCoordinate = viewPortWindowPointToMapCoordinate3D(pixelCoord); + return mapCtFunctor->mapToGeographic(mapCoordinate); +} + + +QString MapImpl::geoToMgrsString(const QPointF &geoPos) const +{ + SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (!mapCtFunctor) return QString(); + + return mapToMgrsString(mapCtFunctor->geographicToMap(geoPos)); +} + +QString MapImpl::mapToMgrsString(const QPointF &mapPos) const +{ + // DEBUG_LOG(m_logger) << LOGFUNCTION << "mapPos=" << mapPos; + + const CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + + if (! ctFunctor) + return QString(); + + const QPointF &pointGeo = ctFunctor->mapToGeographic(mapPos); + + // DEBUG_LOG(m_logger) << LOGFUNCTION << " mapPos= " << mapPos << " pointGeo= " << pointGeo; + + return LtsCoordTrans::mgrsFromGeographic(pointGeo.x(), pointGeo.y()); +} + + +bool MapImpl::eventFilter(QObject *obj, QEvent *e) +{ + if (!e) + return false; + + bool filterDragAndDrop = static_cast(d->m_mapGraphicsView) != obj; + + if (filterDragAndDrop) + { + switch (e->type()) + { + case QEvent::DragEnter: + case QEvent::DragMove: + { + INFO_LOG(m_logger) << LOGFUNCTION << "Drag enter event reached."; + + const QMimeData *mimeData = ((QDropEvent *)e)->mimeData(); + + if (mimeData) + { + for (auto &mgrPtr : std::as_const(d->m_dropEventManagerList)) + { + if (mimeData->hasFormat(mgrPtr->mimeFormatString())) + e->accept(); + } + } + return true; + } break; + + case QEvent::Drop: + { + INFO_LOG(m_logger) << LOGFUNCTION << "Drop event reached."; + + const QMimeData *mimeData = ((QDropEvent *)e)->mimeData(); + + if (mimeData) + { + for (auto mgrPtr : std::as_const(d->m_dropEventManagerList)) + { + if (mimeData->hasFormat(mgrPtr->mimeFormatString())) + mgrPtr->manageDrop(((QDropEvent *)e)->pos(), mimeData); + } + e->accept(); + } + return true; + } break; + default : break; + } // switch + } + + if (QEvent::KeyPress == e->type()) + { + QKeyEvent *keyEvent = static_cast(e); + int key = keyEvent->key(); + Qt::KeyboardModifiers mod = keyEvent->modifiers(); + + if ((Qt::Key_J == key) && (mod.testFlag(Qt::KeyboardModifier::ControlModifier))) + { + INFO_LOG(m_logger) << LOGFUNCTION << " rethink jumpPointRequest Modifier"; + + onJumpPointRequest(static_cast(obj)->mapFromGlobal(QCursor::pos())); + + e->accept(); + return true; + } + } + return false; +} + +void MapImpl::onKeyPressEvent(const QKeyEvent &keyEvent) +{ + const int key = keyEvent.key(); + if (Qt::Key_Escape == key) + { + emit escapePressed(); + } +} + +void MapImpl::installDropEventManager(DropEventManager *demgr) +{ + if (demgr == nullptr) return; + + RefPointer refDemgr(demgr); + if (d->m_dropEventManagerList.contains(refDemgr)) return; + d->m_dropEventManagerList += refDemgr; +} // MapImpl::installDropEventManager + + +void MapImpl::deinstallDropEventManager(DropEventManager *demgr) +{ + if (demgr == nullptr) return; + RefPointer refDemgr(demgr); + if (d->m_dropEventManagerList.contains(refDemgr)) + d->m_dropEventManagerList.removeAll(refDemgr); +} + +void MapImpl::onHandleMouseMove(int windowPosX, int windowPosY) +{ + if (d->m_destroying) + return; + + if (!d->m_mapReadyFlag) + return; + + if (windowPosX < 0 || windowPosY < 0) + return; + + // 1 + emit mouseWindowPositionChanged(windowPosX, windowPosY); + + if (!isReady()) + { + return; + } + + if (mode() == Mode::Mode_2D) + { + // Mode_2D: + const QPointF pixelCoord(windowPosX, windowPosY); + const Point3D &mapCoordinate = viewPortWindowPointToMapCoordinate3D(pixelCoord); + + // 2 + emit mouseMapPositionChanged(mapCoordinate, true); + + SituationMap::CoordTranslationFunctor *mapCtFunctor = coordTranslationFunctor(); + if (mapCtFunctor) + { + if (isSignalConnected(QMetaMethod::fromSignal(&Map::mouseGeoPositionChanged))) + { + // 3 + emit mouseGeoPositionChanged(mapCtFunctor->mapToGeographic(mapCoordinate), true); + } + } + } + else + { + // Mode_3D: + // 'screenToLocation' is expensive. Use screenToLocationHelper: + if (d->m_screenToLocationHelper) + d->m_screenToLocationHelper->screenToLocation(windowPosX, windowPosY); + } +} // MapImpl::onHandleMouseMove + + +bool MapImpl::isReady() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, m_mapReadyFlag=" << d->m_mapReadyFlag; + return d->m_mapReadyFlag; +} + +DrawStatus MapImpl::drawStatus() const +{ + switch (this->mode()) + { + case Mode::Mode_2D: + return DrawStatus::Completed; + break; + case Mode::Mode_3D: + { + if (d->m_sceneGraphicsView) + return static_cast(d->m_sceneGraphicsView->drawStatus()); // save due to static_assert + } + break; + default: + // empty + break; + } // switch + return DrawStatus::Completed; +} + +int MapImpl::wkid() const +{ + return this->getEsriSpatialReference().wkid(); +} + +SituationMap::NavigationConstraint MapImpl::navigationConstraint() const +{ +#ifdef HAS_BELOW_GROUND + Esri::ArcGISRuntime::Surface* baseSurface = nullptr; + if (!d->m_esriScene || !(baseSurface = d->m_esriScene->baseSurface())) + return SituationMap::NavigationConstraint::Undefined; + return static_cast(baseSurface->navigationConstraint()); +#else + return SituationMap::NavigationConstraint::Undefined; +#endif // HAS_BELOW_GROUND +} + +bool MapImpl::setNavigationConstraint(SituationMap::NavigationConstraint navigationConstraint) +{ + if (navigationConstraint == SituationMap::NavigationConstraint::Undefined) + return false; +#ifdef HAS_BELOW_GROUND + Esri::ArcGISRuntime::Surface* baseSurface = nullptr; + if (!d->m_esriScene || !(baseSurface = d->m_esriScene->baseSurface())) + return false; + const SituationMap::NavigationConstraint currentNavConstraint = this->navigationConstraint(); + if (currentNavConstraint != navigationConstraint) + { + baseSurface->setNavigationConstraint(static_cast(navigationConstraint)); // save due to static_assert + emit navigationConstraintChanged(navigationConstraint); + } + return true; +#else + return true; // handle as successful operation +#endif // HAS_BELOW_GROUND +} + +float MapImpl::baseSurfaceOpacity() const +{ +#ifdef HAS_BELOW_GROUND + Esri::ArcGISRuntime::Surface* baseSurface = nullptr; + if (!d->m_esriScene || !(baseSurface = d->m_esriScene->baseSurface())) + return 1.0; // opac, no transparency + return baseSurface->opacity(); +#else + return 1.0; // opac, no transparency +#endif // HAS_BELOW_GROUND +} + +bool MapImpl::setBaseSurfaceOpacity(float surfaceOpacity) +{ +#ifdef HAS_BELOW_GROUND + Esri::ArcGISRuntime::Surface* baseSurface = nullptr; + if (!d->m_esriScene || !(baseSurface = d->m_esriScene->baseSurface())) + return false; + if (!qFuzzyCompare(baseSurface->opacity(), surfaceOpacity)) + { + baseSurface->setOpacity(surfaceOpacity); + emit baseSurfaceOpacityChanged(baseSurface->opacity()); + } + return true; +#else + Q_UNUSED(surfaceOpacity); + return true; // handle as successful operation +#endif // HAS_BELOW_GROUND +} + +OverviewMapHandler &MapImpl::overviewMapHandler() const +{ + if (! d->m_overviewMapHandler) + { + MapImpl *THIS = const_cast(this); + d->m_overviewMapHandler = new OverviewMapHandlerImpl(THIS); + } + return *d->m_overviewMapHandler; +} + + +OperatorMapSettings MapImpl::operatorMapSettings() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" ; + + OperatorMapSettings oms; + + if (!isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " map not ready"; + return oms; + } + + + oms.mode = this->mode(); + + // + // 1. viewpoint/camera : + // + switch (oms.mode) + { + case Mode::Mode_2D: + oms.viewpoint = this->viewpoint2D(); + break; + case Mode::Mode_3D: + oms.camera = this->camera(); +// oms.viewpoint.setScale(this->scale()); // additionally save current scale (which is not in camera) + oms.viewpoint = this->viewpoint2D(); + break; + default: + // empty + break; + } + // : viewpoint/camera + + // + // 1a. below ground : + // + oms.navigationConstraint = this->navigationConstraint(); + oms.baseSurfaceOpacity = this->baseSurfaceOpacity(); + // : 1a. below ground + + // + // 2. orderedLayerList : + // + const LayerFactory &layerFactory = this->layerFactory(); + + const QList &layernames = layerFactory.layernames(); + for (const auto &layername : layernames) + { + QPair pair; + pair.first = layername; + pair.second = layerFactory.layerParameter(layername); + + oms.orderedLayers.append(pair); + } + // : orderedLayerList + + // + // 3. orderedOverlayList : + // + const OverlayHandler &overlayHndl = this->overlayHandler(); + oms.orderedOverlays = overlayHndl.overlays(); + // : orderedOverlayList + + // + // 4. some visiblities + // + oms.mapWidgetSettings.gridVisibility = this->grid().isVisible(); + oms.mapWidgetSettings.positionStringVisibility = this->isPositionStringVisible(); + oms.mapWidgetSettings.exerciseTimeStringVisibility = this->isExerciseTimeStringVisible(); + oms.mapWidgetSettings.timeStringVisibility = this->isTimeStringVisible(); + oms.mapWidgetSettings.compassVisibility = this->isCompassVisible(); + oms.mapWidgetSettings.overviewMapVisibility = this->overviewMapHandler().builtInOverviewMapWidget() != nullptr; + + oms.symbolScale = this->symbolScale(); + // oms.symbolOpacity = this->symbol + + + // + // 5. JumpPointWidget : + // + if (d->m_jumpPointWidget) + { + const QVector &jumpPoints = d->m_jumpPointWidget->jumpPoints(); + + JumpPointData jpd; + for (const JPData &jpKey : jumpPoints) + { + jpd.key = jpKey.m_name; + jpd.originGeo = jpKey.m_originGeo; + jpd.mapScale = jpKey.m_mapScale; + + oms.jumpPoints.append(jpd); + } + } + // : JumpPointWidget + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return oms; +} + +bool MapImpl::restoreOperatorMapSettings(const OperatorMapSettings &oms) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!isReady()) + { + INFO_LOG(m_logger) << LOGFUNCTION << " map not ready"; + return false; + } + + if (!isValid(oms.mode)) + { + INFO_LOG(m_logger) << LOGFUNCTION << " mode no valid"; + return false; + } + + bool success = true; + + const bool switchingBetweenModes = + isValid(this->mode()) && + isValid(oms.mode) && + (this->mode() != oms.mode); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " switchingBetweenModes = " /*<< std::boolalpha*/ << switchingBetweenModes + << " (this->mode()=" << this->mode() + << " oms.mode=" << oms.mode << ")"; + + // conflict with FilterManagement::SymbolAppearanceSettings::Settings::symbolScale + // so skip oms.symbolScale + // this->setSymbolScale(oms.symbolScale); + + // + // 1. viewpoint/camera : + // + + // Possible cases: + // F1 - 2D -> 2D : setViewpoint2D + // F2 - 3D -> 3D : setCamera + // F3 - 2D -> 3D : setViewpoint2D + // F4 - 3D -> 2D : setViewpoint2D ( finalViewpoint ) + + switch (oms.mode) + { + case Mode::Mode_2D: + this->setViewpoint2D(oms.viewpoint); // F1, F3 + break; + case Mode::Mode_3D: + if (this->mode() == Mode::Mode_3D && !oms.camera.isNull()) + this->setCamera(oms.camera); // F2 + else + this->setViewpoint2D(oms.viewpoint); // F4 + break; + default: + // empty + break; + } // switch + + // + // below ground : + // + this->setNavigationConstraint(oms.navigationConstraint); + this->setBaseSurfaceOpacity(oms.baseSurfaceOpacity); + // : below ground + + // + // process orderedOVERLAYList *before* orderedLAYERList + // + // 3. orderedOverlayList : + // + OverlayHandler &overlayHndl = this->overlayHandler(); + for (auto ovlKey : oms.orderedOverlays) + { + QByteArray overlayContent; + + QString displaynameEmpty; // empty -> logicalLayername will we shown. This name will be overwritten if overlay is loaded and updated. + + // GUEZE-811 3D Lage Filter werde nicht korrekt abgebaut... wenn 2D-Snap + // mit SnapshotPlan auf 3D-Topic folgt... + // Szenario: 2D-Snapshuss wird wiedergegeben (App Snapshot|Script) + // und SitMap muss dafür von 3D auf 2D wechseln. + // Dabei werden die Overlays via MapOperatorSettings zunächst vorbereitet. + // Dabei wurde aber in MapImpl::restoreOperatorMapSettings der SNAPSHOT-Overlay + // als herkömmlicher Overlay vorbereitet. + // Beim nachfolgendem OverlayHandlerImpl::setOverlay usw. ergaben sich Fehler + // wg. LayerType::SnapshotOverlay vs. LayerType::Overlay. + // Mit dem FixPart1 wird in restoreOperatorMapSettings der snapShotOverlayId == -4711 + // als isSnapshot==true eintragen. + // Mit FixPart2 bei SnapshotDialog::showSnapshotInternal ein + // setMapReadyCallback genutzt, weil die SitMap noch nicht mapReady ist. + // Mit FixPart3 bei InterfaceImpl::snapshotAdjustSitMap ebenfalls ein + // setMapReadyCallback genutzt, weil die SitMap noch nicht mapReady ist. + + // Zu FixPart1 / GUEZE-1011 + // Mit GUEZE-1011 'Absturz Planeditor' hat sich ein Problem gezeigt: + // Snap aufrufen, PlanEditor (auf dem Snap starten) ein TextObj anlegen... PlanEditor beenden -> Absturz + // Tritt auf, wenn -4711 als isSnapshot = true eingetragen wird. Daher lassen wir dass... + // const int snapShotOverlayId = -4711; // TODO: ??? + + // const bool isSnapshot = false; //(ovlKey == snapShotOverlayId); + + // GUEZE-1723 (swp_2023) + // Der SnapshotOverlay wurde (teilweise, ?3D -> 2D?) bei ersten Mal nicht geladen. + // Daher isSnapshot wieder auf true, + // Dafür den 'Absturz Planeditor' behoben (dtor geomodel at PlanEditor::onSignalClose) + // ---> Nun wird also der der SnapshotOverlay auch als Layer mit isSnapshot==true erstellt, + // korrekt in der layerOrder eingetragen. Displayname wird mit dem Laden "nachgeschoben. + const int snapShotOverlayId = -4711; + const bool isSnapshot = (ovlKey == snapShotOverlayId); + + if (!overlayHndl.setOverlay(ovlKey, displaynameEmpty, overlayContent, isSnapshot)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " addOverlay failed, key=" << ovlKey; + success = false; + } + } // foreach + // : orderedOverlayList + + + // + // 2. orderedLayerList : + // + LayerFactory &layerFactory = this->layerFactory(); + const QStringList &curLayernames = layerFactory.layernames(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " restore curLayernames = " << curLayernames.join(QStringLiteral(";")); + + QStringList newOrderedLayerAll; + newOrderedLayerAll.reserve(oms.orderedLayers.size()); + + for (const auto &item : oms.orderedLayers) + { + const QString &layername = item.first; + LayerParameter layerParameter = item.second; + newOrderedLayerAll.append(layername); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " restore orderedLayerList = " + << " layername='" << layername << "'" + << " layerParameter=" << layerParameter; + + // restore Parameter: + if (curLayernames.contains(layername)) + { + // maybe keep previewImage, displayName, description + const LayerParameter ¤tLp = layerFactory.layerParameter(layername); + if (layerParameter.previewImage.isNull()) layerParameter.previewImage = currentLp.previewImage; + if (layerParameter.displayName.isEmpty()) layerParameter.displayName = currentLp.displayName; + if (layerParameter.description.isEmpty()) layerParameter.description = currentLp.description; + + // // GUEZE-2928 // Filtervoreinstellungen werden nicht übernommen + // GUEZE-2928 steht aber auch im Widerspruch zu GUEZE-1711 + // if (!isReplay() && (layername == layerFactory.emergencyCallLayer(true))) + // { + // // for Live: + // layerParameter.visible = true; + // layerParameter.opacity = 100; + // layerParameter.minScale = -1; + // layerParameter.maxScale = -1; + // DEBUG_LOG(m_logger) << LOGFUNCTION << " 'fix' layerParameter due to emergencyCallLayer"; + // } + + if (! layerFactory.setLayerParameter(layername, layerParameter)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " setLayerParameter failed: layername='" << layername << "'"; + success = false; + } + } + else + WARNING_LOG(m_logger) << LOGFUNCTION << " layer not found: layername='" << layername << "'"; + + } // for + + // restore order : + if (!newOrderedLayerAll.isEmpty()) // in case of initial/empty oms, newOrderedLayerAll is empty + { + if (! layerFactory.orderLayerTo(newOrderedLayerAll)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " orderLayerTo failed"; + success = false; + } + } + // : restore order + + // : orderedLayerList + + + // + // 4. some flags, visiblities etc: + // + this->grid().setVisible(oms.mapWidgetSettings.gridVisibility); + this->showPositionString(oms.mapWidgetSettings.positionStringVisibility); + this->showExerciseTimeString(oms.mapWidgetSettings.exerciseTimeStringVisibility); +#ifndef TABLET_MODE + this->showTimeString(oms.mapWidgetSettings.timeStringVisibility); +#endif + this->showCompass(oms.mapWidgetSettings.compassVisibility); + + if (oms.mapWidgetSettings.overviewMapVisibility) + this->overviewMapHandler().createBuiltInOverviewMap(); + else + this->overviewMapHandler().removeBuiltInOverviewMap(); + + + // + // 5. JumpPointWidget : + // + if (d->m_jumpPointWidget) + { + d->m_jumpPointWidget->cleanup(); + for (const auto &jpd : oms.jumpPoints) + { + d->m_jumpPointWidget->addJumpPoint(jpd.key, + jpd.originGeo, + jpd.mapScale); + + } // foreach + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success = " << success; + return success; +} + + + + +Esri::ArcGISRuntime::GraphicsOverlay *MapImpl::getGraphicsLayer(const QString &logicalName) +{ + return this->layerFactoryImpl().getGraphicsLayer(logicalName); +} + +Esri::ArcGISRuntime::GraphicsOverlay *MapImpl::getGraphicsLayerDict(const QString &logicalName) +{ + return this->layerFactoryImpl().getGraphicsLayerDict(logicalName); +} + +Esri::ArcGISRuntime::GraphicsOverlay *MapImpl::getGraphicsLayer3DExtrusion(const QString &logicalName) +{ + return this->layerFactoryImpl().getGraphicsLayer3DExtrusion(logicalName); +} + +Esri::ArcGISRuntime::GraphicsOverlay *MapImpl::getGraphicsLayer3D_SpAbsolute(const QString &logicalName) +{ + return this->layerFactoryImpl().getGraphicsLayer3D_SpAbsolute(logicalName); +} + + +CoordTranslationFunctor *MapImpl::coordTranslationFunctor() const +{ + return d->m_coordTranslationFunctor; +} + + +ObjectContainer &MapImpl::objectContainer() +{ + return *(d->m_objectContainer.data()); +} +void MapImpl::setObjectContainer(QSharedPointer objectContainer) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_objectContainer = objectContainer; + + this->objectFactory().setObjectContainer(d->m_objectContainer); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PartyIdToColorFunctor &MapImpl::partyIdToColorFunctor() const +{ + return *(d->m_partyIdToColorFunctor.data()); +} + +void MapImpl::setPartyIdToColorFunctor(QSharedPointer partyIdToColor) +{ + d->m_partyIdToColorFunctor = partyIdToColor; + + // delay that signal, in rpelay it occurs more often: + connect(d->m_partyIdToColorFunctor.data(), &PartyIdToColorFunctor::partyChanged, + this, [this] () { + d->m_partychangedDelayTimer->start(); // is restart timer if already running + }); + + // to create UniqueValueRenderer if required: + this->onPartyChanged(); +} + + +SituationMap::OverlayHandler &SituationMap::MapImpl::overlayHandler() const +{ + if (!d->m_overlayHandler) + { + MapImpl *map = const_cast(this); + d->m_overlayHandler = QSharedPointer (new OverlayHandlerImpl(map)); + } + return *(d->m_overlayHandler); +} + + +bool MapImpl::addJumpPoint(const QString &name, const Point3D &originGeo, double mapScale) +{ + if (!d->m_jumpPointWidget) + return false; + return d->m_jumpPointWidget->addJumpPoint(name, originGeo, mapScale); +} +bool MapImpl::removeJumpPoint(const QString &name) +{ + if (!d->m_jumpPointWidget) + return false; + return d->m_jumpPointWidget->removeJumpPoint(name); +} +QStringList MapImpl::jumpPointNames() const +{ + if (!d->m_jumpPointWidget) + return {}; + return d->m_jumpPointWidget->names(); +} + + +void MapImpl::initCoordTranslationFunctor() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (! d->m_coordTranslationFunctorInitialize) + d->m_coordTranslationFunctorInitialize = new CoordTranslationFunctorImpl(this); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +AdvancedSymbology::SymbolDictionary &MapImpl::symbolDictionary() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(d->m_symbologyType); + + // check mapReady and SymbolDictionary::loaded + connect(&dict, &AdvancedSymbology::SymbolDictionary::loaded, + this, &MapImpl::onCheckMapReadyAndSymbolDictionaryLoaded, Qt::UniqueConnection); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return dict; +} + +bool MapImpl::isSymbolDictionaryLoaded() const +{ + if (d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + return true; + + bool isExisting = AdvancedSymbology::SymbolDictionary::alreadyExists(d->m_symbologyType) && symbolDictionary().isLoaded(); + return isExisting; +} + +bool MapImpl::setSymbologyType(AdvancedSymbology::SymbologyType symbologyType) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " set SymbologyType to " << d->m_symbologyType; + + if (d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + { + d->m_symbologyType = symbologyType; + + AdvancedSymbology::SymbolDictionary &dict = this->symbolDictionary(); + dict.load(); + return dict.isOk(); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " modification of symbologyType is not allowed"; + return false; +} + +AdvancedSymbology::SymbologyType MapImpl::symbologyType() +{ + return d->m_symbologyType; +} + + +Viewpoint2D MapImpl::initialViewpoint() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, initialVp=" << d->m_initialViewpoint; + return d->m_initialViewpoint; +} + + +void MapImpl::setInitialViewpoint(const Viewpoint2D &initialViewpoint) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, initialViewpoint=" << initialViewpoint; + + d->m_initialViewpoint = initialViewpoint; + if (d->m_initialViewpoint.scale() <= 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " initialViewpoint.scale less or equal zero!!! set to 1"; + d->m_initialViewpoint.setScale(1.0); + } + + // rad to deg: + QPointF centerGeo = d->m_initialViewpoint.centerGeo(); + centerGeo.rx() *= LtsCoordTrans::rad2deg(); // x = lon + centerGeo.ry() *= LtsCoordTrans::rad2deg(); // y = lat + + // lat, lon + const Esri::ArcGISRuntime::Viewpoint initialVp(centerGeo.y(), centerGeo.x(), + d->m_initialViewpoint.scale()); // skip rotation here + + switch (mode()) + { + case Mode::Mode_2D: + d->m_esriMap->setInitialViewpoint(initialVp); + break; + case Mode::Mode_3D: + d->m_esriScene->setInitialViewpoint(initialVp); + break; + default: + FATAL_LOG(m_logger) << LOGFUNCTION << " mode is undefined!!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " mode is undefined!!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " mode is undefined!!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " mode is undefined!!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " mode is undefined!!!"; + break; + } +} + + +Viewpoint2D MapImpl::viewpoint2D() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + Viewpoint2D vp; + vp.setCenterGeo(this->centerPoint()); + vp.setScale(this->scale()); + vp.setRotation(this->rotation()); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, vp=" << vp; + return vp; +} + +void MapImpl::setViewpoint2D(const Viewpoint2D &viewpoint, float durationSeconds) +{ + this->setViewpoint2DInternal(viewpoint, false, durationSeconds); +} + +void MapImpl::setViewpoint2DAndWait(const Viewpoint2D &viewpoint) +{ + this->setViewpoint2DInternal(viewpoint, true, -1.0); +} + + +void MapImpl::setViewpoint2DInternal(const Viewpoint2D &viewpoint, bool andWait, float durationSeconds) +{ + INFO_LOG(m_logger) << LOGFUNCTION << " START, vp=" << viewpoint << " andWait=" << andWait << " durationSeconds=" << durationSeconds; + + Esri::ArcGISRuntime::GeoView *geoview = this->getEsriGeoView(); + const CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + if (!geoview || !ctFunctor) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return; + } + + const QPointF ¢erPointMap = ctFunctor->geographicToMap(viewpoint.centerGeo()); + const Esri::ArcGISRuntime::Point esriPoint(centerPointMap.x(), centerPointMap.y(), this->getEsriSpatialReference()); + + if (!esriPoint.isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " esriPoint not valid!!!" + << " (viewpoint=" << viewpoint << ")"; + return; + } + + if (viewpoint.isNull()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " viewpoint is null -> skip!!!" + << " (viewpoint=" << viewpoint << ")"; + return; + } + + // scale in Viewport: value of 0 and below may raise an exception. Try to avoid: + double scale = viewpoint.scale(); + if (scale <= 0) + { + scale = Viewpoint2D::defaultScale; + ERROR_LOG(m_logger) << LOGFUNCTION << " viewpoint.scale less or equal zero!!! set to default (" << scale << ")" + << " (viewpoint=" << viewpoint << ")"; + } + + const Esri::ArcGISRuntime::Viewpoint targetVp(esriPoint, + scale, + viewpoint.rotation() * -1.0); // clock vs. counterClock + + if (andWait) + geoview->setViewpointAsync(targetVp); + else + { + + // use default (d->m_animationDuration) if not specified: + if (durationSeconds < 0) + durationSeconds = d->m_animationDuration; + + if (durationSeconds < 0) + geoview->setViewpointAsync(targetVp); + else + geoview->setViewpointAsync(targetVp, durationSeconds); + + } + + d->m_rotationAbsoluteAngleDegrees = viewpoint.rotation(); + emit rotationChanged(d->m_rotationAbsoluteAngleDegrees); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, vp=" << viewpoint; +} + +QPointF MapImpl::centerPoint() const +{ + Esri::ArcGISRuntime::GeoView *geoview = this->getEsriGeoView(); + const CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + if (!geoview || !ctFunctor) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return QPointF(); + } + + const Esri::ArcGISRuntime::Viewpoint &curVp = + geoview->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + + Esri::ArcGISRuntime::Point mapCenter = + static_cast(curVp.targetGeometry()); + + const QPointF center(mapCenter.x(), mapCenter.y()); + + return ctFunctor->mapToGeographic(center); +} + + +void MapImpl::setCenterPoint(const QPointF ¢er, double scale, float durationSeconds) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " center=" << center + << " scale=" << scale + << " durationSeconds=" << durationSeconds + << " sp=" << this->getEsriSpatialReference().wkid(); + + Esri::ArcGISRuntime::GeoView *geoview = this->getEsriGeoView(); + const CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + + if (!geoview || !ctFunctor) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return; + } + + const QPointF ¢erPointMap = ctFunctor->geographicToMap(center); + + const Esri::ArcGISRuntime::Point esriPoint(centerPointMap.x(), centerPointMap.y(), this->getEsriSpatialReference()); + + const Esri::ArcGISRuntime::Viewpoint &curVp = + geoview->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + + Esri::ArcGISRuntime::Viewpoint targetVp(esriPoint, + (scale < 0) ? curVp.targetScale() : scale + ); + // use default (d->m_animationDuration) if not specified: + if (durationSeconds < 0) + durationSeconds = d->m_animationDuration; + + if (durationSeconds < 0) + geoview->setViewpointAsync(targetVp); + else + geoview->setViewpointAsync(targetVp, durationSeconds); + + if (isPositionStringVisible()) + { + d->m_positionStr = this->mapToMgrsString(centerPointMap); + d->updatePositionText(); + } +} + +void MapImpl::zoomToArea(const QVector &coords, int paddingInDIPS) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " coords=" << coords.size(); + + Esri::ArcGISRuntime::GeoView *geoview = this->getEsriGeoView(); + const CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + + if (!geoview || !ctFunctor) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return; + } + + if (coords.size() < 2) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not enough coords (" << coords.size() << ")"; + return; + } + + Esri::ArcGISRuntime::PolygonBuilder pBuilder(geoview->spatialReference()); + for (const auto & coord : coords) + { + QPointF mapCoord = ctFunctor->geographicToMap(coord); + pBuilder.addPoint(mapCoord.x(), mapCoord.y()); + } // for + + switch (this->mode()) + { + case Mode::Mode_2D: + { + Esri::ArcGISRuntime::MapGraphicsView *mapView = static_cast(geoview); + mapView->setViewpointGeometryAsync(pBuilder.toGeometry(), paddingInDIPS); + } + break; + case Mode::Mode_3D: + { + Esri::ArcGISRuntime::SceneGraphicsView *sceneView = static_cast(geoview); + Esri::ArcGISRuntime::Viewpoint esriVp(pBuilder.toGeometry()); // unfortunately padding is not supported here + sceneView->setViewpointAsync(esriVp); + } + break; + case Mode::Undefined: + {} + } // switch +} + +void MapImpl::setTrainingsAreaExtent(const QVector &trainingsAreaExtent) +{ + d->m_trainingsAreaExtent = trainingsAreaExtent; +} + + +QRectF MapImpl::extent() const +{ + if (d->m_mapGraphicsView) + { + const Esri::ArcGISRuntime::Viewpoint &vp = + d->m_mapGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::BoundingGeometry); + + if (vp.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Viewpoint is empty"; + return QRect(); + } + + const Esri::ArcGISRuntime::Envelope &env = + static_cast(vp.targetGeometry()); + + if (std::isnan(env.width()) || std::isnan(env.height())) + ERROR_LOG(m_logger) << LOGFUNCTION << " Envelope: width|height isnan"; + + // DEBUG_LOG(m_logger) << LOGFUNCTION + // << " xMin =" << env.xMin() + // << " yMin =" << env.yMin() + // << " xMax =" << env.xMax() + // << " yMax =" << env.yMax() + // << " width =" << env.width() << " height =" << env.height(); + + CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + if (ctFunctor) + { + const QPointF upperLeft(ctFunctor->mapToGeographic(env.xMax(), + env.yMin())); + const QPointF lowerRight(ctFunctor->mapToGeographic(env.xMin(), + env.yMax())); + + return QRectF(upperLeft, lowerRight); + } + else + { + // QRect -> left, top, width, height + return QRectF(env.xMin(), + env.yMin(), // use yMin, not yMax + env.width(), + env.height()); + } + + + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " no object"; + return QRectF(); + } +} + +void MapImpl::setExtent(const QRectF &extent) +{ + // !!! extent.bottom__.y() > extent.bottom.top__.y() !!! + setExtent(extent.topLeft() .x(), extent.topLeft(). y(), + extent.bottomRight().x(), extent.bottomRight().y()); +} + +void MapImpl::setExtent(double xmin, double ymin, double xmax, double ymax) +{ + INFO_LOG(m_logger) << LOGFUNCTION << " xmin=" << xmin << " ymin=" << ymin << " xmax=" << xmax << " ymax=" << ymax + << " w=" << xmax - xmin << " h= " << ymax - ymin; + + if (d->m_mapGraphicsView) + { + Esri::ArcGISRuntime::Envelope env; + + CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + if (ctFunctor) + { + const QPointF bottomLeft(ctFunctor->geographicToMap(xmin, ymin)); + const QPointF topRight(ctFunctor->geographicToMap(xmax, ymax)); + + // xmin,ymin,xmax,ymax + env = Esri::ArcGISRuntime::Envelope(bottomLeft.x(), bottomLeft.y(), + topRight.x(), topRight.y(), d->m_esriMap->spatialReference()); + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " !!! no CoordTranslationFunctor!!!" + " interpret values as absolute values"; + // setCoords -> xmin,ymin,xmax,ymax + env = Esri::ArcGISRuntime::Envelope(xmin, ymin, xmax, ymax, d->m_esriMap->spatialReference()); + } + d->m_mapGraphicsView->setViewpointAsync(env, d->m_animationDuration); + //d->m_mapGraphicsView->setViewpointGeometry(env); + ///// d->m_absoluteScale = d->m_esriMap->scale(); // update d->m_absoluteScale + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; +} + +void MapImpl::setExtentMapWorld(double xmin, double ymin, double xmax, double ymax) +{ + if (d->m_mapGraphicsView) + { + Esri::ArcGISRuntime::Envelope env(xmin, ymin, xmax, ymax, d->m_esriMap->spatialReference()); + d->m_mapGraphicsView->setViewpointAsync(Esri::ArcGISRuntime::Viewpoint(env)); + d->m_absoluteScale = this->scale(); // update d->m_absoluteScale + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; +} + + +/* +QRectF MapImpl::fullExtent() const +{ + if (d->m_esriMap) + { + const EsriRuntimeQt::Envelope &env = d->m_esriMap->fullExtent(); + + + CoordTranslationFunctor *ctFunctor = this->coordTranslationFunctor(); + if (ctFunctor) + { + const QPointF upperLeft(ctFunctor->mapToGeographic(env.upperLeft())); + const QPointF lowerRight(ctFunctor->mapToGeographic(env.lowerRight())); + + return QRectF(upperLeft, lowerRight); + + } + else + { + // QRect -> left, top, width, height + return QRectF(env.xMin(), + env.yMin(), // use yMin, not yMax + env.width(), + env.height()); + } + + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return QRectF(); + + } +} +*/ + + +double MapImpl::scale() const +{ + + if (!this->isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not ready yet!!!"; + return 0.0; + } + + if (d->m_destroying) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " is going to be destroyed!!!"; + return 0.0; + } + + // Keep in mind: while animation, esriMap delivers to old value. +#ifdef SITUATIONMAP_OWN_WHEEL_HANDLING + return d->m_absoluteScale; +#else + + if (this->getEsriGeoView()) + { + + const Esri::ArcGISRuntime::Viewpoint &vp = + this->getEsriGeoView()->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + + + if (vp.isEmpty()) + return 0.0; + + double scale = vp.targetScale(); + return scale; + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return 0.0; +#endif // SITUATIONMAP_OWN_WHEEL_HANDLING +} + +void MapImpl::setScale(double absoluteScale) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " absoluteScale = " << absoluteScale; + + if (d->m_mapGraphicsView) + { + const Esri::ArcGISRuntime::Viewpoint &curVp = + d->m_mapGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + Esri::ArcGISRuntime::Viewpoint targetVp(curVp.targetGeometry(), absoluteScale); + d->m_mapGraphicsView->setViewpointAsync(targetVp); + d->m_absoluteScale = absoluteScale; + } + else if (d->m_sceneGraphicsView) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " Does this work with V100 ???"; + FATAL_LOG(m_logger) << LOGFUNCTION << " Does this work with V100 ???"; + FATAL_LOG(m_logger) << LOGFUNCTION << " Does this work with V100 ???"; + FATAL_LOG(m_logger) << LOGFUNCTION << " Does this work with V100 ???"; + FATAL_LOG(m_logger) << LOGFUNCTION << " Does this work with V100 ???"; + + // Check: Does this work with QuartzFinal? + // Or Camera::moveForward ?? + const Esri::ArcGISRuntime::Viewpoint &curVp = + d->m_sceneGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + + Esri::ArcGISRuntime::Viewpoint targetVp(curVp.targetGeometry(), + (absoluteScale < 0) ? curVp.targetScale() : absoluteScale); + + d->m_sceneGraphicsView->setViewpointAsync(targetVp); + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; +} + + +void MapImpl::setMinScale(double minScaleToLimitZoomOut) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " minScaleToLimitZoomOut = " << minScaleToLimitZoomOut; + + if (d->m_esriMap) + d->m_esriMap->setMinScale(minScaleToLimitZoomOut); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; +} + + +double MapImpl::minScale() const +{ + if (d->m_esriMap) + return d->m_esriMap->minScale(); + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return 0; +} + + +void MapImpl::setMaxScale(double maxScaleToLimitZoomIn) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " maxScaleToLimitZoomIn = " << maxScaleToLimitZoomIn; + + if (d->m_esriMap) + d->m_esriMap->setMaxScale(maxScaleToLimitZoomIn); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; +} + +double MapImpl::maxScale() const +{ + if (d->m_esriMap) + return d->m_esriMap->maxScale(); + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return 0; +} + + +double MapImpl::rotation() const +{ + // return d->m_rotationAbsoluteAngleDegrees; + + if (this->getEsriGeoView()) + { + const Esri::ArcGISRuntime::Viewpoint &vp = + this->getEsriGeoView()->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + double rot = vp.rotation(); + if (std::isnan(rot)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " nan detected..."; + return 0; + } + rot *= -1.0; // clock vs. counterClock + if (rot < 0) + rot += 360.0; + return rot; + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return 0.0; + } +} + +void MapImpl::setRotation(double absoluteAngleDegrees) +{ + Esri::ArcGISRuntime::GeoView *geoview = this->getEsriGeoView(); + + if (mode() == Mode::Mode_3D) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return; + } + + if (geoview) + { + d->m_rotationAbsoluteAngleDegrees = absoluteAngleDegrees; + + const Esri::ArcGISRuntime::Viewpoint &curVp = + geoview->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + + Esri::ArcGISRuntime::Viewpoint targetVp(curVp.targetGeometry(), + d->m_rotationAbsoluteAngleDegrees * -1.0); // clock vs. counterClock + geoview->setViewpointAsync(targetVp); + emit rotationChanged(d->m_rotationAbsoluteAngleDegrees); + } + + // if (d->m_mapGraphicsView) + // { + // d->m_rotationAbsoluteAngleDegrees = absoluteAngleDegrees; + // d->m_mapGraphicsView->setViewpointRotation( d->m_rotationAbsoluteAngleDegrees * -1.0 ); // clock vs. counterClock + // emit rotationChanged(d->m_rotationAbsoluteAngleDegrees); + // } +} + + +void MapImpl::onEsriRotate() +{ + double rot = rotation(); + + if (!qFuzzyCompare(rot, d->m_rotationAbsoluteAngleDegrees)) + { + d->m_rotationAbsoluteAngleDegrees = rot; + emit rotationChanged(d->m_rotationAbsoluteAngleDegrees); + } +} + + +Esri::ArcGISRuntime::Camera cameraToEsriCamera(const Camera &camIn) +{ + // rad to degree + const double factor = 180 / M_PI; + return Esri::ArcGISRuntime::Camera(camIn.loc_y_lat * factor, // latitude = y + camIn.loc_x_lon * factor, // longitude = x + camIn.loc_z, + camIn.heading * factor, + camIn.pitch * factor, + camIn.roll * factor + ); +} + +Camera esriCameraToCamera(const Esri::ArcGISRuntime::Camera &camIn) +{ + Camera cam; + const Esri::ArcGISRuntime::Point &loc = camIn.location(); + const double factor = M_PI / 180; + cam.loc_x_lon = loc.x() * factor; + cam.loc_y_lat = loc.y() * factor; + cam.loc_z = loc.z(); + + cam.heading = camIn.heading() * factor; + cam.roll = camIn.roll() * factor; + cam.pitch = camIn.pitch() * factor; + + return cam; +} + + +Camera MapImpl::camera() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_sceneGraphicsView) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return Camera(); + } + + const Esri::ArcGISRuntime::Camera &curCam = d->m_sceneGraphicsView->currentViewpointCamera(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return esriCameraToCamera(curCam); +} + +void MapImpl::setCamera(const Camera &camera, float durationSeconds) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + #ifndef WIN32 + << " camera=" << camera + #endif // WIN32 + << ", durationSeconds=" << durationSeconds; + + if (!d->m_sceneGraphicsView) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no object"; + return; + } + + const Esri::ArcGISRuntime::Camera &tarCam = cameraToEsriCamera(camera); + + // avoid neg durationSeconds + if (durationSeconds < 0) + d->m_sceneGraphicsView->setViewpointCameraAsync(tarCam); + else + d->m_sceneGraphicsView->setViewpointCameraAsync(tarCam, durationSeconds); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +Grid &MapImpl::grid() const +{ + if (!d->m_gridImpl) + { + MapImpl *THIS = const_cast(this); + d->m_gridImpl = new SituationMap::GridImpl(THIS); + } + return *(d->m_gridImpl); +} + + +ObjectFactory &MapImpl::objectFactory() +{ + if (!d->m_objectFactory) + d->m_objectFactory = new ObjectFactory(this); + + return *(d->m_objectFactory); +} + +LayerFactory &MapImpl::layerFactory() const +{ + return layerFactoryImpl(); +} + +LayerFactoryImpl &MapImpl::layerFactoryImpl() const +{ + if (!d->m_layerFactoryImpl) + { + MapImpl *THIS = const_cast(this); + d->m_layerFactoryImpl = new LayerFactoryImpl(THIS, THIS); + } + return *(d->m_layerFactoryImpl); +} + +int MapImpl::basemapLayerCount() const +{ + int counter = -1; + if (d->m_basemap && d->m_basemap->baseLayers()) + counter = d->m_basemap->baseLayers()->size(); + return counter; +} + +int MapImpl::graphicsOverlayCount() const +{ + int counter = -1; + if (d->m_mapGraphicsView && d->m_mapGraphicsView->graphicsOverlays()) + counter = d->m_mapGraphicsView->graphicsOverlays()->size(); + return counter; +} + +int MapImpl::technicalLayerCount() const +{ + int counter = 0; + + int cTemp = basemapLayerCount(); + if (cTemp >= 0) + counter += cTemp; + + cTemp = graphicsOverlayCount(); + if (cTemp >= 0) + counter += cTemp; + return counter; +} + +void MapImpl::emitVisualComponentChanged(const VisualComponent &visualComponent) +{ + if (d->m_destroying) + return; + emit visualComponentChanged(visualComponent); +} + + +void MapImpl::onClickMapObject(const QList &uniqueMapIdList, const QPointF &pos) +{ + if (d->m_destroying) + return; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " START onClickMapObject clicked ids at viewport position x = " << pos.x() << " ; y = " << pos.y() + << " uniqueMapIdList.size() = " << uniqueMapIdList.size(); + + RefPointerMapObjectList mapObjList; + QList mapObjIdList; + bool makeObjList = isSignalConnected(QMetaMethod::fromSignal(&Map::mapObjectsClicked)); + bool makeObjIdList = isSignalConnected(QMetaMethod::fromSignal(&Map::mapObjectIdsClicked)); + + for (const UniqueMapId &uniqueMapId : uniqueMapIdList) + { + AbstractMapObject *ifMapObjPtr = d->m_objectFactory->idToMapObject(uniqueMapId); + if (ifMapObjPtr) + { + if (ifMapObjPtr->pickable()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueMapId=" << uniqueMapId + << QStringLiteral(" Address=%1").arg((qint64)ifMapObjPtr, 0, 16) + << " object-debugInfo=" << ifMapObjPtr->debugInfo() + << " object-identifier=" << ifMapObjPtr->identifier(); + + if (makeObjList) mapObjList += RefPointer(ifMapObjPtr); + if (makeObjIdList) mapObjIdList += uniqueMapId; + + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueMapId=" << uniqueMapId + << QStringLiteral(" Address=%1").arg((qint64)ifMapObjPtr, 0, 16) + << " object-debugInfo=" << ifMapObjPtr->debugInfo() + << " object-identifier=" << ifMapObjPtr->identifier() + << " object is NOT pickable!"; + } + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Unknown map object with uniqueMapId=" << uniqueMapId << " found."; + + } // for + + DEBUG_LOG(m_logger) << LOGFUNCTION << " END onClickMapObject clicked ids" + << ", mapObjList=" << mapObjList.size() + << ", makeObjIdList=" << mapObjIdList.size(); + + if (makeObjList) + emit mapObjectsClicked(mapObjList, pos); + + if (makeObjIdList) + emit mapObjectIdsClicked(mapObjIdList, pos); + + +} // MapImpl::onClickMapObject + + +void MapImpl::onContextClick(const QList &uniqueMapIdList, const QPointF &pos) +{ + if (d->m_destroying) + return; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " START onContextClick clicked ids at viewport position x = " << pos.x() << " ; y = " << pos.y(); + + RefPointerMapObjectList mapObjList; + bool makeObjList = isSignalConnected(QMetaMethod::fromSignal((void (Map::*)(const RefPointerMapObjectList &, const QPointF &))(&Map::contextMenuClicked))); + + if (!makeObjList) + return; + + for (UniqueMapId uniqueMapId : uniqueMapIdList) + { + AbstractMapObject *ifMapObjPtr = d->m_objectFactory->idToMapObject(uniqueMapId); + + if (ifMapObjPtr) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueMapId=" << uniqueMapId + << QStringLiteral(" Address=%1").arg((qint64)ifMapObjPtr, 0, 16) + << " object-debugInfo=" << ifMapObjPtr->debugInfo() + << " object-identifier=" << ifMapObjPtr->identifier(); + + if (makeObjList) mapObjList += RefPointer(ifMapObjPtr); + + } + else ERROR_LOG(m_logger) << LOGFUNCTION << "Unknown map object with uniqueMapId=" << uniqueMapId << " found."; + } // for + + DEBUG_LOG(m_logger) << LOGFUNCTION << " END onContextClick clicked ids"; + + if (makeObjList) emit contextMenuClicked(mapObjList, pos); + +} // MapImpl::onContextClick() + + +void MapImpl::onWheelZoom(qreal factor, const QPointF &cursorPositionMap) +{ + if (d->m_destroying) + return; + + TRACE_LOG(m_logger) << LOGFUNCTION << " factor = " << factor + << " cursorPositionMap = " << cursorPositionMap; + + // scale -> non-animated ; zoomTo -> animated + // zoomToScale( d->m_absoluteScale *= factor ); + ///// zoomToScaleMapCoordinate(d->m_absoluteScale *= factor, cursorPositionMap); + +} // MapImpl::onWheelZoom( qreal factor ) + +void MapImpl::startRocket() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " current Scale = " << d->m_mapGraphicsView->mapScale(); + + if (rocketIsRunning()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " rocket is running -> return"; + return; + } + + // Save value + // for 2D + d->m_currentCenterPointBeforeRocket = centerPoint(); + d->m_currentScaleBeforeRocket = scale(); + d->m_currentCameraBeforRocket = camera(); + + // Zoom out + switch (this->mode()) + { + case Mode::Mode_2D: + setCenterPoint(d->m_currentCenterPointBeforeRocket, d->m_currentScaleBeforeRocket * 10, d->m_rocketDuration); + break; + case Mode::Mode_3D: + { + Esri::ArcGISRuntime::Camera curCam = cameraToEsriCamera(d->m_currentCameraBeforRocket); + const double curHeight = d->m_currentCameraBeforRocket.loc_z; + const Esri::ArcGISRuntime::Camera &newCam = curCam.moveForward(curHeight * 10.0 * -1); + setCamera(esriCameraToCamera(newCam), d->m_rocketDuration); + break; + } + case Mode::Undefined: + // empty; + break; + } // switch + + delete d->m_rocketTimer; + d->m_rocketTimer = new QTimer(this); + d->m_rocketTimer->setSingleShot(true); + d->m_rocketTimer->setInterval((d->m_rocketDuration * 1000) + 100); // add small amount to let zoomSlider react :-) + connect(d->m_rocketTimer, &QTimer::timeout, this, &MapImpl::onRocketTimerZoomOutFinished); + + d->m_rocketTimer->start(); // to zoom out + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool SituationMap::MapImpl::rocketIsRunning() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_currentScaleBeforeRocket != 0.0; +} + +double MapImpl::symbolScale() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, symbolScale = " << d->m_symbolScale; + return d->m_symbolScale; +} + +void MapImpl::setSymbolScale(double symbolScale) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, new symbolScale = " << symbolScale; + + // 0 check range: + if (symbolScale < SituationMap::symbolScaleMinimum) + symbolScale = SituationMap::symbolScaleMinimum; + if (symbolScale > SituationMap::symbolScaleMaximum) + symbolScale = SituationMap::symbolScaleMaximum; + + // 1 + d->m_symbolScale = symbolScale; + + // 2 apply + QMutableMapIterator> mapObjIt = this->objectContainer().mutableIterator(); + + while (mapObjIt.hasNext()) + { + mapObjIt.next(); + RefPointer mapObj = mapObjIt.value(); + if (mapObj) + { + mapObj->setScale(symbolScale); + if (! mapObj->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed, debugInfo='" << mapObj->debugInfo() << "'"; + } + } // while + + // 3 emit + emit symbolScaleChanged(d->m_symbolScale); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, new symbolScale = " << symbolScale; +} + +void MapImpl::onRocketTimerZoomOutFinished() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // Zoom in / restore initial values: + switch (this->mode()) + { + case Mode::Mode_2D: + setCenterPoint(d->m_currentCenterPointBeforeRocket, d->m_currentScaleBeforeRocket, d->m_rocketDuration); + break; + case Mode::Mode_3D: + setCamera(d->m_currentCameraBeforRocket, d->m_rocketDuration); + break; + case Mode::Undefined: + // empty; + break; + } // switch + + + delete d->m_rocketTimer; + d->m_rocketTimer = new QTimer(this); + d->m_rocketTimer->setSingleShot(true); + d->m_rocketTimer->setInterval((d->m_rocketDuration * 1000) + 100); // add small amount to let zoomSlider react :-) + connect(d->m_rocketTimer, &QTimer::timeout, this, &MapImpl::onRocketTimerZoomInFinished); + d->m_rocketTimer->start(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void MapImpl::onRocketTimerZoomInFinished() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + delete d->m_rocketTimer; + d->m_rocketTimer = nullptr; + + d->m_currentCenterPointBeforeRocket = QPointF(); + d->m_currentScaleBeforeRocket = 0.0; + d->m_currentCameraBeforRocket.resetCamera(); + + emit rocketFinished(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SituationMap::MapImpl::onJumpPointRequest(const QPointF &viewPortPos) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_jumpPointWidget) + return; + const QPointF &coordGeo = this->viewPortWindowPointToGeoCoordinate(viewPortPos); + connect(d->m_jumpPointWidget, &JumpPointWidget::newName, this, &MapImpl::onName); + d->m_jumpPointWidget->askUserForName(coordGeo); + +} +void SituationMap::MapImpl::onName(const QString &name, const QPointF &coordGeo) +{ + if (name.isEmpty()) + return; + + const double scale = this->scale(); + d->m_jumpPointWidget->addJumpPoint(name, coordGeo, scale); + disconnect(d->m_jumpPointWidget, &JumpPointWidget::newName, this, &MapImpl::onName); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +GeometryModelRenderer *MapImpl::createGeometryModelRenderer(const QString &layerName, GeometryModel *geometryModel, QObject *parent) +{ + return new SituationMap::GeometryModelRendererEsri(this, layerName, *geometryModel, parent); +} + + +RB::RubberbandFactory &MapImpl::rubberbandFactory() const +{ + if (!d->m_rubberbandFactory) + { + MapImpl *THIS = const_cast(this); + d->m_rubberbandFactory = new RB::RubberbandFactory(THIS); + } + return *(d->m_rubberbandFactory); +} + +bool MapImpl::pickMapObjectsArea() const +{ + // assume that we always use a rectangle (orientation north) + + RB::RubberbandRectangle *rubberband = rubberbandFactory().create(); + + if (!rubberband) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed"; + return false; + } + rubberband->constrainOrthogonal(true); + rubberband->setAutoDelete(true); + connect(rubberband, &RB::Rubberband::apply, this, &MapImpl::onRubberbandPickMapObjectsApply); + + return true; +} + + +bool MapImpl::scaleByArea() const +{ + SituationMap::RB::RubberbandRectangle *rubberband = rubberbandFactory().create(); + + if (!rubberband) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed"; + return false; + } + + rubberband->constrainOrthogonal(true); + rubberband->setAutoDelete(true); + + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, [this](SituationMap::RB::RubberbandBase *rubberband) + { + if (rubberband) + { + const QVector &points = rubberband->coordinateVector(); + + if (points.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed, no coords"; + return; + } + + double xmin = points[0].x(); + double ymin = points[0].y(); + double xmax = xmin; + double ymax = ymin; + + for (auto pt : points) + { + if (pt.x() < xmin) xmin = pt.x(); + if (pt.y() < ymin) ymin = pt.y(); + if (pt.x() > xmax) xmax = pt.x(); + if (pt.y() > ymax) ymax = pt.y(); + } + + if ((!qFuzzyCompare(xmin, xmax)) && (!qFuzzyCompare(ymin, ymax))) + const_cast(this)->setExtent(xmin, ymin, xmax, ymax); + } + }); + return true; +} // bool MapImpl::scaleByArea() const + + +ObjectIndicator &MapImpl::objectIndicator() +{ + if (!d->m_objectIndicatorImpl) + { + d->m_objectIndicatorImpl = new ObjectIndicatorImpl(); + d->m_objectIndicatorImpl->init(this); + } + + return *(d->m_objectIndicatorImpl); +} + +LocationDisplay &MapImpl::locationDisplay() +{ + if (!d->m_locationDisplayImpl) + { + d->m_locationDisplayImpl = new LocationDisplayImpl(this); + d->m_locationDisplayImpl->init(); + } + return *(d->m_locationDisplayImpl); +} + +PixelBasedTextBoxHandler &MapImpl::pixelBasedTextBoxHandler() +{ + if (!d->m_pixelBasedTextBoxHandlerImpl) + { + d->m_pixelBasedTextBoxHandlerImpl = new PixelBasedTextBoxHandlerImpl(this); + } + return *(d->m_pixelBasedTextBoxHandlerImpl); +} + + +Measurement &MapImpl::measurement() +{ + if (!d->m_measurementImpl) + d->m_measurementImpl = new MeasurementImpl(this); + + return *(d->m_measurementImpl); +} + +TerrainProfile &MapImpl::terrainProfile() +{ + if (!d->m_terrainProfileImpl) + d->m_terrainProfileImpl = new TerrainProfileImpl(this); + + return *(d->m_terrainProfileImpl); +} + +Viewshed &MapImpl::viewshed() +{ + if (!d->m_viewshedImpl) + d->m_viewshedImpl = new ViewshedImpl(this); + + return *(d->m_viewshedImpl); +} + + +void MapImpl::onErrorGeometryEngine(const Esri::ArcGISRuntime::Error &error) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; +} + +void MapImpl::onErrorMap(const Esri::ArcGISRuntime::Error &error) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; +} + +void MapImpl::onErrorScene(const Esri::ArcGISRuntime::Error &error) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; +} + +void MapImpl::onErrorOperationalLayer(const Esri::ArcGISRuntime::Error &error) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; +} + +void MapImpl::onErrorBasemap(const Esri::ArcGISRuntime::Error &error) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; +} + +void MapImpl::onViewpointChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->m_destroying) + return; + + if (!d->m_mapReadyFlag) + return; + + // to ensure recalculation: + d->m_pixelsize = -1.0; + + + // The very first time, the extent is nan (not a number) + // Therefore suppress this: + // 23.11.16, N.Thoden, V100.0 + if (d->m_suppressFirstViewpointChange) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " suppress firstViewpointChange"; + d->m_suppressFirstViewpointChange = false; + return; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " ************** viewpointChanged=" << toString(this->extent()); + + + emit extentChanged(); + + d->m_maxExtentTimer->start(); // start -> restart + + // TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void MapImpl::onRectChanged() +{ + if (d->m_destroying) + return; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " ************** signal emitted: rectChanged ************** "; + DEBUG_LOG(m_logger) << LOGFUNCTION << " ************** signal emitted: rectChanged ************** " + << " w=" << this->width() << " - h=" << this->height(); + emit rectChanged(); +} + +QImage MapImpl::toImageSync() +{ + QScreen *screen = this->screen(); + if (!screen) + return {}; + + WId winId = this->winId(); + QSize size = this->size(); + QPixmap pix = screen->grabWindow(winId, + 0, 0, + size.width(), size.height()); + return pix.toImage(); +} + + +QUuid MapImpl::toImage() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + Esri::ArcGISRuntime::GeoView *geoview = this->getEsriGeoView(); + + if (! geoview) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no view"; + return QUuid(); + } + + // Create watcher if not exists + if (!d->m_exportImageWatcher) + { + d->m_exportImageWatcher = new QFutureWatcher(this); + connect(d->m_exportImageWatcher, &QFutureWatcher::finished, + this, [this]() { + const QImage &image = d->m_exportImageWatcher->result(); + emit imageCompleted(QUuid::createUuid(), image); + }); + } + + QFuture tw = geoview->exportImageAsync(); + if (! tw.isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " exportImageAsync invalid"; + return QUuid(); + } + + d->m_exportImageWatcher->setFuture(tw); + + const QUuid taskId = QUuid::createUuid(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, taskId=" << taskId; + return taskId; +} + + +bool MapImpl::toPdf(const QString &outputFilename, + const QString &title, + const QString &footerLeft, + const QString &footerCenter, + const QString &footerRight, + const QColor &textColor) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " outputFilename='" << outputFilename << "'"; + + connect(this, &MapImpl::imageCompleted, this, + [this, + outputFilename, + title, + footerLeft, footerCenter, footerRight, + textColor](const QUuid &, const QImage & image) + { + finishToPdf(image, + outputFilename, + title, + footerLeft, footerCenter, footerRight, + textColor); + }); + + if (this->toImage().isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " taskId invalid"; + return false; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " outputFilename='" << outputFilename << "'"; + return true; +} + +void MapImpl::finishToPdf(const QImage &image, + const QString &outputFilename, + const QString &title, + const QString &footerLeft, + const QString &footerCenter, + const QString &footerRight, + const QColor &textColor) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " outputFilename='" << outputFilename << "'"; + + QColor textColorInternal; + if (textColor.isValid()) + textColorInternal = textColor; + else + textColorInternal = this->palette().color(QPalette::WindowText); // RDE_DARK_BLUE + + + QPrinter printer(QPrinter::PrinterMode::HighResolution); // QPrinter::PrinterMode::ScreenResolution ; ScreenResolution is Qt-default + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(outputFilename); + printer.setPageOrientation(QPageLayout::Landscape); + + int marginInt = 20; + QMarginsF margins; + margins.setLeft(marginInt); + margins.setTop(marginInt); + printer.setPageMargins(margins); + + printer.setCreator(QStringLiteral("Rde-") + qAppName()); + + printer.setDocName(title); + + + const QSizeF pageSize = printer.pageLayout().pageSize().size(QPageSize::Millimeter); + DEBUG_LOG(m_logger) << LOGFUNCTION << " pageSize=" << pageSize; + + const QSizeF drawingSize = pageSize - QSizeF(2 * marginInt, 2 * marginInt) * 1.5; + + + QPainter painter(&printer); + + QTextDocument document; + QTextCursor cursor(&document); + + + QTextTableFormat tableFormat; + // tableFormat.setBackground(QColor(QLatin1String("lightGray"))); + tableFormat.setBorder(0); + tableFormat.setCellPadding(10); + + QTextCharFormat defaultFormat; + defaultFormat.setForeground(QBrush(textColorInternal)); + + // + // Header: + // + { + QTextCharFormat format = defaultFormat; + format.setFontWeight(QFont::Bold); + format.setFontPointSize(20); + + QTextTable *headerTable = cursor.insertTable(1, 1, tableFormat); + QTextTableCell cell = headerTable->cellAt(0, 0); + QTextCursor cellCursor = cell.firstCursorPosition(); + cellCursor.mergeCharFormat(format); + cellCursor.insertText(title); + + // create small distance: + format.setFontPointSize(5); + cellCursor.mergeCharFormat(format); + cellCursor.insertBlock(); + + } + cursor.insertBlock(); + + + // + // Image: + // + const QUrl imageUrl(QLatin1String("situationMap://sitMapScreenshot.png")); + document.addResource(QTextDocument::ImageResource, imageUrl, QVariant(image)); + + + QTextImageFormat imageFormat; + imageFormat.setName(imageUrl.toString()); + imageFormat.setWidth(drawingSize.width() * 0.95); + cursor.insertImage(imageFormat); + + + // + // Footer + // + { + QVector constraints; + constraints << QTextLength(QTextLength::FixedLength, imageFormat.width() / 3); + constraints << QTextLength(QTextLength::FixedLength, imageFormat.width() / 3); + constraints << QTextLength(QTextLength::FixedLength, imageFormat.width() / 3); + tableFormat.setColumnWidthConstraints(constraints); + QTextTable *footerTable = cursor.insertTable(1, 3, tableFormat); + + QStringList sl; + sl << footerLeft << footerCenter << footerRight; + + // Footer -> centered: + QTextBlockFormat blockFormat = cursor.blockFormat(); + Qt::Alignment alignment = Qt::AlignHCenter; + + // Read vertical part of current alignment flags + Qt::Alignment vertAlign = blockFormat.alignment() & Qt::AlignVertical_Mask; + + // Mask out vertical part of specified alignment flags + Qt::Alignment horzAlign = alignment & Qt::AlignHorizontal_Mask; + + // Combine current vertical and specified horizontal alignment + Qt::Alignment combAlign = horzAlign | vertAlign; + + // Apply and write + blockFormat.setAlignment(combAlign); + + for (int i = 0; i < 3; ++i) + { + QTextTableCell cell = footerTable->cellAt(0, i); + QTextCursor cellCursor = cell.firstCursorPosition(); + cellCursor.setBlockFormat(blockFormat); + cellCursor.mergeCharFormat(defaultFormat); + cellCursor.insertText(sl.at(i)); + } + + // finally: + document.drawContents(&painter); + } + + emit pdfCompleted(outputFilename); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " outputFilename='" << outputFilename << "'"; +} + + + + +//---------------------------------------------------------------------------- +// MapConnector (Map -> MapObjects) +//---------------------------------------------------------------------------- +bool MapConnector::connectMapObject(MapImpl *mapImpl, AbstractMapObjectImpl *mapObjImpl) +{ + m_mapImpl = mapImpl; + m_mapObjectImpl = mapObjImpl; + if (m_mapImpl && m_mapObjectImpl) + { + connect(m_mapImpl, &MapImpl::extentChangedDelayed, + this, &MapConnector::onMapExtentChanged); + return true; + } + return false; +} + +void MapConnector::onMapExtentChanged() +{ + // if (m_mapImpl && m_mapObjectImpl) // checked in connectObject + m_mapObjectImpl->onMapExtentChanged(); + +} + +} // namespace SituationMap + +#include "map_impl.moc" diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.h new file mode 100755 index 000000000..71b4b4722 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_impl.h @@ -0,0 +1,519 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Map + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + + +// Own +#include // Interface +#include // Interface +#include + + +// ESRI +#include +#include +#include +#include + +namespace Esri +{ +namespace ArcGISRuntime +{ +class Map; +class Basemap; +class GeoView; +class MapGraphicsView; +class Scene; +class SceneGraphicsView; +class GraphicsOverlay; +class Layer; +class Point; +} +} + +class Grid; + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class LayerFactoryImpl; +class ObjectIndicator; +class LocationDisplay; +class ElevationMap; +class ElevationMapManager; + +namespace RB +{ +class RubberbandBase; +} + + +/** + * @brief The MapImpl + * + * + */ +class SITUATIONMAP_EXPORT MapImpl : public SituationMap::Map +{ + Q_OBJECT + + using BaseClass = SituationMap::Map; + +public: + + explicit MapImpl(QWidget *parent = nullptr); + virtual ~MapImpl(); + + MapImpl(const MapImpl &other) = delete; + MapImpl &operator=(const MapImpl &other) = delete; + + bool init(const Mode mapMode_2D3D) override; + + bool load() override; + + Mode mode() const override; + + void setParent(QWidget *parent, bool bBeginDestroyFini) override; + + + + double animationDuration() const override; + void setAnimationDuration(double durationSec) override; + + + void addMapWidget() override; + + bool setTextBox(const PixelTextBoxData &ptbd, bool enableGeometryModifications = true) override; + bool removeTextBox() override; + + +protected: + bool init2D(); + bool initFeature(); + bool init3D(); + + bool fini(); + void fini2D(); + void fini3D(); + + QWidget *addMapWidget_AddToScene(); + QWidget *addMapWidget_OwnWidget(); + void setupLocationToElevationHelper(); + + + static QImage renderCompass(const QColor &colorNorth, + const QColor &colorSouth, + const QColor &colorBackground, + int halfWidth, int halfHeight, + bool b_3D = false); + +public: + bool isPositionStringVisible() const override; + void showPositionString(bool visible = true) override; + void setPositionStringColor(const QColor &foregroundColor, const QColor &backgroundColor) override; + + void setPartitionTime(const qint64 &time) override; + void setReplay(bool b) override; + bool isReplay() const override; + bool isExerciseTimeStringVisible() const override; + void showExerciseTimeString(bool visible = false) override; + void setExerciseTimeStringColor(const QColor &foregroundColor, const QColor &backgroundColor) override; + + bool isTimeStringVisible() const override; + void setTimeString(const QString &timeString) override; + void showTimeString(bool visible = true) override; + void setTimeStringColor(const QColor &foregroundColor, const QColor &backgroundColor) override; + + bool isCompassVisible() const override; + void showCompass(bool visible = true) override; + + void addSideBar() override; + void removeSideBar() override; + QPointer sideBar() override; + + static bool applyLicense(const QString &licenseStr, const QStringList &extensions = QStringList()); + static bool applyNetworkCacheSettings(const EsriNetworkCacheSettings &esriNetworkCacheSettings); + static EsriNetworkCacheSettings networkCacheSettings(); + + + + Esri::ArcGISRuntime::Map *getEsriMap() const; + Esri::ArcGISRuntime::Basemap *getEsriBasemap() const; + Esri::ArcGISRuntime::SpatialReference getEsriSpatialReference() const; + Esri::ArcGISRuntime::Scene *getEsriScene() const; + Esri::ArcGISRuntime::GeoView *getEsriGeoView() const; + Esri::ArcGISRuntime::MapGraphicsView *getEsriMapGraphicsView() const; + Esri::ArcGISRuntime::SceneGraphicsView *getEsriSceneGraphicsView() const; + QGraphicsView *getEsriQGraphicsView() const; + QString toDumpString() const; + + bool isLoaded() const override; + QGraphicsView *getQGraphicsView() const override; + +// bool readSightFanElevationMap(const QString &filePath); +// std::shared_ptr getSightFanElevationMap() const; + + bool initElevationMapManager(const QStringList &filePaths_rough_to_fine); + std::shared_ptr getElevationMapManager() const; + + double pixelSizeMeter() override; + double pixelSizeGeographic() override; + + +public: + bool isReady() const override; + + DrawStatus drawStatus() const override; + + int wkid() const override; + + /*! \name Surface: Below Ground, introduced with V100.5.0 */ + //@{ + SituationMap::NavigationConstraint navigationConstraint() const override; + bool setNavigationConstraint(SituationMap::NavigationConstraint NavigationConstraint) override; + + float baseSurfaceOpacity() const override; + bool setBaseSurfaceOpacity(float surfaceOpacity) override; + //@} + + OverviewMapHandler &overviewMapHandler() const override; + + + OperatorMapSettings operatorMapSettings() const override; + bool restoreOperatorMapSettings(const OperatorMapSettings &oms) override; + + + // Layer : + LayerFactory &layerFactory() const override; + virtual LayerFactoryImpl &layerFactoryImpl() const; + + int basemapLayerCount() const; + int graphicsOverlayCount() const; + int technicalLayerCount() const override; + + + + // convenience : + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayer(const QString &logicalName); + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayerDict(const QString &logicalName); + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayer3DExtrusion(const QString &logicalName); + virtual Esri::ArcGISRuntime::GraphicsOverlay *getGraphicsLayer3D_SpAbsolute(const QString &logicalName); + // : convenience + // : Layer + + // Grid + Grid &grid() const override; + + + // Symbol Dictionary : + bool setSymbologyType(AdvancedSymbology::SymbologyType symbologyType) override; + AdvancedSymbology::SymbologyType symbologyType() override; + AdvancedSymbology::SymbolDictionary &symbolDictionary() const override; + bool isSymbolDictionaryLoaded() const override; + // : Symbol Dictionary + + + // Object Creation : (create MapObjects using a factory) + ObjectFactory &objectFactory() override; + + // For object management (ObjectContainer) + virtual ObjectContainer &objectContainer(); + void setObjectContainer(QSharedPointer objectContainer) override; + + PartyIdToColorFunctor &partyIdToColorFunctor() const override; + void setPartyIdToColorFunctor(QSharedPointer partyIdToColor) override; + + OverlayHandler &overlayHandler() const override; + + /*! \name JumpPoints... */ + //@{ + bool addJumpPoint(const QString &name, const Point3D &originGeo, double mapScale = -1) override; + bool removeJumpPoint(const QString &name) override; + QStringList jumpPointNames() const override; + //@} + + + + void initCoordTranslationFunctor() override; + + CoordTranslationFunctor *coordTranslationFunctor() const override; + // : Object Creation + + + // Map Extent : + Viewpoint2D initialViewpoint() const override; + void setInitialViewpoint(const Viewpoint2D &initialViewpoint) override; + + Viewpoint2D viewpoint2D() const override; + void setViewpoint2D(const Viewpoint2D &viewpoint, float durationSeconds = -1.0) override; + void setViewpoint2DAndWait(const Viewpoint2D &viewpoint) override; + + void setViewpoint2DInternal(const Viewpoint2D &viewpoint, bool andWait, float durationSeconds = -1.0); + + QPointF centerPoint() const override; + void setCenterPoint(const QPointF ¢er, double scale = -1.0, float durationSeconds = -1.0) override; + + void zoomToArea(const QVector &coords, int paddingInDIPS) override; + + void setTrainingsAreaExtent(const QVector &trainingsAreaExtent) override; + + QRectF extent() const override; + void setExtent(const QRectF &extent) override; + void setExtent(double xmin, double ymin, double xmax, double ymax) override; + + /** + * @brief SetExtentMapWorld is for INTERNAL use only + * @param xmin + * @param ymin + * @param xmax + * @param ymax + */ + virtual void setExtentMapWorld(double xmin, double ymin, double xmax, double ymax); + +// QRectF fullExtent() const override; + + + + double scale() const override; + void setScale(double absoluteScale) override; + + void setMinScale(double minScaleToLimitZoomOut) override; + double minScale() const override; + + void setMaxScale(double maxScaleToLimitZoomIn) override; + double maxScale() const override; + + bool scaleByArea() const override; + + // Rotation + double rotation() const override; + void setRotation(double absoluteAngleDegrees) override; + + // for 3D: + Camera camera() const override; + void setCamera(const Camera &camera, float durationSeconds = -1.0) override; + + + + // coordinate conversion + QPointF mapCoordinateToViewPortWindowPoint(const QPointF &mapPos) const override; + QPointF mapCoordinateToViewPortWindowPoint3D(const Point3D &mapPos) const override; + + QPointF viewPortWindowPointToMapCoordinate(const QPointF &pixelCoord) const override; // ret = Point3D + Point3D viewPortWindowPointToMapCoordinate3D(const QPointF &pixelCoord) const override; + + + QPointF geoCoordinateToViewPortWindowPoint(const QPointF &geoPos) const override; + QPointF geoCoordinateToViewPortWindowPoint3D(const Point3D &geoPos) const override; + + + QPointF viewPortWindowPointToGeoCoordinate(const QPointF &pixelCoord) const override; // ret = Point3D + Point3D viewPortWindowPointToGeoCoordinate3D(const QPointF &pixelCoord) const override; + + + QString geoToMgrsString(const QPointF &geoPos) const override; + QString mapToMgrsString(const QPointF &mapPos) const override; + + + GeometryModelRenderer *createGeometryModelRenderer(const QString &layerName, + GeometryModel *geometryModel, + QObject *parent = nullptr) override; + + // Rubberbands + RB::RubberbandFactory &rubberbandFactory() const override; + + // Pick objects by area + bool pickMapObjectsArea() const override; + + + // EventHandling + void installDropEventManager(DropEventManager *demgr) override; + void deinstallDropEventManager(DropEventManager *demgr) override; + + +// // MapObjectIndicator +// bool createMapObjectIndicator(UniqueMapId uniqueId) override; +// void removeMapObjectIndicator() override; +// UniqueMapId mapObjectIndicator() const override; + + + // ObjectIndicator + ObjectIndicator& objectIndicator() override; + + + // LocationDisplay + LocationDisplay &locationDisplay() override; + + + // PixelBasedTextBoxHandler + PixelBasedTextBoxHandler &pixelBasedTextBoxHandler() override; + + + // Tools (Measurement, Viewshed...) + Measurement &measurement() override; + TerrainProfile &terrainProfile() override; + Viewshed &viewshed() override; + + // Rocket + double rocketDuration() const override; + void setRocketDuration(double durationSec) override; + void startRocket() override; + bool rocketIsRunning() const override; + + // Symbol scale + double symbolScale() const override; + void setSymbolScale(double symbolScale) override; + + + void installViewPortEventFilter(QObject *eventFilter) override; + void removeViewPortEventFilter(QObject *eventFilter) override; + + QPointer createMapNotifier() const override; + + + // Screenshot etc: + QImage toImageSync() override; + QUuid toImage() override; + + virtual bool toPdf(const QString &outputFilename, + const QString &title, + const QString &footerLeft, + const QString &footerCenter, + const QString &footerRight, + const QColor &textColor = QColor()) override; + + + +protected: + + bool eventFilter(QObject *obj, QEvent *e) final; + +public slots: + void onHandleMouseMove(int windowPosX, int windowPosY); // used by EventDispatcher (Rubberband) + +protected slots: + void onErrorGeometryEngine(const Esri::ArcGISRuntime::Error &error); + void onErrorMap(const Esri::ArcGISRuntime::Error &error); + void onErrorScene(const Esri::ArcGISRuntime::Error &error); + void onErrorOperationalLayer(const Esri::ArcGISRuntime::Error &error); + void onErrorBasemap(const Esri::ArcGISRuntime::Error &error); + void onViewpointChanged(); + void onRectChanged(); + void onKeyPressEvent(const QKeyEvent& keyEvent); + + void onLoadStatusChanged(Esri::ArcGISRuntime::LoadStatus loadStatus); + void onDoneLoading(const Esri::ArcGISRuntime::Error &loadError); + + void onMouseScenePositionChanged(const Point3D &locationScene, bool isValid); + + + + void onRubberbandPickMapObjectsApply(RB::RubberbandBase *rubberband); + + + void finishToPdf(const QImage &image, + const QString &outputFilename, + const QString &title, + const QString &footerLeft, + const QString &footerCenter, + const QString &footerRight, + const QColor &textColor = QColor()); + + void onPartyChanged(int changedPartyId = -1); + void onAllPartysChanged(); + + + void onCheckMapReadyAndSymbolDictionaryLoaded(); + + + friend class LayerFactoryImpl; + friend class GridImpl; + friend class OverviewMapHandlerImpl; + + void emitAllElevationSourcesLoaded() + { + emit allElevationSourcesLoaded(); + onCheckMapReadyAndSymbolDictionaryLoaded(); + } + + void emitLayerCreateComplete(const QString &layername) + { + emit layerCreateComplete(layername); + } + void emitLayerCreateError(const QString &layername, const QString &error) + { + emit layerCreateError(layername, error); + } + void emitLayerOrderChanged() + { + emit layerOrderChanged(); + } + void emitLayerParameterChanged(const QString &layername, const LayerParameter &newLayerParameter) + { + emit layerParameterChanged(layername, newLayerParameter); + } + + void emitVisualComponentChanged(const VisualComponent &visualComponent); + + + void onClickMapObject(const QList &uniqueMapIdList, const QPointF &pos); + + void onContextClick(const QList &uniqueMapIdList, const QPointF &pos); + + void onWheelZoom(qreal factor, const QPointF &cursorPositionMap); + + void onRocketTimerZoomOutFinished(); + void onRocketTimerZoomInFinished(); + + void onName(const QString &name, const QPointF &coordGeo); + void onJumpPointRequest(const QPointF &viewPos); + + void onEsriRotate(); + +// void showFunctionDialog(); + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + + +//---------------------------------------------------------------------------- +// MapConnector (Map -> MapObjects) +//---------------------------------------------------------------------------- + +// forward: +class AbstractMapObjectImpl; + +class MapConnector : public QObject +{ + Q_OBJECT +public: + bool connectMapObject(MapImpl *mapImpl, AbstractMapObjectImpl *mapObjImpl); + +public Q_SLOTS: + void onMapExtentChanged(); + +private: + MapImpl *m_mapImpl = nullptr; // map (emitting) + AbstractMapObjectImpl *m_mapObjectImpl = nullptr; // 'host' (to be updated) +}; + + + +} // namespace SituationMap + +//---------------------------------------------------------------------------- + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.cpp new file mode 100755 index 000000000..03cf9ed9a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.cpp @@ -0,0 +1,325 @@ +/** + * @RDE_HEADER@ + * MapLayer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "map_layer.h" +#include +#include +#include + + +// ESRI +#include +#include +#include + + +// Qt +// #include + +namespace SituationMap +{ + +MapLayer::MapLayer(const QString &logicalName, + LayerType layerType) + : m_name(logicalName) + , m_layerType(layerType) +{ +} + +bool MapLayer::isValid() const +{ + return ((m_layerType != LayerType::Undefined) && (!m_name.isEmpty())); +} + +QString MapLayer::name() const +{ + return m_name; +} + +LayerType MapLayer::type() const +{ + return m_layerType; +} + + +//------------------------------------------------------------------------------ + +bool MapLayerOrderedList::addLayer(const QString &name, + LayerType layerType) +{ + if (name.isEmpty() || layerType == LayerType::Undefined) + return false; + + // Do NOT allow same names for different LayerTypes: + if (contains(name, LayerType::Undefined)) + return false; + + MapLayer temp(name, layerType); + if (!temp.isValid()) + return false; + + if (layerType == LayerType::Basemap) + { + // Basemap -> bottom; + int cnt = this->count(LayerType::Basemap); + m_mapLayerList.insert(cnt, temp); + } + else if (layerType == LayerType::Overlay) + { + // Overlay -> directly above Basemap; + int cntBM = this->count(LayerType::Basemap); + int cntOVL = this->count(LayerType::Overlay); + m_mapLayerList.insert(cntBM + cntOVL, temp); + } + else + m_mapLayerList.append(temp); + return true; +} + +bool MapLayerOrderedList::contains(const QString &name, + LayerType layerType) const +{ + bool found = false; + for (const MapLayer &layer : m_mapLayerList) + { + if (layerType == LayerType::Undefined) + { + if (layer.name().compare(name) == 0) + { + found = true; + break; + } + } + else + { + if ((layer.type() == layerType) && (layer.name().compare(name) == 0)) + { + found = true; + break; + } + } + } + return found; +} + +MapLayer MapLayerOrderedList::getLayer(const QString &name) const +{ + for (const MapLayer &layer : m_mapLayerList) + { + if (layer.name().compare(name) == 0) + return layer; + } + return MapLayer(); +} + +bool MapLayerOrderedList::removeLayer(const QString &name) +{ + // since a layername has to be unique independent it´s layertype: + +// QMutableListIterator does not realy work, Problems with MapLayer ??? +// QMutableListIterator contIt(m_mapLayerList); + +// while (contIt.hasNext()); +// { +// contIt.next(); +// const MapLayer & layer = contIt.value(); +// if (layer.name().compare(name) == 0) +// { +// contIt.remove(); +// return true; +// } +// } + + + for (int i = 0; i < m_mapLayerList.size(); ++i) + { + const MapLayer &layer = m_mapLayerList[i]; + if (layer.name().compare(name) == 0) + { + m_mapLayerList.removeAt(i); + return true; + } + } + + return false; +} + +int MapLayerOrderedList::count(LayerType layerType) const +{ + if (layerType == LayerType::Undefined) + return m_mapLayerList.size(); + + + int counter = 0; + for (const MapLayer &mapLayer : std::as_const(m_mapLayerList)) + { + if (mapLayer.type() == layerType) + ++counter; + } + return counter; +} + + +QStringList MapLayerOrderedList::layernamesOfType(const QStringList &layernames, LayerType layerType) +{ + QStringList layerCont; + for (const QString &lName : layernames) + { + const MapLayer &ml = getLayer(lName); + if (!ml.isValid()) + continue; + if (ml.type() == layerType) + layerCont.push_back(lName); + } + return layerCont; +} +QStringList MapLayerOrderedList::layernamesOfTypeOther(const QStringList &layernames) +{ + QStringList layerCont; + for (const QString &lName : layernames) + { + const MapLayer &ml = getLayer(lName); + if (!ml.isValid()) + continue; + if ( + (ml.type() != LayerType::Basemap) && + (ml.type() != LayerType::Overlay) && + (ml.type() != LayerType::Operational) && + (ml.type() != LayerType::EmergencyCall) && + (ml.type() != LayerType::Undefined) + ) + layerCont.push_back(lName); + } + return layerCont; +} +QStringList orderStringListPerType(const QStringList &slToOrder, const QStringList &slRequested) +{ + // + // !!!do not lose one layer existing in slToOrder!!! + // + + QStringList slNew = slToOrder; + + int newIdx = 0; + for (const QString &name : slRequested) + { + int idx = slNew.indexOf(name); + if (idx >= 0) + { // found + slNew.move(idx, newIdx++); + } + } + return slNew; +} + + +bool MapLayerOrderedList::orderLayerTo(const QStringList &orderedLayernamesReq, bool *bRequestedOrderIsValid) +{ + // cur =ArcGISTiledLayer_URL_0;ArcGISTiledLayer_URL_1;ArcGISTiledLayer_URL_2;OverlayLayer_1001;OverlayLayer_1002;...;JumpPoints;SnaphotOverlay + // should=ArcGISTiledLayer_URL_0;ArcGISTiledLayer_URL_1;ArcGISTiledLayer_URL_2;OverlayLayer_1002;OverlayLayer_1001;...;JumpPoints + + QStringList slMapLayerList; + slMapLayerList.reserve(m_mapLayerList.size()); + for (const MapLayer &ml : std::as_const(m_mapLayerList)) + slMapLayerList.push_back(ml.name()); + + // + // !!!do not lose one layer existing in the m_mapLayerList!!! + // + + QStringList slBasemapNew; + { + QStringList slCur = layernamesOfType(slMapLayerList, LayerType::Basemap); + QStringList slReq = layernamesOfType(orderedLayernamesReq, LayerType::Basemap); + slBasemapNew = orderStringListPerType(slCur, slReq); + } + + QStringList slOverlayNew; + { + QStringList slCur = layernamesOfType(slMapLayerList, LayerType::Overlay); + QStringList slReq = layernamesOfType(orderedLayernamesReq, LayerType::Overlay); + slOverlayNew = orderStringListPerType(slCur, slReq); + } + + QStringList slOperationalNew; + { + QStringList slCur = layernamesOfType(slMapLayerList, LayerType::Operational); + QStringList slReq = layernamesOfType(orderedLayernamesReq, LayerType::Operational); + slOperationalNew = orderStringListPerType(slCur, slReq); + } + + QStringList slOtherNew; + { + QStringList slCur = layernamesOfTypeOther(slMapLayerList); + QStringList slReq = layernamesOfTypeOther(orderedLayernamesReq); + slOtherNew = orderStringListPerType(slCur, slReq); + } + + // Emergency should stay on top + QStringList slEmergencyCallNew; + { + QStringList slCur = layernamesOfType(slMapLayerList, LayerType::EmergencyCall); + QStringList slReq = layernamesOfType(orderedLayernamesReq, LayerType::EmergencyCall); + slEmergencyCallNew = orderStringListPerType(slCur, slReq); + } + + // put all basemapLayer to front, then overlayLayers, then others and finally EmergencyCall on top + const QStringList &slMapLayerListNEW = slBasemapNew + slOverlayNew + slOperationalNew + slOtherNew + slEmergencyCallNew; + + const bool localRequestedOrderIsValid = slMapLayerListNEW == orderedLayernamesReq; + + if (bRequestedOrderIsValid) + { + // just request, do NOT modify !!! + *bRequestedOrderIsValid = localRequestedOrderIsValid; + } else { + QList mlListNEW; + for(const QString &lName : slMapLayerListNEW) + { + const MapLayer &ml = getLayer(lName); + if (!ml.isValid()) + continue; + mlListNEW.push_back(ml); + } + m_mapLayerList = mlListNEW; + } + + return true; +} + + +int MapLayerOrderedList::count() const +{ + return m_mapLayerList.count(); +} + +QStringList MapLayerOrderedList::layerNamesOrdered() const +{ + QStringList ll; + ll.reserve(m_mapLayerList.size()); + for (const MapLayer &layer : m_mapLayerList) + ll.append(layer.name()); + return ll; +} + +QStringList MapLayerOrderedList::layerNamesOrdered(LayerType layerType) const +{ + QStringList ll; + for (const MapLayer &layer : m_mapLayerList) + { + if (layer.type() == layerType) + ll.append(layer.name()); + } + return ll; +} + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.h new file mode 100755 index 000000000..7086961bb --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/map_layer.h @@ -0,0 +1,94 @@ +/** + * @RDE_HEADER@ + * MapLayer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MapImpl; + + +class MapLayer +{ +public: + + explicit MapLayer(const QString &logicalName = QString(), + LayerType layerType = LayerType::Undefined); + + bool isValid() const; + + // for some read-only values : + QString name() const; + LayerType type() const; + // : for some read-only values + +protected: + QString m_name; + LayerType m_layerType = LayerType::Undefined; + +}; + +/** + * @brief The MapLayerOrderedList class will store an ordered List of the MapLayer. + * The List can be used to get the orderedList of layernames as well as + * control the parameters like on/off, brightness etc. + */ +class MapLayerOrderedList final : public QObject +{ + Q_OBJECT + using BaseClass = QObject; + +public: + MapLayerOrderedList() = default; + ~MapLayerOrderedList() = default; + + + /** + * @brief addLayer add Layername. Will NOT be added if name already exists (regardless layerType) + * !All LayerType::Basemap will below all other! + * @param name Layername to be added. Must be unique! + * @param layerType layerType to be added (Undefined is NOT allowed) + * @return true on success, otherwise false + */ + bool addLayer(const QString &name, LayerType layerType); + bool contains(const QString &name, LayerType layerType) const; + MapLayer getLayer(const QString &name) const; + bool removeLayer(const QString &name); // layername has to be unique + int count() const; + int count(LayerType layerType) const; + + /** + * @brief Order layer according new list + * @param orderedLayernamesReq requested new order + * @param bRequestedOrderIsValid if set just a check will be performed. The result bool contains the check result. + * @return true on success, otherwise false + */ + bool orderLayerTo(const QStringList &orderedLayernamesReq, bool *bRequestedOrderIsValid = nullptr); + + + QStringList layerNamesOrdered() const; + QStringList layerNamesOrdered(LayerType layerType) const; + + +protected: + QStringList layernamesOfType(const QStringList &layernames, LayerType layerType); + QStringList layernamesOfTypeOther(const QStringList &layernames); + QList m_mapLayerList; ///< using logical layernames +}; + + + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.cpp new file mode 100755 index 000000000..52a957400 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.cpp @@ -0,0 +1,428 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "measurement_impl.h" +#include "layer_factory_impl.h" +#include "coord_translation_functor_impl.h" +#include "Rubberbands/rubberband_factory.h" +#include "Rubberbands/rubberband_multi_point_feature.h" + +#include "Map/object_factory.h" +#include "MapObjects/polyline.h" +#include "MapObjects/geo_image.h" +#include "MapObjects/text.h" +#include "MapImpl/abstract_map_object_impl.h" + + +// Esri +#include +#include +#include +#include +#include + + +// Qt + + +namespace SituationMap +{ + + +class MeasurementImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + SituationMap::MapImpl *m_mapImpl = nullptr; + QString m_toolsLayername; + QColor m_color = Qt::magenta; + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + QPointer m_rubberband; + + QVector m_coordsGeo; + QVector m_coordsMap; + + QVector m_lengthPerSegment; + double m_totalLength = 0; + + RefPointer m_polyline; + QVector> m_markerCont; + RefPointer m_textTotalLength; + + void resizeMarkerCont(int newSize) + { + if (newSize < 1) + return; + + // increase + if (m_markerCont.size() < newSize) + { + m_markerCont.resize(newSize); + return; + } + + // decrease + while (m_markerCont.size() > newSize) + { + // do not call 'takeLast' on empty container. But since newSize is >= 0... + RefPointer item = m_markerCont.takeLast(); + item->removeMySelf(); + } + } + + +}; // class MeasurementImpl::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class MeasurementImpl +//----------------------------------------------------------------------------- +MeasurementImpl::MeasurementImpl(SituationMap::MapImpl *mapImpl) + : Measurement() + , QObject() + , m_logger(getLogger("MeasurementImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_mapImpl = mapImpl; + init(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +MeasurementImpl::~MeasurementImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->clear(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +long MeasurementImpl::measureLength() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // cleanup old + clear(); + + startRubberband(); + + return -1; +} + +bool MeasurementImpl::startRubberband() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // verify mapReady + if (!d->m_mapImpl || !d->m_mapImpl->isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Failure - map is not ready yet!!!"; + return false; + } + + RB::RubberbandMultiPointFeature *rubberband = this->createRubberband(); + if (!rubberband) + return false; + + d->m_rubberband = rubberband; + + connect(rubberband, &SituationMap::RB::Rubberband::abort, this, &MeasurementImpl::onRbAbort, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::interimResultsChanged, this, &MeasurementImpl::onRbInterimResultsChanged, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, &MeasurementImpl::onRbApply, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::RubberbandMultiPointFeature::currentCursorPositionChanged, this, &MeasurementImpl::onCurrentCursorPositionChanged, Qt::UniqueConnection); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + + +void MeasurementImpl::clear() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_coordsGeo.clear(); + d->m_coordsMap.clear(); + d->m_lengthPerSegment.clear(); + d->m_totalLength = 0; + + if (d->m_polyline) + d->m_polyline->removeMySelf(); + d->m_polyline.reset(); + + QMutableVectorIterator> it(d->m_markerCont); + while (it.hasNext()) + { + RefPointer &marker = it.next(); + if (marker) + marker->removeMySelf(); + } + d->m_markerCont.clear(); + + if (d->m_textTotalLength) + d->m_textTotalLength->removeMySelf(); + d->m_textTotalLength.reset(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool MeasurementImpl::displayPolyline() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->m_coordsGeo.size() < 2) + return false; + + // create + if (!d->m_polyline) + { + d->m_polyline = d->m_mapImpl->objectFactory().create(d->m_toolsLayername, + QStringLiteral("MeasurementPolyline")); + d->m_polyline->setLineStyle(LineStyle(d->m_color, 3)); + d->m_polyline->setScaleEnable(true); + d->m_polyline->setPickable(false); + } + + // apply data + d->m_polyline->setPositions(d->m_coordsGeo); + for (int segment = 0; segment < d->m_lengthPerSegment.size(); ++segment) + { + const int dist = std::round(d->m_lengthPerSegment[segment]); + d->m_polyline->setTextOnSegment(segment, QStringLiteral("%1m").arg(dist)); + // OutlineColor/Width is not supported yet + } // for + + // update + if (!d->m_polyline->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " polyline::updateView failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool MeasurementImpl::displayVertices() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const int size = d->m_coordsGeo.size(); + if (size < 1) + return false; + + d->resizeMarkerCont(size); + + int idx = 0; + QMutableVectorIterator> it(d->m_markerCont); + + while (it.hasNext()) + { + RefPointer &marker = it.next(); + if (!marker.valid()) + { + marker = d->m_mapImpl->objectFactory().create(d->m_toolsLayername, + QStringLiteral("MeasurementMarker%1").arg(idx)); + marker->setProxyMarker(MarkerSymbolType::Circle); + marker->setProxyMarkerColor(d->m_color); + marker->setProxyMarkerSize(7); + marker->setPickable(false); + } + marker->setOrigin(d->m_coordsGeo[idx]); + + // update + if (!marker->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " marker::updateView failed"; + + ++idx; + } // while + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool MeasurementImpl::displayTotalLength() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->m_coordsGeo.size() < 2) + return false; + + if (!d->m_textTotalLength.valid()) + { + d->m_textTotalLength = d->m_mapImpl->objectFactory().create(d->m_toolsLayername, + QStringLiteral("MeasurementText")); + TextStyle ts = AbstractMapObjectImpl::defaultTextStyle(); + ts.setTextColor(d->m_color); + ts.setBold(true); + ts.setSize(ts.size() * 1.5); + d->m_textTotalLength->setTextStyle(ts); + } + const int dist = std::round(d->m_totalLength); + d->m_textTotalLength->setIdentifier(QStringLiteral("%1m").arg(dist)); + d->m_textTotalLength->setOrigin(d->m_coordsGeo.last()); + + // update + if (!d->m_textTotalLength->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " textTotalLength::updateView failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool MeasurementImpl::calculateLength(const QVector &mapCoords) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (mapCoords.size() < 2) + return false; + + static Esri::ArcGISRuntime::GeometryEngine *gEngine = Esri::ArcGISRuntime::GeometryEngine::instance(); + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + + d->m_totalLength = 0; + d->m_lengthPerSegment.clear(); + d->m_lengthPerSegment.resize(mapCoords.size() - 1); + + QVectorIterator it(mapCoords); + int segment = 0; + while (it.hasNext()) + { + const Point3D &mapCoord1 = it.next(); + if (!it.hasNext()) + break; + const Point3D &mapCoord2 = it.peekNext(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " segment=" << segment; + + Esri::ArcGISRuntime::Point geom1(mapCoord1.x(), mapCoord1.y(), mapCoord1.z(), sr); + Esri::ArcGISRuntime::Point geom2(mapCoord2.x(), mapCoord2.y(), mapCoord2.z(), sr); + + // const double distancePlanar = gEngine->distance(geom1, geom2); + // gEngine->distance return wrong results, if Topographic.tpk is used. Most likly due to wkid. + // Use distanceGeodetic, that works (at least in 2D) for 32632, 3857 + Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistanceResult = gEngine->distanceGeodetic(geom1, geom2, + Esri::ArcGISRuntime::LinearUnit(Esri::ArcGISRuntime::LinearUnitId::Meters), + Esri::ArcGISRuntime::AngularUnit(Esri::ArcGISRuntime::AngularUnitId::Degrees), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + + d->m_lengthPerSegment[segment] = geodeticDistanceResult.distance(); + d->m_totalLength += geodeticDistanceResult.distance(); + DEBUG_LOG(m_logger) << LOGFUNCTION + << " segment=" << segment + << " -> m_totalLength = " << d->m_totalLength; + ++segment; + } // while + + DEBUG_LOG(m_logger) << LOGFUNCTION << " distancePlanar = " << d->m_totalLength; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void MeasurementImpl::onRbAbort(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + clear(); + if (!rbnd) + return; + DEBUG_LOG(m_logger) << LOGFUNCTION << " rb abort"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void MeasurementImpl::onRbInterimResultsChanged(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + return; + + d->m_coordsMap = SituationMap::convert2D3D(rbnd->coordinateVector(RB::CoordinateSystem::Map)); + d->m_coordsGeo = SituationMap::convert2D3D(rbnd->coordinateVector(RB::CoordinateSystem::Geo)); + + calculateLength(d->m_coordsMap); + displayTotalLength(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void MeasurementImpl::onRbApply(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + return; + + d->m_coordsMap = SituationMap::convert2D3D(rbnd->coordinateVector(RB::CoordinateSystem::Map)); + d->m_coordsGeo = SituationMap::convert2D3D(rbnd->coordinateVector(RB::CoordinateSystem::Geo)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " rb apply, d->m_coordsMap.size()=" << d->m_coordsMap.size(); + + calculateLength(d->m_coordsMap); + displayPolyline(); + displayVertices(); + displayTotalLength(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void MeasurementImpl::onCurrentCursorPositionChanged(const Point3D &mapCoord) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " mapCoord = " << mapCoord; + + d->m_coordsMap = SituationMap::convert2D3D(d->m_rubberband->coordinateVector(RB::CoordinateSystem::Map)); + d->m_coordsGeo = SituationMap::convert2D3D(d->m_rubberband->coordinateVector(RB::CoordinateSystem::Geo)); + // DEBUG_LOG(m_logger) << LOGFUNCTION << " rb apply, d->m_coordsMap.size()=" << d->m_coordsMap.size(); + + // append coord of current mouse position: + d->m_coordsMap += mapCoord; + const Point3D &geoCoord = d->m_mapImpl->coordTranslationFunctor()->mapToGeographic(mapCoord); + d->m_coordsGeo += geoCoord; + + calculateLength(d->m_coordsMap); + displayPolyline(); + displayVertices(); + displayTotalLength(); +} + +void MeasurementImpl::init() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_graphicsLayer) + { + LayerFactoryImpl &layerFactory = d->m_mapImpl->layerFactoryImpl(); + + d->m_toolsLayername = layerFactory.toolLayer(); + d->m_graphicsLayer = layerFactory.getGraphicsLayer(d->m_toolsLayername); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +RB::RubberbandMultiPointFeature *MeasurementImpl::createRubberband() +{ + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + RB::RubberbandMultiPointFeature *rubberband = + d->m_mapImpl->rubberbandFactory().create(this); + if (!rubberband || !rubberband->rubberbandIsAlive()) + return nullptr; + rubberband->setAutoDelete(true); + rubberband->enableClosed(false); + rubberband->minimumPointCount(2); + rubberband->maximumPointCount(11); +// rubberband->maximumLength(maxSegmentLength); + return rubberband; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.h new file mode 100755 index 000000000..f05502861 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/measurement_impl.h @@ -0,0 +1,66 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include "map_impl.h" +#include "Rubberbands/rubberband_multi_point_feature.h" + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MeasurementImpl : public QObject, public virtual Measurement +{ + Q_OBJECT + +public: + explicit MeasurementImpl(SituationMap::MapImpl *mapImpl); + virtual ~MeasurementImpl(); + + MeasurementImpl(const MeasurementImpl &other) = delete; + MeasurementImpl &operator=(MeasurementImpl &other) = delete; + + long measureLength() override; + void clear() override; + + bool displayPolyline(); + bool displayVertices(); + bool displayTotalLength(); + bool calculateLength(const QVector &mapCoords); + +protected Q_SLOTS: + void onRbAbort(RB::RubberbandBase *rbnd); + void onRbInterimResultsChanged(RB::RubberbandBase *rbnd); + void onRbApply(RB::RubberbandBase *rbnd); + void onCurrentCursorPositionChanged(const Point3D &mapCoord); + +private: + void init(); + bool startRubberband(); + RB::RubberbandMultiPointFeature *createRubberband(); + + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.cpp new file mode 100755 index 000000000..7e478d9cb --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.cpp @@ -0,0 +1,134 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of ObjectContainer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "object_container_impl.h" + +namespace SituationMap +{ + +ObjectContainerCTC::ObjectContainerCTC(const QSharedPointer &idGenerator) + : BaseClass(idGenerator) + , m_logger(getLogger("ObjectContainerCTC")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +ObjectContainerCTC::~ObjectContainerCTC() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +ObjectContainer::UniqueObjectId ObjectContainerCTC::insertObject(ObjectContainer::Object *object) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + UniqueObjectId newId = m_idGenerator->getNextId(); + m_map.insert(newId, RefPointer(object)); + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " uniqueId = " << newId; + return newId; +} + +ObjectContainer::UniqueObjectId ObjectContainerCTC::insertObject(ObjectContainer::Object *object, const ObjectContainer::UniqueObjectId &uid) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if ((uid >= m_idGenerator->nextFreeId()) || (INVALID_MAP_ID == uid) || containsObject(uid)) + { + // uid is unvalid and can't be used. In this case insert the map object with a new id. + return insertObject(object); + } + + m_map.insert(uid, RefPointer(object)); + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " uniqueId = " << uid; + return uid; +} + +bool ObjectContainerCTC::removeObject(const ObjectContainer::UniqueObjectId &id) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " uniqueId = " << id; + bool success = (m_map.remove(id) == 1); + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " uniqueId = " << id + << " success = " << success; + return success; +} + +void ObjectContainerCTC::clear() +{ + m_map.clear(); +} + +ObjectContainer::Object *ObjectContainerCTC::getObject(const ObjectContainer::UniqueObjectId &id) const +{ + auto it = m_map.constFind(id); + if (it == m_map.constEnd()) + return nullptr; + RefPointer obj = it.value(); + return obj.get(); +} + +QList ObjectContainerCTC::getAllObjects() const +{ + QList list; + + QMapIterator< UniqueObjectId, RefPointer > it(m_map); + while (it.hasNext()) + { + it.next(); + const RefPointer &obj = it.value(); + if (obj) + list.append(const_cast(obj.get())); + } // while + + return list; +} + +QList ObjectContainerCTC::getAllObjects(const std::type_index &type) const +{ + QList list; + + QMapIterator< UniqueObjectId, RefPointer > it(m_map); + while (it.hasNext()) + { + it.next(); + const RefPointer &obj = it.value(); + + // !!! get typ_index of object, not pointer to object: !!! + const Object *objGet = obj.get(); + if (type == std::type_index(typeid(*objGet))) + list.append(const_cast(objGet)); + } // while + + return list; +} + +QList ObjectContainerCTC::getAllKeys() const +{ + return m_map.keys(); +} + +bool ObjectContainerCTC::containsObject(const ObjectContainer::UniqueObjectId &id) const +{ + return m_map.contains(id); +} + +qint64 ObjectContainerCTC::countObjects() const +{ + return m_map.size(); +} + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.h new file mode 100755 index 000000000..af7dfa9e6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_container_impl.h @@ -0,0 +1,105 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of ObjectContainer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + + +// Own +#include +#include +#include + + +// Qt +#include + +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The ObjectContainerCTC class implements the interface SituationMap::ObjectContainer. + * It is a map of UniqueMapId to MapObjects. + * It uses a external IdGenerator to generate a next UniqueMapId returned + * while inserting. + * For documentation of inherented methods, see interface class SituationMap::ObjectContainer. + */ +class SITUATIONMAP_EXPORT ObjectContainerCTC : public ObjectContainer +{ + + using BaseClass = ObjectContainer; + +public: + /*! \name Construction, Destruction */ + //@{ + /** + * @brief ObjectContainerCTC. + * @param idGenerator to be used + * (can be shared among different ObjectContainer instances). + */ + explicit ObjectContainerCTC(const QSharedPointer &idGenerator); + virtual ~ObjectContainerCTC(); + //@} + + /*! \name Insert, Remove, Clear */ + //@{ + + virtual UniqueObjectId insertObject( Object *object ) override; + virtual UniqueObjectId insertObject( ObjectContainer::Object *object + , const ObjectContainer::UniqueObjectId &uid ) override; + virtual bool removeObject( const UniqueObjectId &id ) override; + + virtual void clear() override; + //@} + + /*! \name Access */ + //@{ + virtual QList getAllKeys() const override; + + virtual Object *getObject(const UniqueObjectId &id) const override; // Returns pointer to object + virtual QList getAllObjects() const override; + virtual QList getAllObjects(const std::type_index &type) const override; // without template + + QMutableMapIterator> mutableIterator() override + { + return QMutableMapIterator> (m_map); + } + + //@} + + + /*! \name Check */ + //@{ + virtual bool containsObject(const UniqueObjectId &id) const override; + //@} + + + + /*! \name Count */ + //@{ + /** + * @brief Number of objects. + * @return Current number of objects. + */ + virtual qint64 countObjects() const override; + //@} + + QMap > m_map; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.cpp new file mode 100755 index 000000000..b0674c087 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.cpp @@ -0,0 +1,517 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of ObjectIndicator + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "object_indicator_impl.h" +#include +#include +#include +#include +#include +#include + + +// ESRI +#include +#include + + +// Qt +#include + + +#include + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class ObjectIndicatorImpl +//----------------------------------------------------------------------------- +ObjectIndicatorImpl::ObjectIndicatorImpl() + : BaseClass() + , m_logger(getLogger("ObjectIndicatorImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_markerSize = 15; // size of filled circle at the origin (indicating the center of the map) + m_lineWidth = 3; // lineWidth of (dashed) line from origin to target + + // updateTimer: + m_updateTimer = new QTimer(this); + m_updateTimer->setSingleShot(true); + m_updateTimer->setInterval(100); + connect(m_updateTimer, &QTimer::timeout, this, &ObjectIndicatorImpl::updateImpl); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool ObjectIndicatorImpl::init(MapImpl *mapImpl) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_mapImpl = mapImpl; + + if (!m_mapImpl) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " m_map_impl is nullptr, init failed"; + return false; + } + + // to ensure graceful shutdown: (remove QGraphicsItems BEFORE qGraphicsScene/qGraphicsView is destroyed: + connect(m_mapImpl, &Map::beginDestroy, this, &ObjectIndicatorImpl::onMapDestroyed, Qt::UniqueConnection); + + m_qGraphicsView = static_cast(m_mapImpl->getEsriQGraphicsView()); + if (m_qGraphicsView) + m_qGraphicsScene = m_qGraphicsView->scene(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +ObjectIndicatorImpl::~ObjectIndicatorImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + remove(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +UniqueMapId ObjectIndicatorImpl::mapObjectToPointTo() +{ + return m_uniqueIdToPointTo; +} + +void ObjectIndicatorImpl::setMapObjectToPointTo(const UniqueMapId &newUniqueId, bool setSelected, int lifetimeMsec) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, newUniqueId = " << newUniqueId + << ", (old m_uniqueIdToPointTo = " << m_uniqueIdToPointTo + << ", lifetimeMsec=" << lifetimeMsec; + + m_targetGeo2D = QPointF(); + + connect(m_mapImpl, &MapImpl::extentChangedDelayed, + this, &ObjectIndicatorImpl::update, Qt::UniqueConnection); + + if (m_uniqueIdToPointTo != newUniqueId) + { + addRemoveCallback(m_uniqueIdToPointTo, AddRemove::Remove); + m_uniqueIdToPointTo = newUniqueId; + addRemoveCallback(m_uniqueIdToPointTo, AddRemove::Add); + } + + update(); + + if (m_lifetimeTimer) + m_lifetimeTimer->stop(); + + if (lifetimeMsec > 1) + { + if (!m_lifetimeTimer) + { + m_lifetimeTimer = new QTimer(this); + m_lifetimeTimer->setSingleShot(true); + connect(m_lifetimeTimer.data(), &QTimer::timeout, this, &ObjectIndicatorImpl::remove); // lifetime expired + } + m_lifetimeTimer->setInterval(lifetimeMsec); + m_lifetimeTimer->start(); + } + + m_setSelected = setSelected; + if (m_setSelected) + setMapObjectSelected(m_setSelected); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, newUniqueId = " << newUniqueId; +} + +void ObjectIndicatorImpl::setPointToPointTo(const QPointF &targetGeo, int lifetimeMsec) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, targetGeo = " << targetGeo + << ", lifetimeMsec=" << lifetimeMsec; + + connect(m_mapImpl, &MapImpl::extentChangedDelayed, + this, &ObjectIndicatorImpl::update, Qt::UniqueConnection); + + clearCurrentSelection(); + + addRemoveCallback(m_uniqueIdToPointTo, AddRemove::Remove); + m_uniqueIdToPointTo = INVALID_MAP_ID; + m_targetGeo2D = targetGeo; + + update(); + + if (m_lifetimeTimer) + m_lifetimeTimer->stop(); + + if (lifetimeMsec > 1) + { + if (!m_lifetimeTimer) + { + m_lifetimeTimer = new QTimer(this); + m_lifetimeTimer->setSingleShot(true); + connect(m_lifetimeTimer.data(), &QTimer::timeout, this, &ObjectIndicatorImpl::remove); // lifetime expired + } + m_lifetimeTimer->setInterval(lifetimeMsec); + m_lifetimeTimer->start(); + } +} + +void ObjectIndicatorImpl::clearCurrentSelection() +{ + ObjectContainer &objCont = m_mapImpl->objectContainer(); + const QList &allObj = objCont.getAllObjects(); + + for (ObjectContainer::Object* mapObj : allObj) + { + if (!mapObj) + continue; + if (!mapObj->selected()) + continue; + mapObj->setSelected(false); + mapObj->updateView(); + } +} + +bool ObjectIndicatorImpl::update() +{ + if (!m_updateTimer) + return false; + m_updateTimer->start(); // start is restart + return true; +} + +bool ObjectIndicatorImpl::updateImpl() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << ", m_uniqueIdToPointTo = " << m_uniqueIdToPointTo; + + if (!m_qGraphicsView) + return false; + if (!m_qGraphicsScene) + return false; + + if (!SituationMap::isUniqueMapIdValid(m_uniqueIdToPointTo) && m_targetGeo2D.isNull()) + return false; + + + // + // Line: + // + Point3D targetGeo; + QColor color = m_defaultColor; + + + // center: + bool targetIsValid = false; + if (!m_targetGeo2D.isNull()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " target: m_targetGeo2D= " << m_targetGeo2D; + targetGeo = Point3D::fromQPointF(m_targetGeo2D); + targetIsValid = true; + } else { + DEBUG_LOG(m_logger) << LOGFUNCTION << " target: m_uniqueIdToPointTo= " << m_uniqueIdToPointTo; + if (!SituationMap::isUniqueMapIdValid(m_uniqueIdToPointTo)) + return false; + targetIsValid = centerOfTarget(targetGeo, color); + } + + + QRectF rect = m_qGraphicsScene->sceneRect(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " rect: " << rect; + + QBrush brush(color, Qt::SolidPattern); + QPen pen(brush, m_lineWidth, Qt::DashLine); + + QPointF targetSceneCoordinate; + if (targetIsValid) + { + if (m_mapImpl->getElevationMapManager()) + { + const double ele = m_mapImpl->getElevationMapManager()->elevation(targetGeo.x(), targetGeo.y()); + targetGeo.rz() += ele; // targetGeo should contain an absolute z-value + } + QPointF targetWindowCoord = m_mapImpl->geoCoordinateToViewPortWindowPoint3D(targetGeo); + targetSceneCoordinate = m_qGraphicsView->mapToScene(targetWindowCoord.toPoint()); + if (targetSceneCoordinate.isNull()) + { + INFO_LOG(m_logger) << LOGFUNCTION << " targetSceneCoordinate invisible!!!?"; + targetSceneCoordinate = rect.center(); + } + + QLineF lineData(rect.center(), targetSceneCoordinate); + if (!m_qGraphicsLine) + m_qGraphicsLine = m_qGraphicsScene->addLine(lineData, pen); + m_qGraphicsLine->setLine(lineData); + m_qGraphicsLine->setPen(pen); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << ", rect.center() " << rect.center() + << ", bCenterOfTarget " << targetIsValid + << ", targetGeo:" << targetGeo + << ", targetSceneCoordinate " << targetSceneCoordinate; + } + else + { + delete m_qGraphicsLine; + m_qGraphicsLine = nullptr; + targetSceneCoordinate = rect.center(); + } + + // + // ArrowTip: + // + m_arrowTip = QPixmap::fromImage(renderArrowHeadSymbol(color, 30, 10)); + + if (!m_qGraphicsArrowTip) + { + m_qGraphicsArrowTip = m_qGraphicsScene->addPixmap(m_arrowTip); + m_qGraphicsArrowTip->setOffset(m_arrowTip.width() / -2.0, 0); + } + m_qGraphicsArrowTip->setPixmap(m_arrowTip); + m_qGraphicsArrowTip->setPos(targetSceneCoordinate); + + const double lineAngle = MathUtils::getAngleDegreeNorth(targetSceneCoordinate, rect.center()); + + const double angle = 360.0 - (lineAngle); + DEBUG_LOG(m_logger) << LOGFUNCTION << ", lineAngle " << lineAngle + << ", angle " << angle; + + // Sets the clockwise rotation angle, in degrees, around the Z axis. + m_qGraphicsArrowTip->setRotation(angle); + + // + // centerMarker: + // + rect = QRect(rect.center().x() - m_markerSize * 0.5, rect.center().y() - m_markerSize * 0.5, + m_markerSize, m_markerSize); + + if (!m_qGraphicsCenterMarker) + m_qGraphicsCenterMarker = m_qGraphicsScene->addEllipse(rect, pen, brush); + m_qGraphicsCenterMarker->setRect(rect); + m_qGraphicsCenterMarker->setPen(QPen(color, Qt::DashLine)); + m_qGraphicsCenterMarker->setBrush(brush); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void ObjectIndicatorImpl::onMapDestroyed() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + remove(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +AbstractMapObjectImpl *ObjectIndicatorImpl::getObject(const UniqueMapId &uniqueId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, uniqueId = " << uniqueId; + + if (!SituationMap::isUniqueMapIdValid(uniqueId)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueId invalid: " << uniqueId; + return nullptr; + } + + if (!m_mapImpl->objectFactory().contains(uniqueId)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_uniqueIdToPointTo not found: " << m_uniqueIdToPointTo; + return nullptr; + } + + AbstractMapObjectImpl *impl = dynamic_cast(m_mapImpl->objectFactory().idToMapObject(uniqueId)); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, uniqueId = " << uniqueId; + return impl; +} + +bool ObjectIndicatorImpl::addRemoveCallback(const UniqueMapId &uniqueId, AddRemove addRemove) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, uniqueId = " << uniqueId + << " addRemove = " << (int)addRemove; + + if (!SituationMap::isUniqueMapIdValid(uniqueId)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueId invalid: " << uniqueId; + return false; + } + + AbstractMapObjectImpl *impl = getObject(uniqueId); + + if (!impl) + return false; + + const QString &objToPointToDebugInfo = impl->debugInfo(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " uniqueId is valid, uniqueId=" << uniqueId + << ", (objToPointToDebugInfo=" << objToPointToDebugInfo << ")"; + + if (addRemove == AddRemove::Add) + impl->addChangedCallback(this); + else + impl->removeChangedCallback(this); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, uniqueId = " << uniqueId + << ", addRemove = " << (int)addRemove; + return true; +} + +void ObjectIndicatorImpl::remove() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, m_uniqueIdToPointTo=" << m_uniqueIdToPointTo; + disconnect(this); + + if (m_updateTimer) + m_updateTimer->stop(); + + addRemoveCallback(m_uniqueIdToPointTo, AddRemove::Remove); + + // if we set the object selected, remove selection + if (m_setSelected) + setMapObjectSelected(false); + + m_uniqueIdToPointTo = INVALID_MAP_ID; + m_targetGeo2D = QPointF(); + delete m_qGraphicsCenterMarker; + delete m_qGraphicsLine; + delete m_qGraphicsArrowTip; + + m_qGraphicsCenterMarker = nullptr; + m_qGraphicsLine = nullptr; + m_qGraphicsArrowTip = nullptr; + + updateImpl(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, m_uniqueIdToPointTo=" << m_uniqueIdToPointTo; +} + +void ObjectIndicatorImpl::onOriginPositionChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " update..."; + update(); +} + +bool ObjectIndicatorImpl::centerOfTarget(Point3D &objCenterPoint, QColor &objColor) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // + // point to desired obj: + // + RefPointer objToPointTo; + if (!SituationMap::isUniqueMapIdValid(m_uniqueIdToPointTo)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_uniqueIdToPointTo invalid: " << m_uniqueIdToPointTo; + return false; + } + + if (!m_mapImpl->objectFactory().contains(m_uniqueIdToPointTo)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_uniqueIdToPointTo not found: " << m_uniqueIdToPointTo; + return false; + } + + objToPointTo = m_mapImpl->objectFactory().get(m_uniqueIdToPointTo); + + // retrive values from objToPointTo: + + if (!objToPointTo.valid()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " objToPointTo not found"; + return false; + } + + AbstractMapObjectImpl *impl = dynamic_cast(objToPointTo.get()); + if (!impl) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " dynamic_cast failed"; + return false; + } + + // get target position: + BoundingBox bbox; + objToPointTo->boundingBox(bbox); + if (!bbox.isValid()) + WARNING_LOG(m_logger) << LOGFUNCTION << " boundingBox not valid: bbox = " << bbox; + + const QString &objToPointToDebugInfo = objToPointTo->debugInfo(); + DEBUG_LOG(m_logger) << LOGFUNCTION + << ", objToPointTo is valid, uniqueIdToPointTo = " << m_uniqueIdToPointTo + << ", (objToPointToDebugInfo = " << objToPointToDebugInfo << ")" + << ", bbox= " << bbox; + + + objCenterPoint = bbox.center(); + + // objColor: + objColor = impl->majorColor(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +QImage ObjectIndicatorImpl::renderArrowHeadSymbol(const QColor &color, int length, int halfWidth) +{ + int width = halfWidth * 2; + QImage image(width, length, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + QPainter painter(&image); + painter.setRenderHints(QPainter::Antialiasing); + + QPen pen(color); + pen.setWidth(2); + painter.setPen(pen); + + painter.setBrush(QBrush(color, Qt::BrushStyle::SolidPattern)); + + // 2 || + // | | + // 1 | | 3 + + QPolygon poly; + poly.reserve(3); + poly.append(QPoint(0, length)); + poly.append(QPoint(halfWidth, 0)); + poly.append(QPoint(width, length)); + + painter.drawPolygon(poly); + + painter.end(); + + // image.save(QLatin1String("/tmp/Arrow.png")); + return image; +} + +bool ObjectIndicatorImpl::setMapObjectSelected(bool selected) +{ + // 'clear' current selection: + clearCurrentSelection(); + + if (!selected) + return true; // already done by clearCurrentSelection :-) + + if (!SituationMap::isUniqueMapIdValid(m_uniqueIdToPointTo)) + return false; + + RefPointer objToPointTo = m_mapImpl->objectFactory().get(m_uniqueIdToPointTo); + // retrive values from objToPointTo: + if (!objToPointTo.valid()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " objToPointTo not found"; + return false; + } + AbstractMapObjectImpl *impl = dynamic_cast(objToPointTo.get()); + if (!impl) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " dynamic_cast failed"; + return false; + } + + if (impl->selected() == selected) + return true; + impl->setSelected(selected); + return impl->updateView(); +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.h new file mode 100755 index 000000000..bec24f27d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/object_indicator_impl.h @@ -0,0 +1,114 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of ObjectIndicator + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include +#include // OriginPositionChangedCallback + + +// Qt +#include +#include +#include +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The ObjectIndicatorImpl can be used to ease the locating of an object. + * The ObjectIndicatorImpl can by parametrized with just the UniqueMapId to point to. + * update() will do the rest for you: + * - retrieve the object to point to, + * - get the bounding box of that object and + * - create an indicator from the current center of the map to the object. + * \note If the object to point to disappears, you are responsible for removing the ObjectIndicatorImpl! + */ +class SITUATIONMAP_EXPORT ObjectIndicatorImpl final : public QObject, public virtual ObjectIndicator, public virtual OriginPositionChangedCallback +{ + Q_OBJECT + + using BaseClass = QObject; + +protected: + explicit ObjectIndicatorImpl(); + virtual ~ObjectIndicatorImpl(); + +public: + ObjectIndicatorImpl(const ObjectIndicatorImpl &other) = delete; + ObjectIndicatorImpl &operator=(ObjectIndicatorImpl &other) = delete; + + UniqueMapId mapObjectToPointTo() override; + void setMapObjectToPointTo(const UniqueMapId &newUniqueId, bool setSelected = false, int lifetimeMsec = -1) override; + void setPointToPointTo(const QPointF &targetGeo, int lifetimeMsec = -1) override; + + void clearCurrentSelection(); + +protected Q_SLOTS: + void remove() override; + bool update(); + bool updateImpl(); + void onMapDestroyed(); + +protected: + + enum class AddRemove + { + Add, + Remove + }; + + AbstractMapObjectImpl *getObject(const UniqueMapId &uniqueId); + bool addRemoveCallback(const UniqueMapId &uniqueId, AddRemove addRemove); + + void onOriginPositionChanged() override; + + bool centerOfTarget(Point3D &objCenterPoint, QColor &objColor); + static QImage renderArrowHeadSymbol(const QColor &color, int length, int halfWidth); + + bool setMapObjectSelected(bool selected); + + friend class MapImpl; + bool init(MapImpl *mapImpl); + + // internal/admin + MapImpl *m_mapImpl{nullptr}; + QGraphicsView *m_qGraphicsView{nullptr}; + QGraphicsScene *m_qGraphicsScene{nullptr}; + + QGraphicsEllipseItem *m_qGraphicsCenterMarker{nullptr}; + QGraphicsLineItem *m_qGraphicsLine{nullptr}; + QPixmap m_arrowTip; + QGraphicsPixmapItem *m_qGraphicsArrowTip{nullptr}; + + QColor m_defaultColor{Qt::magenta}; + int m_markerSize{1}; + int m_lineWidth{1}; + + QTimer *m_updateTimer{nullptr}; + QPointer m_lifetimeTimer; + + // logical + UniqueMapId m_uniqueIdToPointTo{INVALID_MAP_ID}; // target to pointTo, #1 + bool m_setSelected{false}; + QPointF m_targetGeo2D; // target to pointTo, #2 + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.cpp new file mode 100755 index 000000000..a700cf7f2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.cpp @@ -0,0 +1,644 @@ +/** + * @RDE_HEADER@ + * Interface of AbstractMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "overlay_handler_impl.h" +#include +#include +#include +#include +#include +#include +#include +#include "geometry_model_renderer_esri.h" + +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +OverlayHandler *OverlayHandler::create(Map *map) +{ + return new OverlayHandlerImpl(map); +} + + +static const QLatin1String s_verlayLayerPrefix = QLatin1String("OverlayLayer_"); + + +class OverlayData +{ +public: + ~OverlayData() + { + // graceful cleanup, destroy associated MapObject (adequate signal will be emitted) + if (m_geoModel) + m_geoModel->clearModel(); + + m_geoModelRenderer.clear(); + m_geoModel.clear(); + // : graceful cleanup + } + + int m_key = -1; + QString m_logicalLayerName; + QString m_technicalLayerName; // just for debugging purpose + + QSharedPointer m_geoModel; + QSharedPointer m_geoModelRenderer; + + bool init(Map *map) + { + if (m_logicalLayerName.isEmpty()) + return false; + + m_geoModel = QSharedPointer(SituationMap::GeometryModelFactory::singleton()->create()); + + m_geoModelRenderer = + QSharedPointer(map->createGeometryModelRenderer(m_logicalLayerName, m_geoModel.data())); + m_geoModelRenderer->setNodesPickable(false); // we just want to display the overlay, no picking + + return !m_geoModelRenderer.isNull(); + } + + QString toString() const + { + QString str; + str.append(QStringLiteral("m_key=%1").arg(m_key)); + str.append(QStringLiteral(" m_logicalLayerName='%1'").arg(m_logicalLayerName)); + str.append(QStringLiteral(" m_technicalLayerName='%1'").arg(m_technicalLayerName)); + return str; + } +}; + + + +//----------------------------------------------------------------------------- +class OverlayHandlerImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + Map *m_map = nullptr; + +private: + QMap> m_overlayCont; + +public: + QSharedPointer &getOverlayData(int key) + { + if (m_overlayCont[key].isNull()) + m_overlayCont[key] = QSharedPointer(new OverlayData()); + return m_overlayCont[key]; + } + + int remove(int key) { return m_overlayCont.remove(key); } + void clear() { m_overlayCont.clear(); } + int size() { return m_overlayCont.size(); } + QList keys() { return m_overlayCont.keys(); } + bool contains(int key) { return m_overlayCont.contains(key); } + + QMutableMapIterator> mutableIterator() + { + return QMutableMapIterator> (m_overlayCont); + } + + +}; // class OverlayHandlerImpl::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +OverlayHandlerImpl::OverlayHandlerImpl(Map *map) + : BaseClass() + , m_logger(getLogger("OverlayHandlerImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_map = map; + + if (! d->m_map) + ERROR_LOG(m_logger) << LOGFUNCTION << " error no Map "; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +OverlayHandlerImpl::~OverlayHandlerImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +QString OverlayHandlerImpl::toString() const +{ + QString str(QLatin1String("OverlayHandlerImpl")); + str.append(QStringLiteral(" m_overlayCont.size=%1").arg(d->size())); + return str; +} + +bool OverlayHandlerImpl::setOverlay(int key, const QString &name, const QByteArray &overlayContent, bool isSnapshot) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " key=" << key + << " name='" << name << "'" + << " overlayContent.size()='" << overlayContent.size() + << " isSnapshot=" << isSnapshot; + if (! d->m_map) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error, no Map " << " key=" << key; + return false; + } + + const bool contains = d->contains(key); + + // data: + QSharedPointer overlayData; + + // create or update? + if (contains) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Current11 SnaphotOverlay" << d->m_map->layerFactory().layerParameter(QStringLiteral("SnaphotOverlay")); + overlayData = d->getOverlayData(key); + + // already created, but update displayname: + if (! d->m_map->layerFactory().addOverlayLayer(overlayData->m_logicalLayerName, name, isSnapshot)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on addOverlayLayer" << " key=" << key; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key; + return false; + } + } + else + { + DEBUG_LOG(m_logger) << " CurrentAA SnaphotOverlay" << d->m_map->layerFactory().layerParameter(QStringLiteral("SnaphotOverlay")); + + overlayData = QSharedPointer(new OverlayData()); + // case 'create' -> create layer: + overlayData->m_key = key; + + if (isSnapshot) + overlayData->m_logicalLayerName = d->m_map->layerFactory().snapshotOverlayLayername(); + else + overlayData->m_logicalLayerName = QString(s_verlayLayerPrefix) + QString::number(key); + + if (! d->m_map->layerFactory().addOverlayLayer(overlayData->m_logicalLayerName, name, isSnapshot)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on addOverlayLayer" << " key=" << key; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key; + return false; + } + // LayerFactory will make sure the technical layer is created. + + DEBUG_LOG(m_logger) << LOGFUNCTION << " key=" << key << " overlayData init..."; + + if (! overlayData->init(d->m_map)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on init" << " key=" << key; + d->m_map->layerFactory().removeOverlayLayer(overlayData->m_logicalLayerName); + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key; + return false; + } + } // if (contains) + + + bool success = false; + + if (overlayContent.isEmpty()) + { + // in case of restoring the OperatorMapSettings / toggle 2D<->3D) + // prepare layer WITHOUNT content + DEBUG_LOG(m_logger) << LOGFUNCTION << " END, overlayContent is empty, so just be prepared" + << ", key=" << key + << ", isSnapshot=" << isSnapshot; + success = true; + } else { + + if (overlayData->m_geoModel && overlayData->m_geoModelRenderer) + { + if (!overlayData->m_geoModel->fromByteArray(overlayContent)) + { + // overlayContent string is not a valid geoModel -> abort + ERROR_LOG(m_logger) << LOGFUNCTION << " error on fromString," << " key=" << key; + if (!d->m_map->layerFactory().removeOverlayLayer(overlayData->m_logicalLayerName)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " removeOverlayLayer failed," << " key=" << key; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key; + return false; + } + // overlayData->m_geoModel->cleanModel(true); has been done in GeometryModel::fromString + + DEBUG_LOG(m_logger) << LOGFUNCTION << " key=" << key << " render..."; + + if (overlayData->m_geoModelRenderer->render()) + { + success = true; + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on render" << " key=" << key; + } + } + } // if (overlayContent.isEmpty()) + + if (success) + { + // add temporary data to internal map: + if (!contains) + d->getOverlayData(key) = overlayData; + //INFO_LOG(m_logger) << LOGFUNCTION << " olData=" << d->getOverlayData(key).data()->toString(); + } + else + { + INFO_LOG(m_logger) << LOGFUNCTION << " removeOverlayLayer" << " key=" << key + << ", m_logicalLayerName " << overlayData->m_logicalLayerName; + d->m_map->layerFactory().removeOverlayLayer(overlayData->m_logicalLayerName); + } + + // + const QString &strRef = + overlayData->m_geoModel->getModelAttribute(SituationMap::GeometryModel::Attribute::MetaPixelBasedTextBoxes).toString(); + const QVector &ptbdCont = + SituationMap::PixelTextBoxData::pixelTextBoxContainerFromJsonStr(strRef); + + PixelBasedTextBoxHandler &pixelBasedTextBoxHandler = d->m_map->pixelBasedTextBoxHandler(); + // cleanup: + const int parentId = key; // key == fileId + pixelBasedTextBoxHandler.removeAllTextBoxesOfParent(parentId); + + for (const SituationMap::PixelTextBoxData &ptbd : std::as_const(ptbdCont)) + { + // create WITHIN loop, since it will be modified by createUpdateTextBox: + PBTB_ID pbtbId; + pbtbId.m_parentId = parentId; + QTextEdit *newTextEdit = pixelBasedTextBoxHandler.createTextBox(pbtbId, ptbd, false); + + if (newTextEdit) + DEBUG_LOG(m_logger) << LOGFUNCTION << " new TextBox created: newTextEdit = " << newTextEdit + << ", pbtbId=" << pbtbId + << ", ptbd=" << ptbd; + else + ERROR_LOG(m_logger) << LOGFUNCTION << " createUpdateTextBox failed" + << ", pbtbId=" << pbtbId + << ", ptbd=" << ptbd; + } // for + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key + << ", size=" << d->size(); + return success; +} + +QByteArray OverlayHandlerImpl::content(int key) const +{ + if (!d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return QByteArray(); + } + + QSharedPointer overlayData = d->getOverlayData(key); + return overlayData->m_geoModel->toByteArray(); +} + + +bool OverlayHandlerImpl::reorderOverlays(const QVector &orderOfIds) +{ + QStringList layerOrder = d->m_map->layerFactory().layernames(); + + // Change the logical layer names in layerOrder list at all positions where a logical layer name has been map to an id ... + int idIndex = 0; + for (QStringList::iterator iter = layerOrder.begin(); layerOrder.end() != iter; ++iter) + { + QVector::const_iterator orderIter = orderOfIds.cbegin(); + + // looking for the current logicalLayername in list of orderOfIds + while (orderOfIds.cend() != orderIter) + { + if (*iter == d->getOverlayData(*orderIter)->m_logicalLayerName) + { + // layer exists in orderOfIds lets change the order + + *iter = d->getOverlayData(orderOfIds[idIndex])->m_logicalLayerName; // set new layername for this position + ++idIndex; + + break; // logical layer name is existing only once and we have changed the order hat this position, so we can leaf the while loop here + } + ++orderIter; + } + } + + // ready now give the new layer order to layer factory + if (!d->m_map->layerFactory().orderLayerTo(layerOrder)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " orderLayerTo failed."; + return false; + } + return true; +} // void OverlayHandlerImpl::reorderOverlays( const QVector & orderOfIds ) + + +double OverlayHandlerImpl::minScale(int key) const +{ + if (! d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return -1.0; + } + + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + return lp.minScale; +} + + +bool OverlayHandlerImpl::setMinScale(int key, double minScale) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " key=" << key << " minScale=" << minScale; + if (! d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return false; + } + + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + + // get LayerParameter, update data and set: + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + lp.minScale = minScale; + if (! d->m_map->layerFactory().setLayerParameter(logicalLayername, lp)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error updating LayerParameter, key=" << key; + return false; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key << " minScale=" << minScale; + return true; +} + +double OverlayHandlerImpl::maxScale(int key) const +{ + if (! d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return -1.0; + } + + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + return lp.maxScale; +} + +bool OverlayHandlerImpl::setMaxScale(int key, double maxScale) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " key=" << key << " maxScale=" << maxScale; + if (! d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return false; + } + + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + + // get LayerParameter, update data and set: + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + lp.maxScale = maxScale; + if (! d->m_map->layerFactory().setLayerParameter(logicalLayername, lp)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error updating LayerParameter, key=" << key; + return false; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key << " maxScale=" << maxScale; + return true; +} + + +double OverlayHandlerImpl::opacity(int key) const +{ + if (! d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return 0.0; + } + + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + + // get LayerParameter, update data and set: + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + + return double(lp.opacity) / 100.0; +} + + +bool OverlayHandlerImpl::setOpacity(int key, double opacity) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " key=" << key << " opacity=" << opacity; + if (! d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return false; + } + + opacity = (opacity < 0.0) ? 0.0 : ((opacity > 1.0) ? 1.0 : opacity); + + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + + // get LayerParameter, update data and set: + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + + lp.opacity = int(opacity * 100); + + if (! d->m_map->layerFactory().setLayerParameter(logicalLayername, lp)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on setLayerParameter" << " key=" << key; + return false; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " key=" << key << " opacity=" << opacity; + return true; +} + + +bool OverlayHandlerImpl::setOverlayVisibility(int key, bool visibility) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " key=" << key + << " visibility=" << visibility; + + // Lookup: + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + INFO_LOG(m_logger) << LOGFUNCTION << " visibility of layer='" << logicalLayername << "'" + << " key=" << key + << " -> " << visibility; + + if (logicalLayername.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " layername is empty, key=" << key << " unknown"; + return false; + } + + // Technical/Esri: + // get LayerParameter, update data and set: + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + lp.visible = visibility; + if (! d->m_map->layerFactory().setLayerParameter(logicalLayername, lp)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error on setLayerParameter" << " key=" << key; + return false; + } + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " key=" << key + << " visibility=" << visibility; + return true; +} + + +bool OverlayHandlerImpl::overlayVisibility(int key) const +{ + QSharedPointer overlayData = d->getOverlayData(key); + const QString &logicalLayername = overlayData->m_logicalLayerName; + + if (logicalLayername.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " layername is empty, key=" << key << " unknown"; + return false; + } + LayerParameter lp = d->m_map->layerFactory().layerParameter(logicalLayername); + return lp.visible; +} // bool OverlayHandlerImpl::overlayVisibility(const int &key) + + +bool OverlayHandlerImpl::removeOverlay(int key) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " key=" << key; + + if (! d->m_map) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " error no Map " << " key=" << key; + return false; + } + + if (! d->contains(key)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " key=" << key << " unknown"; + return false; + } + + + // Lookup: + QSharedPointer overlayData = d->getOverlayData(key); + QString logicalLayername = overlayData->m_logicalLayerName; // create a copy here + + DEBUG_LOG(m_logger) << LOGFUNCTION << " remove layer='" << logicalLayername << "'" + << " key=" << key; + + if (logicalLayername.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " layername is empty, key=" << key; + return false; + } + + // Own data management: + + // + // FIRST: + // + // Remove own MapObject BEFORE the layer + d->remove(key); + overlayData.clear(); // to decrease QSharedPointer <----- important + + // + // SECOND: + // + // Technical/Esri: + if (!d->m_map->layerFactory().removeOverlayLayer(logicalLayername)) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " remove layer='" << logicalLayername << "'" + << " key=" << key; + return false; + } + + // + // Pixel-based TextBoxes: + // + PixelBasedTextBoxHandler &pixelBasedTextBoxHandler = d->m_map->pixelBasedTextBoxHandler(); + const int parentId = key; // key == fileId + pixelBasedTextBoxHandler.removeAllTextBoxesOfParent(parentId); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " key=" << key; + return true; +} + +int OverlayHandlerImpl::removeAllOverlays() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, current size = " << d->size(); + + int removed = 0; + + const QList ¤tKeys = d->keys(); + for (int key : currentKeys) + { + if (removeOverlay(key)) + ++removed; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " removed = " << removed + << " size now = " << d->size(); + return removed; +} + + +int OverlayHandlerImpl::overlayCount() const +{ + return d->size(); +} + +QList OverlayHandlerImpl::overlays() const +{ + return d->keys(); +} + +bool OverlayHandlerImpl::containsOverlay(int key) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->contains(key); +} + + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +std::ostream &operator<< (std::ostream &stream, + const OverlayHandlerImpl &value) +{ + stream << value.toString(); + return stream; +} + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.h new file mode 100755 index 000000000..4a90a58d5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overlay_handler_impl.h @@ -0,0 +1,108 @@ +/** + * @RDE_HEADER@ + * Interface of AbstractMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include "situation_map_export.h" + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class SITUATIONMAP_EXPORT OverlayHandlerImpl : public OverlayHandler +{ + using BaseClass = SituationMap::OverlayHandler; + +public: + /*! \name Construction, Destruction */ + //@{ + explicit OverlayHandlerImpl(Map *map); + ~OverlayHandlerImpl(); + + OverlayHandlerImpl(const OverlayHandlerImpl &other) = delete; + OverlayHandlerImpl &operator=(OverlayHandlerImpl &other) = delete; + //@} + + QString toString() const; + + + /*! \name Adding / Updating */ + //@{ + bool setOverlay(int key, const QString &name, const QByteArray &overlayContent, bool isSnapshot = false) override; + QByteArray content(int key) const override; + + + double minScale(int key) const override; + bool setMinScale(int key, double minScale) override; + + double maxScale(int key) const override; + bool setMaxScale(int key, double maxScale) override; + + double opacity(int key) const override; + bool setOpacity(int key, double opacity) override; + //@} + + bool reorderOverlays(const QVector &orderOfIds) override; + + /*! \name Visibility */ + //@{ + bool setOverlayVisibility(int key, bool visibility) override; + bool overlayVisibility(int key) const override; + //@} + + + /*! \name Removing */ + //@{ + bool removeOverlay(int key) override; + int removeAllOverlays() override; + //@} + + /*! \name Retrieving */ + //@{ + int overlayCount() const override; + QList overlays() const override; + bool containsOverlay(int key) const override; +// QString getOverlay(int key); + //@} + +protected: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; + + + + +//----------------------------------------------------------------------------- +// output operator: +//----------------------------------------------------------------------------- +/** + * @brief Output operator + * @param stream used to stream into. + * @param value to be streamed. + * @return appended stream. + */ +SITUATIONMAP_EXPORT std::ostream &operator<< (std::ostream &stream, + const OverlayHandlerImpl &value); + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.cpp new file mode 100755 index 000000000..243b44dd9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.cpp @@ -0,0 +1,350 @@ +/** + * @RDE_HEADER@ + * Implementation of AbstractMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +// Own +#include "overview_map_handler_impl.h" +#include +#include +#include +#include +#include + +#include + + +// ESRI +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +//----------------------------------------------------------------------------- +class OverviewMapHandlerImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + QPointer m_builtInOverviewMap; + + MapImpl *m_mainMap = nullptr; + + QColor m_color = Qt::red; + +}; // class OverviewMapHandlerImpl::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +OverviewMapHandlerImpl::OverviewMapHandlerImpl(Map *map) + : BaseClass(map) + , m_logger(getLogger("OverviewMapHandlerImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_mainMap = static_cast(map); + + if (d->m_mainMap) + { +// d->m_color = d->m_mainMap->palette().color(QPalette::WindowText); // RDE_DARK_BLUE + + // REGG-4125 - Karte: Bereichsauswahl verdrängt Übersichtskarte + // EventDispatcher und EventDelegater scheinen die OverviewMap zu verdrängen. + // OverviewMap per QGraphicsProxyWidget (QGraphicsView()->scene()->addWidget) schlägt auch fehl + // Daher dieser einfacher workaround: + // Raise nach dem Zerstören eines Rubberbands (und damit Entfernen von EventDispatcher und EventDelegater): + connect(&d->m_mainMap->rubberbandFactory(), &RB::RubberbandFactory::rubberbandDestroyed, + this, &OverviewMapHandlerImpl::onMainMapRectChanged); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " error no mainMap "; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +OverviewMapHandlerImpl::~OverviewMapHandlerImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void OverviewMapHandlerImpl::createBuiltInOverviewMap() +{ + if (d->m_builtInOverviewMap) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " already exists"; + return; + } + + d->m_builtInOverviewMap = QPointer(new OverviewMap(this)); + d->m_builtInOverviewMap->init(d->m_mainMap, d->m_color); + if (! d->m_builtInOverviewMap->createBasicOverviewMap()) + ERROR_LOG(m_logger) << LOGFUNCTION << " createBasicOverviewMap failed"; + + if (! d->m_builtInOverviewMap->widget()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " d->m_builtInOverviewMap->widget() failed"; + return; + } + + // confgure widget: + // disable mouse: + d->m_builtInOverviewMap->widget()->setAttribute(Qt::WA_TransparentForMouseEvents); + d->m_builtInOverviewMap->widget()->setFocusPolicy(Qt::NoFocus); + + d->m_builtInOverviewMap->widget()->setMinimumSize(QSize(100, 100)); + d->m_builtInOverviewMap->widget()->setStyleSheet(QLatin1String("border: 1px solid gray")); + + d->m_builtInOverviewMap->widget()->show(); + + // take care of position: + connect(d->m_mainMap, &MapImpl::rectChanged, + this, &OverviewMapHandlerImpl::onMainMapRectChanged); + + onMainMapRectChanged(); + + d->m_mainMap->emitVisualComponentChanged(VisualComponent::OverviewMap); // to update onOffCheckbox +} + + +void OverviewMapHandlerImpl::removeBuiltInOverviewMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (d->m_builtInOverviewMap) + { + QPointer localPtr = d->m_builtInOverviewMap; + d->m_builtInOverviewMap.clear(); + localPtr->deleteLater(); + d->m_mainMap->emitVisualComponentChanged(VisualComponent::OverviewMap); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QWidget *OverviewMapHandlerImpl::builtInOverviewMapWidget() +{ + if (! d->m_builtInOverviewMap) + return nullptr; + return d->m_builtInOverviewMap->widget(); +} + +void OverviewMapHandlerImpl::removeAll() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + removeBuiltInOverviewMap(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void OverviewMapHandlerImpl::onMainMapRectChanged() +{ + if (! d->m_builtInOverviewMap) + return; + + if (! d->m_builtInOverviewMap->widget()) + return; + + const QSize &mainSize = d->m_mainMap->size(); + const QSize &overviewSize = d->m_builtInOverviewMap->widget()->size(); + + QSize diff = mainSize - overviewSize - QSize(10, 10); // additional border + + d->m_builtInOverviewMap->widget()->move(diff.width(), diff.height()); + + d->m_builtInOverviewMap->widget()->raise(); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +OverviewMap::OverviewMap(QObject *parent) + : BaseClass(parent) + , m_logger(getLogger("OverviewMap")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +OverviewMap::~OverviewMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (m_graphicsView) + m_graphicsView->deleteLater(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void OverviewMap::init(MapImpl *mainMap, const QColor &color) +{ + m_mainMap = mainMap; + m_color = color; +} + +QWidget *OverviewMap::widget() +{ + return m_graphicsView.data(); +} + +bool OverviewMap::createBasicOverviewMap() +{ + if (m_graphicsView) + return true; + + // create required objects: + m_graphicsView = QPointer(new Esri::ArcGISRuntime::MapGraphicsView(m_mainMap)); + + m_graphicsView->setAttributionTextVisible(false); + + QObject *parent = this; + + QString tiledBaseMapLayer = SituationMap::getResourcePath() + QLatin1String("Topographic.tpk"); + + QFile f(tiledBaseMapLayer); + if (!f.exists()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " file does not exist: tiledBaseMapLayer='" << tiledBaseMapLayer << "'"; + return false; + } + + INFO_LOG(m_logger) << LOGFUNCTION << " tiledBaseMapLayer='" << tiledBaseMapLayer << "'"; + + Esri::ArcGISRuntime::TileCache *tpkCache = new Esri::ArcGISRuntime::TileCache(tiledBaseMapLayer, parent); + tpkCache->setObjectName(QLatin1String("OverviewMap-TPK-tpkCache")); + + Esri::ArcGISRuntime::ArcGISTiledLayer *tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(tpkCache, parent); + tiledLayer->setName(QLatin1String("OverviewMap-TPK-tiledLayer")); + + Esri::ArcGISRuntime::Basemap *basemap = new Esri::ArcGISRuntime::Basemap(tiledLayer, parent); + basemap->setName(QLatin1String("TPK-Basemap")); + + // connect signals + // loadStatusChanged -> + connect(tpkCache, &Esri::ArcGISRuntime::TileCache::loadStatusChanged, this, [this](Esri::ArcGISRuntime::LoadStatus loadStatus) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " OverviewMap-TileCache: " << "loadStatus changed to: " << loadStatus; + }); + connect(tiledLayer, &Esri::ArcGISRuntime::ArcGISTiledLayer::loadStatusChanged, this, [this](Esri::ArcGISRuntime::LoadStatus loadStatus) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " OverviewMap-ArcGISTiledLayer: " << "loadStatus changed to: " << loadStatus; + if (loadStatus == Esri::ArcGISRuntime::LoadStatus::Loaded) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " loaded -> setToFullExtent"; + setToFullExtent(); + } + + }); + connect(basemap, &Esri::ArcGISRuntime::Basemap::loadStatusChanged, this, [this](Esri::ArcGISRuntime::LoadStatus loadStatus) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " OverviewMap-Basemap: " << "loadStatus changed to: " << loadStatus; + }); + + + // Map: + Esri::ArcGISRuntime::Map *overviewMap = new Esri::ArcGISRuntime::Map(basemap, parent); + overviewMap->setObjectName(QLatin1String("OverviewMap-EsriMap")); + + // will start esris cascading load dependecies... + m_graphicsView->setMap(overviewMap); + + + // GraphicsOverlay: + Esri::ArcGISRuntime::GraphicsOverlay *graphicalOverlay = new Esri::ArcGISRuntime::GraphicsOverlay(parent); + m_graphicsView->graphicsOverlays()->append(graphicalOverlay); + + + QColor color = m_color; + + // create symbol + Esri::ArcGISRuntime::SimpleLineSymbol *sls = + new Esri::ArcGISRuntime::SimpleLineSymbol(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, + color, 1, parent); + color.setAlpha(100); + Esri::ArcGISRuntime::SimpleFillSymbol *sfs = + new Esri::ArcGISRuntime::SimpleFillSymbol(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, + color, sls, parent); + // create renderer + Esri::ArcGISRuntime::SimpleRenderer *renderer = + new Esri::ArcGISRuntime::SimpleRenderer(sfs, parent); + + // Handle errors of renderer: + connect(renderer, &Esri::ArcGISRuntime::SimpleRenderer::errorOccurred, this, [this](const Esri::ArcGISRuntime::Error & error) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " EsriError occured: " << error; + }); + + // set Renderer to layer: + graphicalOverlay->setRenderer(renderer); + + // prepare graphic + m_graphic = new Esri::ArcGISRuntime::Graphic(graphicalOverlay); + graphicalOverlay->graphics()->append(m_graphic); + + // update first time + onMainMapExtentChanged(); + + // execute on extentChanged + connect(m_mainMap, &MapImpl::extentChangedDelayed, this, &OverviewMap::onMainMapExtentChanged); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void OverviewMap::onMainMapExtentChanged() +{ + if (! m_graphic) + return; + + Esri::ArcGISRuntime::GeoView *geoView = m_mainMap->getEsriGeoView(); + if (! geoView) + return; + + setToFullExtent(); + + const Esri::ArcGISRuntime::Viewpoint curVp = + geoView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::BoundingGeometry); + + m_graphic->setGeometry(curVp.targetGeometry()); // curVp.targetGeometry() should be a rectangle +} + +void OverviewMap::setToFullExtent() +{ + if (!m_graphicsView || !m_mainMap) + return; + + // zoom to fullExtent: + Esri::ArcGISRuntime::Envelope fullExtent = m_mainMap->layerFactoryImpl().fullExtentOfBasemaps(0.2); // add margin + + Esri::ArcGISRuntime::Viewpoint vp(fullExtent); + m_graphicsView->setViewpointAndWait(vp); +} + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.h new file mode 100755 index 000000000..def920478 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/overview_map_handler_impl.h @@ -0,0 +1,113 @@ +/** + * @RDE_HEADER@ + * Implementation of OverviewMap + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include "situation_map_export.h" + +// ESRI +#include + + +// Qt + +// Forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Graphic; +class MapGraphicsView; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +class MapImpl; + +/** + * @brief The OverviewMap is a wrapper around OverviewMapWidget. + */ +class OverviewMap : public QObject +{ + Q_OBJECT + using BaseClass = QObject; +public: + explicit OverviewMap(QObject *parent = nullptr); + ~OverviewMap(); + + void init(MapImpl *mainMap, const QColor &color = Qt::red); + bool createBasicOverviewMap(); + + QWidget *widget(); + +protected slots: + void onMainMapExtentChanged(); + void setToFullExtent(); + +protected: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + + // Own data: + MapImpl *m_mainMap = nullptr; + QPointer m_graphicsView; + Esri::ArcGISRuntime::Graphic *m_graphic = nullptr; + QColor m_color; + +}; + + + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAP_EXPORT OverviewMapHandlerImpl : public QObject, public virtual OverviewMapHandler +{ + Q_OBJECT + + using BaseClass = QObject; + +public: + /*! \name Construction, Destruction */ + //@{ + explicit OverviewMapHandlerImpl(Map *map); + ~OverviewMapHandlerImpl(); + + OverviewMapHandlerImpl(const OverviewMapHandlerImpl &other) = delete; + OverviewMapHandlerImpl &operator=(OverviewMapHandlerImpl &other) = delete; + + void createBuiltInOverviewMap() override; + void removeBuiltInOverviewMap() override; + + QWidget *builtInOverviewMapWidget() override; + + + void removeAll() override; + //@} + +protected slots: + void onMainMapRectChanged(); + +protected: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.cpp new file mode 100755 index 000000000..a758f39ca --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.cpp @@ -0,0 +1,562 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of LocationDisplay + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "pixel_based_textbox_handler_impl.h" +#include "map_impl.h" +#include +#include + + +// ESRI +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// PBTB_ID output operator: +//----------------------------------------------------------------------------- + std::ostream &operator<< (std::ostream &stream, const PBTB_ID &value) + { + stream << value.toString(); + return stream; + } + + +//----------------------------------------------------------------------------- +// class +//----------------------------------------------------------------------------- +class PixelBasedTextBoxHandlerImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + SituationMap::MapImpl *m_mapImpl{nullptr}; + + QVector> m_tbDataCont; +}; +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// class PixelBasedTextBoxHandlerImpl +//----------------------------------------------------------------------------- +PixelBasedTextBoxHandlerImpl::PixelBasedTextBoxHandlerImpl(SituationMap::MapImpl *mapImpl) + : m_logger(getLogger("PixelBasedTextBoxHandlerImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_mapImpl = mapImpl; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PixelBasedTextBoxHandlerImpl::~PixelBasedTextBoxHandlerImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QTextEdit *PixelBasedTextBoxHandlerImpl::createTextBox(PBTB_ID &pbtbIdRef, const PixelTextBoxData &ptbd, bool enableGeometryModifications) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, (((pbtbId=" << pbtbIdRef.toString() + << "))) , ptbd=" << ptbd.toDebugString() + << ", enableGeometryModifications=" << enableGeometryModifications; + if (!d->m_mapImpl) + return nullptr; + + QSharedPointer tbData = getCreateTbData(pbtbIdRef); + + // pbtbId may have changed, write back to caller: + pbtbIdRef = tbData->m_pbtbId; + + // backgroundColor is special (not with QTextEdit), so update tbData here: + if (ptbd.m_backgroundColor.isValid()) + tbData->m_ptbd.m_backgroundColor = ptbd.m_backgroundColor; + else + tbData->m_ptbd.m_backgroundColor = Qt::white; + + TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; + if (!guiDataRef.m_textEdit) + { + QTextEditWithDoubleClick *myTextEdit = new QTextEditWithDoubleClick(); + guiDataRef.m_textEdit = myTextEdit; + guiDataRef.m_textEdit->setObjectName(QStringLiteral("WidgetOverlay_QTextEdit")); + guiDataRef.m_textEdit->setReadOnly(true); + guiDataRef.m_textEdit->setProperty(PBTB_ID_PROPERTY_NAME, tbData->m_pbtbId.toString()); + + if (ptbd.m_size.isValid()) + guiDataRef.m_textEdit->setGeometry(0, 0, ptbd.m_size.width(), ptbd.m_size.height()); + + if (enableGeometryModifications) + { + connect(myTextEdit, &QTextEditWithDoubleClick::doubleClickEvent, + this, [this] (const PBTB_ID &pbtbId) { + emit pixelBasedTextBoxDoubleClickEvent(pbtbId); + }); + } + + // It looks like: + // GraphicsItem <-> ProxyWidget <-> Widget/TextEdit + // (QGraphicsItem is a baseclass of both GraphicsItem as well as ProxyWidget) + // GraphicsItem becomes parent of ProxyWidget + // Then TextEdit is added via setWidget to ProxyWidget + // Now both are connected, TextEdit in front of GraphicsItem which ist little bit bigger + // GraphicsItem is movable, GI an TE are connected to each other... + // 10.05.2023, Norbert Thoden + // (((Idea: Maybe ProxyWidget should be parent of GraphicsItem + // to put GraphicsItem on top of TextEdit???))) + QGraphicsRectItemWithEvents *grItem = new QGraphicsRectItemWithEvents(0, 0, 0, 0); // x,y == rel position, size will be set later + grItem->setProperty(PBTB_ID_PROPERTY_NAME, tbData->m_pbtbId.toString()); + grItem->setCursor(Qt::SizeAllCursor); + if (enableGeometryModifications) + { + grItem->activatePositionChangedEvents(); + connect(grItem, &QGraphicsRectItemWithEvents::positionChangedFinishedEvent, + this, &PixelBasedTextBoxHandler::pixelBasedTextBoxGeometryChanged, Qt::UniqueConnection); + } + + guiDataRef.m_handle_or_proxyControl = grItem; + + + guiDataRef.m_proxy = new QGraphicsProxyWidget(guiDataRef.m_handle_or_proxyControl); // parent is important for cleanup! + + guiDataRef.m_proxy->setObjectName(QStringLiteral("ProxyWidgetOverlay_QTextEdit")); + guiDataRef.m_proxy->setProperty(PBTB_ID_PROPERTY_NAME, tbData->m_pbtbId.toString()); // maybe helpful in future + + guiDataRef.m_proxy->setWidget(guiDataRef.m_textEdit); + + guiDataRef.m_proxy->setPos(QPoint(0, 0)); // relative position to GraphicsItem !? + + guiDataRef.m_proxy->setOpacity(0.99); // avoid white on white + + guiDataRef.m_handle_or_proxyControl->setPen(QPen(Qt::gray)); + guiDataRef.m_handle_or_proxyControl->setBrush(QBrush(Qt::gray)); + + if (enableGeometryModifications) + guiDataRef.m_handle_or_proxyControl->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + + d->m_mapImpl->getQGraphicsView()->scene()->addItem(guiDataRef.m_handle_or_proxyControl); + + if (enableGeometryModifications) + { + // SizeGrip, removeBtn : + QSizeGripWithEvent *sizeGrip = new QSizeGripWithEvent(tbData->m_pbtbId, guiDataRef.m_textEdit); + QHBoxLayout *layout = new QHBoxLayout(guiDataRef.m_textEdit); + layout->setContentsMargins(0, 0, 0, 0); + + QPushButton *btn = new QPushButton(guiDataRef.m_textEdit); + btn->setIcon(QIcon(QStringLiteral("icon:bgv_view_close.svg"))); + + connect(btn, &QPushButton::clicked, this, [this, pbtbIdRef](bool) { + emit pixelBasedTextBoxRemoveRequestedEvent(pbtbIdRef); + }); + + layout->addWidget(btn, 1, Qt::AlignRight | Qt::AlignBottom); + layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom); + + // [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen + // pass geometryChanged via signal-signal-connection: + connect(sizeGrip, &QSizeGripWithEvent::geometryChangedFinishedEvent, + this, &PixelBasedTextBoxHandler::pixelBasedTextBoxGeometryChanged, Qt::UniqueConnection); + + // : SizeGrip, removeBtn + + } + + // Geometry : + tbData->onGeometryChanged(); + if (!tbData->connectGeometryChanges()) + ERROR_LOG(m_logger) << LOGFUNCTION << " connectGeometryChanges failed!"; + + // + // [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen + // // pass geometryChanged via signal-signal-connection: + // connect(tbData.data(), &TB_Data::geometryChanged, + // this, &PixelBasedTextBoxHandler::pixelBasedTextBoxGeometryChanged, Qt::UniqueConnection); + + // : Geometry + } + + // + // update GUI: + // + // update text + guiDataRef.m_textEdit->setText(ptbd.m_htmlText); + if (guiDataRef.m_textEdit->toPlainText().isEmpty()) + { + const QString sansSerif = QLatin1String("Sans Serif"); + QFont textFont = guiDataRef.m_textEdit->font(); + textFont.setFamily(sansSerif); + textFont.setFamilies({sansSerif}); + guiDataRef.m_textEdit->setFont(textFont); + } + + // update backgroundColor + const QColor &bgCol = ptbd.m_backgroundColor; + if (bgCol.isValid()) + { + QString styleSheet = QString(QStringLiteral("QTextEdit { background-color: rgb(%1, %2, %3) }")).arg(bgCol.red()).arg(bgCol.green()).arg(bgCol.blue()); + guiDataRef.m_textEdit->setStyleSheet(styleSheet); + } + + // update geometry and size: + guiDataRef.m_handle_or_proxyControl->setPos(ptbd.m_position); + if (ptbd.m_size.isValid()) + guiDataRef.m_textEdit->setGeometry(0, 0, ptbd.m_size.width(), ptbd.m_size.height()); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbIdRef; + return guiDataRef.m_textEdit.data(); +} + +QTextEdit *PixelBasedTextBoxHandlerImpl::updateTextBox(const PBTB_ID &pbtbId, const PixelTextBoxData &ptbd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, (((pbtbId=" << pbtbId.toString() + << "))) , ptbd=" << ptbd.toDebugString(); + + QSharedPointer tbDataToCheckIfItExists = getTbDataIfExists(pbtbId); + if(!tbDataToCheckIfItExists) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " TextBox does not exists, pbtbId=" << pbtbId.toString(); + return nullptr; + } + + PBTB_ID pbtbIdCopy = pbtbId; + QTextEdit *te = this->createTextBox(pbtbIdCopy, ptbd, false); // enableGeometryModifications==false - that part is already done + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, (((pbtbId=" << pbtbId.toString() + << "))) , ptbd=" << ptbd.toDebugString(); + return te; +}; + + +QTextEdit *PixelBasedTextBoxHandlerImpl::getTextBox(const PBTB_ID &pbtbId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId.toString(); + if (!d->m_mapImpl) + return nullptr; + + QSharedPointer tbData = getTbDataIfExists(pbtbId); + + QTextEdit *te = nullptr; + if (tbData && tbData->hasValidId()) + { + TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; + te = guiDataRef.m_textEdit; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId + << ", te = " << te; + return te; +} + +void PixelBasedTextBoxHandlerImpl::removeSelection() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QMutableVectorIterator> it(d->m_tbDataCont); + while (it.hasNext()) + { + QSharedPointer tbData = it.next(); + if (tbData) + { + TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; + guiDataRef.m_handle_or_proxyControl->setBrush(QBrush(Qt::gray)); + } + } // while + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool PixelBasedTextBoxHandlerImpl::setSelected(const PBTB_ID &pbtbId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; + + // part 1 + removeSelection(); + + // part 2 + QSharedPointer tbData = getCreateTbData(pbtbId); + if (tbData->m_pbtbId != pbtbId) + return false; + + QColor col = Qt::magenta; + col.setAlpha(128); + + TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; + guiDataRef.m_handle_or_proxyControl->setBrush(QBrush(col)); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId; + return true; +} + +QVector PixelBasedTextBoxHandlerImpl::getAllIdsOfParent(const PBTB_ID &pbtbId) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; + + const int parentId = pbtbId.m_parentId; + QVector retVec; + retVec.reserve(d->m_tbDataCont.size()); + + QVectorIterator> it(d->m_tbDataCont); + while (it.hasNext()) + { + QSharedPointer item = it.next(); + if (item->m_pbtbId.m_parentId == parentId) + retVec.append(item->m_pbtbId); + } // while + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId + << ", retVec.size()" << retVec.size(); + return retVec; +} + +PixelTextBoxData PixelBasedTextBoxHandlerImpl::getPixelTextBoxData(const PBTB_ID &pbtbId) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; + + QSharedPointer tbData = getCreateTbData(pbtbId); + if (tbData->m_pbtbId != pbtbId) + return {}; + + PixelTextBoxData &ptbdRef = tbData->m_ptbd; + TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; + + // + // current guiData to ptbd: + // + + // text + ptbdRef.m_htmlText = guiDataRef.m_textEdit->document()->toHtml(); + // ptbdRef.m_backgroundColor; // already updated, see createTextBox() + ptbdRef.m_position = guiDataRef.m_handle_or_proxyControl->pos().toPoint(); + ptbdRef.m_size = guiDataRef.m_textEdit->geometry().size(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId + << ", ptbdRef = " << ptbdRef; + return ptbdRef; +} + +bool PixelBasedTextBoxHandlerImpl::removeTextBox(const PBTB_ID &pbtbId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; + if (!d->m_mapImpl) + return false; + if (d->m_tbDataCont.isEmpty()) + return true; + + QMutableVectorIterator> it(d->m_tbDataCont); + while (it.hasNext()) + { + QSharedPointer item = it.next(); + if (item->m_pbtbId == pbtbId) + { + it.remove(); // dtor will destroy guiElements + TRACE_LOG(m_logger) << LOGFUNCTION << " END, removed pbtbId=" << pbtbId; + return true; + } + } // while + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId; + return false; +} + +bool PixelBasedTextBoxHandlerImpl::removeAllTextBoxesOfParent(int parentId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, parentId=" << parentId; + + if (!d->m_mapImpl) + return false; + + int cnt = 0; + QMutableVectorIterator> it(d->m_tbDataCont); + while (it.hasNext()) + { + QSharedPointer item = it.next(); + if (item->m_pbtbId.m_parentId == parentId) + { + it.remove(); // dtor will destroy guiElements + ++cnt; + } + } // while + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, parentId=" << parentId + << ", removed #items = " << cnt; + return true; +} + +QSharedPointer PixelBasedTextBoxHandlerImpl::getCreateTbData(const PBTB_ID &pbtbId) const +{ + QVectorIterator> it(d->m_tbDataCont); + while (it.hasNext()) + { + QSharedPointer item = it.next(); + if (item->m_pbtbId == pbtbId) + return item; + } // while + + // not found? + // insert and return; + QSharedPointer initialData = QSharedPointer(new TB_Data()); + initialData->m_pbtbId.m_parentId = pbtbId.m_parentId; // !!! keep m_parentId !!! + initialData->m_pbtbId.m_uid = PBTB_ID::getNextId(); + d->m_tbDataCont.append(initialData); + return d->m_tbDataCont.last(); +} + +QSharedPointer PixelBasedTextBoxHandlerImpl::getTbDataIfExists(const PBTB_ID &pbtbId) const +{ + QVectorIterator> it(d->m_tbDataCont); + while (it.hasNext()) + { + QSharedPointer item = it.next(); + if (item->m_pbtbId == pbtbId) + return item; + } // while + + return {}; +} + +TB_Data::TB_Data() +{ +} + +TB_Data::~TB_Data() +{ + m_guiData.resetGui(); +} + +bool TB_Data::hasValidId() const +{ + return m_pbtbId.isValid(); +} + +bool TB_Data::connectGeometryChanges() +{ + if (!m_guiData.m_proxy) + return false; + + // QTextEdit has no geometryChanged or similar, but ProxyWidget has: + connect(m_guiData.m_proxy, &QGraphicsProxyWidget::geometryChanged, + this, &TB_Data::onGeometryChanged); + + return true; +} + +void TB_Data::onGeometryChanged() +{ + // enableGeometryModifications could be 'read' by m_sizeGrip which is only created if modifications are enabled: + const bool enableGeometryModifications = m_guiData.m_handle_or_proxyControl->flags().testFlag(QGraphicsItem::ItemIsMovable); + if (enableGeometryModifications) + m_guiData.m_handle_or_proxyControl->setRect(m_guiData.m_textEdit->geometry().adjusted(-10, -10, 10, 10)); // bigger than textEdit + else + m_guiData.m_handle_or_proxyControl->setRect(m_guiData.m_textEdit->geometry().adjusted(1, 1, -1, -1)); // smaller than textEdit (invisible) + + emit geometryChanged(m_pbtbId); +} + + + +//----------------------------------------------------------------------------- +// class QTextEditWithDoubleClick +//----------------------------------------------------------------------------- +void QTextEditWithDoubleClick::mouseDoubleClickEvent(QMouseEvent */*event*/) +{ + const QString &pbtbProp = this->property(PBTB_ID_PROPERTY_NAME).toString(); + const PBTB_ID &pbtbId = SituationMap::PBTB_ID::fromString(pbtbProp); + + emit doubleClickEvent(pbtbId); + + // do NOT forward to baseClass. So we avoid the text being displayed xor/inverted + // BaseClass::mouseDoubleClickEvent(event); + + // Maybe the selection coud be 'removed' in that way: + // QTextCursor tc = textCursor(); + // tc.clearSelection(); + // setTextCursor(tc); +} + + +//----------------------------------------------------------------------------- +// class QGraphicsRectItemWithMoveEvent +//----------------------------------------------------------------------------- +void QGraphicsRectItemWithEvents::activatePositionChangedEvents() +{ + m_qtimer = new QTimer(this); + m_qtimer->setInterval(100); + m_qtimer->setSingleShot(true); + connect(m_qtimer, &QTimer::timeout, + this, &QGraphicsRectItemWithEvents::onTimeout); +} + +void QGraphicsRectItemWithEvents::onTimeout() +{ + const QString &pbtbProp = this->property(PBTB_ID_PROPERTY_NAME).toString(); + const PBTB_ID &pbtbId = SituationMap::PBTB_ID::fromString(pbtbProp); + emit positionChangedEvent(pbtbId); +} + +void QGraphicsRectItemWithEvents::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + BaseClass::mouseMoveEvent(event); + m_qtimer->start(); + m_isMoving = true; +} + +void QGraphicsRectItemWithEvents::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + BaseClass::mouseReleaseEvent(event); + + if (m_isMoving) + { + const QString &pbtbProp = this->property(PBTB_ID_PROPERTY_NAME).toString(); + const PBTB_ID &pbtbId = SituationMap::PBTB_ID::fromString(pbtbProp); + emit positionChangedFinishedEvent(pbtbId); + m_isMoving = false; + } +} + +//----------------------------------------------------------------------------- +// class QSizeGripWithEvent +//----------------------------------------------------------------------------- +void QSizeGripWithEvent::mouseMoveEvent(QMouseEvent *event) +{ + BaseClass::mouseMoveEvent(event); + m_isGeometryChanging = true; +} + +void QSizeGripWithEvent::mouseReleaseEvent(QMouseEvent *mouseEvent) +{ + BaseClass::mouseReleaseEvent(mouseEvent); + + if (m_isGeometryChanging) + { + m_isGeometryChanging = false; + emit geometryChangedFinishedEvent(m_pbtbId); + } +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.h new file mode 100755 index 000000000..fbf5c1d18 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.h @@ -0,0 +1,221 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of LocationDisplay + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MapImpl; +class TB_Data; + +//----------------------------------------------------------------------------- +// class TB_Data +//----------------------------------------------------------------------------- +class TB_Data : public QObject +{ + Q_OBJECT + +public: + friend class PixelBasedTextBoxHandlerImpl; + TB_Data(); + ~TB_Data(); + + bool hasValidId() const; + bool connectGeometryChanges(); + +public Q_SLOTS: + void onGeometryChanged(); + +Q_SIGNALS: + void geometryChanged(SituationMap::PBTB_ID pbtbId); + void geometryChangedFinished(SituationMap::PBTB_ID pbtbId); // [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen + +public: + PBTB_ID m_pbtbId; + PixelTextBoxData m_ptbd; + + struct TB_GUI_DATA + { + QPointer m_textEdit; + QGraphicsRectItem *m_handle_or_proxyControl{nullptr}; + QGraphicsProxyWidget *m_proxy{nullptr}; + + void resetGui() + { + // ToDo: check + // delete without removing from scene/view? + + delete m_handle_or_proxyControl; + m_handle_or_proxyControl = nullptr; + + // since m_proxy has m_handle_or_proxyControl as parent... + m_proxy = nullptr; + + delete m_textEdit; // QPointer + } + + }; + TB_GUI_DATA m_guiData; + +private: + Q_DISABLE_COPY(TB_Data) +}; +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// class QTextEditWithDoubleClick +//----------------------------------------------------------------------------- +class QTextEditWithDoubleClick : public QTextEdit +{ + Q_OBJECT +public: + using BaseClass = QTextEdit; + QTextEditWithDoubleClick(QWidget *parent = nullptr) + :BaseClass(parent) + {} + virtual ~QTextEditWithDoubleClick() = default; + +Q_SIGNALS: + void doubleClickEvent(PBTB_ID pbtbId); + +protected: + void mouseDoubleClickEvent(QMouseEvent *) override; +}; +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// class QGraphicsRectItemWithMoveEvent +//----------------------------------------------------------------------------- +class QGraphicsRectItemWithEvents : public QObject, public QGraphicsRectItem +{ + Q_OBJECT +public: + using BaseClass = QGraphicsRectItem; + QGraphicsRectItemWithEvents(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent = nullptr) + : QObject(), BaseClass(x, y, w, h, parent) + {} + virtual ~QGraphicsRectItemWithEvents() = default; + + void activatePositionChangedEvents(); + +Q_SIGNALS: + void positionChangedEvent(PBTB_ID pbtbId); + void positionChangedFinishedEvent(PBTB_ID pbtbId); // [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen + +protected Q_SLOTS: + void onTimeout(); + +protected: + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + QTimer *m_qtimer{nullptr}; + bool m_isMoving{false}; +}; +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class QSizeGripWithEvent +//----------------------------------------------------------------------------- +// [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen +// class QSizeGripWithEvent is introduced: +class QSizeGripWithEvent : public QSizeGrip +{ + Q_OBJECT +public: + using BaseClass = QSizeGrip; + QSizeGripWithEvent(const PBTB_ID &pbtbId, QWidget *parent) + : BaseClass(parent), m_pbtbId(pbtbId) + {} + virtual ~QSizeGripWithEvent() = default; + +Q_SIGNALS: + void geometryChangedFinishedEvent(SituationMap::PBTB_ID pbtbId); // [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen + +protected: + void mouseMoveEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *mouseEvent) override; + + PBTB_ID m_pbtbId; + bool m_isGeometryChanging{false}; +}; +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class PixelBasedTextBoxHandlerImpl +//----------------------------------------------------------------------------- +class SITUATIONMAP_EXPORT PixelBasedTextBoxHandlerImpl : public SituationMap::PixelBasedTextBoxHandler +{ + Q_OBJECT + + using BaseClass = SituationMap::PixelBasedTextBoxHandler; + +public: + + explicit PixelBasedTextBoxHandlerImpl(SituationMap::MapImpl *mapImpl); + virtual ~PixelBasedTextBoxHandlerImpl(); + + PixelBasedTextBoxHandlerImpl(const PixelBasedTextBoxHandlerImpl &other) = delete; + PixelBasedTextBoxHandlerImpl &operator=(PixelBasedTextBoxHandlerImpl &other) = delete; + + // create/get + QTextEdit *createTextBox(PBTB_ID &pbtbIdRef, const PixelTextBoxData &ptbd, bool enableGeometryModifications = true) override; + QTextEdit *updateTextBox(const PBTB_ID &pbtbId, const PixelTextBoxData &ptbd) override; + QTextEdit *getTextBox(const PBTB_ID &pbtbId) override; + + + void removeSelection() override; + bool setSelected(const PBTB_ID &pbtbId) override; + + // get: + QVector getAllIdsOfParent(const PBTB_ID &pbtbId) const override; + PixelTextBoxData getPixelTextBoxData(const PBTB_ID &pbtbId) const override; + + // remove: + bool removeTextBox(const PBTB_ID &pbtbId) override; + bool removeAllTextBoxesOfParent(int parentId) override; + +public Q_SLOTS: + +private: + QSharedPointer getCreateTbData(const PBTB_ID &pbtbId) const; + QSharedPointer getTbDataIfExists(const PBTB_ID &pbtbId) const; + +private : + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d{nullptr}; + +}; + +} // namespace SituationMap + +//---------------------------------------------------------------------------- diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.cpp new file mode 100755 index 000000000..296b1318a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.cpp @@ -0,0 +1,144 @@ +/** + * @RDE_HEADER@ + * Base class for ScreenToLocation and LocationToElevation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "screen_location_elevation_helper_base.h" +#include "Map/situation_map_utils.h" + + +// ESRI + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//---------------------------------------------------------------------------- +class ScreenLocationElevationHelperBase::Private +{ +public: + Private() = default; + ~Private() = default; + + QMap> m_taskWatcherCont; + + QTimer *m_mainTimer = nullptr; + QTimer *m_killTimer = nullptr; +}; + +//---------------------------------------------------------------------------- + +ScreenLocationElevationHelperBase::ScreenLocationElevationHelperBase(QObject *parent) + : BaseClass(parent) + , m_logger(getLogger("ScreenLocationElevationHelperBase")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const int mainTimerMsec = 250; + + // mainTimer + d->m_mainTimer = new QTimer(this); + d->m_mainTimer->setInterval(mainTimerMsec); + + connect(d->m_mainTimer, &QTimer::timeout, + this, &ScreenLocationElevationHelperBase::onMainTimoutInternal); + + // killTimer + d->m_killTimer = new QTimer(this); + d->m_killTimer->setSingleShot(true); + d->m_killTimer->setInterval(mainTimerMsec + 50); + + connect(d->m_killTimer, &QTimer::timeout, + this, &ScreenLocationElevationHelperBase::onKillTimout); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +ScreenLocationElevationHelperBase::~ScreenLocationElevationHelperBase() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->stop(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +int ScreenLocationElevationHelperBase::pendingOperationsCount() const +{ + return d->m_taskWatcherCont.size(); +} + +void ScreenLocationElevationHelperBase::cancel() +{ + if (d->m_taskWatcherCont.isEmpty()) + return; + + QMutableMapIterator> it(d->m_taskWatcherCont); + + while (it.hasNext()) + { + it.next(); + // QFuture can't be canceled directly, just remove from tracking + DEBUG_LOG(m_logger) << LOGFUNCTION << " Task removed from tracking"; + it.remove(); + } // while +} + +void ScreenLocationElevationHelperBase::stop() +{ + this->cancel(); + d->m_mainTimer->stop(); + d->m_killTimer->stop(); +} + +void ScreenLocationElevationHelperBase::newComputationRequested() +{ + // long absence? + if (!d->m_mainTimer->isActive()) + d->m_mainTimer->start(); + + // restarting killTimer + d->m_killTimer->start(); +} + +void ScreenLocationElevationHelperBase::onMainTimoutInternal() +{ + this->mainComputation(); +} + +void ScreenLocationElevationHelperBase::onKillTimout() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " killTimer stop mainTimer..." + << " (size of m_taskIdSet=" << d->m_taskWatcherCont.size() << ")"; + d->m_mainTimer->stop(); +} + +bool ScreenLocationElevationHelperBase::insertTask(const QFuture &future) +{ + if (!future.isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " future not valid"; + return false; + } + // Generate a unique ID for tracking + QUuid taskId = QUuid::createUuid(); + d->m_taskWatcherCont.insert(taskId, future); + return true; +} + +bool ScreenLocationElevationHelperBase::removeTask(const QUuid &taskId) +{ + return (d->m_taskWatcherCont.remove(taskId) > 0); +} + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.h new file mode 100755 index 000000000..53e8e8419 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_location_elevation_helper_base.h @@ -0,0 +1,77 @@ +/** + * @RDE_HEADER@ + * Base class for ScreenToLocation and LocationToElevation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include + + +// ESRI + + +// Qt +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +/** + * @brief The ScreenLocationElevationHelperBase class limits the load of + * e.g. Esri::ArcGISRuntime::SceneGraphicsView::screenToLocation + * (cpu load of ~50% was recognized othjerwise) + * This class limits this to 10 computations per second. + */ +class SITUATIONMAP_EXPORT ScreenLocationElevationHelperBase : public QObject +{ + Q_OBJECT + + using BaseClass = QObject; + +public: + /*! \name Construction, Destruction */ + //@{ + explicit ScreenLocationElevationHelperBase(QObject *parent = nullptr); + virtual ~ScreenLocationElevationHelperBase(); + + int pendingOperationsCount() const; + void cancel(); + void stop(); + + void newComputationRequested(); + + ScreenLocationElevationHelperBase(const ScreenLocationElevationHelperBase &other) = delete; + ScreenLocationElevationHelperBase &operator=(ScreenLocationElevationHelperBase &other) = delete; + //@} + + virtual void mainComputation() = 0; + +private Q_SLOTS: + void onMainTimoutInternal(); + void onKillTimout(); + +protected: + bool insertTask(const QFuture &future); + bool removeTask(const QUuid &taskId); + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.cpp new file mode 100755 index 000000000..397fb40f7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.cpp @@ -0,0 +1,122 @@ +/** + * @RDE_HEADER@ + * ScreenToLocationHelper + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "screen_to_location_helper.h" +#include "Map/situation_map_utils.h" + + +// ESRI + + +// Qt +#include + + +// V100.2.1: to guarante good accuracy of elevation we have to ignore 3d objects :-( +#define USE_SCREEN_TO_BASE_SURFACE + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +//---------------------------------------------------------------------------- +class ScreenToLocationHelper::Private +{ +public: + Private() = default; + ~Private() = default; + + + // ESRI data: + QPointer m_sceneGraphicsView; + QFutureWatcher m_locationWatcher; + + int m_windowPosX = 0; + int m_windowPosY = 0; +}; + +//---------------------------------------------------------------------------- + +ScreenToLocationHelper::ScreenToLocationHelper(Esri::ArcGISRuntime::SceneGraphicsView *sceneGraphicsView) + : BaseClass(sceneGraphicsView) + , m_logger(getLogger("ScreenToLocationHelper")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_sceneGraphicsView = sceneGraphicsView; + + connect(&d->m_locationWatcher, &QFutureWatcher::finished, + this, [this]() { + const Esri::ArcGISRuntime::Point &locationMap = d->m_locationWatcher.result(); + const Point3D mapCoordinate(locationMap.x(), locationMap.y(), locationMap.z()); + TRACE_LOG(m_logger) << LOGFUNCTION << " screenToLocationCompl" + << " (m_windowPosX " << d->m_windowPosX << " m_windowPosY " << d->m_windowPosY << ")..." + << " (mapCoordinate " << mapCoordinate << ", z=" << locationMap.z() << ")"; + emit mouseScenePositionChanged(mapCoordinate, !locationMap.isEmpty()); + }); + +} + +ScreenToLocationHelper::~ScreenToLocationHelper() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->stop(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void ScreenToLocationHelper::stop() +{ + if (d->m_sceneGraphicsView) + d->m_sceneGraphicsView->disconnect(this); + disconnect(this); + BaseClass::stop(); +} + +void ScreenToLocationHelper::screenToLocation(int windowPosX, int windowPosY) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " windowPosX = " << windowPosX << " windowPosY = " << windowPosY; + + d->m_windowPosX = windowPosX; + d->m_windowPosY = windowPosY; + + BaseClass::newComputationRequested(); +} + +void ScreenToLocationHelper::mainComputation() +{ + if (!d->m_sceneGraphicsView) + return; + + +#ifdef USE_SCREEN_TO_BASE_SURFACE + // BUG-000109976 - Segmentation fault on Scene Viewer destruction (Esri Case #02020238) + // BUG on destroy of SceneView. So use 'screenToBaseSurface' instead. + // Difference: screenToLocations takes Building and Features into account. + // screenToBaseSurface uses baseSurface only. + const Esri::ArcGISRuntime::Point &locationMap = d->m_sceneGraphicsView->screenToBaseSurface(d->m_windowPosX, d->m_windowPosY); + + emit mouseScenePositionChanged(Point3D(locationMap.x(), locationMap.y(), locationMap.z()), !locationMap.isEmpty()); +#else + +// qCritical() << " ScreenX=" << d->m_windowPosX << " ScreenY=" << d->m_windowPosY; + + + d->m_locationWatcher.setFuture(d->m_sceneGraphicsView->screenToLocationAsync(d->m_windowPosX, d->m_windowPosY)); +#endif // USE_SCREEN_TO_BASE_SURFACE +} + + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.h new file mode 100755 index 000000000..4d05bfeb7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/screen_to_location_helper.h @@ -0,0 +1,71 @@ +/** + * @RDE_HEADER@ + * ScreenToLocationHelper + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "screen_location_elevation_helper_base.h" +#include +#include + +// ESRI +#include +#include + +// Qt +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +/** + * @brief The ScreenToLocationHelper class limits the load of + * Esri::ArcGISRuntime::SceneGraphicsView::screenToLocation + * (cpu load of ~50% was recognized othjerwise) + * This class limits this to 10 computation per second. + */ +class SITUATIONMAP_EXPORT ScreenToLocationHelper : public ScreenLocationElevationHelperBase +{ + Q_OBJECT + + using BaseClass = ScreenLocationElevationHelperBase; + +public: + /*! \name Construction, Destruction */ + //@{ + explicit ScreenToLocationHelper(Esri::ArcGISRuntime::SceneGraphicsView *sceneGraphicsView); + ~ScreenToLocationHelper(); + + void stop(); + + ScreenToLocationHelper(const ScreenToLocationHelper &other) = delete; + ScreenToLocationHelper &operator=(ScreenToLocationHelper &other) = delete; + //@} + + void screenToLocation(int windowPosX, int windowPosY); + + void mainComputation() override; + +Q_SIGNALS: + void mouseScenePositionChanged(const Point3D &mapCoordinate, bool isValid); + +protected: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.cpp new file mode 100755 index 000000000..bf02bbc9b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.cpp @@ -0,0 +1,226 @@ +/** +** +** @RDE_HEADER@ +** Definition of transient map objects for use while rubberbanding is done +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Joerg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 29.09.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + + +// Own +#include "sidc_symbol_impl.h" +#include "map_impl.h" + +#include +#include +#include + +#include +#include + +#include + +// ESRI +#include +#include + + +//Qt +#include + + +namespace SituationMap +{ + + +SidcSymbolImpl::SidcSymbolImpl(const QString &debugInfo) : SidcSymbolImpl(QString(), debugInfo) {;} + +SidcSymbolImpl::SidcSymbolImpl(const QString &sidc, const QString &debugInfo) + : InterfaceClass() // Interface + , BaseClass(debugInfo) // Implementation + , m_sidc(sidc) + , m_color(0, 0, 0, 0) + , m_logger(getLogger("SIDC_SymbolImpl")) +{ + m_visibility = true; + m_visibilityTactical = true; + m_visibilityTechnical = false; + m_selected = false; + m_pickable = false; + + m_positions.setMapObjectImpl(this); + +} /* SidcSymbolImpl::SidcSymbolImpl */ + + +PositionCont SidcSymbolImpl::positions() const { return m_positions.positions(); } +void SidcSymbolImpl::setPositions(const PositionCont &posCont) { m_positions.setPositions(posCont); } + +PositionCont SidcSymbolImpl::positionsTranslated() const { return m_positions.positionsTranslated(); } +void SidcSymbolImpl::setPositionsTranslated(const PositionCont &posCont) { m_positions.setPositionsTranslated(posCont); } + +LineStyle SidcSymbolImpl::lineStyle() const { return BaseClass::lineStyle(); } +LineStyle &SidcSymbolImpl::rLineStyle() { return BaseClass::rLineStyle(); } +void SidcSymbolImpl::setLineStyle(const LineStyle &lineStyle) { BaseClass::setLineStyle(lineStyle); } +FillStyle SidcSymbolImpl::fillStyle() const { return BaseClass::fillStyle(); } +FillStyle &SidcSymbolImpl::rFillStyle() { return BaseClass::rFillStyle(); } +void SidcSymbolImpl::setFillStyle(const FillStyle &fillStyle) { BaseClass::setFillStyle(fillStyle); } + +float SidcSymbolImpl::scale() const { return (m_tacticalAppearance) ? m_tacticalAppearance->scale() : 1; } +void SidcSymbolImpl::setScale(float scale) { if (m_tacticalAppearance) m_tacticalAppearance->setScale(scale); } + + +const QString &SidcSymbolImpl::sidc() const { return m_symbol.value().sidc; } + +const QColor &SidcSymbolImpl::color() const { return m_color; } + + +void SidcSymbolImpl::boundingBox(BoundingBox &bbox) const +{ + if (m_tacticalAppearance) + { + SituationMap::CoordTranslationFunctor *coordTransFunctor = nullptr; + if (const_cast(this)->mapImpl()) + { + // if useful, a MapObjectType specific functor can be prefered here: + // like: if (m_mapObjectImpl->coordTranslationFunctor()) ctFunctor=m_mapObjectImpl->coordTranslationFunctor(); + coordTransFunctor = const_cast(this)->mapImpl()->coordTranslationFunctor(); + } + + if (coordTransFunctor) + { + const Esri::ArcGISRuntime::Envelope &extent = m_tacticalAppearance->getGraphicGeometry().extent(); + + const QPointF &min = coordTransFunctor->mapToGeographic(extent.xMin(), extent.yMin()); + const QPointF &max = coordTransFunctor->mapToGeographic(extent.xMax(), extent.yMax()); + const QPointF ¢er = (min + max) * 0.5; + bbox = BoundingBox(BoundingBox::ExtentType::Sim, Point3D::fromQPointF(center), max.x() - min.x(), max.y() - min.y()); + } + } +} // void SidcSymbolImpl::boundingBox(BoundingBox &bbox) const + + +void SidcSymbolImpl::setSidc(const QString &sidc) +{ + SymbolData symbolData = symbol(); + symbolData.sidc = sidc; + symbolData.useAlwaysDictionaryRenderer = (!AdvancedSymbology::SidcCodec::isCivil(sidc)); + setSymbol(symbolData); + m_sidc = sidc; +} // void SidcSymbolImpl::setSidc( const QString & sidc ) + + + +void SidcSymbolImpl::setColor(const QColor &color) +{ + m_color = color; +} // void SidcSymbolImpl::setColor(const QColor & color) + + +const QMap &SidcSymbolImpl::attributeMap() const { return m_symbol.value().attributeMap; } + + +void SidcSymbolImpl::setAttributeMap(const QMap &attrMap) +{ + SymbolData symbolData = symbol(); + symbolData.attributeMap = attrMap; + setSymbol(symbolData); +} // SidcSymbolImpl::setAttributeMap(const QMap & attrMap) + + +void SidcSymbolImpl::defineSymbol(const QString &sidc, const PositionCont &posCont, const QMap &attrMap, const QColor &color) +{ + SymbolData symbolData = symbol(); + symbolData.sidc = sidc; + symbolData.attributeMap = attrMap; + symbolData.color = (color.alpha() < 1) ? QColor(0, 0, 0, 0) : color; + symbolData.useAlwaysDictionaryRenderer = (!AdvancedSymbology::SidcCodec::isCivil(sidc)); + setSymbol(symbolData); + m_sidc = sidc; + m_color = color; + m_positions.setPositions(posCont); +} // void SidcSymbolImpl::defineSymbol( const QString & sidc, const PositionCont & posCont, const QMap & attrMap ) + + +bool SidcSymbolImpl::updateViewImplPre() +{ + return BaseClass::updateViewImplPre(); +} // SidcSymbolImpl::updateViewImplPre + + +bool SidcSymbolImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + // REGG-5261 - Alle Wirkfelder blinken in 3D, wenn C-Kampf ausgelöst ist + // War mit 100.4 meist, aber nicht immer reproduzierbar. + // Wenn aber per FunctionsDialog die TacticalAppearance sichtbar wurde, + // so war ein massives Flackern erkennbar. + // Daher wird die Ausdehnung nur an SICHTBARE m_tacticalAppearance übergeben + // [basic_path_object, sidc_symbol_impl] + if (m_tacticalAppearance && m_tacticalAppearance->visibility()) + { + m_tacticalAppearance->setSymbol(m_symbol.value()); + m_tacticalAppearance->setPositions(m_positions.positionsTranslated()); + this->resetTacticalAppearanceDrawOrderOffset(); // avoid tacticalAppearance getting drawOrderOffset (2) + } + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + else + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} /* SidcSymbolImpl::updateViewImpl */ + + + +bool SidcSymbolImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!mapImpl) return false; + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + if (success) + { + if (!m_mainGraphicalObject) m_mainGraphicalObject = new StubGraphicalObject(); + + m_symbol.refValue().sidc = m_sidc; + m_symbol.refValue().color = (m_color.alpha() < 255) ? QColor(0, 0, 0, 0) : m_color; // to avoid color replacement (use default color) + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + } + else ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + return success; +} /* SidcSymbolImpl::init */ + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.h new file mode 100755 index 000000000..c9e185191 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sidc_symbol_impl.h @@ -0,0 +1,145 @@ +/** +** +** @RDE_HEADER@ +** Definition of transient map objects for use while rubberbanding is done +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 29.09.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + + +#pragma once + +#include "Map/sidc_symbol.h" +#include "abstract_map_object_impl.h" +#include "coordinate_translation.h" + +#include +#include + + + +namespace SituationMap +{ + +/** + @class SIDC_SymbolImpl SIDC_SymbolImpl.h + The class \c SIDC_SymbolImpl creates a tactical symbol due a sidc code. It is used by class + TransientTacticalSymbol to display transient tactical symbols during rubberband input is done. + + @see TransientTacticalSymbol +*/ +class SITUATIONMAP_EXPORT SidcSymbolImpl : public virtual SidcSymbol, public virtual AbstractMapObjectImpl +{ + using InterfaceClass = SituationMap::SidcSymbol; + using BaseClass = SituationMap::AbstractMapObjectImpl; + +public: + + /** + @name Construction & Destruction + @{ + */ + explicit SidcSymbolImpl(const QString &debugInfo = QString()); + explicit SidcSymbolImpl(const QString &sidc, const QString &debugInfo); + + SidcSymbolImpl(const SidcSymbolImpl &other) = delete; + SidcSymbolImpl &operator=(SidcSymbolImpl &other) = delete; + /** @} */ + + /** + @name Overloaded functions from base class + @see AbstractMapObject + @{ + */ + PositionCont positions() const override; + void setPositions(const PositionCont &posCont) override; + + virtual PositionCont positionsTranslated() const; + virtual void setPositionsTranslated(const PositionCont &posCont); + + LineStyle lineStyle() const override; + LineStyle &rLineStyle() override; + void setLineStyle(const LineStyle &lineStyle) override; + + FillStyle fillStyle() const override; + FillStyle &rFillStyle() override; + void setFillStyle(const FillStyle &fillStyle) override; + + float scale() const override; + void setScale(float scale) override; + /** @} */ + + const QString &sidc() const override; + void setSidc(const QString &) override; + + const QColor &color() const override; + void setColor(const QColor &color = QColor(0, 0, 0, 0)) override; + + const QMap &attributeMap() const override; + void setAttributeMap(const QMap &attrMap) override; + + void defineSymbol(const QString &sidc, + const PositionCont &posCont, + const QMap &attrMap, + const QColor &color = QColor(0, 0, 0, 0)) override; + + /** + Overloaded from base class to implement the construction of GraphicalObjects. + */ + bool init(MapImpl *mapImpl, const QString &logicalLayerName = QStringLiteral("GraphicsLayer")) override; + + void boundingBox(BoundingBox &bbox) const override; + +protected: + + /** + The function \c updateViewImplPre implementents how to begin transfer values to view. It is been called + after changing values from AbstractMapObject::updateView() before calling \c updateViewImplPre(). + @see AbstractMapObject + */ + bool updateViewImplPre() override; + + /** + The function \c updateViewImpl implementents how to transfer values to view. It is been called + after changing values from AbstractMapObject::updateView(). + @see AbstractMapObject + */ + bool updateViewImpl() override; + + friend class MapImpl; + + + QString m_sidc; + QColor m_color; + PositionsTranslated m_positions; + +private: + + LOGGER_T m_logger; ///< Interface of the Logging-Facility + +}; /* SidcSymbolImpl */ + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.cpp new file mode 100755 index 000000000..426bfb1b3 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.cpp @@ -0,0 +1,801 @@ + +/* +** ----------------------------------------------------------------------------------------------- +** +** @RDE_HEADER@ +** Implementation of fan of sight +** $Id$ +** $HeadURL : svn ://192.168.133.21/TDS/branches/product/devel/trunk/Lts/Viewer/SituationMap/Rubberbands/rubberband.cpp $ +** $Author : ehrlicher $ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware - Environment : Intel - PC +** Software - Environment : Windows 7, Qt 5.x.ArcGISRuntimeQt 100.2 +** +** Language ........ : C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Autor ........... : Nikolaus Hanekamp, SEDG, Tel. 2906 +** Jörg Turla, SPDG, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notices ......... : +** +** 01.02.2005, NH : Urversion +** 15.05.2018, JT : adapting GeISiR source to SituationMap +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "sight_fan_impl.h" + +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +using namespace std::chrono_literals; + + +namespace SituationMap +{ + +// class SightFanImpl::Internal; + + +enum SightMode { VISIBLE = 0, INVISIBLE, UNDEFINED, START }; + + +class SightEntry +{ +public: + + SightEntry() : _dist(0.0), _mode(START) {;} + SightEntry(double dist, SightMode mode) : _dist(dist), _mode(mode) {;} + + ~SightEntry() = default; + + double & dist() { return _dist; } + SightMode & mode() { return _mode; } + + double dist() const { return _dist; } + SightMode mode() const { return _mode; } + +private: + + double _dist; + SightMode _mode; + +}; // SightEntry + + + +class SightBeam : public Referenced +{ +public: + + explicit SightBeam(int beamId, const QPointF &directionB, const QPointF &directionE) + : _beamId(beamId) + , _directionBeg(directionB) + , _directionEnd(directionE) + , _sightEntryVec() + , _graphicalQuadVec() {;} + + SightBeam() = delete; + ~SightBeam() = default; + + QPointF direction() const + { + auto normalize = [](const QPointF &v) -> QPointF + { + double l = sqrt(v.x()*v.x() + v.y()*v.y()); + return v / l; + }; + + return normalize(QPointF((_directionBeg + _directionEnd)*0.5)); + } // QPointF direction() + + + void add(const SightEntry &sightE) + { + _sightEntryVec.push_back(sightE); + } // void add( const SightEntry &sightE) + + void clear() { _sightEntryVec.clear(); } + + // void draw(const QPointF &beamCenter, osg::Matrixd mat, QColor colors[]); // todo: Farbtabelle + + void updateGraphic( MapImpl *mapImpl, AbstractMapObjectImpl *meMapObj, SightFanImpl::Internal * sightFanInternal ); + + int _beamId; + +protected: + + QPointF _directionBeg; // Einheitsvektoren die den Beam begrenzen + QPointF _directionEnd; + + using SightEntryVec = QVector; + + SightEntryVec _sightEntryVec; + + QVector> _graphicalQuadVec; + +}; /* SightBeam */ + + +class SightFanImpl::Internal +{ +public: + + using SightBeamVec = std::vector> ; + + QString m_layerName; + std::shared_ptr m_eMapManager; + QPointF m_center; + double m_centHat; + double m_objHat; + double m_radius; + double m_innerRadius; + double m_startAngle; + double m_dAngle; + unsigned int m_nrFans; + + double m_alpha; + + QRectF m_location; // Lage des SightFans in Kartenkoordinaten + + SightBeamVec m_sightBeamVec; + + QColor m_colors[3]; // 0 : visibleCol, 1 : invisibleCol, 2 : undefinedCol + + QColor m_fanColorVec[2][4]; + + float m_fanMult; // Farbabsenkung jedes 2. Beams < 1.0 ! + + MapImpl *m_mapImpl; + Esri::ArcGISRuntime::Surface *m_surface; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + Internal() + : m_layerName() + , m_center() + , m_centHat(1.8) + , m_objHat(0.5) + , m_radius(0.0) + , m_innerRadius(0.0) + , m_startAngle(0.0) + , m_dAngle(2.0*MathConst::pi/180) + , m_nrFans(180) + , m_alpha(0.8) + , m_fanMult(0.75) + , m_mapImpl(nullptr) + , m_surface(nullptr) + , m_logger(getLogger("SightFanImpl")) + {;} + + + void setFanColorMap( double brightness, double bias, double alpha) + { + for (int i = 0; i < 3; i++) + { + double r = (m_colors[i].redF() * brightness) + bias; + double g = (m_colors[i].greenF() * brightness) + bias; + double b = (m_colors[i].blueF() * brightness) + bias; + double a = m_colors[i].alphaF() * alpha; + + m_fanColorVec[0][i] = QColor::fromRgbF(r,g,b,a); + m_fanColorVec[1][i] = QColor::fromRgbF(r*m_fanMult,g*m_fanMult,b*m_fanMult,a); + + } + m_fanColorVec[0][3] = m_fanColorVec[0][2]; + m_fanColorVec[1][3] = m_fanColorVec[1][2]; + } // void setFanColorMap() + + +}; // SightFanImpl::Internal + + +SightFanImpl::SightFanImpl( const QString &debugInfo ) + : SightFan() + , QObject() + , AbstractMapObjectImpl(debugInfo) + , m_internal(new Internal()) + , m_moveDelayTimer(new QTimer(this)) + +{ + initColors(); + m_internal->setFanColorMap(1, 0, 1); + m_moveDelayTimer->setSingleShot(true); + m_moveDelayTimer->setInterval(250ms); + connect(m_moveDelayTimer, &QTimer::timeout, this, &SightFanImpl::delayedMove); +} // SightFanImpl::SightFanImpl + + +SightFanImpl::~SightFanImpl() +{ + delete m_internal; +} // SightFanImpl::~SightFanImpl() + + +void SightFanImpl::initColors() +{ + m_internal->m_fanMult = 0.75; + m_internal->m_colors[0] = QColor(0, 255, 0, 180); + m_internal->m_colors[1] = QColor(255, 0, 0, 180); + m_internal->m_colors[2] = QColor(0, 0, 255, 180); +} // void SightFanImpl::initColors() + + +void SightFanImpl::setFanColor( const QColor &visible, const QColor &invisible, const QColor &undefined) +{ + m_internal->m_colors[0] = visible; + m_internal->m_colors[1] = invisible; + m_internal->m_colors[2] = undefined; +} // void SightFanImpl::setFanColor( const QColor &visible, const QColor &invisible, const QColor &undefined) + + +void SightFanImpl::setFanColorMark( float colMult) +{ + if( colMult > 1.0) + colMult = 1.0; + if( colMult < 0.0) + colMult = 0.0; + + m_internal->m_fanMult = colMult; +} // void SightFanImpl::setFanColorMark( float colMult) + + +void SightFanImpl::setCenter(const QPointF ¢er) { m_internal->m_center = center; } +void SightFanImpl::setCenter(const Point3D ¢er) { m_internal->m_center = center.toPointF(); + m_internal->m_centHat = center.z(); } + +void SightFanImpl::setCenter(const QPointF ¢er, double centerHat) { m_internal->m_center = center; + m_internal->m_centHat = centerHat; } + +void SightFanImpl::setCenterHat(double centerHat) { m_internal->m_centHat = centerHat; } +void SightFanImpl::setObjectHat(double objHat) { m_internal->m_objHat = objHat; } + +void SightFanImpl::setRadius(double radius) { m_internal->m_radius = radius; } +void SightFanImpl::setInnerRadius(double radius) { m_internal->m_innerRadius = radius; } +void SightFanImpl::setStartAngle(double startAngle) { m_internal->m_startAngle = startAngle; } +void SightFanImpl::setdeltaAngle(double dAngle) { m_internal->m_dAngle = dAngle; } +void SightFanImpl::setNoOfFans(double noFans) { m_internal->m_nrFans = noFans; } + + +void SightFanImpl::setAlpha(double alpha) +{ + if (alpha < 0.0) alpha = 0.0; + if (alpha > 1.0) alpha = 1.0; + + m_internal->m_alpha = alpha; +} // void SightFanImpl::setAlpha(double alpha) + + +QPointF SightFanImpl::center() const { return m_internal->m_center; } +double SightFanImpl::centerHat() const { return m_internal->m_centHat; } +double SightFanImpl::objectHat() const { return m_internal->m_objHat; } +double SightFanImpl::innerRadius() const { return m_internal->m_innerRadius; } +double SightFanImpl::radius() const { return m_internal->m_radius; } +double SightFanImpl::startAngle() const { return m_internal->m_startAngle; } +double SightFanImpl::dAngle() const { return m_internal->m_dAngle; } +double SightFanImpl::alpha() const { return m_internal->m_alpha; } +unsigned int SightFanImpl::noOfFans() const { return m_internal->m_nrFans; } + +bool SightFanImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + if (mapImpl) + { + m_internal->m_mapImpl = mapImpl; + m_internal->m_layerName = logicalLayerName; + m_internal->m_eMapManager = mapImpl->getElevationMapManager(); + + if (!m_internal->m_eMapManager) + { + ERROR_LOG(m_internal->m_logger) << LOGFUNCTION << " failure, no ElevationMapManager"; + return false; + } + if (!AbstractMapObjectImpl::init(mapImpl, logicalLayerName)) + { + ERROR_LOG(m_internal->m_logger) << LOGFUNCTION << " failure, AbstractMapObjectImpl::init failed"; + return false; + } + return true; + } + ERROR_LOG(m_internal->m_logger) << LOGFUNCTION << " init SightFanImpl failed"; + return false; +} // bool SightFanImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) + + +void SightFanImpl::boundingBox(BoundingBox &bbox) const +{ + bbox.reset(); + + CoordTranslationFunctor * coordTransFunc = m_internal->m_mapImpl->coordTranslationFunctor(); + + if (coordTransFunc) + { + const QPointF ¢erMap = coordTransFunc->geographicToMap(m_internal->m_center); + double diameter = 2 * m_internal->m_radius; + + bbox = BoundingBox( BoundingBox::ExtentType::Sim, Point3D::fromQPointF(centerMap), diameter, diameter ); + } +} // void SightFanImpl::boundingBox(BoundingBox &bbox) const + +//--------------------------------------------------------------------------------------------------- + +void SightFanImpl::initFan( const Point3D ¢er + , double objHat + , double radius + , double innerRadius + , double startAngle + , double dAngle + , unsigned int nrFans + , double alpha ) +{ + initFan( center.toPointF(), center.z(), objHat, radius, innerRadius, startAngle, dAngle, nrFans, alpha ); +} // void SightFanImpl::init(...) + + +void SightFanImpl::initFan( const QPointF ¢er + , double centHat + , double objHat + , double radius + , double innerRadius + , double startAngle + , double dAngle + , unsigned int nrFans + , double alpha ) +{ + if (!m_internal->m_mapImpl) + { + ERROR_LOG(m_internal->m_logger) << LOGFUNCTION << " no mapImpl"; + return; + } + + if (alpha < 0.0) alpha = 0.0; + if (alpha > 1.0) alpha = 1.0; + + m_internal->m_center = center; + m_internal->m_centHat = centHat; + m_internal->m_objHat = objHat; + m_internal->m_radius = radius; + m_internal->m_innerRadius = innerRadius; + m_internal->m_startAngle = startAngle; + m_internal->m_dAngle = (dAngle < 0) ? -dAngle : dAngle; + m_internal->m_nrFans = nrFans; + m_internal->m_alpha = alpha; + + QPointF offc(m_internal->m_radius, -m_internal->m_radius); + m_internal->m_location = QRectF(m_internal->m_center - offc, m_internal->m_center + offc); + + if ((m_internal->m_nrFans*m_internal->m_dAngle) > (MathConst::pi*2.0)) + { + m_internal->m_nrFans = floor((MathConst::pi*2.0) / m_internal->m_dAngle + 0.5); // To avoid error with floor; 30.3.2006 JT + } + + calculateSightBeamVec(); + +} /* SightFan::SightFan */ + + +void SightFanImpl::calculateSightBeamVec() +{ + m_internal->m_sightBeamVec.clear(); + + double dAlfa = (m_internal->m_dAngle / 2.0) + 0.00025; // 0.00025 wegen Überlappung, auf antialiazed Systemen nicht nötig + + for (unsigned int i = 0; i < m_internal->m_nrFans; i++) + { + double alfa = m_internal->m_startAngle + m_internal->m_dAngle * i; + double alfaBeg = alfa - dAlfa; + double alfaEnd = alfa + dAlfa; + + RefPointer sBeam = RefPointer(new SightBeam(i, QPointF(cos(alfaBeg), sin(alfaBeg)), QPointF(cos(alfaEnd), sin(alfaEnd)))); + calculateSightBeam(*(sBeam.get())); + m_internal->m_sightBeamVec.push_back(sBeam); + } +} // void SightFanImpl::calculateSightBeamVev() + + +void SightFanImpl::move(const QPointF ¢er, double centHat, double objHat) +{ + + m_internal->m_centHat = centHat; + m_internal->m_objHat = objHat; + move(center); +} // void SightFanImpl::move(ElevationMap &eMap, QPointF ¢er, double centHat, double objHat) + + +void SightFanImpl::move(const QPointF & center) +{ + m_moveDelayedCenter = center; + m_moveDelayTimer->start(); + +} + +void SightFanImpl::show() +{ + move( m_internal->m_center ); +} // void SightFanImpl::show + + +bool SightFanImpl::updateViewImplPre() +{ + return true; +} // bool SightFanImpl::updateViewImplPre() + + +bool SightFanImpl::updateViewImpl() +{ + show(); + return true; +} // bool SightFanImpl::updateViewImpl() + + +bool SightFanImpl::updateViewImplPost() +{ + return true; +} // bool SightFanImpl::updateViewImplPost() + + +inline QPointF mittelBreitenRechnung(const QPointF &start, const QPointF &direction, double distance) +{ +// --------------------------------------------------------------- +// Vec2d mittelBreitenRechnung( Vec2d start, Vec2d direction, double distance ) + +// Berechnet näherungsweise die Koordinate der Endposition eines +// Beams gemäß der ersten Aufgabe der Besteckrechnung durch das +// Verfahren der Mittelbreite. +// Siehe http://astrosail.de/de/static/tutorial/best3.php?cat=42 + +// Start ist die Anfangskoordinate im Bogenmaß, direction der +// normierte Richtungsvektor, distance die Entfernung in Metern. +// --------------------------------------------------------------- + double cosKurs = direction.y(); + double sinKurs = direction.x(); + double distanceBogen = distance / MathConst::EarthRadius; + double lat = distanceBogen * cosKurs + start.y(); + double mittelbreite = (start.y() + lat) * 0.5; + double lon = distanceBogen * sinKurs / cos(mittelbreite) + start.x(); + return {lon, lat}; +} + +inline QPointF endOfBeam( const QPointF &start, const QPointF &direction, double distance ) +{ + return mittelBreitenRechnung(start,direction,distance); +} + + +void SightFanImpl::calculateSightBeam(SightBeam &sBeam) +{ + const QPointF &direction = sBeam.direction(); + const QPointF &end = endOfBeam(m_internal->m_center, direction, m_internal->m_radius); + + LOSVector losProfile; + m_internal->m_eMapManager->LOSProfile(m_internal->m_center, end, losProfile); + calculateSightBeam(losProfile, sBeam); + sBeam.updateGraphic(m_internal->m_mapImpl, this, m_internal); +} // void SightFanImpl::calculateSightBeam(ElevationMap &eMap, SightBeam &sBeam) + + +void SightFanImpl::calculateSightBeam( const LOSVector &losProfile, SightBeam &sBeam ) +{ + sBeam.clear(); + + if (losProfile.empty()) // Startpunkt undefiniert oder ausserhalb der Karte + { + sBeam.add( SightEntry(m_internal->m_innerRadius, START) ); + sBeam.add( SightEntry(m_internal->m_radius, UNDEFINED) ); + return; + } + + LOSVector::const_iterator itr = losProfile.begin(); + double centerHeight = itr->height() + m_internal->m_centHat; + + double d1 = 0.0; + double height1 = itr->height(); + + // überspringen bis _innerRadius erreicht ist ! + while(++itr != losProfile.end()) + { + double d2 = itr->dist(); // *EarthRadius; + double height2 = itr->height(); + if (d2 >= m_internal->m_innerRadius) + { + height1 = height1 + (m_internal->m_innerRadius - d1) * ( height2 - height1)/(d2 - d1); + d1 = m_internal->m_innerRadius; + --itr; + break; + } + d1 = d2; + height1 = height2; + } + + // bool visible = true; + SightMode mode = START; + + double curM = (height1 - centerHeight ) / d1; // bestimmende Steigung + + sBeam.add(SightEntry( d1, START)); + + if (itr != losProfile.end()) + { + while (++itr != losProfile.end()) + { + double d2 = itr->dist(); // *EarthRadius; + double height2 = itr->height(); + + if (std::isnan(height2)) // Undefined + { + if (mode != START) + { + sBeam.add(SightEntry(d1, mode)); + } + mode = UNDEFINED; + break; + } + + double hObj = (height2 + m_internal->m_objHat - centerHeight ); + double objM = hObj / d2; // Steigung bezogen auf Objekte( Objekte verdecken nicht !) + + SightMode newMode = ((objM >= curM)) ? VISIBLE : INVISIBLE; + + if(newMode != mode) + { + if ((mode != START) && (d2 != d1)) + { + // Korrekturwert für Distanz berechnen, unter Berücksichtigung der Objekthoehe !! + double localM = (height2 - height1) /(d2 -d1); + // localM == curM kann nicht auftreten, da newMode != mode + double dCorr = (hObj - (curM * d2)) / (localM - curM); + + if (dCorr < 0.0) + dCorr = 0.0; + dCorr = d2 - dCorr; + if (dCorr < d1) + dCorr = d1; + + sBeam.add( SightEntry( dCorr, mode) ); + } + mode = newMode; + } + + double terrM = (height2 - centerHeight) / d2; // Steigung bezogen auf Gelaende + if (terrM >= curM) + { + curM = terrM; // neue bestimmende Steigung + } + + height1 = height2; + d1 = d2; + } /* while */ + } + sBeam.add( SightEntry(m_internal->m_radius, mode) ); +} +/*! + * \brief SightFanImpl::delayedMove + * \param center + * \details Delays the move call to prevent crash of map beacause of the heavy load while moving + */ +void SightFanImpl::delayedMove() +{ + m_internal->m_center = m_moveDelayedCenter; + + m_internal->setFanColorMap(1, 0, 1); + + QPointF offc(m_internal->m_radius, -m_internal->m_radius); + m_internal->m_location = QRectF(m_internal->m_center - offc, m_internal->m_center + offc); + + if (m_internal->m_nrFans == m_internal->m_sightBeamVec.size()) + { + for (RefPointer sBeam : std::as_const(m_internal->m_sightBeamVec)) + { + calculateSightBeam(*sBeam.get()); + } + } + else + { + calculateSightBeamVec(); + } +} + + +void SightBeam::updateGraphic(MapImpl *mapImpl, AbstractMapObjectImpl *meMapObj, SightFanImpl::Internal *sightFanInternal) +{ + if (!mapImpl || !meMapObj || !sightFanInternal) + return; + + if (_sightEntryVec.size() <= 1) + return; + + Esri::ArcGISRuntime::GraphicsOverlay *graphicLayer = sightFanInternal->m_mapImpl->getGraphicsLayer(sightFanInternal->m_layerName); + if (!graphicLayer) + { + ERROR_LOG(sightFanInternal->m_logger) << LOGFUNCTION << " Creating graphic overlayer \'" << sightFanInternal->m_layerName << "\' failed. Can't render sight fan"; + return; + } + + // _sightEntryVec[0] is SightMode::START + + // resize _graphicalQuadVec according _sightEntryVec + // (_sightEntryVec.size() <= 1 is chexck above) + const int newGraphicalSize = _sightEntryVec.size() - 1; + _graphicalQuadVec.resize(newGraphicalSize); + + CoordTranslationFunctor *geoToMap = sightFanInternal->m_mapImpl->coordTranslationFunctor(); + + int quadNo = 0; + SightEntryVec::iterator itr = _sightEntryVec.begin(); + + const QPointF ¢er = sightFanInternal->m_center; + QPointF v0 = endOfBeam(center, _directionBeg, itr->dist()); + QPointF v1 = endOfBeam(center, _directionEnd, itr->dist()); + + if (geoToMap) + { + v0 = geoToMap->geographicToMap(v0); + v1 = geoToMap->geographicToMap(v1); + } + + PositionCont locationVec; + + // skip first, we know mode==SightMode::START + while (++itr != _sightEntryVec.end()) + { + // GraphicalObjectPath already created : + QSharedPointer &spGopRef = _graphicalQuadVec[quadNo]; + if (!spGopRef) + { + GraphicalObjectPath * grQuad = new GraphicalObjectPath( GraphicalObjectPathType::Polygon + , mapImpl->getEsriGeoView() + , graphicLayer + , QStringLiteral("FanOfSight-Beam-%1").arg(_beamId) + , meMapObj->uniqueMapId() ); + grQuad->setPickable(false); + grQuad->rLineStyle().setLinePattern(LinePattern::Null); + grQuad->rFillStyle().setFillPattern(FillPattern::Solid); + + // write to _graphicalQuadVec + spGopRef = QSharedPointer(grQuad); + } + // : GraphicalObjectPath already created + + GraphicalObjectPath * grQuad = _graphicalQuadVec[quadNo].data(); + Q_ASSERT_X(grQuad, "SightBeam", "GraphicalObjectPath invalid"); + + SightMode mode = itr->mode(); + double dist = itr->dist(); + QColor quadColor = sightFanInternal->m_fanColorVec[_beamId % 2][int(mode)]; + + quadColor.setAlphaF( quadColor.alphaF()*sightFanInternal->m_alpha ); + + grQuad->rFillStyle().setFillColor(quadColor); + + QPointF v2 = endOfBeam(center, _directionBeg, dist); + QPointF v3 = endOfBeam(center, _directionEnd, dist); + + if (geoToMap) + { + v2 = geoToMap->geographicToMap(v2); + v3 = geoToMap->geographicToMap(v3); + } + + locationVec.clear(); + locationVec << Point3D::fromQPointF(v0); + locationVec << Point3D::fromQPointF(v1); + locationVec << Point3D::fromQPointF(v2); + locationVec << Point3D::fromQPointF(v3); + + grQuad->setPositions(locationVec); + + grQuad->updateView(); + + v0 = v3; + v1 = v2; + + ++quadNo; + } /* while */ + +} // void SightBeam::updateGraphic(MapImpl *mapImpl, AbstractMapObjectImpl *meMapObj, SightFanImpl::Internal *sightFanInternal) + + +#ifdef NEVER +bool SightFan::draw( const AbstractView * const view, osg::Matrixd mat, + float alpha, float brightness, float bias, MapObject::RenderState rstate ) // todo: Farbtabelle +{ + if ((rstate & _renderState) == 0) return false; + + if( !( _location.intersects( view->rectBound()) )) return false; // isCulled + + + ColorVec colors[2][4]; + for( int i = 0; i < 3; i++) + { + colors[0][i] = (_colors[i] * brightness) + bias; + colors[1][i] = colors[0][i] * _fanMult; + colors[0][i].alpha = alpha * _colors[i].alpha; + colors[1][i].alpha = alpha * _colors[i].alpha; + } + colors[0][3] = colors[0][2]; + colors[1][3] = colors[1][2]; + + GLint shadeModel; + + glGetIntegerv( GL_SHADE_MODEL, &shadeModel ); + glShadeModel(GL_FLAT); + glEnable( GL_BLEND); + + int n = 0; + for( SightBeamVec::iterator itr = _sightBeamVec.begin(); itr != _sightBeamVec.end(); ++itr) + { + (*itr).get()->draw( _center, mat, colors[(++n)%2]); + } + + glDisable( GL_BLEND); + glShadeModel(shadeModel); + return true; +} /* SightFan::draw */ + + + +void SightBeam::draw( Vec2d center, osg::Matrixd mat, ColorVec colors[]) // todo: Farbtabelle !? +{ + float z = 0; + + if( _sightEntryVec.size() <= 1) return; + + SightEntryVec::iterator itr = _sightEntryVec.begin(); + + Vec2d v0 = endOfBeam( center, _directionBeg, (*itr).dist() ); + Vec2d v1 = endOfBeam( center, _directionEnd, (*itr).dist() ); + + osg::Vec3d _v0 = mat.preMult( osg::Vec3d( v0[0], v0[1], 0.0 )); + osg::Vec3d _v1 = mat.preMult( osg::Vec3d( v1[0], v1[1], 0.0 )); + + glBegin( GL_QUAD_STRIP); + { + glVertex3f( _v0.x(), _v0.y(), z); + glVertex3f( _v1.x(), _v1.y(), z); + + + while( ++itr != _sightEntryVec.end() ) + { + SightMode mode = (*itr).mode(); + glColor4f( colors[mode].red, colors[mode].green, colors[mode].blue, colors[mode].alpha); + + v0 = endOfBeam( center, _directionBeg, (*itr).dist() ); + v1 = endOfBeam( center, _directionEnd, (*itr).dist() ); + + _v0 = mat.preMult( osg::Vec3d( v0[0], v0[1], 0.0 )); + _v1 = mat.preMult( osg::Vec3d( v1[0], v1[1], 0.0 )); + + glVertex3f( _v0.x(), _v0.y(), z); + glVertex3f( _v1.x(), _v1.y(), z); + } /* while */ + } + glEnd(); +} /* SightBeam::draw */ +#endif + + + +} // SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.h new file mode 100755 index 000000000..359280058 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/sight_fan_impl.h @@ -0,0 +1,184 @@ +/* +** ----------------------------------------------------------------------------------------------- +** +** @RDE_HEADER@ +** Implementation of fan of sight +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.x. ArcGISRuntimeQt 100.2 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Autor ...........: Nikolaus Hanekamp, SEDG, Tel. 2906 +** Jörg Turla, SPDG, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notices .........: +** +** 01.02.2005, NH : Urversion +** 15.05.2018, JT : adapting GeISiR source to SituationMap +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include + +#include + +#include + + +namespace SituationMap +{ + + class MapImpl; + class SightBeam; + + class SITUATIONMAP_EXPORT SightFanImpl : public QObject, public AbstractMapObjectImpl, public virtual SightFan + { + friend class SightBeam; + + Q_OBJECT + + public: + + explicit SightFanImpl(const QString &debugInfo = QString()); + virtual ~SightFanImpl(); + + void initColors(); + + void initFan( const QPointF ¢er + , double centHat + , double objHat + , double radius + , double innerRadius + , double startAngle + , double dAngle + , unsigned int nrFans + , double alpha ) override; + + void initFan( const Point3D ¢er + , double objHat + , double radius + , double innerRadius + , double startAngle + , double dAngle + , unsigned int nrFans + , double alpha ) override; + + void setCenter(const QPointF ¢er) override; + void setCenter(const Point3D ¢er) override; + void setCenter(const QPointF ¢er, double centerHat) override; + + void setCenterHat(double centerHat) override; + void setObjectHat(double objHat) override; + + void setRadius(double radius) override; + void setInnerRadius(double radius) override; + void setStartAngle(double startAngle) override; + void setdeltaAngle(double dAngle) override; + void setNoOfFans(double noFans) override; + void setAlpha(double alpha) override; + + QPointF center() const override; + double centerHat() const override; + double objectHat() const override; + double innerRadius() const override; + double radius() const override; + double startAngle() const override; + double dAngle() const override; + double alpha() const override; + unsigned int noOfFans() const override; + + void move(const QPointF ¢er) override; + + void move(const QPointF ¢er, double centHat, double objHat) override; + + void setFanColor(const QColor & visible, const QColor &invisible, const QColor &undefined) override; + void setFanColorMark(float colMult) override; + + void show() override; + + /** + Overloaded from base class to implement the construction of GraphicalObjects. + */ + bool init( MapImpl *mapImpl, const QString &logicalLayerName = QStringLiteral("SightFanLayer")) override; + + void boundingBox(BoundingBox &bbox) const override; + + + protected: + + bool updateViewImplPre() override; + bool updateViewImpl() override; + bool updateViewImplPost() override; + + void calculateSightBeamVec(); + + void calculateSightBeam(SightBeam &sBeam); + void calculateSightBeam(const LOSVector &losProfile, SightBeam &sBeam); + + class Internal; + + Internal *m_internal; + + private slots: + void delayedMove(); + private: + QTimer *m_moveDelayTimer; + QPointF m_moveDelayedCenter; + + }; // SightFanImpl + +} // SituationMap + + + +/* ----------------------------------------------------------------------------------------------- +** CVS-Id/Release ...: $Header$ +** ----------------------------------------------------------------------------------------------- +** +** Historie: +** +** $Log: not supported by cvs2svn $ +** Revision 1.9 2007/12/18 18:11:00 joerg +** draw() um bias erweitert und Rückgabe Boolean = true wenn wriklich gezeichnet wurde. +** +** Revision 1.8 2007/08/01 10:50:39 Jörg +** Neu: dirtyFlags und RenderState +** +** Revision 1.7 2007/07/23 14:04:09 Jörg +** AbstractView eingeführt +** +** Revision 1.6 2007/07/11 08:13:46 Jörg +** move jetzt auch nur mit Position +** +** Revision 1.5 2005/11/09 13:44:19 klaus +** Umsetzung auf Linux; +** +** Revision 1.4 2005/11/09 11:12:26 klaus +** Anpassung an Linux: Kommentar hinter #endif mit "//" versehen +** +** Revision 1.3 2005/04/15 13:05:18 joerg +** Resource Control +** +** Revision 1.2 2005/02/10 13:51:01 klaus +** Bias für SightFans eingeführt +** +** Revision 1.1 2005/02/09 17:08:11 klaus +** 1. Version der SightFans +** +** +** ----------------------------------------------------------------------------------------------- */ + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.cpp new file mode 100755 index 000000000..516f675b4 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.cpp @@ -0,0 +1,414 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of TerrainProfile + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "terrain_profile_impl.h" +#include "terrain_profile_processor.h" +#include "layer_factory_impl.h" +#include "coord_translation_functor_impl.h" +#include +#include + +#include "Rubberbands/rubberband_factory.h" + + +//ESRI +#include + + +// Qt +#include +#include +#include + + +#include + +namespace SituationMap +{ + +class TerrainProfileImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + SituationMap::MapImpl *m_mapImpl = nullptr; + Esri::ArcGISRuntime::Surface *m_surface = nullptr; + + TerrainProfileParameter m_terrainProfileParameter; + QPointer m_rubbandBase = nullptr; + + using COUNTER_T = long; + static COUNTER_T m_terrainProfileCounter; + QMap> m_terrainProfileMap; + + std::function< bool(long id, const QVector> &)> m_finishedCallback; + +}; // class TerrainProfileImpl::Private +//----------------------------------------------------------------------------- + +TerrainProfileImpl::Private::COUNTER_T TerrainProfileImpl::Private::m_terrainProfileCounter = 0; + + +//----------------------------------------------------------------------------- +// class TerrainProfileImpl +//----------------------------------------------------------------------------- +TerrainProfileImpl::TerrainProfileImpl(SituationMap::MapImpl *mapImpl) + : BaseClass() + , QObject() + , m_logger(getLogger("TerrainProfileImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_mapImpl = mapImpl; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +TerrainProfileImpl::~TerrainProfileImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->remove(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool TerrainProfileImpl::isReady() +{ + init(); + bool isReady = + d->m_surface && d->m_surface->loadStatus() == Esri::ArcGISRuntime::LoadStatus::Loaded && + d->m_mapImpl->isReady(); + + if (!isReady) + { + Esri::ArcGISRuntime::LoadStatus ls = Esri::ArcGISRuntime::LoadStatus::Unknown; + if (d->m_surface) + ls = d->m_surface->loadStatus(); + DEBUG_LOG(m_logger) << LOGFUNCTION + << " d->m_surface = " << d->m_surface + << " d->m_surface->loadStatus = " << ls + << " d->m_mapImpl->isReady() = " << d->m_mapImpl->isReady(); + } + return isReady; +} + +void TerrainProfileImpl::setFinishedCallback(const std::function< bool(long id, const QVector > &)> &finishedCallback) +{ + d->m_finishedCallback = finishedCallback; +} + +void TerrainProfileImpl::setTerrainProfileDialog(long id, QPointer &terrainProfileDialog) +{ + if (!d->m_terrainProfileMap.contains(id)) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " no proc found for id = " << id; + return; + } + QPointer proc = d->m_terrainProfileMap[id]; + if (!proc) + return; + proc->setTerrainProfileDialog(terrainProfileDialog); +} + +bool TerrainProfileImpl::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " failed, not ready"; + return false; + } + + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + // d->m_rubbandBase; should be cleared now + + const Mode mode = d->m_mapImpl->mode(); + switch (mode) + { + case Mode::Mode_2D: + d->m_rubbandBase = createRubberband2D(); + break; + case Mode::Mode_3D: +// d->m_rubbandBase = createRubberband3D(); + break; + default: + ERROR_LOG(m_logger) << LOGFUNCTION << " mode (" << mode << ")" << " does not support viewshed"; + return false; + break; + } + + return true; +} + +long TerrainProfileImpl::create(const TerrainProfileParameter &terrainProfileParameter) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" << " terrainProfileParameter: " << terrainProfileParameter; + + if (!isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " failed, not ready"; + return -1; + } + + // remove all former + this->remove(); + + + // store current values: + d->m_terrainProfileParameter = terrainProfileParameter; + + TerrainProfileProcessor *proc = new TerrainProfileProcessor(); + + if (!proc->init(d->m_mapImpl, d->m_terrainProfileCounter)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " init of TerrainProfileProcessor failed"; + delete proc; + return -1; + } + d->m_terrainProfileMap[d->m_terrainProfileCounter++] = proc; + + connect(proc, &TerrainProfileProcessor::locationsToElevationsCompleted, + this, &TerrainProfileImpl::onLocationsToElevationsCompleted, Qt::UniqueConnection); + + // GlobalWaitDialog + LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(); + + proc->start(d->m_terrainProfileParameter); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_terrainProfileCounter; +} + +void TerrainProfileImpl::remove(long terrainProfileId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " terrainProfileId=" << terrainProfileId; + + if (terrainProfileId < 0) + { + for (const QPointer &proc : std::as_const(d->m_terrainProfileMap)) + delete proc; + d->m_terrainProfileMap.clear(); + return; + } + + if (d->m_terrainProfileMap.contains(terrainProfileId)) + { + QPointer proc = d->m_terrainProfileMap[terrainProfileId]; + if (proc) + delete d->m_terrainProfileMap[terrainProfileId]; + d->m_terrainProfileMap.remove(terrainProfileId); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " terrainProfileId not found, id=" << terrainProfileId; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " terrainProfileId=" << terrainProfileId; +} + + +void TerrainProfileImpl::init() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_mapImpl) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no mapImpl"; + return; + } + + const LayerFactoryImpl &layerFactoryImpl = d->m_mapImpl->layerFactoryImpl(); + if (!d->m_surface) + d->m_surface = layerFactoryImpl.baseSurface(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +RB::RubberbandMultiPointFeature *TerrainProfileImpl::createRubberband2D() +{ + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + RB::RubberbandMultiPointFeature *rubberband = + d->m_mapImpl->rubberbandFactory().create(this); + + if (!rubberband || !rubberband->rubberbandIsAlive()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " create2D failed"; + return nullptr; + } + rubberband->setAutoDelete(true); + + connect(rubberband, &SituationMap::RB::Rubberband::abort, this, &TerrainProfileImpl::onRbAbort, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, &TerrainProfileImpl::onRb2DApply, Qt::UniqueConnection); + + return rubberband; +} + +/* +RB::RubberbandMultiPointFeature3D *TerrainProfileImpl::createRubberband3D() +{ + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + RB::RubberbandMultiPointFeature3D *rubberband = + d->m_mapImpl->rubberbandFactory().create3D(this); + + if (!rubberband || !rubberband->rubberbandIsAlive()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " create3D failed"; + return nullptr; + } + rubberband->setAutoDelete(true); + +// rubberband->constrainQuadratic(true); +// rubberband->constrainOrthogonal(true); + + connect(rubberband, &SituationMap::RB::Rubberband::abort, this, &TerrainProfileImpl::onRbAbort, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, &TerrainProfileImpl::onRb3DApply, Qt::UniqueConnection); + + return rubberband; +} +*/ + +void TerrainProfileImpl::onRbAbort(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + return; + DEBUG_LOG(m_logger) << LOGFUNCTION << " rb abort"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void TerrainProfileImpl::onRb2DApply(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no rubberband"; + return; + } + + RB::RubberbandMultiPointFeature *polyline = static_cast(rbnd); + + TerrainProfileParameter para; + para.m_coordinates = convert2D3D(polyline->coordinateVector(RB::CoordinateSystem::Geo)); + + long id = this->create(para); + + if (id < 0) + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " para.m_coordinates.size()= " << para.m_coordinates.size(); +} + +void TerrainProfileImpl::onLocationsToElevationsCompleted(long id) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", id=" << id; + + // GlobalWaitDialog + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); + + const auto it = d->m_terrainProfileMap.find(id); + +// if (it == d->m_terrainProfileMap.constEnd()) +// { +// ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; +// return; +// } + + QPointer proc = it.value(); + + if (!proc) + return; + + const QVector> &resultList = proc->results(); + + INFO_LOG(m_logger) << LOGFUNCTION << " START" + << " id =" << id + << " m_locationToElevationBulk.resultList().size()=" << resultList.size(); + + if (d->m_finishedCallback) + d->m_finishedCallback(id, resultList); + else + WARNING_LOG(m_logger) << LOGFUNCTION << " NO exe CallBack!?"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", id=" << id; +} + +/* + void TerrainProfileImpl::onRb3DApply(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no rubberband"; + return; + } + + RB::RubberbandMultiPointFeature3D *rubberband = static_cast(rbnd); + + const QVector &data = rubberband->coordinateVector(RB::CoordinateSystem::Map); // 3D -> RB::CoordinateSystem::Map -> degree + + if (data.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; + return; + } + + if (data.size() < 2) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " insufficiant number of coords"; + return; + } + + ViewshedParameter viewshedParameter; + + // degree to rad + const double factor = M_PI / 180.0; + const QPointF ¢er = data.at(0) * factor; + viewshedParameter.viewshedOrigin_latLon = center; + + + if (data.size() >= 2) + { + static Esri::ArcGISRuntime::GeometryEngine *gEngine = Esri::ArcGISRuntime::GeometryEngine::instance(); + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + + QPointF coord = data.at(0); + Esri::ArcGISRuntime::Point geom1(coord.x(), coord.y(), sr); + coord = data.at(1); + Esri::ArcGISRuntime::Point geom2(coord.x(), coord.y(), sr); + + Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistanceResult = gEngine->distanceGeodetic(geom1, geom2, + Esri::ArcGISRuntime::LinearUnitId::Meters, + Esri::ArcGISRuntime::AngularUnitId::Degrees, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + viewshedParameter.outerRadius = geodeticDistanceResult.distance(); + } + + long id = this->create(viewshedParameter); + + if (id < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} +*/ + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.h new file mode 100755 index 000000000..2f3b7b757 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_impl.h @@ -0,0 +1,78 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of TerrainProfile + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include "map_impl.h" +#include "Rubberbands/rubberband_multi_point_feature.h" +#include + + +// Qt +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class TerrainProfileImpl : public QObject, public virtual TerrainProfile +{ + Q_OBJECT + + using BaseClass = TerrainProfile; + +public: + explicit TerrainProfileImpl(SituationMap::MapImpl *mapImpl); + virtual ~TerrainProfileImpl(); + + TerrainProfileImpl(const TerrainProfileImpl &other) = delete; + TerrainProfileImpl &operator=(TerrainProfileImpl &other) = delete; + + bool isReady(); + + void setFinishedCallback(const std::function< bool(long id, const QVector> &)> &finishedCallback) override; + + void setTerrainProfileDialog(long id, QPointer &terrainProfileDialog) override; + + + bool create() override; + long create(const TerrainProfileParameter &terrainProfileParameter) override; + void remove(long terrainProfileId = -1) override; + +private Q_SLOTS: + void onRbAbort(RB::RubberbandBase *rbnd); + void onRb2DApply(RB::RubberbandBase *rbnd); + + void onLocationsToElevationsCompleted(long id); +private: + void init(); + + + RB::RubberbandMultiPointFeature *createRubberband2D(); + RB::RubberbandMultiPointFeature *createRubberband3D(); + + + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; // TerrainProfileImpl + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.cpp new file mode 100755 index 000000000..8c951bc2e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.cpp @@ -0,0 +1,365 @@ +/** + * @RDE_HEADER@ + * TerrainProfileProcessor + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "terrain_profile_processor.h" + +#include "map_impl.h" +#include +#include +#include +#include +#include + + + +// ESRI +#include +#include + + +// Qt + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class ViewshedBase +//----------------------------------------------------------------------------- +TerrainProfileProcessor::TerrainProfileProcessor(QObject *parent) + : QObject(parent) + , m_logger(getLogger("TerrainProfileProcessor")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +TerrainProfileProcessor::~TerrainProfileProcessor() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, id=" << m_id; + removeUserInput(); + + // maybe clear the view of m_terrainProfileDialog here? + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, id=" << m_id; +} + +bool TerrainProfileProcessor::init(SituationMap::MapImpl *mapImpl, long id) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_id = id; + + m_initialized = false; + + m_mapImpl = mapImpl; + m_srMap = m_mapImpl->getEsriSpatialReference(); + + LayerFactoryImpl &layerFactoryImpl = m_mapImpl->layerFactoryImpl(); + + m_toolsLayername = layerFactoryImpl.toolLayer(); + + if (!m_surface) + m_surface = layerFactoryImpl.baseSurface(); + + if (m_surface && m_surface->loadStatus() == Esri::ArcGISRuntime::LoadStatus::Loaded) + m_initialized = true; + else + { + Esri::ArcGISRuntime::LoadStatus ls = Esri::ArcGISRuntime::LoadStatus::Unknown; + if (m_surface) + ls = m_surface->loadStatus(); + ERROR_LOG(m_logger) << LOGFUNCTION + << " d->m_surface = " << m_surface + << " d->m_surface->loadStatus = " << ls; + } + + if (!m_locationToElevationBulk.init(m_mapImpl, m_surface)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " locationToElevationBulk.init() failed"; + return false; + } + + connect(&m_locationToElevationBulk, &LocationToElevationBulk::locationsToElevationsCompleted, + this, &TerrainProfileProcessor::onLocationsToElevationsCompleted, Qt::UniqueConnection); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_initialized=" << m_initialized; + return m_initialized; +} + +bool TerrainProfileProcessor::start(const TerrainProfileParameter &terrainProfileParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " terrainProfileParameter=" << terrainProfileParameter + << " m_srMap.wkid=" << m_srMap.wkid(); + + // don´t allow more than one at once: + if (m_inProgress) + return false; + + m_inProgress = true; + + m_terrainProfileParameter = terrainProfileParameter; + + removeUserInput(); + displayUserInput(); + + // densify + const Esri::ArcGISRuntime::Polyline &polylineOut_Map = densifiy(m_terrainProfileParameter); + + // calculate distance + + // bulk location to elevation + const Esri::ArcGISRuntime::ImmutablePart &immutablePart = polylineOut_Map.parts().part(0); + const int size = immutablePart.pointCount(); + for (int i = 0; i < size; ++i) + { + const Esri::ArcGISRuntime::Point &esriMapLocation = immutablePart.point(i); + // DEBUG_LOG(m_logger) << LOGFUNCTION << " esriMapLocation: " << QPointF(esriMapLocation.x(), esriMapLocation.y()); + m_locationToElevationBulk.addLocation(esriMapLocation); + } + m_locationToElevationBulk.start(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_viewshedInProgress=" << m_inProgress; + return true; +} + +QVector > TerrainProfileProcessor::results() const +{ + return m_locationToElevationBulk.results(); +} + +void TerrainProfileProcessor::setTerrainProfileDialog(QPointer &terrainProfileDialog) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_terrainProfileDialog = terrainProfileDialog; + + if (m_terrainProfileDialog) + { + m_terrainProfileDialog->setMap(m_mapImpl); + + // mousePositionChanged + connect(m_terrainProfileDialog.data(), &TerrainProfileDialog::mousePositionChanged, + this, &TerrainProfileProcessor::onDialogMousePositionChanged, Qt::UniqueConnection); + + // handle destruction of according dialog: + connect(terrainProfileDialog.data(), &TerrainProfileDialog::destroyed, + this, &TerrainProfileProcessor::deleteLater); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +Esri::ArcGISRuntime::Polyline TerrainProfileProcessor::densifiy(TerrainProfileParameter &terrainProfileParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " terrainProfileParameter=" << terrainProfileParameter; + + // + // PosCont to EsriPolyline: + // + Esri::ArcGISRuntime::PolylineBuilder polylineBuilder(m_srMap, this); + + for (const Point3D &coord : std::as_const(terrainProfileParameter.m_coordinates)) + { + // geo to map (polylineBuilder uses map also) + const Point3D &p3D = m_mapImpl->coordTranslationFunctor()->geographicToMap(coord); + polylineBuilder.addPoint(Esri::ArcGISRuntime::Point(p3D.x(), p3D.y(), p3D.z())); + } + + const Esri::ArcGISRuntime::Polyline &polylineIn = polylineBuilder.toPolyline(); + + terrainProfileParameter.m_length = + Esri::ArcGISRuntime::GeometryEngine::lengthGeodetic(polylineIn, + Esri::ArcGISRuntime::LinearUnit::meters(), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + const int numberCoords = 50; + + double maxSegmenthLength = terrainProfileParameter.m_length / numberCoords; + + INFO_LOG(m_logger) << LOGFUNCTION << " START" + << " lengthMeter=" << terrainProfileParameter.m_length + << " numberCoords=" << numberCoords + << " maxSegmenthLength=" << maxSegmenthLength; + + const Esri::ArcGISRuntime::Geometry &geometryOut = Esri::ArcGISRuntime::GeometryEngine::densifyGeodetic(polylineIn, + maxSegmenthLength, + Esri::ArcGISRuntime::LinearUnit::meters(), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + const Esri::ArcGISRuntime::Polyline polylineOut(geometryOut); + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " lengthMeter=" << terrainProfileParameter.m_length + << " numberCoords=" << numberCoords + << " maxSegmenthLength=" << maxSegmenthLength + << " polylineOut.parts().part(0).pointCount()=" << polylineOut.parts().part(0).pointCount(); + return polylineOut; +} + +void TerrainProfileProcessor::displayUserInput() +{ + displayInputLine(); + displayInputVertices(); +} + +void TerrainProfileProcessor::removeUserInput() +{ + removeInputLine(); + removeInputVertices(); + removeCurrentMarker(); +} + +void TerrainProfileProcessor::displayInputLine() +{ + // create + if (!m_polyline) + { + m_polyline = m_mapImpl->objectFactory().create(m_toolsLayername, + QStringLiteral("TerrainProfileLineId%1").arg(m_id)); + m_polyline->setLineStyle(LineStyle(m_color, 3)); + m_polyline->setScaleEnable(true); + m_polyline->setPickable(false); + } + + // apply data + m_polyline->setPositions(m_terrainProfileParameter.m_coordinates); + + // update + if (!m_polyline->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " polyline::updateView failed"; +} +void TerrainProfileProcessor::removeInputLine() +{ + if (m_polyline) + m_polyline->removeMySelf(); + m_polyline.reset(); +} + +void TerrainProfileProcessor::displayInputVertices() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + PositionCont coordinates = m_terrainProfileParameter.m_coordinates; + const int size = coordinates.size(); + if (size < 1) + return; + + m_markerCont.resize(size); + + int idx = 0; + QMutableVectorIterator> it(m_markerCont); + + while (it.hasNext()) + { + RefPointer &marker = it.next(); + if (!marker.valid()) + { + marker = m_mapImpl->objectFactory().create(m_toolsLayername, + QStringLiteral("TerrainProfileLineId%1_idx%2").arg(m_id).arg(idx)); + marker->setProxyMarker(MarkerSymbolType::Circle); + marker->setProxyMarkerColor(m_color); + marker->setProxyMarkerSize(7); + marker->setPickable(false); + } + marker->setOrigin(coordinates[idx]); + + // update + if (!marker->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " marker::updateView failed"; + + ++idx; + } // while + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} +void TerrainProfileProcessor::removeInputVertices() +{ + QMutableVectorIterator> it(m_markerCont); + while (it.hasNext()) + { + RefPointer &marker = it.next(); + if (marker) + marker->removeMySelf(); + } + m_markerCont.clear(); +} + +void TerrainProfileProcessor::displayCurrentMarker(const Point3D &pointGeo) +{ + // create + if (!m_currentMarker) + { + m_currentMarker = m_mapImpl->objectFactory().create(m_toolsLayername, + QStringLiteral("TerrainProfileLineId%1_currentMarker").arg(m_id)); + m_currentMarker->setProxyMarker(MarkerSymbolType::Circle); + m_currentMarker->setProxyMarkerColor(Qt::blue); + m_currentMarker->setProxyMarkerSize(10); + m_currentMarker->setPickable(false); + } + + // apply data + m_currentMarker->setOrigin(pointGeo); + + // update + if (!m_currentMarker->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " m_currentMarker::updateView failed"; +} + +void TerrainProfileProcessor::removeCurrentMarker() +{ + if (m_currentMarker) + m_currentMarker->removeMySelf(); + m_currentMarker.reset(); +} + +void TerrainProfileProcessor::onLocationsToElevationsCompleted() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_locationToElevationBulk.size()=" << m_locationToElevationBulk.size(); + + const QVector> &results = m_locationToElevationBulk.results(); + + if (m_logger.isDebugEnabled()) + for (const QPair &item : std::as_const(results)) + DEBUG_LOG(m_logger) << LOGFUNCTION << " location = " << item.first << " elevation = " << item.second; + + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " results.size()=" << results.size(); + + + // The caller should set TerrainProfileDialog on this signal: + emit locationsToElevationsCompleted(m_id); + + // maybe (better hopefully) the caller (via finishedCallback of TerrainProfileImpl) + // has updated our m_terrainProfileParameter !?! + if (m_terrainProfileDialog) + { + m_terrainProfileDialog->setData(this->results()); + + QVector marks; + marks.reserve(m_terrainProfileParameter.m_coordinates.size()); + for (const Point3D &coord : std::as_const(m_terrainProfileParameter.m_coordinates)) + { + // geo to map + const Point3D &p3D = m_mapImpl->coordTranslationFunctor()->geographicToMap(coord); + marks.append(p3D.toPointF()); + } + m_terrainProfileDialog->setMarks(marks); + } else { + ERROR_LOG(m_logger) << LOGFUNCTION << " no dialog"; + } + + m_inProgress = false; +} + +void TerrainProfileProcessor::onDialogMousePositionChanged(const QPointF &curPos) +{ + const Point3D &curPosGeo = Point3D::fromQPointF( m_mapImpl->coordTranslationFunctor()->mapToGeographic(curPos) ); + displayCurrentMarker(curPosGeo); +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.h new file mode 100755 index 000000000..afbc7f969 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/terrain_profile_processor.h @@ -0,0 +1,125 @@ +/** + * @RDE_HEADER@ + * TerrainProfileProcessor + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "map_impl.h" +#include "location_to_elevation_bulk.h" +#include +#include "MapObjects/polyline.h" +#include "MapObjects/geo_image.h" +#include + + +// ESRI +#include +#include +#include + + +// Qt +#include +#include +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Map; +class Graphic; +class GraphicsOverlay; +class Polyline; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +class MapImpl; + + +class TerrainProfileProcessor : public QObject +{ + Q_OBJECT + +public: + explicit TerrainProfileProcessor(QObject *parent = nullptr); + virtual ~TerrainProfileProcessor(); + + long id() const { return m_id; } + + bool init(SituationMap::MapImpl *mapImpl, long id); + + + + bool start(const TerrainProfileParameter &terrainProfileParameter); + + bool isRunning() const { return m_inProgress; } + + QVector< QPair< QPointF, double > > results() const; + + void setTerrainProfileDialog(QPointer &terrainProfileDialog); + +signals: + void locationsToElevationsCompleted(long id); + +protected slots: + void onLocationsToElevationsCompleted(); + void onDialogMousePositionChanged(const QPointF &curPos); + +protected: + Esri::ArcGISRuntime::Polyline densifiy(TerrainProfileParameter &terrainProfileParameter); + + void displayUserInput(); + void removeUserInput(); + + void displayInputLine(); + void removeInputLine(); + void displayInputVertices(); + void removeInputVertices(); + void displayCurrentMarker(const Point3D &pointGeo); + void removeCurrentMarker(); + +private: + SituationMap::MapImpl *m_mapImpl = nullptr; + Esri::ArcGISRuntime::SpatialReference m_srMap; + Esri::ArcGISRuntime::Surface *m_surface = nullptr; + + long m_id = -1; + TerrainProfileParameter m_terrainProfileParameter; + + // Calc + LocationToElevationBulk m_locationToElevationBulk; + + bool m_initialized = false; + bool m_inProgress = false; + + // Out + // TerrainProfileDialog (Displaying the results) + QPointer m_terrainProfileDialog; + + QString m_toolsLayername; + QColor m_color = Qt::magenta; + RefPointer m_polyline; + QVector> m_markerCont; + RefPointer m_currentMarker; + + + LOGGER_T m_logger; + +}; // class TerrainProfileProcessor + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.cpp new file mode 100755 index 000000000..110d0c7e1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.cpp @@ -0,0 +1,213 @@ +/** + * @RDE_HEADER@ + * Tile Map Service Layer for Esri + * $Id$ + * $HeadURL$ + * $Author$ + * License: BSD 2-Clause + */ + + +#include "tms_layer.h" + +#include +#include +#include + + +namespace EsriExtension +{ + +TMSLayer::TMSLayer(const QString &url, QObject *parent) + : EsriRuntimeQt::TiledMapServiceLayer(url, parent) + , m_nam(nullptr) +{ + connect(this, SIGNAL(requestTile(int,int,int)), + this, SLOT(onRequestTMSTile(int,int,int))); +} + +TMSLayer::~TMSLayer() +{ + delete m_nam; +} + +void TMSLayer::initialize() +{ + if (!m_nam) + { + m_nam = new QNetworkAccessManager(this); + connect(m_nam, SIGNAL(finished(QNetworkReply*)), + this, SLOT(replyBaseXmlFinished(QNetworkReply*))); + } + + QNetworkReply *reply = m_nam->get(QNetworkRequest(QUrl(url()))); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(replyBaseXmlError(QNetworkReply::NetworkError))); +} + +void TMSLayer::replyBaseXmlFinished(QNetworkReply *reply) +{ + if (!reply || reply->error() != QNetworkReply::NoError) + { + cleanupNetworkReply(reply); + return; + } + QDomDocument doc(QLatin1String("TMSLayerDoc")); + doc.setContent(reply); + const QDomElement domRoot = doc.documentElement(); + const QDomElement domSRS = domRoot.firstChildElement(QLatin1String("SRS")); + const QDomElement domBoundingBox = domRoot.firstChildElement(QLatin1String("BoundingBox")); + const QDomElement domOrigin = domRoot.firstChildElement(QLatin1String("Origin")); + const QDomElement domTileFormat = domRoot.firstChildElement(QLatin1String("TileFormat")); + const QDomElement domTileSets = domRoot.firstChildElement(QLatin1String("TileSets")); + + if (domSRS.isNull() || + domBoundingBox.isNull() || + domOrigin.isNull() || + domTileFormat.isNull() || + domTileSets.isNull()) + { + cleanupNetworkReply(reply); + const QString errStr(QString(QLatin1String("Got invalid xml from tms server (url: %1)")).arg(url())); + layerCreateError(errStr); + qWarning("%s", qPrintable(errStr)); + return; + } + + // optional + if (domTileFormat.hasAttribute(QLatin1String("extension"))) + m_extension = domTileFormat.attribute(QLatin1String("extension")); + + //LODs: + const double dpi = 90.7; // according to spec + const double ipf = 12.0; // inch per foot + const double dpu = dpi * ipf; // dots per unit + + // We expect a SRS string in the format 'EPSG:' here ... + const EsriRuntimeQt::SpatialReference sr(domSRS.text().split(QLatin1Char(':')).at(1).toInt()); + // Setting the image format is not needed (the image reader correctly guesses the format) + // Setting the spatial reference is also not needed - if not set, the default one + // from the layer is used. + EsriRuntimeQt::TileInfo tileInfo; + + tileInfo.setCols(domTileFormat.attribute(QLatin1String("width")).toInt()); + tileInfo.setRows(domTileFormat.attribute(QLatin1String("height")).toInt()); + + tileInfo.setDpi(dpi); + + EsriRuntimeQt::LOD lod; + QList lods; + const double minX = domBoundingBox.attribute(QLatin1String("minx")).toDouble(); + const double minY = domBoundingBox.attribute(QLatin1String("miny")).toDouble(); + const double maxX = domBoundingBox.attribute(QLatin1String("maxx")).toDouble(); + const double maxY = domBoundingBox.attribute(QLatin1String("maxy")).toDouble(); + + // Since ArcGIS calculates from upper left (instead lower left as tms defines), + // we need to make sure all our zoom levels have the same upper y value + // TODO: use from zoom level + const double utm32MaxYcalc = 16384 * 256 * 3; // 3 - Anzahl kacheln lowest zoom level + const double utm32MinX = 166021.44309607695; // min utm 32 boundary - calc from proj4 or smth. like this + + const QString tsAttr(QLatin1String("TileSet")); + QDomElement tileSetSibling = domTileSets.firstChildElement(tsAttr); + for (; !tileSetSibling.isNull(); tileSetSibling = tileSetSibling.nextSiblingElement(tsAttr)) + { + if (!tileSetSibling.hasAttribute(QLatin1String("order"))) + { + qWarning("Could not find 'order' attribute for LOD - skipping lod"); + continue; + } + const int lodLevel = tileSetSibling.attribute(QLatin1String("order")).toInt(); + + lod.setLevel(lodLevel); + lod.setResolution(tileSetSibling.attribute(QLatin1String("units-per-pixel")).toDouble()); + lod.setScale(lod.resolution() / dpu); + + if (!tileSetSibling.hasAttribute(QLatin1String("href"))) + { + qWarning("Could not find 'href' attribute for LOD - skipping lod"); + continue; + } + + lods.append(lod); + m_tileUrls.insert(lodLevel, tileSetSibling.attribute(QLatin1String("href"))); + + // Since ArcGIS calculates from upper left (instead lower left as tms defines), + // we need to make sure all our zoom levels have the same upper y value + m_tilesYAxis.insert(lodLevel, qRound(utm32MaxYcalc / (lod.resolution() * tileInfo.cols()))); + //qWarning("LOD: %d, %lf %lf, %lf", lodLevel, lod.resolution(), lod.scale(), + // lod.resolution() * 256 * m_tilesYAxis.value(lodLevel)); + + //qWarning("Y Tile count for level %d is %d - %lf", lodLevel, m_tilesYAxis.value(lodLevel), + // utm32MaxYcalc / (lod.resolution() * tileInfo.cols())); + } + if (lods.isEmpty()) + { + const QString errStr(QLatin1String("Could not find any layer in the xml file!")); + layerCreateError(errStr); + qWarning("%s", qPrintable(errStr)); + cleanupNetworkReply(reply); + return; + } + tileInfo.setLODs(lods); + + const EsriRuntimeQt::Envelope envelope(minX, minY, maxX, maxY, sr); + // TMS service origin is bottom left, arcgis upper left + tileInfo.setOrigin({utm32MinX, utm32MaxYcalc, sr}); + + setDefaultSpatialReference(sr); + setFullExtent(envelope); + setInitialExtent(envelope); + setTileInfo(tileInfo); + + // TODO: setMin/Max scale ? + + EsriRuntimeQt::TiledMapServiceLayer::initialize(); + + // clean up + cleanupNetworkReply(reply); +} + +void TMSLayer::replyBaseXmlError(QNetworkReply::NetworkError error) +{ + const QString errStr(QString(QLatin1String("Error retrieving xml from %1, error: %2")).arg(url()).arg(error)); + emit layerCreateError(errStr); + qWarning("%s", qPrintable(errStr)); +} + +void TMSLayer::onRequestTMSTile(int level, int col, int row) +{ + if (!m_tilesYAxis.contains(level)) + { + qWarning("Invalud zoom level (%d)!", level); + return; + } + // TMS starts counting from bottom left, ArcGIS from upper left + // -> we need to mirror the y-axis + int reqRow = m_tilesYAxis.value(level) - row - 1; + //qWarning("onRequestTMSTile: %d, %d, %d (%d)", level, col, reqRow, row); + QString requestUrl(m_tileUrls.value(level) + + QLatin1Char('/') + QString::number(col) + + QLatin1Char('/') + QString::number(reqRow)); + if (!m_extension.isEmpty()) + requestUrl += QLatin1Char('.') + m_extension; + //qWarning("RequestUrl: %s", qPrintable(requestUrl)); + setTile(level, col, row, QUrl(requestUrl)); +} + +void TMSLayer::cleanupNetworkReply(QNetworkReply *reply) +{ + if (reply) + { + reply->deleteLater(); + reply->disconnect(this); + } + if (m_nam) + { + m_nam->disconnect(this); + m_nam->deleteLater(); + m_nam = nullptr; + } +} + +} // namespace EsriExtension diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.h new file mode 100755 index 000000000..a9f2755bf --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/tms_layer.h @@ -0,0 +1,50 @@ +/** + * @RDE_HEADER@ + * Tile Map Service Layer for Esri + * $Id$ + * $HeadURL$ + * $Author$ + * License: BSD 2-Clause + */ + +#pragma once + +#include +#include + +class QNetworkAccessManager; + +namespace EsriExtension +{ + +/** A small Wrapper around EsriRuntimeQt::TiledMapServiceLayer to + * be able to access a tile map service through Qt Runtime SDK. + * Sadly Esri does not provide this layer for Qt... + * so we have to write one on our own + * It's far from perfect but works for our needs. + * It was only tested with geoserver 2.8 and EPSG:23632 projection + */ +class TMSLayer : public EsriRuntimeQt::TiledMapServiceLayer +{ + Q_OBJECT + +public: + explicit TMSLayer(const QString &url, QObject *parent = nullptr); + ~TMSLayer(); + + void initialize() override; +private Q_SLOTS: + void replyBaseXmlFinished(QNetworkReply *reply); + void replyBaseXmlError(QNetworkReply::NetworkError); + void onRequestTMSTile(int level, int col, int row); +private: + void cleanupNetworkReply(QNetworkReply *reply); +private: + QMap m_tileUrls; + QMap m_tilesYAxis; + QString m_extension; + QNetworkAccessManager *m_nam; + +}; + +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.cpp new file mode 100755 index 000000000..3d4a609d1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.cpp @@ -0,0 +1,120 @@ +/** + * @RDE_HEADER@ + * ViewshedAnalysis + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include +#include "viewshed_analysis.h" + + +// ESRI +#include + +#include +#include +#include + +// Qt +#include + +namespace SituationMap +{ + +ViewshedAnalysis::ViewshedAnalysis(Esri::ArcGISRuntime::SceneGraphicsView *sceneGraphicsView, QObject *parent) + : ViewshedBase(parent) + , m_analysisOverlayModel(sceneGraphicsView->analysisOverlays()) + , m_analysisOverlay(new Esri::ArcGISRuntime::AnalysisOverlay(this)) + , m_viewsheds(3) // ESRI viewshed support 120 degree maximum, merge 3 of them + , m_logger(getLogger("ViewshedAnalysis")) // P.S.: from release 100.3 ESRI supports 360 degree viewshed +{ + m_analysisOverlay->setVisible(true); + for (int i = 0; i < m_viewsheds.size(); ++i) + m_viewsheds[i] = nullptr; + QObject::connect(m_analysisOverlay->analyses(), &Esri::ArcGISRuntime::AnalysisListModel::errorOccurred, this, &ViewshedAnalysis::onErrorOccured); + + m_analysisOverlayModel->append(m_analysisOverlay); +} // ViewshedAnalysis::ViewshedAnalysis + + +ViewshedAnalysis::~ViewshedAnalysis() +{ + for (int i = 0; i < m_viewsheds.size(); ++i) + { + delete m_viewsheds[i]; + m_viewsheds[i] = nullptr; + } + m_analysisOverlayModel->removeOne(m_analysisOverlay); + delete m_analysisOverlay; +} // ViewshedAnalysis::~ViewshedAnalysis() + + +bool ViewshedAnalysis::execute(const QPointF &viewshedPoint_map_degree, double outerRadius, double observerOffset) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " viewshedPoint_map_degree= " << viewshedPoint_map_degree + << " outerRadius= " << outerRadius + << " observerOffset= " << observerOffset; + + if (!m_analysisOverlay) + return false; + + Esri::ArcGISRuntime::Point viewshedCenter(viewshedPoint_map_degree.x(), viewshedPoint_map_degree.y(), observerOffset); + double heading(0.0); + double deltaHeading(360.0 / m_viewsheds.size()); + + // ToDo: + // Keep in mind that the number of viewsheds is restricted to 8 on mobile devices and 16 on desktop devices. + // The horizontal angle have to be less than 120 degree. + + for (int i = 0; i < m_viewsheds.size(); ++i) + { + Esri::ArcGISRuntime::LocationViewshed *viewshed = m_viewsheds[i]; + + if (viewshed) + { + m_analysisOverlay->analyses()->removeOne(viewshed); + delete viewshed; + } + + viewshed = new Esri::ArcGISRuntime::LocationViewshed(viewshedCenter + , heading + , 90.0 // pitch limited to 180 + , deltaHeading // horizontalAngle limited to 120, from release 100.3 maximum is 360 here + , 90.0 // verticalAngle limited to 120 + , 10.0 // minDistance + , outerRadius // maxDistance + , this); + viewshed->setVisible(true); + viewshed->setFrustumOutlineVisible(false); + viewshed->setFrustumOutlineColor(QColor(0, 0, 255, 255)); + + // Add the Viewshed to the Analysis Overlay + m_analysisOverlay->analyses()->append(viewshed); + + m_viewsheds[i] = viewshed; + heading += deltaHeading; + } // for + + return true; +} // bool ViewshedAnalysis::execute(const QPointF &viewshedPoint_map, double outerRadius, double observerOffset) +} // namespace SituationMap + + +namespace SituationMap +{ +void ViewshedAnalysis::onErrorOccured(const Esri::ArcGISRuntime::Error &error) +{ + ERROR_LOG(m_logger) << LOGFUNCTION << " EsriError occured: " << error; +// qCritical() << "Error reported from ESRI Analyse Overlay:" << error.message() << " code:" << error.code(); +} // void ViewshedAnalysis::onErrorOccured(const Esri::ArcGISRuntime::Error &error) + +} // namespace SituationMap + + + + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.h new file mode 100755 index 000000000..4a1902322 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_analysis.h @@ -0,0 +1,64 @@ +/** + * @RDE_HEADER@ + * ViewshedAnalysis + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "viewshed_base.h" + +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class AnalysisOverlay; +class AnalysisOverlayListModel; +class LocationViewshed; +class SceneGraphicsView; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class ViewshedAnalysis : public ViewshedBase +{ + Q_OBJECT + +public: + + explicit ViewshedAnalysis(Esri::ArcGISRuntime::SceneGraphicsView *sceneGraphicsView + , QObject *parent = nullptr); + + virtual ~ViewshedAnalysis(); + + bool execute(const QPointF &viewshedPoint_map_degree, double outerRadius, double observerOffset) override; + +protected slots: + + void onErrorOccured(const Esri::ArcGISRuntime::Error &error); + +private: + + Esri::ArcGISRuntime::AnalysisOverlayListModel *m_analysisOverlayModel; + Esri::ArcGISRuntime::AnalysisOverlay *m_analysisOverlay; + QVector m_viewsheds; + + LOGGER_T m_logger; +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.cpp new file mode 100755 index 000000000..c99783f73 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.cpp @@ -0,0 +1,27 @@ +/** + * @RDE_HEADER@ + * Baseclass of Viewsheds + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "viewshed_base.h" + + +// Qt + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class ViewshedBase +//----------------------------------------------------------------------------- +ViewshedBase::ViewshedBase(QObject *parent) + : QObject(parent) +{} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.h new file mode 100755 index 000000000..71f2fc369 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_base.h @@ -0,0 +1,62 @@ +/** + * @RDE_HEADER@ + * Baseclass of Viewsheds + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include + +// ESRI +#include +#include + + +// Qt +#include +#include +#include + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Map; +class Graphic; +class GraphicsOverlay; +class GeoprocessingTask; +class GeoprocessingResult; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class ViewshedBase : public QObject +{ + Q_OBJECT + +public: + + explicit ViewshedBase(QObject *parent = nullptr); + virtual ~ViewshedBase() = default; + + ViewshedBase(const ViewshedBase &other) = delete; + ViewshedBase &operator=(ViewshedBase &other) = delete; + + virtual bool execute(const QPointF &viewshedPoint_map, double outerRadius, double observerOffset) = 0; + +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.cpp new file mode 100755 index 000000000..0c20dd423 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.cpp @@ -0,0 +1,569 @@ +/** + * @RDE_HEADER@ + * ViewshedGeoProcessing + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "viewshed_geo_processing.h" + +#include "map_impl.h" +#include +#include +#include +#include + + +// ESRI +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// Qt +#include +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class ViewshedBase +//----------------------------------------------------------------------------- +ViewshedGeoProcessing::ViewshedGeoProcessing(const QString &url, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + const Esri::ArcGISRuntime::SpatialReference &spatialReference, + QObject *parent) + : ViewshedBase(parent) + , m_url(url) + , m_resultsOverlay(graphicsLayer) + , m_srMap(spatialReference) + , m_logger(getLogger("ViewshedGeoProcessing")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +ViewshedGeoProcessing::~ViewshedGeoProcessing() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + clearGraphics(); + delete m_viewshedTask; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool ViewshedGeoProcessing::init(SituationMap::MapImpl *mapImpl) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // check viewshedContraint (V100.2.1: disable usage of GPU to avoid failure: + /* + 2018-03-21 13:06:15.911 ERROR ViewshedGeoProcessing |#| void SituationMap::ViewshedGeoProcessing::onErrorOccuredGeoprocessingJob(const Esri::ArcGISRuntime::Error &) GeoprocessingJob -> EsriError occured: sender= 'GeoprocessingJob', code=400, domain=0, extendedErrorType=0, message='Unable to complete operation.', additionalMessage='Error executing tool. Viewshed2Feature : ERROR 010151: No features found in C:\users\ctc\Temp\arcgisruntime_8\LegatusBremen201803151510.gpk\jobs\_\j5a4e467cb51047c28a463a703c2f2bcd\scratch\scratch.gdb\Viewshed_Polygon. Possible empty feature class. + Failed to execute (Raster to Polygon). + Failed to execute (Viewshed2Feature). + Failed to execute (Viewshed2Feature).' (pl/viewshed_geo_processing.cpp:529) + 2018-03-21 13:06:15.911 ERROR Qt |#| Geoprocessing Task failed (ication/ngl_qt_msg_handler.inc:26) + 2018-03-21 13:06:15.911 ERROR Qt |#| Geoprocessing Task failed message: code = 400 domain = 0 message = "Unable to complete operation." additionalMessage = "Error executing tool. Viewshed2Feature : ERROR 010151: No features found in C:\\users\\ctc\\Temp\\arcgisruntime_8\\LegatusBremen201803151510.gpk\\jobs\\_\\j5a4e467cb51047c28a463a703c2f2bcd\\scratch\\scratch.gdb\\Viewshed_Polygon. Possible empty feature class.\nFailed to execute (Raster to Polygon).\nFailed to execute (Viewshed2Feature).\nFailed to execute (Viewshed2Feature)." extendedErrorType = 0 (ication/ngl_qt_msg_handler.inc:26) + 2018-03-21 13:06:15.911 INFO ViewshedGeoProcessing |#| void SituationMap::ViewshedGeoProcessing::onJobStatusChanged() jobStatusChanged is now (m_jobStatus): Job failed (pl/viewshed_geo_processing.cpp:256) + */ + + { + const char cuda[] = "CUDA_VISIBLE_DEVICES"; + bool ok = qEnvironmentVariableIsSet(cuda) && + (qEnvironmentVariableIsEmpty(cuda) || qEnvironmentVariableIntValue(cuda) == -1); + + if (ok) + DEBUG_LOG(m_logger) << LOGFUNCTION << " VIEWSHED_CONTRAINT fulfilled: " + << cuda << " = '" << qgetenv(cuda) << "'"; + else + { + FATAL_LOG(m_logger) << LOGFUNCTION << " VIEWSHED_CONTRAINT NOT fulfilled: " << cuda << " = '" << qgetenv(cuda) << "'"; + + QMessageBox::critical(nullptr, + tr("Configuration Error"), + tr("Environmentvariable '%1' is not set to deactivate GPU. Operations can fail").arg(QString::fromLatin1(cuda)), + QMessageBox::Ok); + } + } + + + if (m_url.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " url is empty"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return false; + } + + m_mapImpl = mapImpl; // mapImpl is required only if processRasterResults is used + + delete m_viewshedTask; + m_viewshedTask = new Esri::ArcGISRuntime::GeoprocessingTask(QUrl(m_url), this); + m_viewshedTask->setObjectName(QLatin1String("GeoprocessingTask")); + + // Handle errors of GeoprocessingTask: + connect(m_viewshedTask, &Esri::ArcGISRuntime::GeoprocessingTask::errorOccurred, + this, &ViewshedGeoProcessing::onErrorOccuredGeoprocessingTask); + + m_initialized = true; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_initialized=" << m_initialized; + return m_initialized; +} + + +bool ViewshedGeoProcessing::execute(const QPointF &viewshedPoint_map, double outerRadius, double observerOffset) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " viewshedPoint_map=" << viewshedPoint_map + << " m_srMap.wkid=" << m_srMap.wkid() + << " outerRadius=" << outerRadius + << " observerOffset=" << observerOffset + << " m_viewshedInProgress=" << m_viewshedInProgress + << " url='" << m_url << "'"; + + // check point is the viewshed point + Esri::ArcGISRuntime::Point esriViewshedPoint(viewshedPoint_map.x(), viewshedPoint_map.y(), m_srMap); // Esri::ArcGISRuntime::SpatialReference::webMercator()); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " esriViewshedPoint.toJson: " << esriViewshedPoint.toJson(); + + m_viewshedInProgress = true; + + createInputGraphic(esriViewshedPoint); + createExtentGraphic(esriViewshedPoint, outerRadius); + clearResultGraphics(); + + // Create a new feature collection table based upon point geometries using the current map view spatial reference + Esri::ArcGISRuntime::FeatureCollectionTable *inputFeatures = + new Esri::ArcGISRuntime::FeatureCollectionTable(QList() + , Esri::ArcGISRuntime::GeometryType::Point + , m_srMap + , this); + + // Create a new feature from the feature collection table. It will not have a coordinate location (x,y) yet + Esri::ArcGISRuntime::Feature *inputFeature = inputFeatures->createFeature(this); + + // Assign a physical location to the new point feature based upon where the user clicked on the map view + inputFeature->setGeometry(esriViewshedPoint); + + // connect to addFeature status changed signal + connect(inputFeatures, &Esri::ArcGISRuntime::FeatureCollectionTable::addFeatureCompleted, this, [this, inputFeatures, outerRadius, observerOffset](QUuid, bool success) + { + INFO_LOG(m_logger) << LOGFUNCTION << " addFeatureCompleted..."; + + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no success"; + return; + } + + // Create the parameters that are passed to the used geoprocessing task + Esri::ArcGISRuntime::GeoprocessingParameters viewshedParameters = + Esri::ArcGISRuntime::GeoprocessingParameters(Esri::ArcGISRuntime::GeoprocessingExecutionType::SynchronousExecute); // AsynchronousSubmit | SynchronousExecute + // GeoprocessingExecutionType should match Esri::ArcGISRuntime::GeoprocessingServiceType of LocalGeoprocessingService + + + // Request the output features to use the same SpatialReference as the map view + viewshedParameters.setOutputSpatialReference(m_srMap); + + // Add an input location to the geoprocessing parameters + QMap inputs; + inputs[QLatin1String("Input_point_or_polyline_observer_features")] = new Esri::ArcGISRuntime::GeoprocessingFeatures(inputFeatures, this); + + // Radius + inputs[QLatin1String("Inner_radius")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(55.0, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); + inputs[QLatin1String("Outer_radius")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(outerRadius, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); + + // Observer +// //!!! DO NOT DEFINE, keep elevation of ellipsoid in mind // inputs[QLatin1String("Observer_elevation")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(0.0, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); + inputs[QLatin1String("Observer_offset")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(observerOffset, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); + + // Target + inputs[QLatin1String("Surface_offset")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(0.0, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); + + // setInputs + viewshedParameters.setInputs(inputs); + + // Print geoprocessingParameters for debugging purpose: + QMapIterator inputsIt(inputs); + while (inputsIt.hasNext()) + { + inputsIt.next(); + QString value(QLatin1String("-")); + Esri::ArcGISRuntime::GeoprocessingParameter *gpPara = inputsIt.value(); + Esri::ArcGISRuntime::GeoprocessingLinearUnit *gpParaLU = dynamic_cast(gpPara); + if (gpParaLU) + value = QStringLiteral("%1 %2").arg(gpParaLU->distance()).arg(gpParaLU->linearUnit().name()); + DEBUG_LOG(m_logger) << LOGFUNCTION << " key= '" << inputsIt.key() << "'" << " value= '" << value << "'"; + } // while + + + + // Create the job that handles the communication between the application and the geoprocessing task + m_viewshedJob = m_viewshedTask->createJob(viewshedParameters); + m_viewshedJob->setObjectName(QLatin1String("GeoprocessingJob")); + + // Handle errors of GeoprocessingJob: + connect(m_viewshedJob, &Esri::ArcGISRuntime::GeoprocessingJob::errorOccurred, this, &ViewshedGeoProcessing::onErrorOccuredGeoprocessingJob); + + // Create signal handler for the job + +#if ACRGIS_VERSION >= 0x640E00 // 100.14.0 + // since V100.14 + connect(m_viewshedJob, &Esri::ArcGISRuntime::GeoprocessingJob::statusChanged, this, &ViewshedGeoProcessing::onJobStatusChangedWithJsParameter); +#else + // until V100.14: + connect(m_viewshedJob, &Esri::ArcGISRuntime::GeoprocessingJob::jobStatusChanged, this, &ViewshedGeoProcessing::onJobStatusChanged); +#endif // ACRGIS_VERSION + + + // start the job + if (m_viewshedJob->start()) + LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(); + else + ERROR_LOG(m_logger) << LOGFUNCTION << " viewshedJob->start() failed"; + + }); + // Add the new feature with (x,y) location to the feature collection table + inputFeatures->addFeature(inputFeature); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_viewshedInProgress=" << m_viewshedInProgress; + return true; +} + +void ViewshedGeoProcessing::onJobStatusChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // qCritical() << "jobStatusChanged... viewshedJob->jobStatus()=" << (int)viewshedJob->jobStatus(); + + bool viewshedInProgressOld = m_viewshedInProgress; + + switch (m_viewshedJob->jobStatus()) + { + case Esri::ArcGISRuntime::JobStatus::NotStarted: + ERROR_LOG(m_logger) << LOGFUNCTION << "Geoprocessing Task failed"; + m_viewshedInProgress = false; + m_jobStatus = QLatin1String("Job NotStarted"); + break; + case Esri::ArcGISRuntime::JobStatus::Failed: + { + ERROR_LOG(m_logger) << LOGFUNCTION << "Geoprocessing Task failed"; + + const Esri::ArcGISRuntime::Error &err = m_viewshedJob->error(); + ERROR_LOG(m_logger) << LOGFUNCTION << "Geoprocessing Task failed message: " << err; + + m_viewshedInProgress = false; + m_jobStatus = QLatin1String("Job failed"); + } + break; + case Esri::ArcGISRuntime::JobStatus::Started: + m_viewshedInProgress = true; + m_jobStatus = QLatin1String("Job in progress..."); + break; + case Esri::ArcGISRuntime::JobStatus::Paused: + m_viewshedInProgress = false; + m_jobStatus = QLatin1String("Job paused..."); + break; + case Esri::ArcGISRuntime::JobStatus::Succeeded: + m_viewshedInProgress = false; + m_jobStatus = QLatin1String("Job succeeded"); + // handle the results + processResults(m_viewshedJob->result()); + break; + default: + break; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " jobStatusChanged is now (m_jobStatus): " << m_jobStatus; + + if ((m_viewshedInProgress == false) && (viewshedInProgressOld != m_viewshedInProgress)) + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); + + // emit signals + // emit viewshedInProgressChanged(); + // emit jobStatusChanged(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +#if ACRGIS_VERSION >= 0x640E00 // 100.14.0 +void ViewshedGeoProcessing::onJobStatusChangedWithJsParameter(const Esri::ArcGISRuntime::JobStatus &/*newStatus*/) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + onJobStatusChanged(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} +#endif // ACRGIS_VERSION + + +bool ViewshedGeoProcessing::processResults(Esri::ArcGISRuntime::GeoprocessingResult *results) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!results) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no results"; + return false; + } + + const QMap &outputsMap = results->outputs(); + + if (outputsMap.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no keys, outputsMap is empty "; + return false; + } + + // clear graphics: + clearResultGraphics(); + + + QString key; + + key = QStringLiteral("Output_polygon_features"); + if (outputsMap.contains(key)) + { + // Get the results from the outputs as GeoprocessingFeatures + Esri::ArcGISRuntime::GeoprocessingFeatures *viewshedResultFeatures = + static_cast(outputsMap[key]); + + return processFeatureResults(viewshedResultFeatures); + } + + key = QStringLiteral("Output_raster"); + if (outputsMap.contains(key)) + { + Esri::ArcGISRuntime::GeoprocessingRaster *viewshedResultRaster = + static_cast(outputsMap[key]); + return processRasterResults(viewshedResultRaster); + } + + ERROR_LOG(m_logger) << LOGFUNCTION << " failure, no known keyword found in outputsMap" + << ", outputsMap.size() " << outputsMap.size(); + return false; +} + +bool ViewshedGeoProcessing::processFeatureResults(Esri::ArcGISRuntime::GeoprocessingFeatures *viewshedResultFeatures) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " processFeatureResults"; + + if (!viewshedResultFeatures) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no viewshedResultFeatures"; + return false; + } + + Esri::ArcGISRuntime::SimpleFillSymbol sfsVisi (Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, QColor(0, 226, 0, 255), this); + Esri::ArcGISRuntime::SimpleFillSymbol sfsInVisi(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, QColor(226, 0, 0, 255), this); + + // Add all the features from the result feature set as a graphics to the map + Esri::ArcGISRuntime::FeatureIterator features = viewshedResultFeatures->features()->iterator(); + while (features.hasNext()) + { + Esri::ArcGISRuntime::Feature *feat = features.next(this); + + const QString &attKey(QLatin1String("gridcode")); + if (feat->attributes()->containsAttribute(attKey)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " attributes()->attributeValue=== " << feat->attributes()->attributeValue(attKey).toString(); + + if (feat->attributes()->attributeValue(attKey).toInt()) + m_resultGraphics.append(new Esri::ArcGISRuntime::Graphic(feat->geometry(), &sfsVisi)); + else + m_resultGraphics.append(new Esri::ArcGISRuntime::Graphic(feat->geometry(), &sfsInVisi)); + + // ERROR_LOG(m_logger) << LOGFUNCTION << " attributeValue " << feat->attributes()->attributeValue(attKey).toInt(); + } else + ERROR_LOG(m_logger) << LOGFUNCTION << " no key attributeValue "; + + } + + // transfer from own container to resultsOverlay: + m_resultsOverlay->graphics()->append(m_resultGraphics); + + return true; +} + +bool ViewshedGeoProcessing::processRasterResults(Esri::ArcGISRuntime::GeoprocessingRaster *viewshedResultRaster) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " viewshedResultRaster -> url = " << viewshedResultRaster->url().toString(); + + connect(viewshedResultRaster, &Esri::ArcGISRuntime::GeoprocessingRaster::fetchFileCompleted, this, [this] (QUuid, const QString& filePath) { + DEBUG_LOG(m_logger) << LOGFUNCTION << " filePath=" << filePath; + + Esri::ArcGISRuntime::Raster *raster = new Esri::ArcGISRuntime::Raster(filePath, this); + if (!raster) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " raster failed"; + return; + } + connect(raster, &Esri::ArcGISRuntime::Raster::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) { + ERROR_LOG(m_logger) << LOGFUNCTION << " raster EsriError occured: " << error; + }); + connect(raster, &Esri::ArcGISRuntime::Raster::loadStatusChanged, this, [this, raster] (Esri::ArcGISRuntime::LoadStatus loadStatus) { + ERROR_LOG(m_logger) << LOGFUNCTION << " raster loadStatus=" << loadStatus; + + if (loadStatus == Esri::ArcGISRuntime::LoadStatus::Loaded) + { + + + Esri::ArcGISRuntime::RasterLayer *rl = new Esri::ArcGISRuntime::RasterLayer(raster, this); + if (!rl) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rl failed"; + return; + } + connect(rl, &Esri::ArcGISRuntime::RasterLayer::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) { + ERROR_LOG(m_logger) << LOGFUNCTION << " RasterLayer EsriError occured: " << error; + }); + connect(rl, &Esri::ArcGISRuntime::RasterLayer::loadStatusChanged, this, [this] (Esri::ArcGISRuntime::LoadStatus loadStatus) { + ERROR_LOG(m_logger) << LOGFUNCTION << " RasterLayer loadStatus=" << loadStatus; + }); + + + DEBUG_LOG(m_logger) << LOGFUNCTION << " getEsriMap()->operationalLayers()->size" << m_mapImpl->getEsriMap()->operationalLayers()->size(); + m_mapImpl->getEsriMap()->operationalLayers()->append(rl); + DEBUG_LOG(m_logger) << LOGFUNCTION << " getEsriMap()->operationalLayers()->size" << m_mapImpl->getEsriMap()->operationalLayers()->size(); + + + DEBUG_LOG(m_logger) << LOGFUNCTION << " rasterLayer loading ..."; + rl->load(); + } + }); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " raster loading ..."; + raster->load(); + }); + + QFuture future = viewshedResultRaster->fetchFileAsync(QStringLiteral("/tmp/viewshedFetched.tif")); + if (!future.isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " m_mapImpl failed"; + return false; + } + + return true; +} + + +void ViewshedGeoProcessing::clearGraphics() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; + clearInputGraphics(); + clearExtentGraphics(); + clearResultGraphics(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void ViewshedGeoProcessing::createInputGraphic(const Esri::ArcGISRuntime::Point &origin) +{ + if (!m_originGraphic) + { + m_originGraphic = new Esri::ArcGISRuntime::Graphic(this); + m_resultsOverlay->graphics()->append(m_originGraphic); + } + + m_originGraphic->setGeometry(origin); + + Esri::ArcGISRuntime::SimpleMarkerSymbol sms(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle, QColor("red"), 12.0); + m_originGraphic->setSymbol(&sms); + m_originGraphic->setZIndex(std::numeric_limits::max()); +} + +void ViewshedGeoProcessing::createExtentGraphic(const Esri::ArcGISRuntime::Point &origin, double radius) +{ + if (!m_extentGraphic) + { + m_extentGraphic = new Esri::ArcGISRuntime::Graphic(this); + m_resultsOverlay->graphics()->append(m_extentGraphic); + } + + const Esri::ArcGISRuntime::Polygon &circlePoly = Esri::ArcGISRuntime::GeometryEngine::buffer(origin, radius); // circlePoly.parts().part(0).pointCount(); -> 96 + + m_extentGraphic->setGeometry(circlePoly); + + Esri::ArcGISRuntime::SimpleLineSymbol sls(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, QColor("red"), 3.0); + m_extentGraphic->setSymbol(&sls); + m_extentGraphic->setZIndex(std::numeric_limits::max()); +} + +void ViewshedGeoProcessing::clearInputGraphics() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; + if (m_originGraphic) + { + m_resultsOverlay->graphics()->removeOne(m_originGraphic); + delete m_originGraphic; + m_originGraphic = nullptr; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void ViewshedGeoProcessing::clearExtentGraphics() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; + if (m_extentGraphic) + { + m_resultsOverlay->graphics()->removeOne(m_extentGraphic); + delete m_extentGraphic; + m_extentGraphic = nullptr; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void ViewshedGeoProcessing::clearResultGraphics() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; + + for (auto graphic : std::as_const(m_resultGraphics)) + { + m_resultsOverlay->graphics()->removeOne(graphic); + delete graphic; + } + m_resultGraphics.clear(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void ViewshedGeoProcessing::onErrorOccuredGeoprocessingTask(const Esri::ArcGISRuntime::Error &error) +{ + QString senderName; + Esri::ArcGISRuntime::GeoprocessingTask *senderObj = qobject_cast(sender()); + if (senderObj) + senderName = senderObj->objectName(); + + ERROR_LOG(m_logger) << LOGFUNCTION << " GeoprocessingTask -> EsriError occured: " << error + << ", sender= '" << senderName << "'"; +} + +void ViewshedGeoProcessing::onErrorOccuredGeoprocessingJob(const Esri::ArcGISRuntime::Error &error) +{ + QString senderName; + Esri::ArcGISRuntime::GeoprocessingJob *senderObj = qobject_cast(sender()); + if (senderObj) + senderName = senderObj->objectName(); + + ERROR_LOG(m_logger) << LOGFUNCTION << " GeoprocessingJob -> EsriError occured: " << error + << ", sender= '" << senderName << "'"; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h new file mode 100755 index 000000000..9ca82ef21 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h @@ -0,0 +1,123 @@ +/** + * @RDE_HEADER@ + * ViewshedGeoProcessing + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "viewshed_base.h" + +// ESRI +#include +#include + + +// Qt +#include +#include +#include + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Map; +class Graphic; +class GraphicsOverlay; +class GeoprocessingTask; +class GeoprocessingJob; +class GeoprocessingResult; +class GeoprocessingFeatures; +class GeoprocessingRaster; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +class MapImpl; + + +class ViewshedGeoProcessing : public ViewshedBase +{ + Q_OBJECT + +public: + explicit ViewshedGeoProcessing(const QString &url, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + const Esri::ArcGISRuntime::SpatialReference &spatialReference, + QObject *parent = nullptr); + + virtual ~ViewshedGeoProcessing(); + + + bool init(SituationMap::MapImpl *mapImpl = nullptr); // mapImpl is required only if processRasterResults is used + bool initialized() const { return m_initialized; } + + + bool execute(const QPointF &viewshedPoint_map, double outerRadius, double observerOffset) override; + + bool isRunning() const { return m_viewshedInProgress; } + + bool processResults(Esri::ArcGISRuntime::GeoprocessingResult *results); + + void clearGraphics(); + + void createInputGraphic(const Esri::ArcGISRuntime::Point &origin); + void clearInputGraphics(); + void createExtentGraphic(const Esri::ArcGISRuntime::Point &origin, double radius); + void clearExtentGraphics(); + + void clearResultGraphics(); + + +protected slots: + void onJobStatusChanged(); +#if ACRGIS_VERSION >= 0x640E00 // 100.14.0 + void onJobStatusChangedWithJsParameter(const Esri::ArcGISRuntime::JobStatus &); +#endif // ACRGIS_VERSION + + void onErrorOccuredGeoprocessingTask(const Esri::ArcGISRuntime::Error &error); + void onErrorOccuredGeoprocessingJob(const Esri::ArcGISRuntime::Error &error); + + +protected: + bool processFeatureResults(Esri::ArcGISRuntime::GeoprocessingFeatures *viewshedResultFeatures); + bool processRasterResults(Esri::ArcGISRuntime::GeoprocessingRaster *viewshedResultRaster); + +private: + + QString m_url; + + Esri::ArcGISRuntime::GraphicsOverlay *m_resultsOverlay; + + SituationMap::MapImpl *m_mapImpl = nullptr; + Esri::ArcGISRuntime::SpatialReference m_srMap; + + Esri::ArcGISRuntime::GeoprocessingTask *m_viewshedTask = nullptr; + QString m_jobStatus; + + Esri::ArcGISRuntime::GeoprocessingJob *m_viewshedJob = nullptr; + + bool m_initialized = false; + + bool m_viewshedInProgress = false; + QList m_resultGraphics; + + Esri::ArcGISRuntime::Graphic *m_originGraphic = nullptr; + Esri::ArcGISRuntime::Graphic *m_extentGraphic = nullptr; + + LOGGER_T m_logger; + +}; // class ViewshedGeoProcessing + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h_ConsultingBuchta.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h_ConsultingBuchta.txt new file mode 100755 index 000000000..0c2077d3f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_geo_processing.h_ConsultingBuchta.txt @@ -0,0 +1,116 @@ +/** + * @RDE_HEADER@ + * ViewshedGeoProcessing + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "viewshed_base.h" + +// ESRI +#include +#include + + +// Qt +#include +#include +#include + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class Map; +class Graphic; +class GraphicsOverlay; +class GeoprocessingTask; +class GeoprocessingJob; +class GeoprocessingResult; +class GeoprocessingFeatures; +class GeoprocessingRaster; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +class MapImpl; + + +class ViewshedGeoProcessing : public ViewshedBase +{ + Q_OBJECT + +public: + explicit ViewshedGeoProcessing(const QString &url, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + const Esri::ArcGISRuntime::SpatialReference &spatialReference, + QObject *parent = nullptr); + + virtual ~ViewshedGeoProcessing(); + + + bool init(SituationMap::MapImpl *mapImpl = nullptr); + bool initialized() const { return m_initialized; } + + + bool execute(const QPointF &viewshedPoint_map, const double distance, double zOffset) override; + + bool isRunning() const { return m_viewshedInProgress; } + + bool processResults(Esri::ArcGISRuntime::GeoprocessingResult *results); + + void clearGraphics(); + + void createInputGraphic(const Esri::ArcGISRuntime::Point &origin); + void clearInputGraphics(); + + void clearResultGraphics(); + + +protected slots: + void onJobStatusChanged(); + void onErrorOccuredGeoprocessingTask(const Esri::ArcGISRuntime::Error &error); + void onErrorOccuredGeoprocessingJob(const Esri::ArcGISRuntime::Error &error); + + +protected: + bool processFeatureResults(Esri::ArcGISRuntime::GeoprocessingFeatures *viewshedResultFeatures); + bool processRasterResults(Esri::ArcGISRuntime::GeoprocessingRaster *viewshedResultRaster); + +private: + + QString m_url; + + Esri::ArcGISRuntime::GraphicsOverlay *m_resultsOverlay; + + SituationMap::MapImpl *m_mapImpl = nullptr; + Esri::ArcGISRuntime::SpatialReference m_srMap; + + Esri::ArcGISRuntime::GeoprocessingTask *m_viewshedTask = nullptr; + QString m_jobStatus; + + Esri::ArcGISRuntime::GeoprocessingJob *m_viewshedJob = nullptr; + + bool m_initialized = false; + + bool m_viewshedInProgress = false; + QList m_resultGraphics; + + Esri::ArcGISRuntime::Graphic *m_originGraphic = nullptr; + + LOGGER_T m_logger; + +}; // class ViewshedGeoProcessing + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.cpp new file mode 100755 index 000000000..edbe46667 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.cpp @@ -0,0 +1,880 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Viewshed + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "viewshed_impl.h" +#include "viewshed_analysis.h" +#include "viewshed_geo_processing.h" +#include "layer_factory_impl.h" + +#include "coord_translation_functor_impl.h" +#include + +#include +#include + +#include +#include + +#include "Rubberbands/rubberband_factory.h" + + +// Qt + + +namespace SituationMap +{ + +class ViewshedImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + SituationMap::MapImpl *m_mapImpl = nullptr; + + QPointer m_rubbandBase = nullptr; + + QString m_url; + + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; // graphicsLayer used in 2D + + ViewshedParameter m_viewshedParameter; + + using COUNTER_T = long; + static COUNTER_T m_viewshedCounter; + QMap m_viewshedMap; + QMap m_sightFanMap; + +}; // class ViewshedImpl::Private +//----------------------------------------------------------------------------- + +ViewshedImpl::Private::COUNTER_T ViewshedImpl::Private::m_viewshedCounter = 0; + + +//----------------------------------------------------------------------------- +// class ViewshedImpl +//----------------------------------------------------------------------------- +ViewshedImpl::ViewshedImpl(SituationMap::MapImpl *mapImpl) + : BaseClass() + , m_logger(getLogger("ViewshedImpl")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_mapImpl = mapImpl; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +ViewshedImpl::~ViewshedImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->remove(); + delete d; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void ViewshedImpl::setViewshedUrl(const QString &viewshedServiceUrl) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, viewshedServiceUrl='" << viewshedServiceUrl << "'"; + + d->m_url = viewshedServiceUrl; + + if (d->m_url.isEmpty()) + { + // only demo!!! 10.2.6!? http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/GPServer/Viewshed + // only demo!!! V100.2 https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/ESRI_Elevation_World/GPServer/Viewshed + d->m_url = QLatin1String("https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/ESRI_Elevation_World/GPServer/Viewshed"); // only demo!!! + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, d->m_url'" << d->m_url << "'"; +} + +bool ViewshedImpl::isReady() +{ + init(); + bool isReady = (d->m_graphicsLayer) && d->m_mapImpl->isReady(); + + if (!isReady) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " d->m_url.isEmpty() = " << d->m_url.isEmpty() + << " d->m_graphicsLayer = " << d->m_graphicsLayer + << " d->m_mapImpl->isReady() = " << d->m_mapImpl->isReady(); + } + return isReady; +} + +bool ViewshedImpl::create() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " failed, not ready"; + return false; + } + + // remove all former + this->remove(); + + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + // d->m_rubbandBase; should be cleared now + + const Mode mode = d->m_mapImpl->mode(); + switch (mode) { + case Mode::Mode_2D: + d->m_rubbandBase = createRubberband2D(); + break; + case Mode::Mode_3D: + d->m_rubbandBase = createRubberband3D(); + break; + default: + ERROR_LOG(m_logger) << LOGFUNCTION << " mode (" << mode << ")" << " does not support viewshed"; + return false; + } + + return true; +} + + +bool ViewshedImpl::create(double alpha, double innerRadius, double observerHeight, double targetHeight, double beamAngle) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " failed, not ready"; + return false; + } + + if (alpha < 0.0) alpha = 0.0; + if (alpha > 1.0) alpha = 1.0; + + if (beamAngle < 0.5) beamAngle = 0.5; + + // remove all former + this->remove(); + + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + d->m_viewshedParameter.innerRadius = innerRadius; + d->m_viewshedParameter.observerHeight = observerHeight; + d->m_viewshedParameter.targetHeight = targetHeight; + d->m_viewshedParameter.dAngle = beamAngle*MathConst::pi/180.0; // grad to rad + d->m_viewshedParameter.alpha = alpha; + + const Mode mode = d->m_mapImpl->mode(); + switch (mode) { + case Mode::Mode_2D: + d->m_rubbandBase = createSightFanRubberband2D(); + break; + case Mode::Mode_3D: + d->m_rubbandBase = createSightFanRubberband3D(); + break; + default: + ERROR_LOG(m_logger) << LOGFUNCTION << " mode (" << mode << ")" << " does not support viewshed"; + return false; + } + + return true; + // d->m_rubbandBase; should be cleared now +} + + +long ViewshedImpl::create(const ViewshedParameter &viewshedParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " viewshedParameter: " << viewshedParameter; + + if (!isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " failed, not ready"; + return -1; + } + + // store current values: + d->m_viewshedParameter = viewshedParameter; + + ViewshedBase *viewshed = nullptr; + + // GPU-based viewshed (viewshedAnalysis) is today supported for 3D only: + const Mode mode = d->m_mapImpl->mode(); + switch (mode) { + case Mode::Mode_2D: + viewshed = create_viewshedGP(d->m_viewshedParameter); + break; + case Mode::Mode_3D: + viewshed = create_viewshedAnalysis(d->m_viewshedParameter); + break; + default: + ERROR_LOG(m_logger) << LOGFUNCTION << " mode (" << mode << ")" << " does not support viewshed"; + return -1; + } + + if (!viewshed) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " creation of viewshed failed" << " mode (" << mode << ")"; + return -1; + } + + d->m_viewshedMap[++d->m_viewshedCounter] = viewshed; + + emit viewshedFinished(d->m_viewshedCounter); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + + return d->m_viewshedCounter; + +} // long ViewshedImpl::create(const ViewshedParameter &viewshedParameter) + + +bool ViewshedImpl::modify(long viewshedId, double alpha, double innerRadius, double observerHeight, double targetHeight, double beamAngle) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!isReady()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " failed, not ready"; + return false; + } + + if (alpha < 0.0) alpha = 0.0; + if (alpha > 1.0) alpha = 1.0; + if (beamAngle < 0.5) beamAngle = 0.5; + + if (viewshedId < 0) viewshedId = d->m_viewshedCounter; + + if (d->m_sightFanMap.contains(viewshedId)) + { + SightFan *sightFan = d->m_sightFanMap[viewshedId]; + + d->m_viewshedParameter.innerRadius = innerRadius; + d->m_viewshedParameter.observerHeight = observerHeight; + d->m_viewshedParameter.targetHeight = targetHeight; + d->m_viewshedParameter.dAngle = beamAngle * MathConst::pi / 180.0; // grad to rad + d->m_viewshedParameter.noBeams = MathConst::pi_two / d->m_viewshedParameter.dAngle; + d->m_viewshedParameter.alpha = alpha; + + sightFan->setAlpha( d->m_viewshedParameter.alpha ); + sightFan->setInnerRadius( d->m_viewshedParameter.innerRadius ); + sightFan->setCenterHat( d->m_viewshedParameter.observerHeight ); + sightFan->setObjectHat( d->m_viewshedParameter.targetHeight ); + sightFan->setdeltaAngle( d->m_viewshedParameter.dAngle ); + sightFan->setNoOfFans( d->m_viewshedParameter.noBeams ); + + sightFan->show(); + return true; + } + return false; +} // bool ViewshedImpl::modify(long viewshedId, double alpha, double innerRadius, double observerHeight, double targetHeight, double beamAngle) + + +ViewshedBase *ViewshedImpl::create_viewshedGP(const ViewshedParameter &viewshedParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " viewshedParameter: " << viewshedParameter; + + if (!d->m_graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no graphicsLayer for viewshed"; + return nullptr; + } + if (d->m_url.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no url for viewshed"; + return nullptr; + } + + ViewshedGeoProcessing *viewshed = new ViewshedGeoProcessing(d->m_url, d->m_graphicsLayer, d->m_mapImpl->getEsriSpatialReference()); + if (!viewshed->init(d->m_mapImpl)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + delete viewshed; + return nullptr; + } + + // coordTranslation: + CoordTranslationFunctor *coordTransFunctor = d->m_mapImpl->coordTranslationFunctor(); + const QPointF &viewshedPointTranslated = coordTransFunctor ? coordTransFunctor->geographicToMap(viewshedParameter.viewshedOrigin_latLon) : QPointF(); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " viewshedParameter= " << viewshedParameter; + + if (!viewshed->execute(viewshedPointTranslated, // Map-Coord + viewshedParameter.outerRadius, + viewshedParameter.observerHeight)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " executing GeoProcessing viewshed failed."; + delete viewshed; + return nullptr; + } + return viewshed; +} // ViewshedBase *ViewshedImpl::create_viewshedGP(const ViewshedParameter &viewshedParameter) + + +ViewshedBase *ViewshedImpl::create_viewshedAnalysis(const ViewshedParameter &viewshedParameter) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " viewshedParameter: " << viewshedParameter; + + if (!d->m_mapImpl->getEsriSceneGraphicsView()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " getEsriSceneGraphicsView failed for viewsheds"; + return nullptr; + } + + ViewshedAnalysis *viewshed = new ViewshedAnalysis(d->m_mapImpl->getEsriSceneGraphicsView()); + + // rad to degree + const double factor = 180 / MathConst::pi; + const QPointF ¢erDegree = viewshedParameter.viewshedOrigin_latLon * factor; + + if (!viewshed->execute(centerDegree, // degree + viewshedParameter.outerRadius, + viewshedParameter.observerHeight)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " executing analysis viewshed failed."; + delete viewshed; + return nullptr; + } + return viewshed; +} // ViewshedBase *ViewshedImpl::create_viewshedAnalysis(const ViewshedParameter &viewshedParameter) + + +void ViewshedImpl::remove(long viewshedId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " viewshedId=" << viewshedId; + + //if (viewshedId < 0) + { + // ok, not special viewshed is adressed --> remove all + for (ViewshedBase *viewshed : std::as_const(d->m_viewshedMap)) + delete viewshed; + d->m_viewshedMap.clear(); + + for (SightFan *sightFan : std::as_const(d->m_sightFanMap)) + { + if (sightFan) + sightFan->removeMySelf(); + } + d->m_sightFanMap.clear(); + return; + } + + if (d->m_viewshedMap.contains(viewshedId)) + { + delete d->m_viewshedMap[viewshedId]; + d->m_viewshedMap.remove(viewshedId); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " viewshedId=" << viewshedId; + return; + } + + if (d->m_sightFanMap.contains(viewshedId)) + { + d->m_sightFanMap[viewshedId]->removeMySelf(); + d->m_sightFanMap[viewshedId] = nullptr; + d->m_sightFanMap.remove(viewshedId); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " viewshedId=" << viewshedId; + return; + } + + ERROR_LOG(m_logger) << LOGFUNCTION << " viewShedId not found, id=" << viewshedId; +} + + +ViewshedParameter ViewshedImpl::getViewshedParameter(long viewshedId, bool *ok) const +{ + if (ok) + *ok = false; + + if (viewshedId < 0) + viewshedId = d->m_viewshedCounter; + + if (d->m_sightFanMap.contains(viewshedId)) + { + const SightFan *sightFan = d->m_sightFanMap[viewshedId]; + + if (sightFan) + { + ViewshedParameter vp; + + vp.viewshedOrigin_latLon = sightFan->center(); + vp.observerHeight = sightFan->centerHat(); + vp.targetHeight = sightFan->objectHat(); + vp.innerRadius = sightFan->innerRadius(); + vp.outerRadius = sightFan->radius(); + vp.startAngle = sightFan->startAngle(); + vp.dAngle = sightFan->dAngle(); + vp.alpha = sightFan->alpha(); + vp.noBeams = sightFan->noOfFans(); + + if (ok) + *ok = true; + return vp; + } + } + return d->m_viewshedParameter; +} // const ViewshedParameter & ViewshedImpl::getViewshedParameter(long viewshedId = -1, bool *ok = nullptr) + + +void ViewshedImpl::init() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_graphicsLayer) + { + LayerFactoryImpl &layerFactory = d->m_mapImpl->layerFactoryImpl(); + const QString &toolsLayername = layerFactory.toolLayer(); + d->m_graphicsLayer = layerFactory.getGraphicsLayer(toolsLayername); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +RB::RubberbandEllipse *ViewshedImpl::createRubberband2D() +{ + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + RB::RubberbandEllipse *rubberband = + d->m_mapImpl->rubberbandFactory().create(this); + + if (!rubberband || !rubberband->rubberbandIsAlive()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " create2D failed"; + return nullptr; + } + rubberband->setAutoDelete(true); + + rubberband->constrainQuadratic(true); + rubberband->constrainOrthogonal(true); +// rubberband->minWidth(radiusMin * 2); // Should be 2*radiusMin +// rubberband->maxWidth(radiusMax * 2); // Should be 2*radiusMax + + + connect(rubberband, &SituationMap::RB::Rubberband::abort, this, &ViewshedImpl::onRbAbort, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, &ViewshedImpl::onRb2DApply, Qt::UniqueConnection); + + return rubberband; +} + + +RB::RubberbandEllipse *ViewshedImpl::createSightFanRubberband2D() +{ + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + RB::RubberbandEllipse *rubberband = + d->m_mapImpl->rubberbandFactory().create(this); + + if (!rubberband || !rubberband->rubberbandIsAlive()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " create2D failed"; + return nullptr; + } + rubberband->setAutoDelete(true); + + rubberband->constrainQuadratic(true); + rubberband->constrainOrthogonal(true); + rubberband->enableSector(false); // set enableSector to true to allow sector sight fans instead of full circles + + rubberband->minWidth(d->m_viewshedParameter.innerRadius * 3); // radius should be larger than innerRadius + + // rubberband->maxWidth(radiusMax * 2); // Should be 2*radiusMax + + + connect(rubberband, &SituationMap::RB::Rubberband::abort, this, &ViewshedImpl::onRbAbort, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, &ViewshedImpl::onSightFanRb2DApply, Qt::UniqueConnection); + + return rubberband; +} // RB::RubberbandEllipse *ViewshedImpl::createSightFanRubberband2D() + + +RB::RubberbandMultiPointFeature3D *ViewshedImpl::createSightFanRubberband3D() +{ + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + RB::RubberbandMultiPointFeature3D *rubberband = + d->m_mapImpl->rubberbandFactory().create3D(this); + + if (!rubberband || !rubberband->rubberbandIsAlive()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " create3D failed"; + return nullptr; + } + rubberband->setAutoDelete(true); + rubberband->enableCircular(true); + +// rubberband->constrainQuadratic(true); +// rubberband->constrainOrthogonal(true); + + connect(rubberband, &SituationMap::RB::Rubberband::abort, this, &ViewshedImpl::onRbAbort, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, &ViewshedImpl::onSightFanRb3DApply, Qt::UniqueConnection); + + return rubberband; +} // RB::RubberbandMultiPointFeature3D *ViewshedImpl::createSightFanRubberband3D() + + +RB::RubberbandMultiPointFeature3D *ViewshedImpl::createRubberband3D() +{ + // remove any pending/active rubberbands of our map: + RB::Rubberband::deleteAllActiveRubberbands(d->m_mapImpl); + + RB::RubberbandMultiPointFeature3D *rubberband = + d->m_mapImpl->rubberbandFactory().create3D(this); + + if (!rubberband || !rubberband->rubberbandIsAlive()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " create3D failed"; + return nullptr; + } + rubberband->setAutoDelete(true); + rubberband->enableCircular(true); + + // rubberband->constrainQuadratic(true); + // rubberband->constrainOrthogonal(true); + + connect(rubberband, &SituationMap::RB::Rubberband::abort, this, &ViewshedImpl::onRbAbort, Qt::UniqueConnection); + connect(rubberband, &SituationMap::RB::Rubberband::apply, this, &ViewshedImpl::onSightFanRb3DApply, Qt::UniqueConnection); + + return rubberband; +} + + +void ViewshedImpl::onRbAbort(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + return; + DEBUG_LOG(m_logger) << LOGFUNCTION << " rb abort"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void ViewshedImpl::onSightFanRb2DApply(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no rubberband"; + return; + } + + RB::RubberbandEllipse *ellipse = static_cast(rbnd); + const QString &toolLayerName = d->m_mapImpl->layerFactoryImpl().toolLayer(); + + double diameter; + double dummy; + QPointF direction; + + ellipse->rectangleParameter( d->m_viewshedParameter.viewshedOrigin_latLon, diameter, dummy, direction ); + + if (diameter > 0.0) + { + d->m_viewshedParameter.outerRadius = diameter * 0.5; + if (ellipse->hasSector()) + { + double startAngle = SituationMap::MathUtils::getMathAngle(ellipse->sectorStart()); + double endAngle = SituationMap::MathUtils::getMathAngle(ellipse->sectorEnd()); + + if (startAngle > endAngle) + { + startAngle -= MathConst::pi_two; + } + + d->m_viewshedParameter.noBeams = ceil( (endAngle - startAngle) / d->m_viewshedParameter.dAngle ); + d->m_viewshedParameter.startAngle = startAngle; + } + else + { + d->m_viewshedParameter.startAngle = 0.0; + d->m_viewshedParameter.noBeams = MathConst::pi_two / d->m_viewshedParameter.dAngle; + } + + + ObjectFactory &objFac = d->m_mapImpl->objectFactory(); + SightFan *sightFan = objFac.create(toolLayerName).take(); + if (sightFan) + { + sightFan->initFan( d->m_viewshedParameter.viewshedOrigin_latLon + , d->m_viewshedParameter.observerHeight + , d->m_viewshedParameter.targetHeight + , d->m_viewshedParameter.outerRadius + , d->m_viewshedParameter.innerRadius + , d->m_viewshedParameter.startAngle + , d->m_viewshedParameter.dAngle + , d->m_viewshedParameter.noBeams + , d->m_viewshedParameter.alpha ); + + long sightFanId = ++d->m_viewshedCounter; + d->m_sightFanMap[sightFanId] = sightFan; + + emit viewshedFinished(sightFanId); + + // create a RubberbandPointFeature to let the user move around the sight fan + + RB::RubberbandPointFeature *moveRB = d->m_mapImpl->rubberbandFactory().create(this); + if (moveRB) + { + moveRB->setAutoDelete(true); + moveRB->enableModificationState(); + moveRB->setCoordinateVector(QVector() << sightFan->center()); + + std::function moveSightFan = [this, sightFanId](SituationMap::RB::RubberbandBase* rbBase) -> bool + { + SituationMap::RB::RubberbandPointFeature *rubberPFeat = dynamic_cast(rbBase); + + SightFan * sightFan = d->m_sightFanMap[sightFanId]; + if (nullptr == sightFan) + { + if (rubberPFeat) rubberPFeat->deleteMySelf(); + return false; + } + + if (rubberPFeat) + { + const QPointF & loc = rubberPFeat->singleCoordinate(); + sightFan->move(loc); + } + return true; + }; + + moveRB->installExternalUpdate(moveSightFan); + } + } // if sightFan + } // if diameter +} // void ViewshedImpl::onSightFanRb2DApply(RB::RubberbandBase *rbnd) + + +void ViewshedImpl::onSightFanRb3DApply(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no rubberband"; + return; + } + + RB::RubberbandMultiPointFeature3D *rubberband = static_cast(rbnd); + const QString &toolLayerName = d->m_mapImpl->layerFactoryImpl().toolLayer(); + const QVector &data = rubberband->coordinateVector(RB::CoordinateSystem::Map); // 3D -> RB::CoordinateSystem::Map -> degree + + if (data.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; + return; + } + + if (data.size() < 2) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " insufficiant number of coords"; + return; + } + + ViewshedParameter viewshedParameter; + + // degree to rad + const double factor = MathConst::pi / 180.0; + const QPointF ¢er = data.at(0) * factor; +// viewshedParameter.viewshedOrigin_latLon = center; + + { + static Esri::ArcGISRuntime::GeometryEngine *gEngine = Esri::ArcGISRuntime::GeometryEngine::instance(); + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + + QPointF coord = data.at(0); + Esri::ArcGISRuntime::Point geom1(coord.x(), coord.y(), sr); + coord = data.at(1); + Esri::ArcGISRuntime::Point geom2(coord.x(), coord.y(), sr); + + Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistanceResult = gEngine->distanceGeodetic(geom1, geom2, + Esri::ArcGISRuntime::LinearUnitId::Meters, + Esri::ArcGISRuntime::AngularUnitId::Degrees, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + viewshedParameter.outerRadius = geodeticDistanceResult.distance(); + } + + unsigned int noBeams = floor((MathConst::pi_two) / d->m_viewshedParameter.dAngle); + + + ObjectFactory &objFac = d->m_mapImpl->objectFactory(); + RefPointer objRef = objFac.create(toolLayerName); + SightFan *sightFan = objRef.get(); + + if (sightFan) + { + if (d->m_viewshedParameter.innerRadius*1.5 > viewshedParameter.outerRadius) + { + viewshedParameter.outerRadius = d->m_viewshedParameter.innerRadius*1.5; + } + + sightFan->initFan(center + , d->m_viewshedParameter.observerHeight + , d->m_viewshedParameter.targetHeight + , viewshedParameter.outerRadius + , d->m_viewshedParameter.innerRadius + , 0.0 + , d->m_viewshedParameter.dAngle + , noBeams + , d->m_viewshedParameter.alpha ); + + long sightFanId = ++d->m_viewshedCounter; + d->m_sightFanMap[sightFanId] = sightFan; + + emit viewshedFinished(sightFanId); + + // create a RubberbandPointFeature3D to let the user move around the sight fan + + RB::RubberbandPointFeature3D *moveRB = d->m_mapImpl->rubberbandFactory().create3D(this); + if (moveRB) + { + moveRB->setAutoDelete(true); + moveRB->enableModificationState(); + moveRB->setCoordinateVector(QVector() << sightFan->center()); + + std::function moveSightFan3D = [this, sightFanId](SituationMap::RB::RubberbandBase* rbBase) -> bool + { + SituationMap::RB::RubberbandPointFeature3D *rubberPFeat = dynamic_cast(rbBase); + + SightFan * sightFan = d->m_sightFanMap[sightFanId]; + if (nullptr == sightFan) + { + if (rubberPFeat) rubberPFeat->deleteMySelf(); + return false; + } + + if (rubberPFeat) + { + const QPointF & loc = rubberPFeat->singleCoordinate(); + sightFan->move(loc); + } + return true; + }; + + moveRB->installExternalUpdate(moveSightFan3D); + } + } + +} // void ViewshedImpl::onSightFanRb3DApply(RB::RubberbandBase *rbnd) + + +void ViewshedImpl::onRb2DApply(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no rubberband"; + return; + } + + RB::RubberbandEllipse *ellipse = static_cast(rbnd); + + const QVector &data = ellipse->rectangleVerticies(); + const LtsCoordTrans::UtmCoordinate p0(LtsCoordTrans::UtmCoordinate::fromGeographic(data.at(0))); + const LtsCoordTrans::UtmCoordinate p1(LtsCoordTrans::UtmCoordinate::fromGeographic(data.at(2), p0.zone)); + + const QPointF ¢er = ellipse->centerPoint(); + double radius = fabs((p0.x - p1.x) / 2); + + // 23.03.2018, N. Thoden + // LegatusBremen201803151510.gpk contains dem with cell size of ~ 50m + // So the inner_radius has to be >= 50m + // And of course: outer_radius > inner_radius + // So outerRadius > 60 + + if (radius < 100.0) + radius = 100.0; + + ViewshedParameter viewshedParameter; + viewshedParameter.viewshedOrigin_latLon = center; + viewshedParameter.outerRadius = radius; + + long id = this->create(viewshedParameter); + + if (id < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void ViewshedImpl::onRb3DApply(RB::RubberbandBase *rbnd) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!rbnd) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no rubberband"; + return; + } + + RB::RubberbandMultiPointFeature3D *rubberband = static_cast(rbnd); + + const QVector &data = rubberband->coordinateVector(RB::CoordinateSystem::Map); // 3D -> RB::CoordinateSystem::Map -> degree + + if (data.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; + return; + } + + if (data.size() < 2) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " insufficiant number of coords"; + return; + } + + ViewshedParameter viewshedParameter; + + // degree to rad + const double factor = MathConst::pi / 180.0; + const QPointF ¢er = data.at(0) * factor; + viewshedParameter.viewshedOrigin_latLon = center; + + { + static Esri::ArcGISRuntime::GeometryEngine *gEngine = Esri::ArcGISRuntime::GeometryEngine::instance(); + const Esri::ArcGISRuntime::SpatialReference &sr = d->m_mapImpl->getEsriSpatialReference(); + + QPointF coord = data.at(0); + Esri::ArcGISRuntime::Point geom1(coord.x(), coord.y(), sr); + coord = data.at(1); + Esri::ArcGISRuntime::Point geom2(coord.x(), coord.y(), sr); + + Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistanceResult = gEngine->distanceGeodetic(geom1, geom2, + Esri::ArcGISRuntime::LinearUnitId::Meters, + Esri::ArcGISRuntime::AngularUnitId::Degrees, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + viewshedParameter.outerRadius = geodeticDistanceResult.distance(); + } + + long id = this->create(viewshedParameter); + + if (id < 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " rubberband failed!?"; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.h new file mode 100755 index 000000000..9cdcd8f24 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapImpl/viewshed_impl.h @@ -0,0 +1,91 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Viewshed + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include "map_impl.h" +#include "Rubberbands/rubberband_ellipse.h" +#include "Rubberbands/rubberband_multi_point_feature_3d.h" + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class ViewshedBase; + +class ViewshedImpl : public /*virtual*/ Viewshed +{ + using BaseClass = Viewshed; + + public: + + explicit ViewshedImpl(SituationMap::MapImpl *mapImpl); + virtual ~ViewshedImpl(); + + ViewshedImpl(const ViewshedImpl &other) = delete; + ViewshedImpl &operator=(ViewshedImpl &other) = delete; + + void setViewshedUrl(const QString &viewshedServiceUrl) override; + + bool isReady(); + + bool create() override; + bool create(double alpha, double innerRadius, double observerHeight, double targetHeight, double beamAngle) override; + long create(const ViewshedParameter &viewshedParameter) override; + + bool modify(long viewshedId, double alpha, double innerRadius, double observerHeight, double targetHeight, double beamAngle) override; + + void remove(long viewshedId = -1) override; + + ViewshedParameter getViewshedParameter(long viewshedId = -1, bool *ok = nullptr) const override; + + + private Q_SLOTS: + + void onRbAbort(RB::RubberbandBase *rbnd); + void onRb2DApply(RB::RubberbandBase *rbnd); + void onRb3DApply(RB::RubberbandBase *rbnd); + + void onSightFanRb2DApply(RB::RubberbandBase *rbnd); + void onSightFanRb3DApply(RB::RubberbandBase *rbnd); + + private: + + ViewshedBase *create_viewshedGP(const ViewshedParameter &viewshedParameter); + ViewshedBase *create_viewshedAnalysis(const ViewshedParameter &viewshedParameter); + + void init(); + + + RB::RubberbandEllipse *createRubberband2D(); + RB::RubberbandMultiPointFeature3D *createRubberband3D(); + + RB::RubberbandEllipse *createSightFanRubberband2D(); + RB::RubberbandMultiPointFeature3D *createSightFanRubberband3D(); + + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; // ViewshedImpl + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/_single_fire_central.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/_single_fire_central.h new file mode 100755 index 000000000..7c9f117b2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/_single_fire_central.h @@ -0,0 +1,58 @@ +/** + * @RDE_HEADER@ + * Interface of SingleFireCentral + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The SingleFireCentral class supports Indirect Single Fire. + */ +class SingleFireCentral : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + using BaseClass = AbstractMultiPointMapObject; + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + SingleFireCentral() : BaseClass() {;} ///< Contructor + + virtual ~SingleFireCentral() = default; ///< Destructor + + /*! \name Specific data*/ + //@{ + struct DetonationData + { + DetonationData() = default; + DetonationData(const Point3D &origin, double radius) + : m_origin(origin), m_radius(radius) + {} + Point3D m_origin; + double m_radius{0.0}; + }; + virtual void setDetonation(const DetonationData &detonationData) = 0; + virtual DetonationData detonation() const = 0; + virtual bool startDetonationAnimation() = 0; + + // required??? + virtual Point3D impactOrigin() const = 0; + virtual void setImpactOrigin(const Point3D &impactOrigin) = 0; + //@} + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_multi_point_map_object.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_multi_point_map_object.h new file mode 100755 index 000000000..b1738b0d6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_multi_point_map_object.h @@ -0,0 +1,65 @@ +/** + * @RDE_HEADER@ + * Interface of AbstractMultiPointMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The AbstractMultiPointMapObject class serves as baseclass for + * MapObjects with a multiple coordinates. + * Therefore setPositions and positions are supported. + */ +class AbstractMultiPointMapObject : public virtual AbstractMapObject +{ + + using BaseClass = AbstractMapObject; + +public: + + /** \name Positions + * Set positions of the object. + */ + //@{ + virtual PositionCont positions() const = 0; + virtual bool setPositions(const PositionCont &posCont) = 0; + virtual PositionCont positionsTranslated() const = 0; + virtual bool setPositionsTranslated(const PositionCont &posCont) = 0; + //@} + + /** \name LineStyle + * See AbstractMapObject for details. + */ + //@{ + virtual LineStyle lineStyle() const = 0; + virtual LineStyle &rLineStyle() = 0; + virtual void setLineStyle(const LineStyle &lineStyle) = 0; + //@} + + + /** \name FillStyle + * See AbstractMapObject for details. + */ + //@{ + virtual FillStyle fillStyle() const = 0; + virtual FillStyle &rFillStyle() = 0; + virtual void setFillStyle(const FillStyle &FillStyle) = 0; + //@} + +}; + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_single_point_map_object.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_single_point_map_object.h new file mode 100755 index 000000000..7d1298c22 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/abstract_single_point_map_object.h @@ -0,0 +1,63 @@ +/** + * @RDE_HEADER@ + * Interface of AbstractSinglePointMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The AbstractSinglePointMapObject class serves as baseclass for + * MapObjects with a single coordinate. + * Therefore setOrigin and origin are supported. + */ +class AbstractSinglePointMapObject : public virtual AbstractMapObject +{ + + using BaseClass = AbstractMapObject; + +public: + /** \name Origin + * Set origin of the object. + */ + //@{ + virtual Point3D origin() const = 0; + virtual bool setOrigin(const Point3D &origin) = 0; + //@} + + + /** \name LineStyle + * See AbstractMapObject for details. + */ + //@{ + virtual LineStyle lineStyle() const = 0; + virtual LineStyle &rLineStyle() = 0; + virtual void setLineStyle(const LineStyle &lineStyle) = 0; + //@} + + /** \name FillStyle + * See AbstractMapObject for details. + */ + //@{ + virtual FillStyle fillStyle() const = 0; + virtual FillStyle &rFillStyle() = 0; + virtual void setFillStyle(const FillStyle &FillStyle) = 0; + //@} + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/air_support.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/air_support.h new file mode 100755 index 000000000..11bba5d88 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/air_support.h @@ -0,0 +1,38 @@ +/** + * @RDE_HEADER@ + * Interface of AirSupport + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The AirSupport class supports AirSupport. + */ +class AirSupport : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + AirSupport() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~AirSupport() = default; ///< Destructor + + virtual void setReferencedObject(const UniqueMapId &UniqueMapId) = 0; +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/area_fire.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/area_fire.h new file mode 100755 index 000000000..fb424c801 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/area_fire.h @@ -0,0 +1,90 @@ +/** + * @RDE_HEADER@ + * Interface of AreaFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The AreaFire class supports AreaFire with impacts. + */ +//class SITUATIONMAP_EXPORT +class AreaFire : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + + using BaseClass = SituationMap::AbstractMultiPointMapObject; + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + AreaFire() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + virtual ~AreaFire() = default; ///< Destructor + + /*! \name Specific data - impacts */ + //@{ + virtual PositionCont scheduledImpacts() const = 0; + virtual PositionCont impacts() const = 0; + virtual PositionCont impactsAbsoluteHeight() const = 0; + + /** + * @brief Set impacts of AreaFire. + * @param posCont Impacts to be set. + */ + virtual void setScheduledImpacts(const PositionCont &posCont, const QColor &explicitColor = QColor()) = 0; + virtual void setImpacts(const PositionCont &posCont) = 0; + virtual void setImpactsAbsoluteHeight(const PositionCont &posCont) = 0; + //@} + + + virtual PositionCont detonationsAbsolute() const = 0; + virtual PositionCont detonationsDraped() const = 0; + + /** + * @brief Set detonation (3D) of AreaFire. + * @param detonationRadius radius to be used. + * @param detonationsPositions detonations to be set. + */ + virtual bool setDetonationsAbsolute(const PositionCont &detonationsPositions, int detonationRadius) = 0; // Absolute + virtual bool setDetonationsDraped(const PositionCont &detonationsPositions, int detonationRadius) = 0; + + /*! \name Specific data - GunToTargetLine / GunToTargetTrajectory */ + //@{ + virtual void setGunToTargetLineSurface(const Point3D &start, const Point3D &end) = 0; + virtual void setGunToTargetTrajectory3D(const PositionCont &gunToTargetPath) = 0; + //@} + + /*! \name Specific data - NonLethalArea */ + //@{ + virtual void setNonLethalArea(const PositionCont &nonLethalArea) = 0; + //@} + + + /*! \name Specific data - trajectory */ + //@{ + virtual PositionCont trajectory(int index) const = 0; + + /** + * @brief Set trajectoryPath of AreaFire. + * @param index path index. + * @param trajectoryPath path to be set. + */ + virtual bool setTrajectory(int index, const PositionCont &trajectoryPath) = 0; + //@} + + +}; + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/biological_agent.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/biological_agent.h new file mode 100755 index 000000000..e569dcc52 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/biological_agent.h @@ -0,0 +1,46 @@ +/** + * @RDE_HEADER@ + * Interface of BiologicalAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The BiologicalAgent class supports BiologicalAgent. + */ +class BiologicalAgent : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + + using BaseClass = AbstractMultiPointMapObject; + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + BiologicalAgent() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + virtual ~BiologicalAgent() = default; ///< Destructor + + /*! \name Specific data*/ + //@{ + virtual Point3D origin() const = 0; + virtual void setOrigin(const Point3D &origin) = 0; + virtual float radius() const = 0; + virtual void setRadius(float radius) = 0; + //@} + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/building_outline.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/building_outline.h new file mode 100755 index 000000000..c3ee2d24d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/building_outline.h @@ -0,0 +1,58 @@ +/** + * @RDE_HEADER@ + * Interface of BuildingOutline + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" + +// Qt +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The BuildingOutline class supports BuildingOutline. + */ +class BuildingOutline : public virtual AbstractMultiPointMapObject +{ + + using BaseClass = AbstractMultiPointMapObject; + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + BuildingOutline() : AbstractMultiPointMapObject() {;} ///< Contructor + + virtual ~BuildingOutline() = default; ///< Destructor + + virtual LineStyle lineStyle() const = 0; + virtual LineStyle &rLineStyle() = 0; + virtual void setLineStyle(const LineStyle &lineStyle) = 0; + + virtual FillStyle fillStyle() const = 0; + virtual FillStyle &rFillStyle() = 0; + virtual void setFillStyle(const FillStyle &FillStyle) = 0; + + virtual void setMarker(const Point3D &markerOrigin) = 0; + + // The 3D model is using the markerOrigin + struct ModelSceneSymbolData + { + QUrl url; + float heading{0.0}; // degree, clockwise, 0 is north/12 + float scale{1.0}; + }; + virtual ModelSceneSymbolData getModelSceneSymbolData() = 0; + virtual void setModelSceneSymbolData(const ModelSceneSymbolData &modelSceneSymbolData) = 0; +}; + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/chemical_agent.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/chemical_agent.h new file mode 100755 index 000000000..eba12b457 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/chemical_agent.h @@ -0,0 +1,46 @@ +/** + * @RDE_HEADER@ + * Interface of ChemicalAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The ChemicalAgent class supports different agent types. + */ +class ChemicalAgent : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + ChemicalAgent() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~ChemicalAgent() = default; ///< Destructor + + /*! \name Specific data*/ + //@{ + virtual Point3D origin() const = 0; + virtual void setOrigin(const Point3D &origin) = 0; + virtual float radius() const = 0; + virtual void setRadius(float radius, unsigned int maxPointCount = 0) = 0; + //@} + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/cube.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/cube.h new file mode 100755 index 000000000..8e71e1f75 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/cube.h @@ -0,0 +1,44 @@ +/** + * @RDE_HEADER@ + * Interface of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The Cube class supports staked cubes displaying the building ownership for example. + * The cubes can have more than 3 positions. + * But be aware that there is no 'roof' yes. + */ +class Cube : public virtual AbstractMultiPointMapObject +{ + + using BaseClass = AbstractMultiPointMapObject; + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + Cube() : BaseClass() {;} ///< Contructor + + virtual ~Cube() = default; ///< Destructor + + + virtual double height() const = 0; + virtual void setHeight(double height) = 0; + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/danger_area.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/danger_area.h new file mode 100755 index 000000000..b32b1220a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/danger_area.h @@ -0,0 +1,37 @@ +/** + * @RDE_HEADER@ + * Interface of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The DangerArea class supports DangerAreas of different agents. + */ +class DangerArea : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + DangerArea() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~DangerArea() = default; ///< Destructor + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/functional_area.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/functional_area.h new file mode 100755 index 000000000..b9bb9a4c8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/functional_area.h @@ -0,0 +1,48 @@ +/** + * @RDE_HEADER@ + * Interface of FunctionalArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The FunctionalArea class supports FunctionalArea. + */ +class FunctionalArea : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + FunctionalArea() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~FunctionalArea() = default; ///< Destructor + + + /*! \name Point symbol */ + //@{ + virtual Point3D pointSymbolOrigin() const = 0; + virtual void setPointSymbolOrigin(const Point3D &pointSymbolOrigin) = 0; + + virtual const QString &pointSymbolSidc() const = 0; + virtual void setPointSymbolSidc(const QString &pointSymbolOriginSidc) = 0; + //@} + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geo_image.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geo_image.h new file mode 100755 index 000000000..8ebfc26a1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geo_image.h @@ -0,0 +1,146 @@ +/** + * @RDE_HEADER@ + * Interface of GeoImage + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_single_point_map_object.h" + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The GeoImage class supports displaying image proxies like a small dot. + * By clicking of the proxy the image and an optional description will be displayed. + */ +class GeoImage : public virtual AbstractSinglePointMapObject +{ +public: + + struct GeoImageData + { + MarkerSymbolType proxyMarker = MarkerSymbolType::Undefined; ///< if not set, a Diamond will be used + int proxyMarkerSize = 20; + QColor proxyMarkerColor = Qt::red; + QImage proxyImage; ///< if not set, marker will be used + QSize proxyImageDefaultSize; + + QImage mainImage; + QString description; + bool pinEnabled = false; + + /** + * @brief operator== Compare two objects + * @param other to be compared with + * @return true if identical, otherwise false. + */ + bool operator==(const GeoImageData &other) const + { + return ((this->description == other.description) && + (this->mainImage == other.mainImage) && + (this->proxyImage == other.proxyImage) && + (this->proxyMarkerSize == other.proxyMarkerSize) && + (this->proxyMarkerColor== other.proxyMarkerColor) && + (this->proxyMarker == other.proxyMarker) + ); + } + + /** + * @brief operator!= Compare two objects + * @param other to be compared with + * @return true if NOT identical, otherwise false. + */ + bool operator!=(const GeoImageData &other) const + { + return !(*this == other); + } + + QString toString() const + { + QString str = QLatin1String("GeoImage("); + if (proxyImage.isNull()) + str.append(QLatin1String("noProxyImage, ")); + else + str.append(QLatin1String("withProxyImage, ")); + + if (mainImage.isNull()) + str.append(QLatin1String("noMainImage, ")); + else + str.append(QLatin1String("withMainImage, ")); + + str.append(QStringLiteral("desc='%1')").arg(description)); + + return str; + } + }; + + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + GeoImage() : AbstractSinglePointMapObject() {;} ///< Contructor + + virtual ~GeoImage() = default; ///< Destructor + + /*! \name Set data in one call */ + //@{ + virtual GeoImageData geoImageData() const = 0; + virtual void setGeoImageData(const GeoImageData &geoImageData) = 0; + //@} + + /*! \name Set data separately */ + //@{ + virtual MarkerSymbolType proxyMarker() const = 0; + virtual void setProxyMarker(const MarkerSymbolType &proxyMarker) = 0; + virtual int proxyMarkerSize() const = 0; + virtual void setProxyMarkerSize(int proxyMarkerSize) = 0; + virtual QColor proxyMarkerColor() const = 0; + virtual void setProxyMarkerColor(const QColor &proxyMarkerColor) = 0; + + virtual QImage proxyImage() const = 0; + virtual QSize proxyImageDefaultSize() const = 0; + + /** + * @brief setProxyImage to set a proxy image be displayed on the map (instead of marker) + * @param proxyImage proxy image be displayed on the map + * @param proxyImageDefaultSize default size image if (user) scale is 1.0. + * The image will be scaled to the proxyImageDefaultSize * scale. + * To ensure that the image will be displayed with high resolution, the proxyImage must have this resolution. + * Example: + * Set an image of QSize(100,100), and set a proxyImageDefaultSize of QSize(20,20). + * Initially (scale = 1.0) the image will be downscaled to QSize(20,20). + * If the uses increases the scale to 4.0, the image will be scaled to QSize(80,80) + * This should ensure a good image quality. + */ + virtual void setProxyImage(const QImage &proxyImage, const QSize &proxyImageDefaultSize) = 0; + + + virtual QImage mainImage() const = 0; + virtual void setMainImage(const QImage &mainImage) = 0; + + virtual QString description() const = 0; + virtual void setDescription(const QString &description) = 0; + + // for better visibility at 3D + virtual void setPinEnabled(bool enablePin) = 0; + virtual bool isPinEnabled() const = 0; + //@} + + + /*! \name Create default widget */ + //@{ + virtual QWidget *createWidget(QWidget *parent = nullptr) const = 0; + //@} + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.cpp new file mode 100755 index 000000000..5d62eb985 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.cpp @@ -0,0 +1,75 @@ +/** + * @RDE_HEADER@ + * Class Geometry + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "geometry.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +QList Geometry::createCircle(const QPointF ¢er, qreal radius, int steps) +{ + QList circle; + circle.reserve(steps); + + if (steps < 10) + steps = 10; + for (int i = 0; i < 360; i += (360 / steps)) + { + double winkel = i * M_PI / 180.0; + circle << (center + QPointF(cos(winkel) * radius, sin(winkel) * radius)); + } + circle << circle.value(0); // 'close' the circle + // circle.translate(center); + return circle; +} + + +QList Geometry::createArcSegment(const QPointF ¢er, qreal radius, + double startAngle_degree, + double angleOffset_clockwise_degree, + int stepsPer360) +{ + QList points; + + if (stepsPer360 < 10) + stepsPer360 = 10; + + startAngle_degree *= -1.0; + startAngle_degree += 90; + + // normalize + startAngle_degree = fmod(startAngle_degree, 360); + angleOffset_clockwise_degree = fmod(angleOffset_clockwise_degree, 360); + + // steps + int steps = fabs(angleOffset_clockwise_degree) * stepsPer360 / 360; + points.reserve(steps + 1); + + double startAngle_rad = startAngle_degree * M_PI / 180.0; + double angleOffset_right_rad = angleOffset_clockwise_degree * M_PI / 180.0; + for (int i = 0; i < steps; ++i) + { + // '-' to go clockwise: + double winkel_rad = (startAngle_rad - (i * angleOffset_right_rad / steps)); + points << (center + QPointF(cos(winkel_rad) * radius, sin(winkel_rad) * radius)); + } + // calculate very last iteration (independent from 'steps'): + double winkel_rad = (startAngle_rad - (angleOffset_right_rad)); + points << (center + QPointF(cos(winkel_rad) * radius, sin(winkel_rad) * radius)); + + return points; +} + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.h new file mode 100755 index 000000000..b73359d5b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/geometry.h @@ -0,0 +1,46 @@ +/** + * @RDE_HEADER@ + * Class Geometry + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "situation_map_export.h" + + +// Qt +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SITUATIONMAP_EXPORT Geometry final +{ +public: + Geometry() = default; + ~Geometry() = default; + + static QList createCircle(const QPointF ¢er, + qreal radius, + int steps = 72); // 72 -> 5° Schritte + + static QList createArcSegment(const QPointF ¢er, qreal radius, + double startAngle_degree, + double angleOffset_clockwise_degree, + int stepsPer360 = 72); // 72 -> 5° Schritte + + static const int minNumberOfVerticesPerCircle = 10; + +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/hit_line.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/hit_line.h new file mode 100755 index 000000000..f96468406 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/hit_line.h @@ -0,0 +1,58 @@ +/** + * @RDE_HEADER@ + * Interface of HitLine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The HitLine class supports HitLines of different agents. + * The method setPositions() should never be called. + */ +class HitLine : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + HitLine() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~HitLine() = default; ///< Destructor + + + /*! \name Specific data*/ + //@{ + +// virtual PositionCont impacts() const = 0; + + /** + * @brief Start and end point:. + */ + virtual Point3D startPoint() const = 0; + virtual Point3D endPoint() const = 0; + + virtual void setStartAndEndPoint(const Point3D &start, const Point3D &end) = 0; + //@} + + + // void setPositions(const PositionCont& posCont) //SHOULD NEVER BE CALLED + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/line_of_sight.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/line_of_sight.h new file mode 100755 index 000000000..4834f8564 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/line_of_sight.h @@ -0,0 +1,64 @@ +/** + * @RDE_HEADER@ + * Interface of LineOfSight + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The LineOfSight class supports a "single" line with two colors. + * The method setPositions() should never be called. + */ +class LineOfSight : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + LineOfSight() + : AbstractMultiPointMapObject(), AbstractEffectSimulation() + {} ///< Contructor + + virtual ~LineOfSight() = default; ///< Destructor + + + /*! \name Specific data*/ + //@{ + + + /*! + * \brief set relvants Points + * \param observer observerPoint ( + * \param obstacle obstacle point + * \param target targetPoint + * \param visibleColor color to visualize the visible part + * \param obstructedColor color to visualize the obstructed part + */ + virtual void setPoints(const Point3D &observer, + const Point3D &obstacle, + const Point3D &target, + const QColor &visibleColor = Qt::green, + const QColor &obstructedColor = Qt::red) = 0; + //@} + + + // void setPositions(const PositionCont& posCont) //SHOULD NEVER BE CALLED + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_attributes.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_attributes.h new file mode 100755 index 000000000..e7ddb01c1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_attributes.h @@ -0,0 +1,35 @@ +/** + * @RDE_HEADER@ + * Interface of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_single_point_map_object.h" + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief + */ +class MapObjectAttributes : public virtual AbstractSinglePointMapObject +{ + +public: + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + MapObjectAttributes() : AbstractSinglePointMapObject() {;} ///< Contructor + + virtual ~MapObjectAttributes() = default; ///< Destructor + + virtual QVariantMap attributes() const = 0; + virtual void setAttributes(const QVariantMap &attributes) = 0; +}; + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_indicator.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_indicator.h new file mode 100755 index 000000000..f761d6bac --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/map_object_indicator.h @@ -0,0 +1,80 @@ +/** + * @RDE_HEADER@ + * Interface of MapObjectIndicator + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The MapObjectIndicator points to exactly one MapObject defined by its uniqueMapId. + * + * This MapObjectIndicator should be smart, you normally don´t have to set the position, + * color or similar directly. + * Just call setMapObjectToPointTo() and updateView(); + * \n The MapObjectIndicator shoud 'follow' the object to be pointed to. + * \n The color is taken from the objectToBePointedTo (if that changes, call updateView() again). + * + * Some Annotations / Recommendations: + * - Layer: create the MapObjectIndicator in the topmost (operational) layer (LayerType::Operational) + * - The drawOrder is internal choosen to be OVER the objectToBePointedTo + * - The method setPositions() should never be called. + * + */ +class MapObjectIndicator : public virtual AbstractMultiPointMapObject +{ + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + MapObjectIndicator() : AbstractMultiPointMapObject() {;} ///< Contructor + + virtual ~MapObjectIndicator() = default; ///< Destructor + + + /*! \name Specific data*/ + //@{ + + + /** + * @brief Start and end point:. + */ + virtual Point3D startPoint() const = 0; + virtual Point3D endPoint() const = 0; + + virtual void setStartAndEndPoint(const Point3D &start, const Point3D &end) = 0; + + /** + * @brief mapObjectToPointTo + * @return uniqueId of the object to be pointed to. + */ + virtual UniqueMapId mapObjectToPointTo() = 0; + + /** + * @brief Set uniqueId of object should to be pointed to. + * @attention But there is NO automatic update yet. Caller is responsable for operforming updateView()! + * @param uniqueId of the object to be pointed to. + */ + virtual void setMapObjectToPointTo(UniqueMapId uniqueId) = 0; + //@} + + + // void setPositions(const PositionCont& posCont) //SHOULD NEVER BE CALLED + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine.h new file mode 100755 index 000000000..34b5710c4 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine.h @@ -0,0 +1,53 @@ +/** + * @RDE_HEADER@ + * Interface of Mine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_single_point_map_object.h" + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The SingleFire class supports Indirect Single Fire. + */ +class Mine : public virtual AbstractSinglePointMapObject +{ + + using BaseClass = AbstractSinglePointMapObject; + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + Mine() : AbstractSinglePointMapObject() {;} ///< Contructor + + virtual ~Mine() = default; ///< Destructor + + /*! \name Specific data*/ + //@{ + /** + * @brief Set mine state (true = aimed/active => filled symbol). + * @return True for aimed/active, otherwise false. + */ + virtual bool state() const = 0; + + /** + * @brief Set new mine state. + * @param state (true = aimed/active => filled symbol) + */virtual void setState(bool state) = 0; + //@} + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine_field.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine_field.h new file mode 100755 index 000000000..604e558c6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/mine_field.h @@ -0,0 +1,90 @@ +/** + * @RDE_HEADER@ + * Interface of MineField + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The MineField class supports MineFields with Activated and DeActivated mines. + */ +class MineField : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + + using BaseClass = AbstractMapObject; + +public: + enum class MineState + { + Activated, ///< live, aimed + DeActivated ///< + }; + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + MineField() : AbstractMultiPointMapObject(), AbstractEffectSimulation() { ; } + virtual ~MineField() = default; ///< Destructor + + + /*! \name Segments */ + //@{ + virtual PositionCont segment(int key) const = 0; + /** + * @brief Set positions of one segment. + * @param key of segment to be set. + * @param posCont positions of segment. + * @note: is key is negativ, no fill pattern is aplied. + * If key is >=0, the pattern of the segments take over the main pattern. + */ + virtual void setSegment(int key, const PositionCont &posCont) = 0; + + /** + * @brief Remove one segment + * @param key to be removed. + */ + virtual void removeSegment(int key) = 0; + + /** + * @brief Remove all segments. + */ + virtual void removeSegments() = 0; + + /** + * @brief Get a list of keys in use. + * @return List of keys in use. + */ + virtual QList keysInUse() const = 0; + + + /*! \name Positions of Mines */ + //@{ + /** + * @brief Set all positions of mines (of all states) + * @param mineState to be set. + * @param posCont positions of mines to be set. + */ + virtual void setPositionsMine(MineState mineState, const PositionCont &posCont) = 0; + //@} + + virtual bool mineVisibility(MineState mineState) const = 0; + virtual void setMineVisibility(MineState mineState, bool visible) = 0; + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nine_liner.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nine_liner.h new file mode 100755 index 000000000..c6c4621bf --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nine_liner.h @@ -0,0 +1,112 @@ +/** + * @RDE_HEADER@ + * Interface of NineLiner + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The NineLiner class supports NineLiners of different agents. + * The method setPositions() should never be called. + */ +struct NineLiner : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + class NineLinerData + { + public: + Point3D initialPoint; + Point3D targetPosition; + + int friendliesBearing = -1; // friendlies is optional + double friendliesDistance = -1; // friendlies is optional + + int fah = 0; //finalAttackHeading + int fahLow = 0; //finalAttackHeading-SectorLow + int fahHigh = 0; //finalAttackHeading-SectorHigh + + /** + * @brief operator== Compare two objects + * @param other to be compared with + * @return true if identical, otherwise false. + */ + bool operator==(const NineLinerData &other) const + { + return ((this->initialPoint == other.initialPoint) && + (this->targetPosition == other.targetPosition) && + + (this->friendliesBearing == other.friendliesBearing) && + (this->friendliesDistance == other.friendliesDistance) && + + (this->fah == other.fah) && + (this->fahLow == other.fahLow) && + (this->fahHigh == other.fahHigh) + ); + } + + /** + * @brief operator!= Compare two objects + * @param other to be compared with + * @return true if NOT identical, otherwise false. + */ + bool operator!=(const NineLinerData &other) const + { + return !(*this == other); + } + + QString toString() const + { + QString str; + + std::ostringstream stream; + stream << " initialPoint = " << initialPoint + << " targetPosition = " << targetPosition; + str.append(QLatin1String(stream.str().c_str())); + + str.append(QStringLiteral(", friendliesBearing=%1").arg(friendliesBearing)); + str.append(QStringLiteral(", friendliesDistance=%1").arg(friendliesDistance)); + + str.append(QStringLiteral(", fah=%1").arg(fah)); + str.append(QStringLiteral(", fahLow=%1").arg(fahLow)); + str.append(QStringLiteral(", fahHigh=%1").arg(fahHigh)); + return str; + } + }; // class NineLinerData + + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + NineLiner() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~NineLiner() = default; ///< Destructor + + + /*! \name Specific data*/ + //@{ + + virtual void setNineLinerData(const NineLinerData &data) = 0; + +// virtual PositionCont impacts() const = 0; + + //@} + + + // void setPositions(const PositionCont& posCont) //SHOULD NEVER BE CALLED + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nuclear_agent.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nuclear_agent.h new file mode 100755 index 000000000..0fb154e82 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/nuclear_agent.h @@ -0,0 +1,98 @@ +/** + * @RDE_HEADER@ + * Interface of NuclearAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The ChemicalAgent class supports different agent types. + */ +class NuclearAgent : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + NuclearAgent() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~NuclearAgent() = default; ///< Destructor + + + /*! \name Subsidiary polyline/polygons */ + //@{ + /** + * @brief Get subsidiary line with key. + * @param key to be retrieved. + * @return subsidiary line at index + */ + virtual PositionCont subsidiaryLine(int key) const = 0; + + /** + * @brief Set subsidiary line with key. + * @param key to be set. + * @param positions subsidiary line to be set. + */ + + struct OptionalData + { + // ctor + OptionalData() {} + // member + bool closed{false}; + QSharedPointer lineStyle; // default is 'empty', do not honor + QSharedPointer fillStyle; // default is 'empty', do not honor + }; + +// virtual void setSubsidiaryLine(int key, const PositionCont &positions, bool clo = false) = 0; + virtual void setSubsidiaryLine(int key, const PositionCont &positions, const OptionalData &optData = OptionalData()) = 0; + + /** + * @brief Get a list of keys in use. + * @return List of keys in use. + */ + virtual QList subsidiaryKeysInUse() = 0; + + /** + * @brief Remove one subsidiary line. + * @param key to be removed. + */ + virtual void removeSubsidiaryLine(int key) = 0; + + /** + * @brief Remove all subsidiaryPositions. + */ + virtual void removeSubsidiaryLines() = 0; + //@} + + /*! \name Impact*/ + //@{ + virtual Point3D impactOrigin() const = 0; + virtual void setImpactOrigin(const Point3D &impactOrigin) = 0; + //@} + + /*! \name Single Detonation */ + //@{ + virtual void setDetonation(const Point3D &origin, double radius) = 0; + virtual bool hasDetonation() const = 0; + virtual void resetDetonation() = 0; + //@} + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/participant.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/participant.h new file mode 100755 index 000000000..4f8b0adeb --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/participant.h @@ -0,0 +1,279 @@ +/** + * @RDE_HEADER@ + * Interface of Participant + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include + + +// Qt +#include +#include +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The Participant class supports Participant and their wide range of parameters. + * + */ +class Participant : public virtual AbstractMapObject +{ + + using BaseClass = AbstractMapObject; + +public: + + /*! \name TextModifier */ + //@{ + /** + * @brief The TextModifier enum contains some standard Text Modifier (MIL2525, APP6) + */ + enum class TextModifier + { + EF_ReinforcedReduced = 0, ///< E -> FrameShapeModifier; F-> reinforced of reduced + M_HigherFormation, ///< Tactical Identifier (right handed) + T_UniqueDesignation, ///< Designator (left handed) + W_DateTimeGroup, + XY_AltitudeLocation, + V_Type, + Z_Speed, + G_StaffComments, + H_AdditionalInformation, + JKLNP, + END ///< This MUST mark the end, Do NOT use on interface + }; + + // Some helper functions: + static inline QLatin1String toString(TextModifier value) ///< 'EF_ReinforcedReduced', ... + { + switch (value) + { + case TextModifier::EF_ReinforcedReduced: + return QLatin1String("EF_ReinforcedReduced"); + case TextModifier::M_HigherFormation: + return QLatin1String("M_HigherFormation"); + case TextModifier::T_UniqueDesignation: + return QLatin1String("T_UniqueDesignation"); + case TextModifier::W_DateTimeGroup: + return QLatin1String("W_DateTimeGroup"); + case TextModifier::XY_AltitudeLocation: + return QLatin1String("XY_AltitudeLocation"); + case TextModifier::V_Type: + return QLatin1String("V_Type"); + case TextModifier::Z_Speed: + return QLatin1String("Z_Speed"); + case TextModifier::G_StaffComments: + return QLatin1String("G_StaffComments"); + case TextModifier::H_AdditionalInformation: + return QLatin1String("H_AdditionalInformation"); + case TextModifier::JKLNP: + return QLatin1String("JKLNP"); + case TextModifier::END: + break; + } //switch + return QLatin1String("ERROR-TextModifier"); + } + + static inline QLatin1String toDisplayString(TextModifier value) ///< 'ReinforcedReduced', ... + { + switch (value) + { + case TextModifier::EF_ReinforcedReduced: + return QLatin1String("ReinforcedReduced"); + case TextModifier::M_HigherFormation: + return QLatin1String("HigherFormation"); + case TextModifier::T_UniqueDesignation: + return QLatin1String("UniqueDesignation"); + case TextModifier::W_DateTimeGroup: + return QLatin1String("DateTimeGroup"); + case TextModifier::XY_AltitudeLocation: + return QLatin1String("AltitudeLocation"); + case TextModifier::V_Type: + return QLatin1String("Type"); + case TextModifier::Z_Speed: + return QLatin1String("Speed"); + case TextModifier::G_StaffComments: + return QLatin1String("StaffComments"); + case TextModifier::H_AdditionalInformation: + return QLatin1String("AdditionalInformation"); + case TextModifier::JKLNP: + return QLatin1String("JKLNP"); + case TextModifier::END: + break; + } //switch + return QLatin1String("ERROR-TextModifier"); + } + + static QVector textModifiers() ///< only relevant TextModifier items -> without 'END' + { + return + { + TextModifier::EF_ReinforcedReduced, + TextModifier::M_HigherFormation, + TextModifier::T_UniqueDesignation, + TextModifier::W_DateTimeGroup + }; + } + //@} + + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + Participant() : BaseClass() {;} ///< Contructor + + virtual ~Participant() = default; ///< Destructor + + /*! \name Origin*/ + //@{ + virtual Point3D origin() const = 0; + virtual bool setOrigin(const Point3D &origin) = 0; + + virtual void setPositions(const PositionCont &posCont) = 0; ///< To enable using as AES item (template@AdapterDds) + //@} + + /*! \name Symbol Display*/ + //@{ + virtual float userSymbolOpacity() const = 0; + virtual void setUserSymbolOpacity(float userSymbolOpacity) = 0; + virtual SymbolDataToRender symbolDataToRender(bool ignoreForcedColor = true) const = 0; + virtual QImage actualImage() const = 0; + virtual AdvancedSymbology::SymbologyType symbologyType() const = 0; + //@} + + /*! \name Text Modifier*/ + //@{ + virtual QString textModifier(TextModifier textModifier) const = 0; + virtual void setTextModifier(TextModifier textModifier, const QString &text) = 0; + virtual bool textModifierVisibility(TextModifier textModifier) const = 0; + virtual void setTextModifierVisibility(TextModifier textModifier, bool visible) = 0; + //@} + + /*! \name Graphical Modifier*/ + //@{ + virtual Injury injury() const = 0; + virtual QColor injuryColor() const = 0; + virtual bool setInjury(Injury injury, const QColor &injuryColor) = 0; ///< Attention: Qt::red is an int + virtual int injuryPartyId() const = 0; + virtual bool setInjury(Injury injury, int partyId) = 0; ///< Attention: Qt::red is an int + + virtual bool additionalSymbol(AdditionalSymbolType additionalSymbolType) const = 0; + + /*! + * \brief setAdditionalSymbol + * \param additionalSymbolType to set + * \param newState new value + * \return true if changed, otherwise false + */ + virtual bool setAdditionalSymbol(AdditionalSymbolType additionalSymbolType, bool newState) = 0; + + virtual bool isMarked() const = 0; + /*! + * \brief setMarked + * \param marked new value + * \return true if changed, otherwise false + */ + virtual bool setMarked(bool marked) = 0; + //@} + + /*! \name Ranges*/ + //@{ + virtual QVector ranges() const = 0; + virtual bool setRanges(const QVector &ranges) = 0; + + virtual bool isRangesVisible() const = 0; + virtual void setRangesVisibility(bool visible) = 0; + //@} + + /*! \name DirectionIndicator like Turret Alignment*/ + //@{ + struct DirectionIndicatorData + { + double directionDegree = 0.0; ///< degree, clockwise (0 means 12 on a analog clock) + int lineWidth = 2; ///< px @ sizeL==default + Qt::PenStyle lineStyle = Qt::SolidLine; + QColor color; ///< invalid, means partyColor + }; + + virtual void setDirectionIndicator(int key, const DirectionIndicatorData &data) = 0; + virtual void removeDirectionIndicator(int key = -1) = 0; + + virtual bool isDirectionIndicatorsVisible() const = 0; + virtual void setDirectionIndicatorsVisibility(bool visible) = 0; + //@} + + + /*! \name Designation*/ + //@{ + struct DesignationData + { + Point3D spotLocation; + int code; + + bool isNull() const { return spotLocation.isNull(); } + + bool operator==(const DesignationData& other) const + { + return this->spotLocation == other.spotLocation && this->code == other.code; + } + bool operator!=(const DesignationData& other) const + { + return !(*this == other); + } + }; + + virtual bool setDesignations(const QVector &designations) = 0; + //@} + + + /*! \name Movement Track + * First of all you have the enable the MovementTrack using enableMovementTrack(). + * \n Then you can optionally preset a couple of positions using setMovementTrack() or addToMovementTrack() to an MovementTrackContainer. + * \n Afterwards movement - transmitted by setOrigin() and a following updateView() - will be added to the MovementTrackContainer. + * \n If you hide the track by setMovementTrackVisibility() the movement will still be tracked until you disable the MovementTrack. + * + * Behaviour if you enabled the MovementTrack and update the origin (using setOrigin()): + * - Every position (different to last position) is added to track. + * \n There is currently no minimum movement required to be added to the track. + * - The number of positions in the track is unlimited. + * - There is no time-awareness of the positions. + */ + //@{ + virtual bool isMovementTrackEnabled() const = 0; + virtual void setMovementTrackEnabled(bool enable) = 0; + + virtual void setMovementTrack(const PositionCont &movementTrack) = 0; + virtual void addToMovementTrack(const Point3D &addToMovementTrack) = 0; + virtual PositionCont movementTrack() const = 0; + virtual int sizeOfMovementTrack() const = 0; + + virtual bool isMovementTrackVisible() const = 0; + virtual void setMovementTrackVisibility(bool visible) = 0; + //@} + + +private: + // Forbid some overloaded methods without effects of this object: +// virtual QString identifier() const { return QString(); } +// virtual void setIdentifier(const QString& identifier) { Q_UNUSED(identifier); } + +// virtual bool visibility(ObjectAppearance appearance) const { Q_UNUSED(appearance); return true;} +// virtual void setVisibility(ObjectAppearance appearance, bool visibility) { Q_UNUSED(appearance); Q_UNUSED(visibility); }; + +}; + + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polygon.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polygon.h new file mode 100755 index 000000000..8dd2fb4ee --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polygon.h @@ -0,0 +1,39 @@ +/** + * @RDE_HEADER@ + * Interface of Polygon + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The Polygon class supports Polygons. + */ +class Polygon : public virtual AbstractMultiPointMapObject +{ + + using BaseClass = AbstractMultiPointMapObject; + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + Polygon() : BaseClass() {;} ///< Contructor + + virtual ~Polygon() = default; ///< Destructor + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polyline.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polyline.h new file mode 100755 index 000000000..63fb414fd --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/polyline.h @@ -0,0 +1,56 @@ +/** + * @RDE_HEADER@ + * Interface of Polyline + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The Polyline class supports polylines. + */ +class Polyline : public virtual AbstractMultiPointMapObject +{ + + using BaseClass = AbstractMultiPointMapObject; + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + Polyline() : BaseClass() {;} ///< Contructor + + virtual ~Polyline() = default; ///< Destructor + + /*! \name ArrowHeads */ + //@{ + virtual bool arrowHeadStart() const = 0; + virtual void setArrowHeadStart(bool enable) = 0; + + virtual bool arrowHeadEnd() const = 0; + virtual void setArrowHeadEnd(bool enable) = 0; + //@} + + /*! \name Text on lineSegments */ + //@{ + virtual QString textOnSegment(uint segment) const = 0; + virtual void setTextOnSegment(uint segment, const QString &segmentText) = 0; + virtual void clearTextOnSegment(uint segment) = 0; + //@} + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/single_fire.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/single_fire.h new file mode 100755 index 000000000..434d27d91 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/single_fire.h @@ -0,0 +1,61 @@ +/** + * @RDE_HEADER@ + * Interface of SingleFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The SingleFire class supports Indirect Single Fire. + */ +class SingleFire : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + + using BaseClass = AbstractMultiPointMapObject; + +public: + + enum class ImpactType + { + normalImpact, + doubleImpact + }; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + SingleFire() : BaseClass() {;} ///< Contructor + + virtual ~SingleFire() = default; ///< Destructor + + /*! \name Specific data*/ + //@{ + virtual Point3D impactOrigin() const = 0; + virtual void setImpactOrigin(const Point3D &impactOrigin) = 0; + + virtual ImpactType impactType() const = 0; + virtual void setImpactType(ImpactType impactType = ImpactType::normalImpact) = 0; + + virtual Point3D origin() const = 0; + virtual void setOrigin(const Point3D &origin) = 0; + virtual float radius() const = 0; + virtual void setRadius(float radius) = 0; + virtual void setOriginatorToTargetLineSurface(const Point3D &start, const Point3D &end) = 0; + //@} + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/sound_area.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/sound_area.h new file mode 100755 index 000000000..1ea17ca21 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/sound_area.h @@ -0,0 +1,123 @@ +/** + * @RDE_HEADER@ + * Interface of SoundArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MarkerData +{ +public: + MarkerData() = default; + + /// ctor for marker + MarkerData(MarkerSymbolType _markerSymbolType, int _proxyMarkerSize, const QColor &_markerColor) + : m_proxyMarker(_markerSymbolType), m_proxyMarkerSize(_proxyMarkerSize), m_proxyMarkerColor(_markerColor) + {} + + /** + * @brief proxyImage to set a proxy image be displayed on the map (instead of marker) + * @param _proxyImage proxy image be displayed on the map + * @param _proxyImageDefaultSize default size image if (user) scale is 1.0. + * The image will be scaled to the proxyImageDefaultSize * scale. + * To ensure that the image will be displayed with high resolution, the proxyImage must have this resolution. + * Example: + * Set an image of QSize(100,100), and set a proxyImageDefaultSize of QSize(20,20). + * Initially (scale = 1.0) the image will be downscaled to QSize(20,20). + * If the user increases the scale to 4.0, the image will be scaled to QSize(80,80) + * This should ensure a good image quality. + */ + MarkerData(const QImage &_proxyImage, const QSize &_proxyImageDefaultSize) + : m_proxyImage(_proxyImage) + , m_proxyImageDefaultSize(_proxyImageDefaultSize) + {} + + /** + * @brief operator== Compare two objects + * @param other to be compared with + * @return true if identical, otherwise false. + */ + bool operator==(const MarkerData &other) const + { + return ((this->m_proxyImage == other.m_proxyImage) && + (this->m_proxyMarkerSize == other.m_proxyMarkerSize) && + (this->m_proxyMarkerColor == other.m_proxyMarkerColor) && + (this->m_proxyMarker == other.m_proxyMarker) + ); + } + + /** + * @brief operator!= Compare two objects + * @param other to be compared with + * @return true if NOT identical, otherwise false. + */ + bool operator!=(const MarkerData &other) const + { + return !(*this == other); + } + + bool hasMarker() const { return m_proxyMarker != MarkerSymbolType::Undefined; } + bool hasProxyImage() const { return !m_proxyImage.isNull(); } + QImage proxyImage() const { return m_proxyImage; } + bool isEmpty() const { return !hasMarker() && !hasProxyImage(); } + + QString toString() const + { + QString str = QLatin1String("GeoImage("); + if (m_proxyImage.isNull()) + str.append(QLatin1String("noProxyImage, ")); + else + str.append(QLatin1String("withProxyImage, ")); + return str; + } + +protected: + friend class SoundAreaImpl; + MarkerSymbolType m_proxyMarker{MarkerSymbolType::Undefined}; + int m_proxyMarkerSize{20}; + QColor m_proxyMarkerColor{Qt::red}; + QImage m_proxyImage; ///< if not set, marker will be used + QSize m_proxyImageDefaultSize; + +}; + + +/** + * @brief The SoundArea class supports SoundArea. + */ +class SoundArea : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + SoundArea() ///< Contructor + : AbstractMultiPointMapObject(), AbstractEffectSimulation() + {} + + virtual ~SoundArea() = default; ///< Destructor + + /*! \name Point symbol */ + //@{ + virtual Point3D markerOrigin() const = 0; + virtual void setMarkerOrigin(const Point3D &origin) = 0; + + virtual const MarkerData &markerData() const = 0; + virtual void setMarkerData(const MarkerData &markerData) = 0; + //@} + + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/text.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/text.h new file mode 100755 index 000000000..018c91980 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/text.h @@ -0,0 +1,53 @@ +/** + * @RDE_HEADER@ + * Interface of Text + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_single_point_map_object.h" + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The Polyline class supports polylines. + */ +class Text : public virtual AbstractSinglePointMapObject +{ + + using BaseClass = AbstractSinglePointMapObject; + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + Text() : BaseClass() {;} ///< Contructor + + virtual ~Text() = default; ///< Destructor + + /** + * @brief Set flag 'showAnchorMarker' to switch on additional anchor marker graphic + * @return value of flag 'showAnchorPosition' + * + * If 'showAnchorMarker' is switched on, some additional graphic is drawn to visualize anchor points of text lines. Per default 'showAnchorMarker' is switched off. + */ + virtual void setShowAnchorMarker(bool on) = 0; + + /** + * @brief Get flag 'showAnchorPosition' + * @return value of flag 'showAnchorMarker' + */ + virtual bool showAnchorMarker() const = 0; + +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/virtual_mine.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/virtual_mine.h new file mode 100755 index 000000000..004f49ae5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjects/virtual_mine.h @@ -0,0 +1,111 @@ +/** + * @RDE_HEADER@ + * Interface of VirtualMine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "abstract_multi_point_map_object.h" +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The ChemicalAgent class supports different agent types. + */ +class VirtualMine : public virtual AbstractMultiPointMapObject, public virtual AbstractEffectSimulation +{ + +public: + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Don't use "= default" because of bug in VS2015 JT 2016/11/7 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + VirtualMine() : AbstractMultiPointMapObject(), AbstractEffectSimulation() {;} ///< Contructor + + virtual ~VirtualMine() = default; ///< Destructor + + + /*! \name Subsidiary polyline/polygons */ + //@{ + /** + * @brief Get subsidiary line with key. + * @param key to be retrieved. + * @return subsidiary line at index + */ + virtual PositionCont subsidiaryLine(int key) const = 0; + + /** + * @brief Set subsidiary line with key. + * @param key to be set. + * @param positions subsidiary line to be set. + */ + + struct OptionalData + { + // ctor + OptionalData() {} + // member + bool closed{false}; + QSharedPointer lineStyle; // default is 'empty', do not honor + QSharedPointer fillStyle; // default is 'empty', do not honor + }; + +// virtual void setSubsidiaryLine(int key, const PositionCont &positions, bool clo = false) = 0; + virtual void setSubsidiaryLine(int key, const PositionCont &positions, const OptionalData &optData = OptionalData()) = 0; + + /** + * @brief Get a list of keys in use. + * @return List of keys in use. + */ + virtual QList subsidiaryKeysInUse() = 0; + + /** + * @brief Remove one subsidiary line. + * @param key to be removed. + */ + virtual void removeSubsidiaryLine(int key) = 0; + + /** + * @brief Remove all subsidiaryPositions. + */ + virtual void removeSubsidiaryLines() = 0; + //@} + + /*! \name 3D marker / SimpleMarkerSceneSymbol (via renderer) */ + //@{ + virtual bool has3DMarkerRenderer() const = 0; + virtual void set3DMarkerRenderer(const Point3D &markerOrigin, const QVariantMap &attribMap) = 0; + //@} + + /*! \name 3D marker / SimpleMarkerSceneSymbol (explicit symbol) */ + //@{ + struct SceneSymbolData + { + // ctor + SceneSymbolData() {} + // member + Point3D markerOrigin; + QColor color{Qt::gray}; + double radius{1.0}; + + bool cone{false}; // cone - sphere + double heading{0.0}; + double pitch{0.0}; + double roll{0.0}; + }; + + virtual bool has3DMarkerSceneSymbol() const = 0; + virtual void set3DMarkerSceneSymbol(const Point3D &markerOrigin, const SceneSymbolData &sceneSymbolData) = 0; + //@} +}; + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/CMakeLists.txt new file mode 100755 index 000000000..d518cb26d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/CMakeLists.txt @@ -0,0 +1,110 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +FIND_PACKAGE(ArcGIS REQUIRED) + + +# LIBRARY +SET(MapObj_SRCS +# geometry.cpp +) +SET(MapObj_HDRS + situation_map_objects_export.h +) + +SET(MapObjImpl_SRCS + abstract_multi_point_map_object_impl.cpp + abstract_single_point_map_object_impl.cpp + air_support_impl.cpp + area_fire_impl.cpp + basic_path_object_impl.cpp + biological_agent_impl.cpp + building_outline_impl.cpp + chemical_agent_impl.cpp + cube_impl.cpp + danger_area_impl.cpp + functional_area_impl.cpp + geo_image_impl.cpp + hit_line_impl.cpp + line_of_sight_impl.cpp + indication_container.cpp + mine_impl.cpp + map_object_attributes_impl.cpp + map_object_indicator_impl.cpp + mine_field_impl.cpp + nine_liner_impl.cpp + nuclear_agent_impl.cpp + path_container.cpp + path_container.h + participant_impl.cpp + polygon_impl.cpp + polyline_impl.cpp + single_fire_impl.cpp +# single_fire_central_impl.cpp + sound_area_impl.cpp + text_impl.cpp + virtual_mine_impl.cpp +) + +SET(MapObjImpl_HDRS + abstract_multi_point_map_object_impl.h + abstract_single_point_map_object_impl.h + air_support_impl.h + area_fire_impl.h + basic_path_object_impl.h + biological_agent_impl.h + building_outline_impl.h + chemical_agent_impl.h + cube_impl.h + danger_area_impl.h + functional_area_impl.h + geo_image_impl.h + hit_line_impl.h + line_of_sight_impl.h + indication_container.h + map_object_attributes_impl.h + map_object_indicator_impl.h + mine_field_impl.h + mine_impl.h + nuclear_agent_impl.h + nine_liner_impl.h + participant_impl.h + polygon_impl.h + polyline_impl.h + single_fire_impl.h +# single_fire_central_impl.h + sound_area_impl.h + text_impl.h + virtual_mine_impl.h + + situation_map_objects_export.h +) + + +SOURCE_GROUP(FunctionDialog FILES ${FunctionDialog_SRCS}) +SOURCE_GROUP(MapObjectsImpl FILES ${MapObj_SRCS} ${MapObj_HDRS} ${MapObjImpl_SRCS} ${MapObjImpl_HDRS}) + +NGL_ADD_LIBRARY(WS situationMapObjects SHARED ${MapObj_SRCS} ${MapObj_HDRS} ${MapObjImpl_SRCS} ${MapObjImpl_HDRS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(situationMapObjects + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + + +NGL_TARGET_LINK_LIBRARIES(situationMapObjects + PRIVATE + NGLLogger + ArcGIS::ArcGIS + LtsCoordTrans + PUBLIC + situationMap + Qt6::Core + Qt6::Widgets +) + +NGL_INSTALL_TARGET(situationMapObjects) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.cpp new file mode 100755 index 000000000..976575e93 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.cpp @@ -0,0 +1,415 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of SingleFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "single_fire_central_impl.h" +#include "single_fire_impl.h" +#include +#include +#include +#include + + +#include + +// ESRI +#include +#include + +#include + +// Qt +#include +#include +#include +#include + + +namespace SituationMap +{ + +class MyAnimation : public QVariantAnimation +{ +public: + MyAnimation(GraphicalObjectSceneMarker *detonationSceneMarker) + : m_detonationSceneMarker(detonationSceneMarker) + {} + ~MyAnimation() = default; + + void updateCurrentValue(const QVariant &value) + { + if (!m_detonationSceneMarker) + return; + fs = m_detonationSceneMarker->fillStyle(); + col = fs.fillColor(); + col.setAlphaF(value.toFloat()); + fs.setFillColor(col); + m_detonationSceneMarker->setFillStyle(fs); + m_detonationSceneMarker->updateView(); + } +private: + GraphicalObjectSceneMarker *m_detonationSceneMarker{nullptr}; + FillStyle fs; + QColor col; +}; + + +//----------------------------------------------------------------------------- +// class SingleFireCentralImpl +//----------------------------------------------------------------------------- +SingleFireCentralImpl::SingleFireCentralImpl(const QString &debugInfo) + : SingleFireCentral() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("SingleFireCentralImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_fillColorAlpha = 200; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SingleFireCentralImpl::~SingleFireCentralImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete m_animation; + delete m_centerImage; + delete m_detonationPath; + delete m_detonationSceneMarker; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +QString SingleFireCentralImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + str.append(BasicPathObjectImpl::toString()); + return str; +} + +Point3D SingleFireCentralImpl::impactOrigin() const +{ + return BaseClass::tacticalOrigin(); +} +void SingleFireCentralImpl::setImpactOrigin(const Point3D &impactOrigin) +{ + BaseClass::setTacticalOrigin(impactOrigin); +} + +void SingleFireCentralImpl::setDetonation(const SingleFireCentral::DetonationData &detonationData) +{ + if (m_detonationOrigin.value().origin() != detonationData.m_origin) + m_detonationOrigin.refValue().setOrigin(detonationData.m_origin); + m_detonationRadius = detonationData.m_radius; +} + +SingleFireCentral::DetonationData SingleFireCentralImpl::detonation() const +{ + return {m_detonationOrigin.value().origin(), m_detonationRadius.value()}; +} + +bool SingleFireCentralImpl::startDetonationAnimation() +{ + if (!m_detonationSceneMarker) + return false; + if (!m_animation) + { + m_animation = new MyAnimation(m_detonationSceneMarker); + m_animation->setDuration(500); + m_animation->setKeyValueAt(0.0 , 1.0); + m_animation->setKeyValueAt(0.2 , 0.5); + m_animation->setKeyValueAt(1.0 , 0.1); + // m_animation.setEasingCurve(QEasingCurve::InOutQuart); + } + m_animation->stop(); + m_animation->start(); + + + + if (!m_symbol3D) + { + qCritical() << " ModelSceneSymbol: ....:"; + + +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Runtime/Data/3D/Bristol/Collada/Bristol.dae")), 5.0); // works +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Proof_of_concept_Explosion_DAE/Proof_of_concept_Explosion_DAE_01.dae")), 10.0); +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Proof_of_concept_Explosion_DAE/Proof_of_concept_Explosion_DAE_02_60FPS.dae")), 10.0); +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Proof_of_concept_Explosion_DAE/Proof_of_concept_Explosion_DAE_Sampled.dae")), 10.0); +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Proof_of_concept_Explosion_DAE/Proof_of_concept_Explosion_DAE_unbaked.dae")), 10.0); +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Proof_of_concept_Explosion_DAE/Proof_of_concept_Explosion_3ds_NoAnimation.3ds")), 10.0); + + +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Runtime/Data/3D/3dexport/explosion/explosionFBX.fbx")), 10.0); + +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Runtime/Data/3D/3dexport/JetAnim/jet anim.fbx")), 10.0); // works +// m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QLatin1Literal("/shd/CTC/TOOLS/ArcGIS/Runtime/Data/3D/bradley_low_3ds/bradle.3ds")), 10.0); // works + m_symbol3D = new Esri::ArcGISRuntime::ModelSceneSymbol(QUrl(QStringLiteral("/shd/CTC/TOOLS/ArcGIS/Runtime/Data/3D/dolmus_3ds/dolmus.3ds")), 10.0); + + + QObject::connect(m_symbol3D, &Esri::ArcGISRuntime::ModelSceneSymbol::errorOccurred, + m_animation, [](const Esri::ArcGISRuntime::Error &loadError){ + qCritical() << " Error: : isEmpty:" << loadError.isEmpty(); + qCritical() << " Error: : code:" << loadError.code(); + qCritical() << " Error: : loadError:" << loadError.message(); + }); + + + QObject::connect(m_symbol3D, &Esri::ArcGISRuntime::ModelSceneSymbol::doneLoading, + m_animation, [](const Esri::ArcGISRuntime::Error &loadError){ + qCritical() << " ModelSceneSymbol: doneLoading: isEmpty:" << loadError.isEmpty(); + qCritical() << " ModelSceneSymbol: doneLoading: code:" << loadError.code(); + qCritical() << " ModelSceneSymbol: doneLoading: loadError:" << loadError.message(); + }); + m_symbol3D->load(); + + if (!m_graphic3D) + { + Point3D pos = m_detonationOrigin.value().originTranslated(); + m_graphic3D = new Esri::ArcGISRuntime::Graphic( Esri::ArcGISRuntime::Point(pos.x(), pos.y(), 20, m_mapImpl->getEsriSpatialReference()), m_symbol3D); + m_graphicsLayer->graphics()->append(m_graphic3D); + } + + } + + + + + + return true; +} + +AesEffectState SingleFireCentralImpl::aesState() const +{ + return BaseClass::aesState(); +} +void SingleFireCentralImpl::setAesState(AesEffectState aesState) +{ + BaseClass::setAesState(aesState); +} + +SymbolImage SingleFireCentralImpl::symbolImage() const +{ + SymbolImage symImage = m_tacticalAppearance->symbolImage(); + return symImage; +} + + + +bool SingleFireCentralImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " positions =" << this->positions() + << " radius =" << this->radius() + << " partyId =" << this->partyId() + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + if (m_tacticalAppearance) + { + if (partyChanged()) + m_symbol.refValue().color = partyColor(); + } + + // centerImage: + if (!m_centerImage) + { + m_centerImage = + new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#CenterImage"), + m_uniqueMapId); + } + + if (partyChanged()) + { + QImage img = SingleFireImpl::renderImpactSymbol(partyColor(), 20, true); // filled + m_centerImage->setImage(img); + } + +// m_centerImage->setScale(m_mainGraphicalObject->scale()); // do not scale centerImage, equal to mine of MineArea + + m_centerImage->setOrigin(m_tacticalOrigin.value().originTranslated()); + m_centerImage->setVisibility(m_visibility && m_visibilityTechnical); + + bool success = BaseClass::updateViewImpl(); // will call onMapExtentChanged and therefore m_centerImage->updateView + + bool successDetonationSceneMarker = true; + switch (m_mapImpl->mode()) + { + case Mode::Mode_2D: + successDetonationSceneMarker = updateDetonationPath(); + break; + case Mode::Mode_3D: + successDetonationSceneMarker = updateDetonationSceneMarker(); + break; + case Mode::Undefined: + // empty + break; + } + + success = success && successDetonationSceneMarker; + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool SingleFireCentralImpl::updateDetonationPath() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = true; + + // do we have a detonationPoint / impactOrigin? + if (m_detonationOrigin.value().origin().isNull() && (m_detonationRadius.value() < 1.0)) + { + delete m_detonationPath; + m_detonationPath = nullptr; + } else { + if (!m_detonationPath) + { + m_detonationPath = + new GraphicalObjectEllipse(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#DetonationEllipse"), + m_uniqueMapId); + } + { + EllipseParameters ellipsePara; + ellipsePara.setLengthX(m_detonationRadius.value()); + ellipsePara.setLengthY(m_detonationRadius.value()); + ellipsePara.setMaxSegmentLength(1); + ellipsePara.setMaxPointCount(30); // limit to 30 to reduce the number of vertices. If to high, keep performance impact to 3D in mind!!! + m_detonationPath->setEllipseParameters(ellipsePara); + } + m_detonationPath->setOrigin(m_detonationOrigin.value().originTranslated()); + m_detonationPath->setPickable(m_pickable); + // path == 0; indicationCont == 1; Marker == 2 + m_detonationPath->setDrawOrder(m_internalDrawOrder+2); + QColor col = partyColor(); + m_detonationPath->setLineStyle(LineStyle(col, 2)); + col.setAlphaF(col.alphaF() / 2.0); + m_detonationPath->setFillStyle(FillStyle(col, + m_mainGraphicalObject->fillStyle().fillPattern())); // take fillpattern + m_detonationPath->setScale(this->scale()); + success = m_detonationPath->updateView(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; + +} + +bool SingleFireCentralImpl::updateDetonationSceneMarker() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = true; + + // do we have a detonationPoint / impactOrigin? + if (m_detonationOrigin.value().origin().isNull() && (m_detonationRadius.value() < 1.0)) + { + delete m_detonationSceneMarker; + m_detonationSceneMarker = nullptr; + } else { + if (!m_detonationSceneMarker) + { + m_detonationSceneMarker = + new GraphicalObjectSceneMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#DetonationSceneMarker"), + m_uniqueMapId); + } + m_detonationSceneMarker->setOrigin(m_detonationOrigin.value().originTranslated()); + m_detonationSceneMarker->setDiameter(m_detonationRadius.value()*2.0); + m_detonationSceneMarker->setPickable(m_pickable); + // path == 0; indicationCont == 1; Marker == 2 + m_detonationSceneMarker->setDrawOrder(m_internalDrawOrder+2); + QColor col = partyColor(); + col.setAlphaF(col.alphaF() / 2.0); + m_detonationSceneMarker->setFillStyle(FillStyle(col, FillPattern::Solid)); + success = m_detonationSceneMarker->updateView(); + } + + // DEBUG: Animation in a loop + startDetonationAnimation(); + + + // check intersect + if (m_detonationSceneMarker && m_mainGraphicalObject ){ + Esri::ArcGISRuntime::GeometryEngine* geoEngine = Esri::ArcGISRuntime::GeometryEngine::instance(); + + Esri::ArcGISRuntime::Geometry geoLine = m_mainGraphicalObject->getGraphicGeometry(); + qCritical() << " geoLine " << geoLine.toJson(); + + Esri::ArcGISRuntime::Geometry geoSphere = m_detonationSceneMarker->getGraphicGeometry(); + qCritical() << " geoSphere " << geoSphere.toJson(); + + Esri::ArcGISRuntime::Geometry resultGeometry = + geoEngine->intersection(geoLine, geoSphere); + + qCritical() << " resultGeometry " << resultGeometry.toJson(); + } + + + + if (m_animation) + QObject::connect(m_animation, &QVariantAnimation::finished, [this]() + { + QTimer::singleShot(2000, m_animation, [this]() { + m_animation->start(); + }); + }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool SingleFireCentralImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polyline); + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + setLineStyle(LineStyle(Qt::black, 2)); + + m_detonationOrigin.refValue().setMapObjectImpl(this); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool SingleFireCentralImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool successCenterImage = true; + bool visiScale = m_visibilityScaleRange.scaleHasVisibility(m_mapImpl->scale()); + if (m_centerImage) + { + m_centerImage->setVisibility(visiScale && m_visibility && m_visibilityTechnical); + successCenterImage = m_centerImage->updateView(); + } + bool successDetonationSceneMarker = true; + if (m_detonationSceneMarker) + { + m_detonationSceneMarker->setVisibility(visiScale && m_visibility && m_visibilityTechnical); + successCenterImage = m_detonationSceneMarker->updateView(); + } + + bool success = BaseClass::onMapExtentChanged() && successCenterImage && successDetonationSceneMarker; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; + +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.h new file mode 100755 index 000000000..0340291ce --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/_single_fire_central_impl.h @@ -0,0 +1,116 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of SingleFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "basic_path_object_impl.h" +#include // Interface +#include +#include +#include + +#include + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +class MyAnimation; + + +/** + * @brief The SingleFireCentralImpl class displays a single impact. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (displaying a polyline) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT SingleFireCentralImpl : public BasicPathObjectImpl, public virtual SingleFireCentral +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit SingleFireCentralImpl(const QString &debugInfo = QString()); + virtual ~SingleFireCentralImpl(); + +public: + SingleFireCentralImpl(const SingleFireCentralImpl &other) = delete; + SingleFireCentralImpl &operator=(SingleFireCentralImpl &other) = delete; + + QString toString() const override; + + + // some set/get methods: + //@{ + void setDetonation(const DetonationData &detonationData) override; + DetonationData detonation() const override; + bool startDetonationAnimation() override; + + // required??? + Point3D impactOrigin() const override; + void setImpactOrigin(const Point3D &impactOrigin) override; + //@} + + // Delegate to BaseClass: + AesEffectState aesState() const override; + void setAesState(AesEffectState aesState) override; + + SymbolImage symbolImage() const override; + +protected: + bool updateViewImpl() override; + + bool updateViewImplPost() override + { + BaseClass::updateViewImplPost(); + m_detonationOrigin.reset(); + m_detonationRadius.reset(); + return true; + } + + bool updateDetonationPath(); + bool updateDetonationSceneMarker(); + + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + bool onMapExtentChanged() override; + + // internal/admin + + // logical + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + GraphicalObjectPictureMarker *m_centerImage{nullptr}; + + SimpleValue m_detonationOrigin; + SimpleValue m_detonationRadius{0.0}; + GraphicalObjectEllipse *m_detonationPath{nullptr}; // 2D + GraphicalObjectSceneMarker *m_detonationSceneMarker{nullptr}; // 3D + + MyAnimation *m_animation{nullptr}; + + Esri::ArcGISRuntime::Graphic *m_graphic3D{nullptr}; + Esri::ArcGISRuntime::ModelSceneSymbol *m_symbol3D{nullptr}; +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.cpp new file mode 100755 index 000000000..046361381 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.cpp @@ -0,0 +1,287 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AbstractMultiPointMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include +#include +#include +#include +#include + +// ESRI + + +// Qt +#include +#include + + +namespace SituationMap +{ + +// taken from AreaEffectSimulation::Icons::imageFromPolygon() +QImage imageFromForm(const PositionCont &polyAbsGeoPos, + QColor lineColor, + bool closed, + Qt::BrushStyle style, + QColor fillColor = Qt::black, + const int width = 100) +{ + if (polyAbsGeoPos.isEmpty()) + return QImage(); + QPolygonF polyInUtmCoords; + polyInUtmCoords.reserve(polyAbsGeoPos.size()); + int zone = LtsCoordTrans::UtmCoordinate::fromGeographic(polyAbsGeoPos.first().toPointF()).zone; + for (const Point3D &point : polyAbsGeoPos) + { + const LtsCoordTrans::UtmCoordinate &coord = LtsCoordTrans::UtmCoordinate::fromGeographic(point.toPointF(), zone); + polyInUtmCoords << QPointF(coord.x, coord.y); + } + + const QPolygonF bounding = polyInUtmCoords.boundingRect(); + + // center to center / Todo:: Does it always work? + const QTransform mTrans(QTransform::fromTranslate(-bounding.boundingRect().bottomLeft().x(), -bounding.boundingRect().bottomLeft().y())); + + + qreal w = bounding.boundingRect().width(); + qreal h = bounding.boundingRect().height(); + double scale = w > h ? width / w : width / h; + w *= scale; + h *= scale; + scale *= 0.90; // Leave 10% border (i.e. 5% each side) + const QTransform mScale(QTransform::fromScale(scale, -scale)); + const QTransform mTransSmall(QTransform::fromTranslate(0.05 * width + (width - w) / 2, 0.05 * width + (width - h) / 2)); + + QImage img(width, width, QImage::Format_ARGB32); + img.fill(Qt::transparent); + + QPainter painter(&img); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setPen(QPen(lineColor, 2)); + + QPolygonF mPoly = mTrans.map(polyInUtmCoords); + mPoly = mScale.map(mPoly); + mPoly = mTransSmall.map(mPoly); + + if (closed && fillColor.isValid()) + { + // polyGON / closed: + painter.setBrush(QBrush(fillColor, style)); + painter.drawPolygon(mPoly); + } + else + { + // polyLINE / open: + painter.drawPolyline(mPoly); + } + painter.end(); + + return img; +} + +//----------------------------------------------------------------------------- +// class AbstractMultiPointMapObjectImpl +//----------------------------------------------------------------------------- +AbstractMultiPointMapObjectImpl::AbstractMultiPointMapObjectImpl(const QString &debugInfo) + : AbstractMultiPointMapObject() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("AbstractMultiPointMapObjectImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_positions.setMapObjectImpl(this); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +AbstractMultiPointMapObjectImpl::~AbstractMultiPointMapObjectImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +PositionCont AbstractMultiPointMapObjectImpl::positions() const +{ + return m_positions.positions(); +} + +PositionCont AbstractMultiPointMapObjectImpl::positionsTranslated() const +{ + return m_positions.positionsTranslated(); +} + +bool AbstractMultiPointMapObjectImpl::setPositionsTranslated(const PositionCont &positions) +{ + m_updateObjectIndicator = m_positions.setPositionsTranslated(positions) || m_updateObjectIndicator; + return m_updateObjectIndicator; +} + +bool AbstractMultiPointMapObjectImpl::setPositions(const PositionCont &positions) +{ + m_updateObjectIndicator = m_positions.setPositions(positions) || m_updateObjectIndicator; + return m_updateObjectIndicator; +} + +LineStyle AbstractMultiPointMapObjectImpl::lineStyle() const +{ + return BaseClass::lineStyle(); +} +LineStyle &AbstractMultiPointMapObjectImpl::rLineStyle() +{ + return BaseClass::rLineStyle(); +} +void AbstractMultiPointMapObjectImpl::setLineStyle(const LineStyle &lineStyle) +{ + BaseClass::setLineStyle(lineStyle); +} +FillStyle AbstractMultiPointMapObjectImpl::fillStyle() const +{ + return BaseClass::fillStyle(); +} +FillStyle &AbstractMultiPointMapObjectImpl::rFillStyle() +{ + return BaseClass::rFillStyle(); +} +void AbstractMultiPointMapObjectImpl::setFillStyle(const FillStyle &fillStyle) +{ + BaseClass::setFillStyle(fillStyle); +} + +void AbstractMultiPointMapObjectImpl::updateIdentifierPosition() +{ +// NOTICE_LOG(m_logger) << LOGFUNCTION << " START" << " debugInfo=" << m_debugInfo; + + if (!m_mainGraphicalObject) + return; + + + if (m_positions.isEmpty()) + return; + + // find right coordinate: + Point3D resultPos; + const PositionCont &posCont = m_positions.positionsTranslated(); + for (const Point3D &pos : std::as_const(posCont)) + if (pos.x() > resultPos.x()) + resultPos = pos; + + if (!qFuzzyIsNull(resultPos.x())) + m_identifierPositionCandidateTranslated = resultPos; +} + +QString AbstractMultiPointMapObjectImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + str.append(QStringLiteral(" positions.count='%1'").arg(m_positions.positions().count())); + return str; +} + +void AbstractMultiPointMapObjectImpl::boundingBox(BoundingBox &bbox) const +{ + bbox.reset(); + + const PositionCont &posCont = m_positions.positions(); + if (posCont.isEmpty()) + return; + + QVectorIterator it(posCont); + const Point3D &pos = it.next(); + + double xmin = pos.x(); + double ymin = pos.y(); + double xmax = pos.x(); + double ymax = pos.y(); + + while (it.hasNext()) + { + const Point3D &pos2 = it.next(); + xmin = std::min(xmin, pos2.x()); + ymin = std::min(ymin, pos2.y()); + xmax = std::max(xmax, pos2.x()); + ymax = std::max(ymax, pos2.y()); + } + + double width = xmax - xmin; + double height = ymax - ymin; + + + QPointF center(xmin + width / 2.0, + ymin + height / 2.0); + + bbox = BoundingBox(BoundingBox::ExtentType::Sim, Point3D::fromQPointF(center), width, height); +} + +QImage AbstractMultiPointMapObjectImpl::previewImage() const +{ + return previewImageSpecific(); +} + +Qt::BrushStyle fillPatternToBrushStyle(const FillPattern &fillPattern) +{ + Qt::BrushStyle bs = Qt::SolidPattern; + + switch (fillPattern) { + case FillPattern::BackwardDiagonal: // / + bs = Qt::BDiagPattern; + break; + case FillPattern::Cross: + bs = Qt::CrossPattern; + break; + case FillPattern::DiagonalCross: + bs = Qt::DiagCrossPattern; + break; + case FillPattern::ForwardDiagonal: // '\' + bs = Qt::FDiagPattern; + break; + case FillPattern::Horizontal: + bs = Qt::HorPattern; + break; + case FillPattern::Null: + bs = Qt::NoBrush; + break; + case FillPattern::Solid: + bs = Qt::SolidPattern; + break; + case FillPattern::Vertical: + bs = Qt::VerPattern; + break; + default: + bs = Qt::SolidPattern; + break; + } + + return bs; +} + + + +QImage AbstractMultiPointMapObjectImpl::previewImageSpecific() const +{ + if (m_mainGraphicalObject) + { + FillStyle fs = m_mainGraphicalObject->fillStyle(); + Qt::BrushStyle bs = fillPatternToBrushStyle(fs.fillPattern()); + + return imageFromForm(this->positions(), + m_mainGraphicalObject->lineStyle().lineColor(), + m_isClosed, + bs, + fs.fillColor()); + } + + QColor lineColor = this->majorColor(); + if (!lineColor.isValid()) + lineColor = Qt::black; + return imageFromForm(this->positions(), lineColor, m_isClosed, Qt::BrushStyle::SolidPattern); +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.h new file mode 100755 index 000000000..503f09458 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_multi_point_map_object_impl.h @@ -0,0 +1,83 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AbstractMultiPointMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include // Interface +#include "situation_map_objects_export.h" + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The AbstractMultiPointMapObjectImpl class adds a unlimited number of positions (PositionCont) to the AbstractMapObjectImpl. + * By this a polyline and a polygon can be described. Accordingly a LineStyle and a FillStyle is added also. + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT AbstractMultiPointMapObjectImpl : public AbstractMapObjectImpl, public virtual AbstractMultiPointMapObject +{ + using BaseClass = AbstractMapObjectImpl; + +public: + explicit AbstractMultiPointMapObjectImpl(const QString &debugInfo = QString()); + virtual ~AbstractMultiPointMapObjectImpl(); + + AbstractMultiPointMapObjectImpl(const AbstractMultiPointMapObjectImpl &other) = delete; + AbstractMultiPointMapObjectImpl &operator=(AbstractMultiPointMapObjectImpl &other) = delete; + + QString toString() const override; + + + PositionCont positions() const override; + bool setPositions(const PositionCont &positions) override; + PositionCont positionsTranslated() const override; + bool setPositionsTranslated(const PositionCont &positions) override; + + void boundingBox(BoundingBox &bbox) const override; + + QImage previewImage() const override; + QImage previewImageSpecific() const override; + + + LineStyle lineStyle() const override; + LineStyle &rLineStyle() override; + void setLineStyle(const LineStyle &lineStyle) override; + + FillStyle fillStyle() const override; + FillStyle &rFillStyle() override; + void setFillStyle(const FillStyle &fillStyle) override; + + +protected: + // internal/admin + void updateIdentifierPosition() override; + + + // logical + PositionsTranslated m_positions; + bool m_isClosed = true; + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.cpp new file mode 100755 index 000000000..aa3f050b0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.cpp @@ -0,0 +1,175 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AbstractSinglePointMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "abstract_single_point_map_object_impl.h" +#include +#include +#include +#include + + +// ESRI + + +// Qt + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class AbstractSinglePointMapObjectImpl +//----------------------------------------------------------------------------- +AbstractSinglePointMapObjectImpl::AbstractSinglePointMapObjectImpl(const QString &debugInfo) + : BaseClass(debugInfo) // Implementation + , m_logger(getLogger("AbstractSinglePointMapObjectImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_origin.setMapObjectImpl(this); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +AbstractSinglePointMapObjectImpl::~AbstractSinglePointMapObjectImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString AbstractSinglePointMapObjectImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + + std::ostringstream stream; + stream << " " << m_origin.origin(); + str.append(QLatin1String(stream.str().c_str())); + + str.append(QStringLiteral(" radius=%1").arg(m_radius.value())); + + return str; +} + + +Point3D AbstractSinglePointMapObjectImpl::origin() const +{ + return m_origin.origin(); +} + +Point3D AbstractSinglePointMapObjectImpl::originTranslated() const +{ + return m_origin.originTranslated(); +} + +bool AbstractSinglePointMapObjectImpl::setOrigin(const Point3D &origin) +{ + m_updateObjectIndicator = m_origin.setOrigin(origin) || m_updateObjectIndicator; + return m_updateObjectIndicator; +} + +void AbstractSinglePointMapObjectImpl::boundingBox(BoundingBox &bbox) const +{ + if (m_radius.value() > 0) + bbox = BoundingBox(BoundingBox::ExtentType::Sim, m_origin.origin(), 2 * m_radius.value(), 2 * m_radius.value()); + else + { + GraphicalObjectPictureMarker *goPicture = dynamic_cast(m_mainGraphicalObject); + if (goPicture) + { + // ToDo: offset of goPicture has to be honored... + + if (!goPicture->image().isNull()) + { + // goPicture has Image + const QSize &imgSize = goPicture->currentSize(); // currentSize + bbox = BoundingBox(BoundingBox::ExtentType::Pixel, + m_origin.origin(), // Sim + imgSize.width(), imgSize.height() // pixel + ); + } else + { + // goPicture has Marker + int markerSize = goPicture->markerSymbolSize(); + + bbox = BoundingBox(BoundingBox::ExtentType::Pixel, + m_origin.origin(), // Sim + markerSize, markerSize // pixel + ); + + } + } + else + bbox = BoundingBox(BoundingBox::ExtentType::Sim, m_origin.origin()); // widht and heigth will be 0! + } +} + + +float AbstractSinglePointMapObjectImpl::radius() const +{ + return m_radius.value(); +} +void AbstractSinglePointMapObjectImpl::setRadius(float radius) +{ + m_radius = radius; +} + + + + +LineStyle AbstractSinglePointMapObjectImpl::lineStyle() const +{ + return BaseClass::lineStyle(); +} +LineStyle &AbstractSinglePointMapObjectImpl::rLineStyle() +{ + return BaseClass::rLineStyle(); +} +void AbstractSinglePointMapObjectImpl::setLineStyle(const LineStyle &lineStyle) +{ + BaseClass::setLineStyle(lineStyle); +} +FillStyle AbstractSinglePointMapObjectImpl::fillStyle() const +{ + return BaseClass::fillStyle(); +} +FillStyle &AbstractSinglePointMapObjectImpl::rFillStyle() +{ + return BaseClass::rFillStyle(); +} +void AbstractSinglePointMapObjectImpl::setFillStyle(const FillStyle &fillStyle) +{ + BaseClass::setFillStyle(fillStyle); +} + +void AbstractSinglePointMapObjectImpl::updateIdentifierPosition() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" << " debugInfo=" << m_debugInfo; + + if (m_mainGraphicalObjectText) + { + SituationMap::BoundingBox bbox; + this->boundingBox(bbox); + + if (bbox.isValid() && bbox.extentType() == BoundingBox::ExtentType::Pixel) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" << " bbox=" << bbox; + m_mainGraphicalObjectText->setXOffset((bbox.width() / 2.0) + 10); + } + else + m_mainGraphicalObjectText->setXOffset(10); + + } + + BaseClass::updateIdentifierPosition(); // calculating the origin +} + + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.h new file mode 100755 index 000000000..2182b0118 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/abstract_single_point_map_object_impl.h @@ -0,0 +1,82 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AbstractSinglePointMapObject + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include // Interface +#include "situation_map_objects_export.h" + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The AbstractSinglePointMapObjectImpl class adds excatly one positions (QPointF) to the AbstractMapObjectImpl. + * For convenience a radius can also be parametrized. Accordingly a LineStyle and a FillStyle is added also. + * \note Maybe the radius be be removed in future. + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT AbstractSinglePointMapObjectImpl : public AbstractMapObjectImpl, public virtual AbstractSinglePointMapObject +{ + using BaseClass = AbstractMapObjectImpl; + +public: + explicit AbstractSinglePointMapObjectImpl(const QString &debugInfo = QString()); + virtual ~AbstractSinglePointMapObjectImpl(); + + AbstractSinglePointMapObjectImpl(const AbstractSinglePointMapObjectImpl &other) = delete; + AbstractSinglePointMapObjectImpl &operator=(AbstractSinglePointMapObjectImpl &other) = delete; + + QString toString() const override; + + + Point3D origin() const override; + bool setOrigin(const Point3D &origin) override; + virtual Point3D originTranslated() const; + + virtual float radius() const; + virtual void setRadius(float radius); + + void boundingBox(BoundingBox &bbox) const override; + + + LineStyle lineStyle() const override; + LineStyle &rLineStyle() override; + void setLineStyle(const LineStyle &lineStyle) override; + + FillStyle fillStyle() const override; + FillStyle &rFillStyle() override; + void setFillStyle(const FillStyle &fillStyle) override; + + +protected: + // internal/admin + void updateIdentifierPosition() override; + + // logical + OriginTranslated m_origin; + + SimpleValue m_radius = -1; + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.cpp new file mode 100755 index 000000000..e9b71af53 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.cpp @@ -0,0 +1,587 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AirSupport + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "air_support_impl.h" +#include +#include +#include +#include +#include +#include + + +// ESRI +#include +#include + + +// Qt + + +namespace SituationMap +{ + +const int AIR_SUPPORT_LINE_WIDTH = 2; + +class AirSupportImpl::Private +{ +public: + Private() = default; + ~Private() + { + removeAesBuffer(); + removeAFP(); + removeALP(); + m_pm_AFP = nullptr; + m_pm_ALP = nullptr; + m_aesBuffer = nullptr; + } + + // ESRI data: + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + + // Own data: + MapImpl *m_mapImpl = nullptr; + UniqueMapId m_referencedObject = INVALID_MAP_ID; + + QString m_debugInfo; // will be appended by consecutive number + UniqueMapId m_uniqueMapId; + QColor m_color = Qt::black; + + + GraphicalObjectPictureMarker *m_pm_AFP = nullptr; + GraphicalObjectPictureMarker *m_pm_ALP = nullptr; + GraphicalObjectPath *m_aesBuffer = nullptr; + + QMap > m_cont; /// one polyline per subsidiary line + typedef QMapIterator> MAP_IT_T; +// int m_drawOrder = 0; + + + GraphicalObjectPictureMarker *getAFP() + { + if (!m_pm_AFP) + { + QString debugInfo = QStringLiteral("%1#Marker(AFP)").arg(m_debugInfo); + m_pm_AFP = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + debugInfo, + m_uniqueMapId); + m_pm_AFP->setMarkerSymbol(MarkerSymbolType::Circle, 10, Qt::black); + } + return m_pm_AFP; + } + void removeAFP() + { + delete m_pm_AFP; + m_pm_AFP = nullptr; + } + + GraphicalObjectPictureMarker *getALP() + { + if (!m_pm_ALP) + { + QString debugInfo = QStringLiteral("%1#Marker(ALP)").arg(m_debugInfo); + m_pm_ALP = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + debugInfo, + m_uniqueMapId); + m_pm_ALP->setMarkerSymbol(MarkerSymbolType::Circle, 10, Qt::black); + } + return m_pm_ALP; + } + void removeALP() + { + delete m_pm_ALP; + m_pm_ALP = nullptr; + } + + GraphicalObjectPath *getAesBuffer() + { + if (!m_aesBuffer) + { + QString debugInfo = QStringLiteral("%1#AesBuffer").arg(m_debugInfo); + m_aesBuffer = new GraphicalObjectPath(GraphicalObjectPathType::Polygon, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + debugInfo, + m_uniqueMapId); + m_aesBuffer->setFillStyle(FillStyle(FillPattern::Null)); + } + return m_aesBuffer; + } + void removeAesBuffer() + { + delete m_aesBuffer; + m_aesBuffer = nullptr; + } + + + + bool hasPathObject(int key) { return m_cont.contains(key); } + QList keysInUse() { return m_cont.keys(); } + void removePathObject(int key) { m_cont.remove(key); } + void clearPathObjects() { m_cont.clear(); } + + QSharedPointer getPathObject(int key) + { + if (m_cont[key].isNull()) + { + QString debugInfo = QStringLiteral("%1#SubsidiaryLine(%2)").arg(m_debugInfo).arg(key); + GraphicalObjectPath *temp = new GraphicalObjectPath(GraphicalObjectPathType::Polyline, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + debugInfo, + m_uniqueMapId); + if (temp) + m_cont[key] = QSharedPointer(temp); + } + return m_cont[key]; + } +}; // class AirSupportImpl::Private +//----------------------------------------------------------------------------- + + + +//----------------------------------------------------------------------------- +// class AirSupportImpl +//----------------------------------------------------------------------------- +AirSupportImpl::AirSupportImpl(const QString &debugInfo) + : AirSupport() // Interface + , BaseClass(debugInfo) // Implementation + , d(new Private) + , m_logger(getLogger("AirSupportImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +AirSupportImpl::~AirSupportImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void AirSupportImpl::setUniqueId(const UniqueMapId &uniqueId) +{ + BaseClass::setUniqueId(uniqueId); + d->m_uniqueMapId = m_uniqueMapId; +} + +void AirSupportImpl::setReferencedObject(const UniqueMapId &referencedObject) +{ + d->m_referencedObject = referencedObject; +} + +bool AirSupportImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_debugInfo=" << m_debugInfo + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical + << " this->drawOrder=" << this->drawOrder() + << " this->positions()=" << this->positions(); + + // m_mainGraphicalObject stores the outline/'bounding box' (techn and tact) + // Those geometry should never be visible. + // The text of should be visible. + this->setVisibilityOfGeometry(false); + + if (this->positions().size() < 2) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " number of coordinates is not sufficiant" + << " (#coord=" << this->positions().size() << ")"; + return false; + } + + if (m_mainGraphicalObject) + d->m_color = m_mainGraphicalObject->lineStyle().lineColor(); + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + + // real work: + + // + // First, draw AFP and ALP + // + + // AFP + { + Point3D coordTranslated = getCoordinateTranslated(COORD_INDEX_AFP); + if (coordTranslated.isNull()) + { + d->removeAFP(); + } else { + GraphicalObjectPictureMarker *pm = d->getAFP(); + pm->setMarkerSymbol(MarkerSymbolType::Circle, 10, d->m_color); + pm->setOrigin(coordTranslated); + pm->setVisibility(m_visibility && m_visibilityTechnical); + pm->setScale(m_mainGraphicalObject->scale()); + pm->setDrawOrder(m_mainGraphicalObject->drawOrder()); + if (!pm->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed"; + } + } + + // ALP + { + Point3D coordTranslated = getCoordinateTranslated(COORD_INDEX_ALP); + if (coordTranslated.isNull()) + { + d->removeALP(); + } else { + GraphicalObjectPictureMarker *pm = d->getALP(); + pm->setMarkerSymbol(MarkerSymbolType::Circle, 10, d->m_color); + pm->setOrigin(coordTranslated); + pm->setVisibility(m_visibility && m_visibilityTechnical); + pm->setScale(m_mainGraphicalObject->scale()); + pm->setDrawOrder(m_mainGraphicalObject->drawOrder()); + if (!pm->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed"; + } + } + + // + // delegate remaining to onMapExtentChanged + // + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool AirSupportImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + + bool success = BaseClass::onMapExtentChanged(); + + + if (!m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_mainGraphicalObject yet"; + return false; + } + + // 1. create aesBuffer; + updateViewImplBuffer(); + + // 2. and see, if Alp is within: + bool contains = bufferContainsAlp(); // buffer and alp must exist!!! + DEBUG_LOG(m_logger) << LOGFUNCTION << " buffer contains Alp: " << contains; + + + // Line Afp Alp + Point3D start = this->getCoordinateTranslated(COORD_INDEX_AFP); + Point3D end = this->getCoordinateTranslated(COORD_INDEX_ALP); + if (end.isNull()) + updateViewImplLineAfpAlp(false, Point3D(), Point3D()); + else + updateViewImplLineAfpAlp(true, start, end); + + // Line Alp Aes + start = this->getCoordinateTranslated(COORD_INDEX_ALP); + end = this->getCoordinateTranslated(COORD_INDEX_AES); + + + // try to replace end by intersection with Aes: + Point3D intersection = this->intersectionAesAndLineAlpAes(); + DEBUG_LOG(m_logger) << " intersectionAes " << intersection; + if (!intersection.isNull()) + end = intersection; + + // try to replace end by intersection with buffer of Aes: + if (d->m_aesBuffer) + { + intersection = this->intersectionBufferAndLineAlpAes(); + DEBUG_LOG(m_logger) << " intersectionBuffer " << intersection; + + if (!intersection.isNull()) + end = intersection; + } + + // Maybe 3rd coordinate is not provided? + if (start.isNull() || end.isNull()) + updateViewImplLineAlpAes(false, Point3D(), Point3D()); + else + updateViewImplLineAlpAes(!contains, start, end); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return success; +} + +bool AirSupportImpl::updateViewImplBuffer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " pixelSize= " << m_mapImpl->pixelSizeMeter(); + + if (m_mapImpl->pixelSizeMeter() < 1) + { + d->removeAesBuffer(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; + } + + double bufferDistance = m_mapImpl->pixelSizeMeter() * 100; + DEBUG_LOG(m_logger) << LOGFUNCTION << " bufferDistance= " << bufferDistance; + + if (bufferDistance > 2000) + bufferDistance = 2000; + + // AesBuffer of referenced object + if (!isUniqueMapIdValid(d->m_referencedObject)) + { + // This can happen, if the object is just created: + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_referencedObject is invalid" + << " (m_referencedObject=" << d->m_referencedObject << ")"; + return false; + } + + AbstractMapObject *amo = d->m_mapImpl->objectContainer().getObject(d->m_referencedObject); + if (!amo) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " m_referencedObject not found" + << " (m_referencedObject=" << d->m_referencedObject << ")"; + return false; + } + + QVector resultVec = MathUtils::GeometryEngine::buffer(amo, bufferDistance); + + GraphicalObjectPath *buffer = d->getAesBuffer(); + if (buffer) + { + buffer->setVisibility(m_visibility && m_visibilityTechnical); + buffer->setScale(m_mainGraphicalObject->scale()); + buffer->setLineStyle(LineStyle(d->m_color, AIR_SUPPORT_LINE_WIDTH, LinePattern::Solid)); + buffer->setPositions(resultVec); + buffer->setDrawOrder(m_mainGraphicalObject->drawOrder()); + if (!buffer->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed"; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void AirSupportImpl::updateViewImplLineAfpAlp(bool show, const Point3D &start, const Point3D &end) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (show) + { + QSharedPointer line = d->getPathObject(LINE_INDEX_AFP_ALP); + if (line) + { + line->setVisibility(m_visibility && m_visibilityTechnical); + line->setScale(m_mainGraphicalObject->scale()); + line->setDrawOrder(m_mainGraphicalObject->drawOrder()); + line->setLineStyle(LineStyle(d->m_color, AIR_SUPPORT_LINE_WIDTH, LinePattern::Dash)); + PositionCont posCont; + posCont.reserve(2); + posCont.append(start); + posCont.append(end); + line->setPositions(posCont); + if (!line->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed"; + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + } + else + d->removePathObject(LINE_INDEX_AFP_ALP); +} + +void AirSupportImpl::updateViewImplLineAlpAes(bool show, const Point3D &start, const Point3D &end) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (show) + { + QSharedPointer line = d->getPathObject(LINE_INDEX_ALP_AES); + if (line) + { + line->setVisibility(m_visibility && m_visibilityTechnical); + line->setScale(m_mainGraphicalObject->scale()); + line->setDrawOrder(m_mainGraphicalObject->drawOrder()); + line->setLineStyle(LineStyle(d->m_color, AIR_SUPPORT_LINE_WIDTH, LinePattern::Solid)); + PositionCont posCont; + posCont.reserve(2); + posCont.append(start); + posCont.append(end); + line->setPositions(posCont); + if (!line->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed"; + } + } + else + d->removePathObject(LINE_INDEX_ALP_AES); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool AirSupportImpl::bufferContainsAlp() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + Esri::ArcGISRuntime::Geometry geoHull; + if (d->m_aesBuffer) + geoHull = d->m_aesBuffer->getGraphicGeometry(); + + Esri::ArcGISRuntime::Geometry pntToCheck; + if (d->getALP()) + pntToCheck = d->getALP()->getGraphicGeometry(); + + bool contains = false; + + if (!geoHull.isEmpty() && !pntToCheck.isEmpty()) + contains = Esri::ArcGISRuntime::GeometryEngine::contains(geoHull, pntToCheck); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, contains" << contains; + return contains; +} + +Point3D AirSupportImpl::intersectionBufferAndLineAlpAes() +{ + Esri::ArcGISRuntime::Geometry geo1; + if (d->m_aesBuffer) + geo1 = d->m_aesBuffer->getGraphicGeometry(); + + if (geo1.isEmpty()) + return Point3D(); + + Esri::ArcGISRuntime::Geometry geo2 = geometryOfLine(getCoordinateTranslated(COORD_INDEX_ALP), + getCoordinateTranslated(COORD_INDEX_AES)); + + if (geo2.isEmpty()) + return Point3D(); + + return intersection(geo1, geo2); +} + +Point3D AirSupportImpl::intersectionAesAndLineAlpAes() +{ + AbstractMapObject *amo = d->m_mapImpl->objectContainer().getObject(d->m_referencedObject); + AbstractMapObjectImpl *mapObjImpl = dynamic_cast(amo); + + if (!mapObjImpl) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_referencedObject not found/dynamic_cast failed" + << " (m_referencedObject=" << d->m_referencedObject << ")"; + return Point3D(); + } + + Esri::ArcGISRuntime::Geometry geo1; + if (mapObjImpl) + geo1 = mapObjImpl->getEsriGeometry(); + + if (geo1.isEmpty()) + return Point3D(); + + Esri::ArcGISRuntime::Geometry geo2 = geometryOfLine(getCoordinateTranslated(COORD_INDEX_ALP), + getCoordinateTranslated(COORD_INDEX_AES)); + + if (geo2.isEmpty()) + return Point3D(); + + return intersection(geo1, geo2); +} + +Point3D AirSupportImpl::intersection(const Esri::ArcGISRuntime::Geometry &geo1, const Esri::ArcGISRuntime::Geometry &geo2) +{ + if (geo1.isEmpty() || geo2.isEmpty()) + return Point3D(); + + Esri::ArcGISRuntime::Geometry geoResult = + Esri::ArcGISRuntime::GeometryEngine::intersection(geo1, geo2); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " geoInter = " << geoResult.toJson(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " geoInter dimension = " << geoResult.dimension(); + + Esri::ArcGISRuntime::Point intersectPoint; + + if (geoResult.dimension() == 1) // dim 1 -> Lines and Polyline + { + Esri::ArcGISRuntime::Polyline polylineResult(geoResult); + if (polylineResult.parts().part(0).pointCount() > 0) + intersectPoint = polylineResult.parts().part(0).startPoint(); + } + else if (geoResult.dimension() == 0) + { + intersectPoint = Esri::ArcGISRuntime::Point(geoResult); // This is not tested yet + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " intersectPoint = " << intersectPoint.toJson(); + + if (std::isnan(intersectPoint.x()) || + std::isnan(intersectPoint.y()) || + std::isnan(intersectPoint.z())) + return Point3D(); + + Point3D intersection(intersectPoint.x(), + intersectPoint.y(), + intersectPoint.z()); + return intersection; +} + +Esri::ArcGISRuntime::Geometry AirSupportImpl::geometryOfLine(const Point3D &start, const Point3D &end) +{ + Esri::ArcGISRuntime::PolylineBuilder polylineBuilder(m_mapImpl->getEsriSpatialReference()); + polylineBuilder.addPoint(Esri::ArcGISRuntime::Point(start.x(), start.y())); + polylineBuilder.addPoint(Esri::ArcGISRuntime::Point(end.x(), end.y())); + + const Esri::ArcGISRuntime::Geometry &geo = polylineBuilder.toGeometry(); + return geo; +} + +bool AirSupportImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // no symbol required + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polyline); + + setLineStyle(LineStyle(Qt::black, 1)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + // set some values to Private: + d->m_mapImpl = m_mapImpl; + d->m_debugInfo = m_debugInfo; + d->m_uniqueMapId = m_uniqueMapId; + d->m_graphicsLayer = m_graphicsLayer; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +Point3D AirSupportImpl::getCoordinateTranslated(int idx) const +{ + if (idx < 0 || idx > 2) + return Point3D(); + + const PositionCont &posContTranslated = m_positions.positionsTranslated(); + if (idx >= posContTranslated.size()) + return Point3D(); + + return posContTranslated[idx]; +} + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.h new file mode 100755 index 000000000..62bb89fd0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/air_support_impl.h @@ -0,0 +1,119 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AirSupport + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + +/* + o AFP (Anflugpunkt) + \ + \ + o ALP (Ausloesepunkt) + | + | + | + ------- + | | AesItem / Buffer (Waffeneinsatzflaeche) + ------- +*/ + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward: +class ParticipantImpl; + + +/** + * @brief The AirSupportImpl class displays (guez) air support, aka AerialOrdenance + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * Line from 'Anflugpunkt' (AFP) over 'Ausloesepunkt' (ALP) + * to target (center of EffectSimulation) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + * + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT AirSupportImpl : public BasicPathObjectImpl, public virtual AirSupport +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit AirSupportImpl(const QString &debugInfo = QString()); + virtual ~AirSupportImpl(); + + void setUniqueId(const UniqueMapId &uniqueId) override; + +public: + AirSupportImpl(const AirSupportImpl &other) = delete; + AirSupportImpl &operator=(AirSupportImpl &other) = delete; + + void setReferencedObject(const UniqueMapId &referencedObject) override; + +protected: + bool updateViewImpl() override; + + bool onMapExtentChanged() override; + + bool updateViewImplBuffer(); + void updateViewImplLineAfpAlp(bool show, const Point3D &start, const Point3D &end); + void updateViewImplLineAlpAes(bool show, const Point3D &start, const Point3D &end); + + bool bufferContainsAlp(); + Point3D intersectionBufferAndLineAlpAes(); + Point3D intersectionAesAndLineAlpAes(); + Point3D intersection(const Esri::ArcGISRuntime::Geometry &geo1, const Esri::ArcGISRuntime::Geometry &geo2); + Esri::ArcGISRuntime::Geometry geometryOfLine(const Point3D &start, const Point3D &end); + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + static const int COORD_INDEX_AFP = 0; // Anflugpunkt + static const int COORD_INDEX_ALP = 1; // Ausloesepunkt + static const int COORD_INDEX_AES = 2; // target / EffectSimulation + + static const int LINE_INDEX_AFP_ALP = 0; // Anflugpunkt + static const int LINE_INDEX_ALP_AES = 1; // Ausloesepunkt + + Point3D getCoordinateTranslated(int idx) const; + + // logical + + +private: + + // logical + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.cpp new file mode 100755 index 000000000..8ef0d778e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.cpp @@ -0,0 +1,820 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AreaFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "area_fire_impl.h" +#include +#include +#include +#include +#include "single_fire_impl.h" + +#include + +// ESRI +#ifdef LTS_DEBUG +#include +#include +#endif // LTS_DEBUG + +// Qt + + +//#define USE_MARKER + + +namespace SituationMap +{ + +static int s_gunToTargetLine3DKEY = -1; +static int s_gunToTargetLineSurfaceKEY = 0; +static int s_nonLethalAreaKEY = 1; + + +class AreaFireImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: +// Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + + // Own data: + MapImpl *m_mapImpl = nullptr; + QString m_logicalLayer; + QString m_debugInfo; // will be appended by consecutive number + UniqueMapId m_uniqueMapId = INVALID_MAP_ID; + + SimpleValue m_scheduledImpactsExplcitColor; // by default invalid color + + struct Detonations + { + PositionsTranslated m_detonationPositions; + SimpleValue m_detonationRadius{5}; + bool m_detonationChanged{true}; + + bool changed() const { return m_detonationRadius.changed() || m_detonationChanged; } + void reset() { m_detonationRadius.reset(); m_detonationChanged = false; } + + + + } m_detonationsAbsolute, m_detonationsDraped; + + // + // ABSOLUTE using SceneMarker + // + + struct ValueDataGOSM_Abs + { + QSharedPointer m_goSceneMarker; + }; + QVector m_cont_VDGOSM_Abs; + + void resizeGOSM_Abs(int newSize) { m_cont_VDGOSM_Abs.resize(newSize); } + + ValueDataGOSM_Abs& getObjectGOSM_abs(int idx) + { + ValueDataGOSM_Abs &retRef = m_cont_VDGOSM_Abs[idx]; + if (retRef.m_goSceneMarker.isNull()) + { + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = m_mapImpl->getGraphicsLayer3D_SpAbsolute(m_logicalLayer); + if (!graphicsLayer) + { + qCritical() << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " getGraphicsLayer3D_SpAbsolute failed"; + } + + QString debugInfo = QStringLiteral("%1#ModelSceneMarkerAbs(%2)").arg(m_debugInfo).arg(idx); + GraphicalObjectSceneMarker *temp = new GraphicalObjectSceneMarker(SituationMap::SceneMarkerType::SPHERE, + m_mapImpl->getEsriGeoView(), + graphicsLayer, + debugInfo, + m_uniqueMapId); + temp->setUniqueMapId(m_uniqueMapId); + + retRef.m_goSceneMarker = QSharedPointer(temp); + } + return retRef; + } + + // + // DRAPED a) using renderer approach + // +#ifdef AREA_FIRE_DRAPED_RENDERER // GUEZE_2933 + struct ValueDataGORA_Drpd + { + QSharedPointer m_goRendererAtt; + }; + + QVector m_cont_GORA_drpd; + void resizeDraped_GORA(int newSize) + { + m_cont_GORA_drpd.resize(newSize); + } + + ValueDataGORA_Drpd& getObjectDraped_GORA(int idx, Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer) + { + Q_ASSERT(graphicsLayer); + + ValueDataGORA_Drpd &retRef = m_cont_GORA_drpd[idx]; + if (retRef.m_goRendererAtt.isNull()) + { + QString debugInfo = QStringLiteral("%1#Drpd_goRendererAtt_%2").arg(m_debugInfo).arg(idx); + retRef.m_goRendererAtt = QSharedPointer + (new GraphicalObjectRendererAttributes(m_mapImpl->getEsriGeoView() + , graphicsLayer + , debugInfo + , m_uniqueMapId) + ); + retRef.m_goRendererAtt->setUniqueMapId(m_uniqueMapId); + } + + return retRef; + } + + static QVariantMap createGORA_AttributeMap(const QColor &majorColor, double radius) + { + QVariantMap attribMap; + QString attribValue; + { + attribValue = QString(QStringLiteral("Impact_Color_%1")).arg(majorColor.name()); + attribMap.insert(QStringLiteral("IMPACT_COLOR"), attribValue); + } + { + const double radiusWithMin = std::max(radius, 1.0); // at least 1 meter + attribValue = QString(QStringLiteral("Impact_Radius_%1")).arg(radiusWithMin); + attribMap.insert(QStringLiteral("IMPACT_RADIUS"), attribValue); + } + { + LayerFactory::UniqueValueDataAesCentral::TYPE type{LayerFactory::UniqueValueDataAesCentral::TYPE::SPHERE}; + attribValue = QString(QStringLiteral("Impact_Type_%1")).arg(LayerFactory::toString(type)); + attribMap.insert(QStringLiteral("IMPACT_TYPE"), attribValue); + } +// { +// // SceneProperties: HEADING, PITCH, ROLL +// int heading = 0; +// if (data.rotationAngle().is_set()) +// heading = static_cast(data.rotationAngle().get()); +// attribMap.insert(QStringLiteral("HEADING"), ((heading+90)%360)); // data.rotationAngle() +// attribMap.insert(QStringLiteral("PITCH"), 0); +// attribMap.insert(QStringLiteral("ROLL"), 90); +// } + +// qCritical() << " ident " << ", " << attribMap; + return attribMap; + } +#else + // + // DRAPED b) using SceneMarker (see also GUEZE_2933) + // + struct ValueDataGOSM_Drpd + { + QSharedPointer m_goSceneMarker; + }; + QVector m_cont_GOSM_drpd; + void resizeDraped_GOSM(int newSize) + { + m_cont_GOSM_drpd.resize(newSize); + } + + ValueDataGOSM_Drpd& getObjectDraped_GOSM(int idx, Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer) + { + Q_ASSERT(graphicsLayer); + + ValueDataGOSM_Drpd &retRef = m_cont_GOSM_drpd[idx]; + if (retRef.m_goSceneMarker.isNull()) + { + QString debugInfo = QStringLiteral("%1#Drpd_GOSM_%2").arg(m_debugInfo).arg(idx); + GraphicalObjectSceneMarker *temp = new GraphicalObjectSceneMarker(SituationMap::SceneMarkerType::SPHERE, + m_mapImpl->getEsriGeoView(), + graphicsLayer, + debugInfo, + m_uniqueMapId); + temp->setUniqueMapId(m_uniqueMapId); + + retRef.m_goSceneMarker = QSharedPointer(temp); + } + return retRef; + } +#endif // AREA_FIRE_DRAPED_RENDERER + + + +}; // class AreaFireImpl::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class AreaFireImpl +//----------------------------------------------------------------------------- +AreaFireImpl::AreaFireImpl(const QString &debugInfo) + : AreaFire() // Interface + , BaseClass(debugInfo) // Implementation + , d(new Private) + , m_logger(getLogger("AreaFireImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo=" << m_debugInfo; + m_fillColorAlpha = 200; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo=" << m_debugInfo; +} + +AreaFireImpl::~AreaFireImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo=" << m_debugInfo; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo=" << m_debugInfo; +} + +void AreaFireImpl::setUniqueId(const UniqueMapId &uniqueId) +{ + BaseClass::setUniqueId(uniqueId); + d->m_uniqueMapId = m_uniqueMapId; +} + +PositionCont AreaFireImpl::scheduledImpacts() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return this->rIndicationContainer().positions(1); +} +void AreaFireImpl::setScheduledImpacts(const PositionCont &posCont, const QColor &explicitColor) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " scheduledImpactsSize=" << posCont.size() + << " explicitColor=" << explicitColor + ; + + IndicationContainer &drapedImpactsIC = this->rIndicationContainer(); + drapedImpactsIC.setDebugInfo(m_debugInfo + QStringLiteral("DefaultSpDraped")); + drapedImpactsIC.setPositions(1, posCont); + + d->m_scheduledImpactsExplcitColor = explicitColor; +} + +PositionCont AreaFireImpl::impacts() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return this->rIndicationContainer().positions(0); +} +void AreaFireImpl::setImpacts(const PositionCont &posCont) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", impactSize=" << posCont.size(); + + IndicationContainer &drapedImpactsIC = this->rIndicationContainer(); + + drapedImpactsIC.setDebugInfo(m_debugInfo + QStringLiteral("DefaultSpDraped")); + drapedImpactsIC.setPositions(0, posCont); + +#ifdef USE_MARKER + drapedImpactsIC.setMarker(0, MarkerSymbolType::X, 20, partyColor()); +#endif + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PositionCont AreaFireImpl::impactsAbsoluteHeight() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return this->rIndicationContainerAbsolute().positions(0); +} + +void AreaFireImpl::setImpactsAbsoluteHeight(const PositionCont &posCont) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " impactSize=" << posCont.size() + << " lineStyle().lineColor()=" << lineStyle().lineColor(); + + IndicationContainer &impactsAbsolute = this->rIndicationContainerAbsolute(); + + impactsAbsolute.setDebugInfo(m_debugInfo + QStringLiteral("SpAbso")); + impactsAbsolute.setPositions(0, posCont); + +#ifdef USE_MARKER + impacts.setMarker(0, MarkerSymbolType::X, 20, partyColor()); +#endif + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PositionCont AreaFireImpl::detonationsAbsolute() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_detonationsAbsolute.m_detonationPositions.positions(); +} + +bool AreaFireImpl::setDetonationsAbsolute(const PositionCont &detonationsPositions, int detonationRadius) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", set detonationsPositions = " << detonationsPositions + << ", radius=" << detonationRadius; + bool bHasChanged = d->m_detonationsAbsolute.m_detonationPositions.setPositions(detonationsPositions); + d->m_detonationsAbsolute.m_detonationRadius = detonationRadius; + d->m_detonationsAbsolute.m_detonationChanged = d->m_detonationsAbsolute.m_detonationChanged || bHasChanged; // Do not reset of changed==true; (maybe it was already changed) + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", bHasChanged=" << d->m_detonationsAbsolute.changed(); + return d->m_detonationsAbsolute.changed(); +} + +PositionCont AreaFireImpl::detonationsDraped() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_detonationsDraped.m_detonationPositions.positions(); +} + +bool AreaFireImpl::setDetonationsDraped(const PositionCont &detonationsPositions, int detonationRadius) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", set detonationsPositions = " << detonationsPositions + << ", radius=" << detonationRadius; + bool bHasChanged = d->m_detonationsDraped.m_detonationPositions.setPositions(detonationsPositions); + d->m_detonationsDraped.m_detonationRadius = detonationRadius; + d->m_detonationsDraped.m_detonationChanged = d->m_detonationsDraped.m_detonationChanged || bHasChanged; // Do not reset of changed==true; (maybe it was already changed) + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", bHasChanged=" << d->m_detonationsDraped.changed(); + return d->m_detonationsDraped.changed(); +} + +void AreaFireImpl::setGunToTargetLineSurface(const Point3D &start, const Point3D &end) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", start=" << start << ", end=" << end << ", debugInfo=" << m_debugInfo; + + PathContainer &path = this->rPathContainer(); // should be SP::Draped + path.setDebugInfo(m_debugInfo + QStringLiteral("SpDraped")); + + if (start.isNull() && end.isNull()) + { + path.clear(s_gunToTargetLineSurfaceKEY); // 0 + } else { + LineStyle ls = m_mainGraphicalObject->lineStyle(); + ls.setLinePattern(LinePattern::Dot); + ls.setLineWidth(2); + path.setLineStyle(ls); + path.setPath(s_gunToTargetLineSurfaceKEY, {start, end}); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", start=" << start << ", end=" << end << ", debugInfo=" << m_debugInfo; +} + +void AreaFireImpl::setGunToTargetTrajectory3D(const PositionCont &gunToTargetPath) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", gunToTargetPath=" << gunToTargetPath.size() + << ", debugInfo=" << m_debugInfo; + + PathContainer &path3D_Abs = this->rPathContainerSpAbsolute2(); // should be SP::Absolute + path3D_Abs.setDebugInfo(m_debugInfo + QStringLiteral("Absolute")); + path3D_Abs.setDrawOrder(5); + + if (gunToTargetPath.isEmpty()) + { + path3D_Abs.clear(s_gunToTargetLine3DKEY); // -1 + } else { + LineStyle ls = m_mainGraphicalObject->lineStyle(); + + // Try to reduce z-Fighting between gunToTarget and trajectory. + // by reducing the opacity (GUEZE-2722) + QColor lCol = ls.lineColor(); + lCol.setAlphaF(0.99); + ls.setLineColor(lCol); + + ls.setLinePattern(LinePattern::Dot); + ls.setLineWidth(1); + path3D_Abs.setLineStyle(ls); + path3D_Abs.setPath(s_gunToTargetLine3DKEY, gunToTargetPath); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", gunToTargetPath=" << gunToTargetPath.size() + << ", debugInfo=" << m_debugInfo; +} + +void AreaFireImpl::setNonLethalArea(const PositionCont &nonLethalArea) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", nonLethalArea=" << nonLethalArea.size() + << ", debugInfo=" << m_debugInfo; + + // avoid creating PathContainer in case of non-existing data: + if (nonLethalArea.size() < 3) + { + clearPathContainerSpDrapedPolygon(s_nonLethalAreaKEY); + return; + } + + PathContainer &path = this->rPathContainerSpDrapedPolygon(); + path.setDebugInfo(m_debugInfo + QStringLiteral("SpDrapedNonLethalArea")); + + // LineStyle ls = m_mainGraphicalObject->lineStyle(); + // ls.setLineColor(partyColor()); + // ls.setLinePattern(LinePattern::Solid); + // ls.setLineWidth(2); + // path.setLineStyle(ls); + + // const FillStyle &fs = m_mainGraphicalObject->fillStyle(); + // path.setFillStyle(fs); + + path.setPath(s_nonLethalAreaKEY, nonLethalArea); // 1 + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", nonLethalArea=" << nonLethalArea.size() + << ", debugInfo=" << m_debugInfo; +} + +PositionCont AreaFireImpl::trajectory(int index) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", index=" << index; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", index=" << index; + return this->rPathContainerSpAbsolute1().path(index); +} + +bool AreaFireImpl::setTrajectory(int index, const PositionCont &trajectoryPath) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " index=" << index + << ", trajectoryPath.size=" << trajectoryPath.size() + << ", lineStyle().lineColor()=" << lineStyle().lineColor(); + + PathContainer &path = this->rPathContainerSpAbsolute1(); // Trajectory -> Absolute + path.setDebugInfo(m_debugInfo + QStringLiteral("Draped")); + path.setDrawOrder(6); // 'above' guntToTargetLine/guntToTargetCurve + LineStyle ls = this->lineStyle(); + ls.setLinePattern(LinePattern::Solid); + ls.setLineWidth(2); + path.setLineStyle(ls); + return path.setPath(index, trajectoryPath); +} + + +bool AreaFireImpl::updateViewImpl() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << ", m_debugInfo=" << m_debugInfo + << ", visibility=" << m_visibility + << ", visiTechn=" << m_visibilityTechnical + << ", visiTact=" << m_visibilityTactical + // IndicationContainer &drapedImpactsIC = this->rIndicationContainer(); + // IndicationContainer &impactsAbsolute = this->rIndicationContainerAbsolute(); + << ", d->m_detonationsAbsolute.changed() = " << d->m_detonationsAbsolute.changed() + << ", d->m_detonationsAbsolute.size() = " << d->m_detonationsAbsolute.m_detonationPositions.size() + << ", d->m_detonationsDraped.changed() = " << d->m_detonationsDraped.changed() + << ", d->m_detonationsDraped.size() = " << d->m_detonationsDraped.m_detonationPositions.size(); + + for (IndicationContainer *indCont : {m_indicationCont, m_indicationContSpAbsolute}) + { + if (!indCont) + continue; + + if (m_mainGraphicalObject) + { +#ifdef USE_MARKER + indCont->setMarker(0, col); +#else + if (indCont->image(0).isNull() || m_party.partyId.changed()) + { + const QColor &col = m_mainGraphicalObject->lineStyle().lineColor(); + indCont->setImage(0, SingleFireImpl::renderImpactSymbol(col, 20, true)); // filled for impacts + } + if (indCont->image(1).isNull() || m_party.partyId.changed() || d->m_scheduledImpactsExplcitColor.changed()) + { + d->m_scheduledImpactsExplcitColor.reset(); + QColor col = d->m_scheduledImpactsExplcitColor.value(); + if (!col.isValid()) + col = m_mainGraphicalObject->lineStyle().lineColor(); + indCont->setImage(1, SingleFireImpl::renderImpactSymbol(col, 20, false)); // open for scheduledImpacts + } +#endif + } + + indCont->setSelected(0, m_selected); + indCont->setPickable(0, m_pickable); + indCont->setVisible(0, m_visibility); + indCont->setSelected(1, m_selected); + indCont->setPickable(1, m_pickable); + indCont->setVisible(1, m_visibility); + } // for (IndicationContainer *indCont... + + // control selection + if (m_pathCont_SpDraped) + { + m_pathCont_SpDraped->setSelected(m_selected); + // m_pathCont_SpDraped->setPickable(0, m_pickable); + m_pathCont_SpDraped->setVisibility(s_gunToTargetLineSurfaceKEY, m_visibility); // 0 -> GunToTargetLineSurface + } + if (m_pathCont_SpDrapedPolygon) + { + m_pathCont_SpDrapedPolygon->setSelected(m_selected); + // m_pathCont->setPickable(0, m_pickable); + m_pathCont_SpDrapedPolygon->setVisibility(s_nonLethalAreaKEY, m_visibility); + + LineStyle ls = m_mainGraphicalObject->lineStyle(); + ls.setLinePattern(LinePattern::Solid); + ls.setLineWidth(2); + m_pathCont_SpDrapedPolygon->setLineStyle(ls); + + FillStyle fs = m_mainGraphicalObject->fillStyle(); + m_pathCont_SpDrapedPolygon->setFillStyle(fs); + } + + if (m_pathCont_SpAbsolute1) + { + const auto &keys = m_pathCont_SpAbsolute1->getIndicationIndexList(); + for (auto keyIndex : std::as_const(keys)) + m_pathCont_SpAbsolute1->setVisibility(keyIndex, m_visibility); // trajectories + + m_pathCont_SpAbsolute1->setSelected(m_selected); + // m_pathCont->setPickable(0, m_pickable); + } + if (m_pathCont_SpAbsolute2) + { + m_pathCont_SpAbsolute2->setSelected(m_selected); + // m_pathCont->setPickable(0, m_pickable); + m_pathCont_SpAbsolute2->setVisibility(s_gunToTargetLine3DKEY, m_visibility); // -1 -> GunToTargetLine3D + } + + const bool mgoVisiOld = m_mainGraphicalObject->visibility(); + + bool success = BaseClass::updateViewImpl(); + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + // Detonations (3D) + bool successDetonationsAbs = true; + + if ( (m_mapImpl->mode() == Mode::Mode_3D) && + (d->m_detonationsAbsolute.changed() || (mgoVisiOld != m_mainGraphicalObject->visibility())) + ) + { + const PositionCont &positionsTranslated = d->m_detonationsAbsolute.m_detonationPositions.positionsTranslated(); + const int newSize = positionsTranslated.size(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " resizeGOSM_Abs" << d->m_cont_VDGOSM_Abs.size() << " to " << newSize; + + d->resizeGOSM_Abs(newSize); + + int idx = 0; + for(const Point3D &point3DTranslated : std::as_const(positionsTranslated)) + { + Private::ValueDataGOSM_Abs &vdGOSM_absRef = d->getObjectGOSM_abs(idx); + QSharedPointer goSceneMarker = vdGOSM_absRef.m_goSceneMarker; + + goSceneMarker->setOrigin(point3DTranslated); + goSceneMarker->setDiameter(d->m_detonationsAbsolute.m_detonationRadius.value() * 2); + goSceneMarker->setPickable(false); + goSceneMarker->setSelected(false); + + QColor col = partyColor(); + col.setAlphaF(col.alphaF() / 2.0); + goSceneMarker->setFillStyle(FillStyle(col, FillPattern::Solid)); + goSceneMarker->setVisibility(m_visibility); + + if (!goSceneMarker->updateView()) + successDetonationsAbs = false; + ++idx; + } // for + + if (!successDetonationsAbs) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView m_goSceneMarker failed, m_debugInfo = " << m_debugInfo; + + d->m_detonationsAbsolute.reset(); + } else { + // To simplifiy debugging: + d->m_detonationsAbsolute.reset(); + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << ", m_debugInfo=" << m_debugInfo + << ", d->m_detonationsDraped.changed = " << d->m_detonationsDraped.changed(); + + + bool successDetonationsDrpd = true; + if ( (m_mapImpl->mode() == Mode::Mode_3D) && + (d->m_detonationsDraped.changed() || (mgoVisiOld != m_mainGraphicalObject->visibility())) ) + { + +#ifdef AREA_FIRE_DRAPED_RENDERER + successDetonationsDrpd = updateViewImplDetonation3dDraped_RendererApproach(); +#else + successDetonationsDrpd = updateViewImplDetonation3dDraped_SceneMarkerApproach(); +#endif // AREA_FIRE_DRAPED_RENDERER + + } else { + // to simplify debugging: + d->m_detonationsDraped.reset(); + } + + success = success && successDetonationsAbs && successDetonationsDrpd; + + +#ifdef LTS_DEBUG + const bool afVisi = m_visibility; + const bool mgoVisi = m_mainGraphicalObject->visibility(); + const bool esriGraphicVisi = m_mainGraphicalObject->graphic()->isVisible(); + // REGG-5360 - Wenn man beim Beobachten der Live-Lage über den Wirksimulationsfilter alle gepla.. + // ... sind Flächenfeuer entgegen dem Filter sichtbar + + if ( (afVisi != mgoVisi) || (afVisi != esriGraphicVisi) ) + { + for (int i = 0; i < 100; ++i) + FATAL_LOG(m_logger) << LOGFUNCTION << " INKONSISTENCY!!!!!!!!!!!!!!"; + + FATAL_LOG(m_logger) << LOGFUNCTION << " INKONSISTENCY!!!!!!!!!!!!!!" + << ", afVisi=" << afVisi + << ", mgoVisi=" << mgoVisi + << ", esriGraphicVisi" << esriGraphicVisi; + + QMessageBox::critical(nullptr, + QLatin1String("INKONSISTENCY"), + QLatin1String("INKONSISTENCY, corefile wil be created ; check logfile regarding REGG-5360..")); + abort(); + + } +#endif // LTS_DEBUG + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; + +} + +#ifdef AREA_FIRE_DRAPED_RENDERER +bool AreaFireImpl::updateViewImplDetonation3dDraped_RendererApproach() // GUEZE_2933 +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << ", m_debugInfo=" << m_debugInfo + << ", visibility=" << m_visibility + << ", visiTechn=" << m_visibilityTechnical + << ", visiTact=" << m_visibilityTactical + << ", d->m_detonationsDraped.changed() = " << d->m_detonationsDraped.changed() + << ", d->m_detonationsDraped.size() = " << d->m_detonationsDraped.m_detonationPositions.size(); + + bool successDetonationsDrpd = true; + if ((m_mapImpl->mode() == Mode::Mode_3D) && d->m_detonationsDraped.changed()) + { + const PositionCont &positionsTranslated_drpdTrans = d->m_detonationsDraped.m_detonationPositions.positionsTranslated(); + const int newSize = positionsTranslated_drpdTrans.size(); + d->resizeDraped_GORA(newSize); + + int idx = 0; + for(const Point3D &point3DTranslated : std::as_const(positionsTranslated_drpdTrans)) + { + Private::ValueDataGORA_Drpd &vdGORA_drpdRef = d->getObjectDraped_GORA(idx, m_graphicsLayer); + QSharedPointer goRendererAtt = vdGORA_drpdRef.m_goRendererAtt; + + goRendererAtt->setOrigin(point3DTranslated); + + const QVariantMap &attribMap = d->createGORA_AttributeMap(m_mainGraphicalObject->lineStyle().lineColor(), d->m_detonationsDraped.m_detonationRadius.value()); + + if (m_logger.isDebugEnabled()) + qCritical() << LOGFUNCTION << " START" + << ", m_debugInfo=" << m_debugInfo + << ", attribMap = " << attribMap + << ", point3DTranslated = " << point3DTranslated.x() << " / " << point3DTranslated.y() << " / " << point3DTranslated.z() + << ", m_visibility = " << m_visibility; + + + goRendererAtt->setAttributeMap(attribMap); + goRendererAtt->setVisibility(m_visibility); + goRendererAtt->setPickable(false); + goRendererAtt->setSelected(false); + + if (!goRendererAtt->updateView()) + successDetonationsDrpd = false; + ++idx; + } // for + if (!successDetonationsDrpd) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl m_goRendererAtt failed, m_debugInfo=" << m_debugInfo; + + d->m_detonationsDraped.reset(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, successDetonationsDrpd=" << successDetonationsDrpd; + return successDetonationsDrpd; +} + +#else // AREA_FIRE_DRAPED_RENDERER // GUEZE_2933 + +bool AreaFireImpl::updateViewImplDetonation3dDraped_SceneMarkerApproach() // GUEZE_2933 +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << ", m_debugInfo=" << m_debugInfo + << ", visibility=" << m_visibility + << ", visiTechn=" << m_visibilityTechnical + << ", visiTact=" << m_visibilityTactical + << ", d->m_detonationsDraped.changed() = " << d->m_detonationsDraped.changed() + << ", d->m_detonationsDraped.size() = " << d->m_detonationsDraped.m_detonationPositions.size(); + + bool successDetonationsDrpd = true; + if ((m_mapImpl->mode() == Mode::Mode_3D) && d->m_detonationsDraped.changed()) + { + const PositionCont &positionsTranslated_drpdTrans = d->m_detonationsDraped.m_detonationPositions.positionsTranslated(); + const int newSize = positionsTranslated_drpdTrans.size(); + d->resizeDraped_GOSM(newSize); + + int idx = 0; + for(const Point3D &point3DTranslated : std::as_const(positionsTranslated_drpdTrans)) + { + Private::ValueDataGOSM_Drpd &GOSM_drpdRef = d->getObjectDraped_GOSM(idx, m_graphicsLayer); + + QSharedPointer goSceneMarker = GOSM_drpdRef.m_goSceneMarker; + + goSceneMarker->setOrigin(point3DTranslated); + goSceneMarker->setDiameter(d->m_detonationsDraped.m_detonationRadius.value() * 2); + goSceneMarker->setPickable(false); + goSceneMarker->setSelected(false); + + QColor col = partyColor(); + col.setAlphaF(col.alphaF() / 2.0); + goSceneMarker->setFillStyle(FillStyle(col, FillPattern::Solid)); + goSceneMarker->setVisibility(m_visibility); + + if (!goSceneMarker->updateView()) + successDetonationsDrpd = false; + ++idx; + } // for + if (!successDetonationsDrpd) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl GOSM_drpdRef failed, m_debugInfo=" << m_debugInfo; + + d->m_detonationsDraped.reset(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, successDetonationsDrpd=" << successDetonationsDrpd; + return successDetonationsDrpd; +} +#endif // AREA_FIRE_DRAPED_RENDERER + + + +bool AreaFireImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500002408010000"); // SymbolSet = 25 240801 + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + // m_symbol.refValue().m_sidc = QLatin1String("GHFPATB---"); // GHFPATB--- 0879, Area Target Bomb + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GHFPATS---"); // GHFPATB--- 0879, Area Target Smoke + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GHCPFLKS--"); // APP6B: 3965: GHCPFLKS-- -> Polygon + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::black, 2)); + + // setup Private: + d->m_mapImpl = mapImpl; + d->m_logicalLayer = logicalLayerName; + d->m_debugInfo = this->debugInfo(); + d->m_detonationsAbsolute.m_detonationPositions.setMapObjectImpl(this); + d->m_detonationsDraped.m_detonationPositions.setMapObjectImpl(this); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool AreaFireImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + for (auto item : {m_indicationCont, m_indicationContSpAbsolute}) + { + const bool visiScale = m_visibilityScaleRange.scaleHasVisibility(m_mapImpl->scale()); + if (item) + { + item->setVisible(0, m_visibility && m_visibilityTechnical && visiScale); + item->setVisible(1, m_visibility && m_visibilityTechnical && visiScale); + } + } + + bool success = BaseClass::onMapExtentChanged(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.h new file mode 100755 index 000000000..e68aa4f93 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/area_fire_impl.h @@ -0,0 +1,108 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of AreaFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// GUEZE_2933 +// // #define AREA_FIRE_DRAPED_RENDERER + + +/** + * @brief The AreaFireImpl class displays a area of impacts. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + * - an infinite number indication objects for displaying impacts (inherented from BasicPathObjectImpl) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT AreaFireImpl : public BasicPathObjectImpl, public virtual AreaFire +{ + using BaseClass = BasicPathObjectImpl; + +public: + explicit AreaFireImpl(const QString &debugInfo = QString()); + virtual ~AreaFireImpl(); + + AreaFireImpl(const AreaFireImpl &other) = delete; + AreaFireImpl &operator=(AreaFireImpl &other) = delete; + + void setUniqueId(const UniqueMapId &uniqueId) override; + + PositionCont scheduledImpacts() const override; + void setScheduledImpacts(const PositionCont &posCont, const QColor &explicitColor = QColor()) override; + + PositionCont impacts() const override; + void setImpacts(const PositionCont &posCont) override; + + PositionCont impactsAbsoluteHeight() const override; + void setImpactsAbsoluteHeight(const PositionCont &posCont) override; + + + PositionCont detonationsAbsolute() const override; + bool setDetonationsAbsolute(const PositionCont &detonationsPositions, int detonationRadius) override; + + PositionCont detonationsDraped() const override; + bool setDetonationsDraped(const PositionCont &detonationsPositions, int detonationRadius) override; + + void setGunToTargetLineSurface(const Point3D &start, const Point3D &end) override; + void setGunToTargetTrajectory3D(const PositionCont &gunToTargetPath) override; + + void setNonLethalArea(const PositionCont &nonLethalArea) override; + + PositionCont trajectory(int index) const override; + bool setTrajectory(int index, const PositionCont &trajectoryPath) override; + +protected: + virtual bool updateViewImpl() override; + +#ifdef AREA_FIRE_DRAPED_RENDERER + virtual bool updateViewImplDetonation3dDraped_RendererApproach(); +#else + virtual bool updateViewImplDetonation3dDraped_SceneMarkerApproach(); +#endif // AREA_FIRE_DRAPED_RENDERER + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + bool onMapExtentChanged() override; + + + // internal/admin + + // logical + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.cpp new file mode 100755 index 000000000..aed0b9bcc --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.cpp @@ -0,0 +1,1112 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of BasicPathObjectImpl + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "basic_path_object_impl.h" +#include "indication_container.h" +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include + + +// ESRI +#include +#include + + +// Qt +#include +#include +#include +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class BasicPathObjectImpl +//----------------------------------------------------------------------------- +BasicPathObjectImpl::BasicPathObjectImpl(const QString &debugInfo) + : BaseClass(debugInfo) // Implementation + , AbstractEffectSimulationImpl() // Secondary implementation + , m_logger(getLogger("BasicPathObjectImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo << "'"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo << "'"; +} + +BasicPathObjectImpl::~BasicPathObjectImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo << "'"; + delete m_indicationCont; + delete m_indicationContSpAbsolute; + delete m_pathCont_SpDraped; + delete m_pathCont_SpDrapedPolygon; + delete m_pathCont_SpAbsolute1; + delete m_pathCont_SpAbsolute2; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo << "'"; +} + + +QString BasicPathObjectImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + str.append(AbstractEffectSimulationImpl::toString()); + return str; +} + +Point3D BasicPathObjectImpl::tacticalOrigin() const +{ + return m_tacticalOrigin.value().origin(); +} + +void BasicPathObjectImpl::setTacticalOrigin(const Point3D &tacticalOrigin) +{ + if (m_tacticalOrigin.value().origin() != tacticalOrigin) + m_tacticalOrigin.refValue().setOrigin(tacticalOrigin); +} + +Point3D BasicPathObjectImpl::origin() const +{ + return m_origin.value(); +} +void BasicPathObjectImpl::setOrigin(const Point3D &origin) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo << "'" + << " origin=" << origin; + m_origin = origin; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo << "'"; +} + +float BasicPathObjectImpl::radius() const +{ + return m_radius.value(); +} +void BasicPathObjectImpl::setRadius(float radius, unsigned int maxPointCount) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo=" << m_debugInfo << "'" + << " radius=" << radius + << " maxPointCount=" << maxPointCount; + m_radius = radius; + m_maxPointCount = maxPointCount; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo=" << m_debugInfo << "'"; +} + + +#ifdef SUPPORT_SUBSIDIARY_LINES + +PositionCont BasicPathObjectImpl::subsidiaryPositions(int pathIndex) const +{ + if (m_subsidiaryPositionsCont.contains(pathIndex)) + return m_subsidiaryPositionsCont[pathIndex].positions(); + return PositionCont(); +} + +void BasicPathObjectImpl::setSubsidiaryPositions(int pathIndex, const PositionCont &positions) +{ + m_subsidiaryPositionsCont[pathIndex].setPositions(positions); +} + +QList BasicPathObjectImpl::subsidiaryPathIndexInUse() +{ + return m_subsidiaryPositionsCont.keys(); +} + +void BasicPathObjectImpl::clearSubsidiaryPositions(int pathIndex) +{ + m_subsidiaryPositionsCont.remove(pathIndex); +} + +void BasicPathObjectImpl::clearSubsidiaryPositions() +{ + m_subsidiaryPositionsCont.clear(); +} +#endif // SUPPORT_SUBSIDIARY_LINES + + +GraphicalObjectPathType BasicPathObjectImpl::pathType() const +{ + GraphicalObjectPath *path = static_cast(m_mainGraphicalObject); + return (path) ? path->pathType() : GraphicalObjectPathType::Polyline; +} + +int BasicPathObjectImpl::segmentCount() const +{ + if (m_positions.size() >= 2) + return m_positions.size() - 1; + + return 0; +} + + +AesEffectState BasicPathObjectImpl::aesState() const +{ + return BaseClass::aesState(); +} +void BasicPathObjectImpl::setAesState(AesEffectState aesState) +{ + BaseClass::setAesState(aesState); +} + + + + +IndicationContainer &BasicPathObjectImpl::rIndicationContainer() const +{ + if (!m_indicationCont) + { + m_indicationCont = new IndicationContainer(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_mapImpl->coordTranslationFunctor(), + m_uniqueMapId); + } + return *m_indicationCont; +} + +IndicationContainer &BasicPathObjectImpl::rIndicationContainerAbsolute() const +{ + if (!m_indicationContSpAbsolute) + { + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = m_mapImpl->getGraphicsLayer3D_SpAbsolute(m_logicalLayerName); + if (!graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " getGraphicsLayer3D_SpAbsolute failed"; + } + m_indicationContSpAbsolute = new IndicationContainer(m_mapImpl->getEsriGeoView(), + graphicsLayer, + m_mapImpl->coordTranslationFunctor(), + m_uniqueMapId); + } + return *m_indicationContSpAbsolute; +} + +PathContainer &BasicPathObjectImpl::rPathContainer() const +{ + if (!m_pathCont_SpDraped) + { + m_pathCont_SpDraped = new PathContainer(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_mapImpl->coordTranslationFunctor(), + m_uniqueMapId); + } + return *m_pathCont_SpDraped; +} + +PathContainer &BasicPathObjectImpl::rPathContainerSpDrapedPolygon() const +{ + if (!m_pathCont_SpDrapedPolygon) + { + m_pathCont_SpDrapedPolygon = new PathContainer(GraphicalObjectPathType::Polygon, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_mapImpl->coordTranslationFunctor(), + m_uniqueMapId); + } + return *m_pathCont_SpDrapedPolygon; +} + +void BasicPathObjectImpl::clearPathContainerSpDrapedPolygon(int idx) +{ + if (!m_pathCont_SpDrapedPolygon) + return; + rPathContainerSpDrapedPolygon().clear(idx); +} + + +PathContainer &BasicPathObjectImpl::rPathContainerSpAbsolute1() const +{ + if (!m_pathCont_SpAbsolute1) + { + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = m_mapImpl->getGraphicsLayer3D_SpAbsolute(m_logicalLayerName); + if (!graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " getGraphicsLayer3D_SpAbsolute failed"; + } + + m_pathCont_SpAbsolute1 = new PathContainer(m_mapImpl->getEsriGeoView(), + graphicsLayer, + m_mapImpl->coordTranslationFunctor(), + m_uniqueMapId); + } + return *m_pathCont_SpAbsolute1; +} +PathContainer &BasicPathObjectImpl::rPathContainerSpAbsolute2() const +{ + if (!m_pathCont_SpAbsolute2) + { + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = m_mapImpl->getGraphicsLayer3D_SpAbsolute(m_logicalLayerName); + if (!graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " getGraphicsLayer3D_SpAbsolute failed"; + } + + m_pathCont_SpAbsolute2 = new PathContainer(m_mapImpl->getEsriGeoView(), + graphicsLayer, + m_mapImpl->coordTranslationFunctor(), + m_uniqueMapId); + } + return *m_pathCont_SpAbsolute2; +} + + + + +bool BasicPathObjectImpl::arrowHeadStart() const +{ + return m_arrowHeadStart.enable; +} +void BasicPathObjectImpl::setArrowHeadStart(bool enable) +{ + m_arrowHeadStart.enable = enable; +} + +bool BasicPathObjectImpl::arrowHeadEnd() const +{ + return m_arrowHeadEnd.enable; +} +void BasicPathObjectImpl::setArrowHeadEnd(bool enable) +{ + m_arrowHeadEnd.enable = enable; +} + +void BasicPathObjectImpl::setArrowHeadsSelected(bool select) +{ + if (m_arrowHeadStart.pmArrowHead) + m_arrowHeadStart.pmArrowHead->setSelected(select); + if (m_arrowHeadEnd.pmArrowHead) + m_arrowHeadEnd.pmArrowHead->setSelected(select); +} + +void BasicPathObjectImpl::setDetonation(const Point3D &origin, double radius) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (m_detonationOrigin.value().origin() != origin) + m_detonationOrigin.refValue().setOrigin(origin); + m_detonationRadius = radius; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool BasicPathObjectImpl::hasDetonation() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return m_detonationRadius.value() > 0; +} + +void BasicPathObjectImpl::resetDetonation() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + setDetonation({}, 0.0); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString BasicPathObjectImpl::textOnSegment(uint segment) const +{ + return m_textDataCont[segment].text; +} + +void BasicPathObjectImpl::setTextOnSegment(uint segment, const QString &segmentText) +{ + m_textDataCont[segment].text = segmentText; +} + +void BasicPathObjectImpl::clearTextOnSegment(uint segment) +{ + m_textDataCont.remove(segment); +} + + +QImage BasicPathObjectImpl::renderArrowHeadSymbol(const QColor &color, int length, int halfWidth) +{ + int width = halfWidth * 2; + QImage image(width, length, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + QPainter painter(&image); + painter.setRenderHints(QPainter::Antialiasing); + + QPen pen(color); + pen.setWidth(2); + painter.setPen(pen); + + painter.setBrush(QBrush(color, Qt::BrushStyle::SolidPattern)); + + // 2 || + // | | + // 1 | | 3 + + QPolygon poly; + poly.reserve(3); + poly.append(QPoint(0 , length)); + poly.append(QPoint(halfWidth, 0)); + poly.append(QPoint(width , length)); + + painter.drawPolygon(poly); + + painter.end(); + + // image.save(QLatin1String("/tmp/Arrow.png")); + return image; +} + + +bool BasicPathObjectImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_debugInfo=" << m_debugInfo + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + if (!m_graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no graphicsLayer"; + return false; + } + + if (!m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << "no m_mainGraphicalObject"; + return false; + } + + // + // Use positions or origin/radius= + // + + if (m_radius.value() > 0) + { + // origin/radius : + if (m_origin.changed() || m_radius.changed() || m_maxPointCount.changed()) + { + // clear/disable some other things: + m_positions.clear(); + m_arrowHeadStart.enable = false; + m_arrowHeadEnd.enable = false; + m_textDataCont.clear(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " positions,arrowHeads,textDataCont cleared"; + } + + OriginTranslated orgTemp; + orgTemp.setMapObjectImpl(this); + orgTemp.setOrigin(m_origin.value()); + const Point3D &orgMapPoint = orgTemp.originTranslated(); + +// for 2D +// QList posContTrans = Geometry::createCircle(orgMapPoint, m_radius.value()); +// Q_FOREACH(const QPointF &pp, posContTrans) +// qWarning() << "posContTrans" << pp; + +// m_positions.setPositionsTranslated( posContTrans ); +// m_mainGraphicalObject->setPositions(this->positionsTranslated()); + +// for 2D and 3D: + Esri::ArcGISRuntime::Point centerP(orgMapPoint.x(), orgMapPoint.y(), orgMapPoint.z(), this->mapImpl()->getEsriSpatialReference()); + PositionCont posCont; + if (! GraphicalObjectEllipse::createCirclePolygon(centerP, m_radius.value(), m_maxPointCount.value(), posCont)) + ERROR_LOG(m_logger) << LOGFUNCTION << " createCirclePolygon failed"; + else + { + m_positions.setPositionsTranslated(posCont); + m_mainGraphicalObject->setPositions(this->positionsTranslated()); + } + + // : origin/radius + } + else + { + if (!m_positions.isEmpty()) + m_mainGraphicalObject->setPositions(this->positionsTranslated()); + } + + // verify: + const int minPoints = this->pathType() == GraphicalObjectPathType::Polyline ? 2 : 3; + if (m_mainGraphicalObject->positions().count() < minPoints) + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " debugInfo1='" << m_debugInfo << "'" + << " incorrect number of positions" + << " (" << this->positions().count() << ")"; + return false; + } + + // REGG-5261 - Alle Wirkfelder blinken in 3D, wenn C-Kampf ausgelöst ist + // War mit 100.4 meist, aber nicht immer reproduzierbar. + // Wenn aber per FunctionsDialog die TacticalAppearance sichtbar wurde, + // so war ein massives Flackern erkennbar. + // Daher wird die Ausdehnung nur an SICHTBARE m_tacticalAppearance übergeben + // [basic_path_object, sidc_symbol_impl] + if (m_tacticalAppearance && m_tacticalAppearance->visibility()) + { + // m_tacticalOrigin has priority (SingleFire) + if (!m_tacticalOrigin.value().origin().toPointF().isNull()) // check if x,y are defined, just z is not sufficiant + m_tacticalAppearance->setOrigin(m_tacticalOrigin.value().originTranslated()); + else + m_tacticalAppearance->setPositions(m_mainGraphicalObject->positions()); + } + + + // Indication: + if (m_indicationCont) + { + // The drawOrder should be above the m_mainGraphicalObject (==Path) + // (case: path is closed and filled -> guarantee that indication is visible.) + m_indicationCont->setDrawOrder(m_internalDrawOrder + 1); + } + if (m_indicationContSpAbsolute) + { + m_indicationContSpAbsolute->setDrawOrder(m_internalDrawOrder + 1); + } + + // Paths: + if (m_pathCont_SpDraped) + { + // The drawOrder should be above the m_mainGraphicalObject (==Path) + // (case: path is closed and filled -> guarantee that indication is visible.) + m_pathCont_SpDraped->setDrawOrder(m_internalDrawOrder + 1); + + // lineStyle has to be set explicit + // m_pathCont->setLineStyle(m_mainGraphicalObject->lineStyle()); + } + + // Paths: + if (m_pathCont_SpDrapedPolygon) + { + // The drawOrder should be above the m_mainGraphicalObject (==Path) + // (case: path is closed and filled -> guarantee that indication is visible.) + m_pathCont_SpDrapedPolygon->setDrawOrder(m_internalDrawOrder + 1); + } + + +// if (m_pathCont_SpAbsolute) +// { +// // DrawOrder for SpAbsolute not really helpful: +// // m_pathCont_SpAbsolute->setDrawOrder(m_internalDrawOrder + 1); +// // m_pathCont_SpAbsolute->setLineStyle(m_mainGraphicalObject->lineStyle()); +// } + + if (m_pathCont_SpDraped) + if (!m_pathCont_SpDraped->updateView()) + WARNING_LOG(m_logger) << LOGFUNCTION << " m_pathCont_SpDraped updateView failed"; + if (m_pathCont_SpDrapedPolygon) + if (!m_pathCont_SpDrapedPolygon->updateView()) + WARNING_LOG(m_logger) << LOGFUNCTION << " m_pathCont_SpDrapedPolygon updateView failed"; + + if (m_pathCont_SpAbsolute1) + if (!m_pathCont_SpAbsolute1->updateView()) + WARNING_LOG(m_logger) << LOGFUNCTION << " m_pathCont_SpAbsolute1 updateView failed"; + if (m_pathCont_SpAbsolute2) + if (!m_pathCont_SpAbsolute2->updateView()) + WARNING_LOG(m_logger) << LOGFUNCTION << " m_pathCont_SpAbsolute2 updateView failed"; + + + // Single Scene Marker + // has to be executed in final class + // updateSingleSceneMarker(); + // updateSingleSceneMarker(true); + + + // Arrowhead: + updateArrowHead(m_arrowHeadStart, true); + updateArrowHead(m_arrowHeadEnd, false); + + // SegmentText: + bool successSegText = updateSegmentText(); + if (!successSegText) + ERROR_LOG(m_logger) << LOGFUNCTION << " successSegText failed"; + + +#ifdef SUPPORT_SUBSIDIARY_LINES + // subsidiaryPositionsCont : + GraphicalObjectMultiPath *multiPath = static_cast(m_mainGraphicalObject); + + QList pathIdxInUse; + int pathIdx = 0; + QMutableMapIterator mapIt(m_subsidiaryPositionsCont); + while (mapIt.hasNext()) + { + mapIt.next(); + PositionsTranslated &posTranslated = mapIt.value(); + posTranslated.setMapObjectImpl(this); // let functor work :-) + + // PRE-increment! (pathIdx=0 is the main path in GraphicalObjectMultiPath + multiPath->setPath(++pathIdx, posTranslated.positionsTranslated()); + pathIdxInUse.append(pathIdx); + + } // while + + // remove non-existing subsidiary idx from multiPath: + multiPath->reduceTo(pathIdxInUse); + + // : subsidiaryPositionsCont +#endif // SUPPORT_SUBSIDIARY_LINES + + + // BaseClass: + bool success = BaseClass::updateViewImpl() && successSegText; + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool BasicPathObjectImpl::updateSingleSceneMarker(bool newVisibility) +{ + DEBUG_LOG(m_logger) << " START, newVisibility = " << newVisibility + << ", m_debugInfo" << m_debugInfo; + + // do we have a detonationPoint / impactOrigin? + if (m_detonationOrigin.value().origin().isNull() && (m_detonationRadius.value() < 1.0)) + { + m_detonationSceneMarker.clear(); + } else { + if (!m_detonationSceneMarker) + { + m_detonationSceneMarker = QSharedPointer(new GraphicalObjectSceneMarker(SituationMap::SceneMarkerType::SPHERE, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#DetonationSceneMarker"), + m_uniqueMapId)); + } + m_detonationSceneMarker->setOrigin(m_detonationOrigin.value().originTranslated()); + m_detonationSceneMarker->setDiameter(m_detonationRadius.value()*2.0); + m_detonationSceneMarker->setPickable(m_pickable); + // path == 0; indicationCont == 1; Marker == 2 + m_detonationSceneMarker->setDrawOrder(m_internalDrawOrder+2); + + QColor col = fillStyle().fillColor(); //partyColor(); partyColor could be invalid, e.g. in case of NContamination! + col.setAlphaF(col.alphaF() / 2.0); + m_detonationSceneMarker->setFillStyle(FillStyle(col, FillPattern::Solid)); + + m_detonationSceneMarker->setVisibility(newVisibility); + + if (!m_detonationSceneMarker->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION + << " updateView failed for" + << " debugInfo='" << m_detonationSceneMarker->debugInfo() << "'"; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +double viewAngleOfMapCoordinates3D(MapImpl * mapImpl, const Point3D &mapPos0, const Point3D &mapPos1, bool &bOkOut) +{ + bOkOut = false; + + if (!mapImpl) + return 0.0; + + QPointF p0 = mapImpl->mapCoordinateToViewPortWindowPoint3D(mapPos0); + if (p0.isNull()) + return 0.0; + QPointF p1 = mapImpl->mapCoordinateToViewPortWindowPoint3D(mapPos1); + if (p1.isNull()) + return 0.0; + + p0.ry() = p0.ry() * -1; + p1.ry() = p1.ry() * -1; + + double angle = MathUtils::getAngleDegreeNorth(p0, p1); + +// qCritical() << " mapPos0 = " << mapPos0 << " - mapPos1 = " << mapPos1; +// qCritical() << " p0 = " << p0 << " - p1 = " << p1; +// qCritical() << " angle = " << angle; + + bOkOut = true; + return angle; +} + +bool BasicPathObjectImpl::updateArrowHead(BasicPathObjectImpl::ArrowHead &arrowHead, + bool start) +{ + // pre-condition: + if (m_positions.size() < 2) + return false; + + if (!m_mainGraphicalObject) + return false; + + bool realEnable = arrowHead.enable; + + if (arrowHead.enable && + m_visibility && + m_visibilityTechnical && + (m_mapImpl->mode() == Mode::Mode_3D) + ) + { + // 3D -> take distance into account: + Point3D pt = m_positions.startPointTranslated(); + Esri::ArcGISRuntime::Point esriEnd3D(pt.x(), pt.y(), pt.z(), m_mapImpl->getEsriSpatialReference()); + const Esri::ArcGISRuntime::Point &camPoint = m_mapImpl->getEsriSceneGraphicsView()->currentViewpointCamera().location(); + + Esri::ArcGISRuntime::GeodeticDistanceResult distRes = Esri::ArcGISRuntime::GeometryEngine::distanceGeodetic(esriEnd3D, + camPoint, + Esri::ArcGISRuntime::LinearUnitId::Meters, + Esri::ArcGISRuntime::AngularUnitId::Degrees, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + realEnable = distRes.distance() < 20000; + } + + if (realEnable) + { + // delay creation if not visible: + if (arrowHead.pmArrowHead.isNull() && m_visibility && m_visibilityTechnical) + { + arrowHead.pmArrowHead = QSharedPointer + (new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , m_debugInfo + QLatin1String("#ArrowHead") + , m_uniqueMapId) + ); + } + + if (arrowHead.pmArrowHead) + { + + // maybe only if partyId changed or scale changed or selectionChanged + { + // colorChanged -> must be rendered again: + const float scale = m_mainGraphicalObject->scale() * m_mainGraphicalObject->lineStyle().lineWidth() * 0.5; + int lengthHeight = m_arrowHeadLengthHeight * scale; + int halfWidth = m_arrowHeadHalfWidth * scale; + + const QImage arrowImg = + renderArrowHeadSymbol(m_mainGraphicalObject->lineStyle().lineColor(), lengthHeight, halfWidth); + arrowHead.pmArrowHead->setImage(arrowImg); + arrowHead.pmArrowHead->setYOffset(lengthHeight / -2.0); + } + + + // take mapRotation into account, if necessary + double mapRotation = 0; + if (this->m_graphicsLayer->renderingMode() == Esri::ArcGISRuntime::GraphicsRenderingMode::Dynamic) + mapRotation = m_mapImpl->rotation(); + + + if (start) + { + arrowHead.pmArrowHead->setOrigin(m_positions.startPointTranslated()); + if (m_mapImpl->mode() == Mode::Mode_2D) + { + arrowHead.pmArrowHead->setRotationAngle(m_positions.angleOfStartSegment() + 180 + mapRotation); + } else { + // Mode::Mode_3D: + const PositionCont &posTranslated = m_positions.positionsTranslated(); + if (m_mapImpl && posTranslated.size() >= 2) + { + bool bOkOut = false; + double viewAngle = viewAngleOfMapCoordinates3D(m_mapImpl, posTranslated[1], posTranslated[0], bOkOut); + if (bOkOut) + arrowHead.pmArrowHead->setRotationAngle(viewAngle); + else + arrowHead.pmArrowHead->setRotationAngle(90); + } + } + } + else + { + switch (pathType()) + { + case GraphicalObjectPathType::Polyline: + arrowHead.pmArrowHead->setOrigin(m_positions.endPointTranslated()); + if (m_mapImpl->mode() == Mode::Mode_2D) + arrowHead.pmArrowHead->setRotationAngle(m_positions.angleOfEndSegment() + mapRotation); + else { + // Mode::Mode_3D: + const PositionCont &posTranslated = m_positions.positionsTranslated(); + const int size = posTranslated.size(); + if (m_mapImpl && size >= 2) + { + bool bOkOut = false; + double viewAngle = viewAngleOfMapCoordinates3D(m_mapImpl, posTranslated.at(size - 2), posTranslated.at(size - 1), bOkOut); + if (bOkOut) + arrowHead.pmArrowHead->setRotationAngle(viewAngle); + else + arrowHead.pmArrowHead->setRotationAngle(-90); + } + } + break; + case GraphicalObjectPathType::Polygon: + arrowHead.pmArrowHead->setOrigin(m_positions.startPointTranslated()); + if (m_mapImpl->mode() == Mode::Mode_2D) + arrowHead.pmArrowHead->setRotationAngle(m_positions.angleOfClosingSegment() + mapRotation); + else { + // Mode::Mode_3D: + const PositionCont &posTranslated = m_positions.positionsTranslated(); + const int size = posTranslated.size(); + if (m_mapImpl && size >= 2) + { + bool bOkOut = false; + double viewAngle = viewAngleOfMapCoordinates3D(m_mapImpl, posTranslated.first(), posTranslated.last(), bOkOut); + if (bOkOut) + arrowHead.pmArrowHead->setRotationAngle(viewAngle); + else + arrowHead.pmArrowHead->setRotationAngle(90); + } + } + break; + } // switch + + } + + arrowHead.pmArrowHead->setDrawOrder(m_internalDrawOrder + 1); + arrowHead.pmArrowHead->setVisibility(m_visibility && m_visibilityTechnical); + arrowHead.pmArrowHead->setPickable(m_pickable); + arrowHead.pmArrowHead->setSelected(m_selected); + arrowHead.pmArrowHead->updateView(); + + } // if (arrowHead.pmArrowHead) + + } + else + arrowHead.pmArrowHead.clear(); + + return true; +} + +bool BasicPathObjectImpl::updateSegmentText() +{ + // Qt5.4: QMap::lastKey(): "This function assumes that the map is not empty." + if (m_textDataCont.isEmpty()) + return true; + + if (!m_mapImpl) + return false; + + bool success = true; + + // Maybe the number of coordinates has been decreased and therefore the number of segments: + // They have to be removed to delete the TextData.goText (should be removed from screen.) + + while (!m_textDataCont.isEmpty() && m_textDataCont.lastKey() >= static_cast(this->segmentCount())) + { + // see QTBUG-59373 + const auto key = m_textDataCont.lastKey(); + m_textDataCont.remove(key); + } + + // ok, 'resized' :-) + // go on -> + + QMutableMapIterator it(m_textDataCont); + int i = -1; + while (it.hasNext()) + { + ++i; + + // current number of lineSegment exists? + if (i >= segmentCount()) + break; + + it.next(); + TextData &textData = it.value(); + + if (textData.goText.isNull()) + { + QString debugInfo = QStringLiteral("%1#Text_seg(%2)").arg(m_debugInfo).arg(i); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " create new Object " + << " " << debugInfo; + textData.goText = QSharedPointer + (new GraphicalObjectText(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId) + ); + } + + // calculate textOrigin on this segment: + const Point3D &mapCoord1 = m_positions.positionsTranslated()[i]; + const Point3D &mapCoord2 = m_positions.positionsTranslated()[i + 1]; + + const Point3D &textOrigin = MathUtils::getPointOnLine(mapCoord1, mapCoord2, 0.5); + + double angle = 0.0; + + if (m_mapImpl->mode() == Mode::Mode_2D) + { + angle = MathUtils::getAngleDegreeNorth(mapCoord1.toPointF(), mapCoord2.toPointF()); + } else { + // Mode::Mode_3D: + const PositionCont &posTranslated = m_positions.positionsTranslated(); + if (m_mapImpl && posTranslated.size() >= 2) + { + bool bOkOut = false; + angle = viewAngleOfMapCoordinates3D(m_mapImpl, mapCoord1, mapCoord2, bOkOut); + if (!bOkOut) + angle = 90; // degree, 3 o'clock + } + } + + + // take mapRotation into account, if necessary + if ((this->m_graphicsLayer->renderingMode() == Esri::ArcGISRuntime::GraphicsRenderingMode::Dynamic) && + (m_mapImpl->mode() == Mode::Mode_2D)) + angle += m_mapImpl->rotation(); + + if (angle > 360.0) + angle -= 360; + else if (angle < -360.0) + angle += 360; + + if ((angle >= 0) && (angle < 180)) + { + // Q1 & Q2 + angle -= 90; + } + else + { + // Q3 & Q4 + angle += 90; + } + + textData.goText->setOrigin(textOrigin); + textData.goText->setVisibility(m_visibility && m_visibilityTechnical); + textData.goText->setDrawOrder(m_internalDrawOrder + 1); + textData.goText->setText(textData.text); + + if (m_mainGraphicalObject) + { + textData.goText->setScale(m_mainGraphicalObject->scale()); + textData.goText->setPickable(m_mainGraphicalObject->pickable()); + } + + + TextStyle ts = defaultTextStyle(); + if (m_mainGraphicalObject) + ts.setTextColor(m_mainGraphicalObject->lineStyle().lineColor()); + // ts.setTextOutlineColor(Qt::white); + // ts.setTextOutlineWidth(2); + ts.setHorizontalAlignment(HorizontalAlignment::Center); + ts.setVerticalAlignment(VerticalAlignment::Bottom); + + ts.setAngle(angle); + // int outlineWith = 2 * scale(); + // if (outlineWith > 4) + // outlineWith = 4; + // ts.setTextOutlineWidth( outlineWith ); + textData.goText->setTextStyle(ts); + + + +// TextStyle ts; +// ts.setTextOutlineColor(Qt::white); +// ts.setTextOutlineWidth(2); +// ts.setHorizontalAlignment(HorizontalAlignment::Center); +// ts.setVerticalAlignment(VerticalAlignment::Middle); + +// ts.setAngle(angle); +// ts.setSize(ts.size() * scale()); +// int outlineWith = 2 * scale(); +// if (outlineWith > 4) +// outlineWith = 4; +// ts.setTextOutlineWidth( outlineWith ); +// textData.goText->setTextStyle(ts); + + if (! textData.goText->updateView()) + { + success = false; + ERROR_LOG(m_logger) << LOGFUNCTION + << " updateView failed" + << " debugInfo1='" << m_debugInfo << "'" + << " debugInfo2='" << textData.goText->debugInfo() << "'"; + } + + + } // while + + return success; +} + + + +bool BasicPathObjectImpl::init(MapImpl *mapImpl, + const QString &logicalLayerName, + GraphicalObjectPathType pathType, + double maxSegmentLength) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " logicalLayerName='" << logicalLayerName << "'" + << " pathType=" << pathType; + + // to create adequate preview: + m_isClosed = (pathType == GraphicalObjectPathType::Polygon); + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + if (success) + { + // create underlying object (to save most attributes only there) + if (!m_mainGraphicalObject) + { + // m_mainGraphicalObject = new GraphicalObjectMultiPath(pathType, + GraphicalObjectPath *goPath = new GraphicalObjectPath(pathType, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#GOPath"), + m_uniqueMapId); + goPath->setMaxSegmentLength(maxSegmentLength); + m_mainGraphicalObject = goPath; + } + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + m_tacticalOrigin.refValue().setMapObjectImpl(this); + m_detonationOrigin.refValue().setMapObjectImpl(this); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +double BasicPathObjectImpl::maxSegmentLength() const +{ + if (!m_mainGraphicalObject) + return -1; + GraphicalObjectPath *goPath = dynamic_cast(m_mainGraphicalObject); + if (!goPath) + return -1; + return goPath->maxSegmentLength(); +} + +bool BasicPathObjectImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo=" << debugInfo(); + + bool success = BaseClass::onMapExtentChanged(); + + bool successIndication = true; + if (m_indicationCont) + { + QElapsedTimer timer; + + if (m_logger.isDebugEnabled()) + timer.start(); + + successIndication = m_indicationCont->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo=" << m_debugInfo + << " Indication.updateView took[ms] " << timer.elapsed(); + } + bool successIndicationAbs = true; + if (m_indicationContSpAbsolute) + { + QElapsedTimer timer; + + if (m_logger.isDebugEnabled()) + timer.start(); + + successIndicationAbs = m_indicationContSpAbsolute->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo=" << m_debugInfo + << " IndicationSpAbsolute.updateView took[ms] " << timer.elapsed(); + } + + + + bool successPath1 = true; + if (m_pathCont_SpDraped) + { + QElapsedTimer timer; + + if (m_logger.isDebugEnabled()) + timer.start(); + + successPath1 = m_pathCont_SpDraped->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo=" << m_debugInfo + << " m_pathCont_SpDraped.updateView took[ms] " << timer.elapsed(); + } + + bool successPath2 = true; + if (m_pathCont_SpDrapedPolygon) + { + QElapsedTimer timer; + + if (m_logger.isDebugEnabled()) + timer.start(); + + successPath2 = m_pathCont_SpDrapedPolygon->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo=" << m_debugInfo + << " m_pathCont_SpDrapedPolygon.updateView took[ms] " << timer.elapsed(); + } + + bool successPathSpA1 = true; + if (m_pathCont_SpAbsolute1) + { + QElapsedTimer timer; + + if (m_logger.isDebugEnabled()) + timer.start(); + + successPathSpA1 = m_pathCont_SpAbsolute1->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo=" << m_debugInfo + << " m_pathCont_SpAbsolute1.updateView took[ms] " << timer.elapsed(); + } + bool successPathSpA2 = true; + if (m_pathCont_SpAbsolute2) + { + QElapsedTimer timer; + + if (m_logger.isDebugEnabled()) + timer.start(); + + successPathSpA2 = m_pathCont_SpAbsolute2->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo=" << m_debugInfo + << " m_pathCont_SpAbsolute2.updateView took[ms] " << timer.elapsed(); + } + + // to honor mapRotation: + updateArrowHead(m_arrowHeadStart, true); + updateArrowHead(m_arrowHeadEnd, false); + updateSegmentText(); + + success = success && successIndication && successIndicationAbs && successPath1 && successPath2 && successPathSpA1 && successPathSpA2; + + if (!success) + ERROR_LOG(m_logger) + << " successIndication=" << successIndication + << " successIndicationAbs=" << successIndicationAbs + << " successPath1=" << successPath1 + << " successPath2=" << successPath2 + << " successPathSpA1=" << successPathSpA1 + << " successPathSpA2=" << successPathSpA2; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.h new file mode 100755 index 000000000..3a6d98724 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/basic_path_object_impl.h @@ -0,0 +1,295 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of BasicPathObjectImpl + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "indication_container.h" +#include "path_container.h" +#include +#include +#include +#include + + +// Qt +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +class GraphicalObjectSceneMarker; + + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +/** + * @brief The BasicPathObjectImpl class combines a couple of usefull features required by path objects. + * + * These features are: + * - For convenience an origin and radius can also be parametrized. This will be transformed into a polygon during updateView(). + * - an arrowHead at the start and/or at the end of that path (both polyline and polygon) + * - m_arrowHeadStart, m_arrowHeadEnd of type ArrowHead / GraphicalObjectPictureMarker / Image + * + * - text positioned on the line segments + * - m_textDataCont of type TextData / GraphicalObjectText + * + * - an infinite number of different indication objects for displaying impacts, mines etc. + * - m_indicationCont of type IndicationContainer / GraphicalObjectPictureMarker / Image + * - m_pathCont of type PathContainer (on main Layer) + * - m_pathCont_SpAbsolute of type PathContainer (on subLayer with SurfacePlacementMode::Absolute) + */ +class SITUATIONMAPOBJECTS_EXPORT BasicPathObjectImpl : public AbstractMultiPointMapObjectImpl, public AbstractEffectSimulationImpl +{ + using BaseClass = AbstractMultiPointMapObjectImpl; + +public: + /*! \name Construction, Destruction */ + //@{ + + explicit BasicPathObjectImpl(const QString &debugInfo = QString()); + virtual ~BasicPathObjectImpl(); + + BasicPathObjectImpl(const BasicPathObjectImpl &other) = delete; + BasicPathObjectImpl &operator=(BasicPathObjectImpl &other) = delete; + //@} + + QString toString() const override; + + virtual Point3D tacticalOrigin() const; + virtual void setTacticalOrigin(const Point3D &tacticalOrigin); + + /*! \name Set origin and radius as alternative to 'setPositions' */ + //@{ + virtual Point3D origin() const; + virtual void setOrigin(const Point3D &origin); + virtual float radius() const; + virtual void setRadius(float radius, unsigned int maxPointCount = 0); + //@} + +#ifdef SUPPORT_SUBSIDIARY_LINES + /*! \name Subsidiary polyline/polygons */ + //@{ + /** + * @brief Get positions at pathIndex. + * @param pathIndex to be retrieved (if '0' identical to positions();) + * @return positions at index + */ + virtual PositionCont subsidiaryPositions(int pathIndex) const; + + /** + * @brief Set positions at pathIndex. + * @param pathIndex to be set. + * @param positions values to be set. + */ + virtual void setSubsidiaryPositions(int pathIndex, const PositionCont &positions); + + virtual QList subsidiaryPathIndexInUse(); + + + /** + * @brief Clear positions. + * @param pathIndex to be cleared. + * @note if not pathIndex is specified, ALL are cleared!) + */ + virtual void clearSubsidiaryPositions(int pathIndex); + + /** + * @brief Clear all subsidiaryPositions. + */ + virtual void clearSubsidiaryPositions(); + + //@} +#endif // SUPPORT_SUBSIDIARY_LINES + + + + GraphicalObjectPathType pathType() const; + + virtual int segmentCount() const; + + // Delegate to BaseClass: + virtual AesEffectState aesState() const override; + virtual void setAesState(AesEffectState aesState) override; + + + /*! \name Indication (for displaying impacts, mines etc.) */ + //@{ + virtual IndicationContainer &rIndicationContainer() const; + virtual IndicationContainer &rIndicationContainerAbsolute() const; + //@} + + /*! \name Path (for displaying paths like trajectories) */ + //@{ + virtual PathContainer &rPathContainer() const; + + virtual PathContainer &rPathContainerSpDrapedPolygon() const; + virtual void clearPathContainerSpDrapedPolygon(int idx); + + virtual PathContainer &rPathContainerSpAbsolute1() const; + + virtual PathContainer &rPathContainerSpAbsolute2() const; + //@} + + /*! \name ArrowHeads */ + //@{ + + //@} + + /*! \name ArrowHeads */ + //@{ + virtual bool arrowHeadStart() const; + virtual void setArrowHeadStart(bool enable); + + virtual bool arrowHeadEnd() const; + virtual void setArrowHeadEnd(bool enable); + + virtual void setArrowHeadsSelected(bool select); + //@} + + + /*! \name Single Detonation */ + //@{ + void setDetonation(const Point3D &origin, double radius); + bool hasDetonation() const; + void resetDetonation(); + //@} + + + /*! \name Text on lineSegments */ + //@{ + virtual QString textOnSegment(uint segment) const; + virtual void setTextOnSegment(uint segment, const QString &segmentText); + virtual void clearTextOnSegment(uint segment); + //@} + + + static QImage renderArrowHeadSymbol(const QColor &color, int length, int halfWidth); + +protected: + bool updateViewImplPost() override + { + BaseClass::updateViewImplPost(); + m_tacticalOrigin.reset(); + m_origin.reset(); + m_radius.reset(); + m_maxPointCount.reset(); + + // Single Detonation by SingleSceneMarker + m_detonationOrigin.reset(); + m_detonationRadius.reset(); + return true; + } + + + bool updateViewImpl() override; + + bool updateSingleSceneMarker(bool newVisibility); + + + friend class MapImpl; + virtual bool init(MapImpl *mapImpl, + const QString &logicalLayerName, + GraphicalObjectPathType pathType, + double maxSegmentLength = -1); + + double maxSegmentLength() const; + + bool onMapExtentChanged() override; + + // internal/admin + + // logical + SimpleValue m_tacticalOrigin; // has priority if set (related to mainGO->positions - SingleFire) + + // origin, radius as alternative to 'setPositions' : + SimpleValue m_origin; + SimpleValue m_radius = 0; + SimpleValue m_maxPointCount = 0; + // : origin, radius as alternative to 'setPositions' + + + // Indication : + mutable IndicationContainer *m_indicationCont{nullptr}; + mutable IndicationContainer *m_indicationContSpAbsolute{nullptr}; // SurfacePlacementMode::Absolute + // : Indication + + // Paths : + mutable PathContainer *m_pathCont_SpDraped{nullptr}; + mutable PathContainer *m_pathCont_SpDrapedPolygon{nullptr}; // SurfacePlacementMode::Draped + + mutable PathContainer *m_pathCont_SpAbsolute1{nullptr}; // SurfacePlacementMode::Absolute + mutable PathContainer *m_pathCont_SpAbsolute2{nullptr}; // SurfacePlacementMode::Absolute + // : Paths + + // Single Scene Marker + // (arbitrary geometry like detonations of nContamination makes it impossible to use renderer approach) + SimpleValue m_detonationOrigin; + SimpleValue m_detonationRadius{0.0}; + QSharedPointer m_detonationSceneMarker; // 3D + // : Single Scene Marker + + + // ArrowHeads : + struct ArrowHead + { + bool enable = false; + QSharedPointer pmArrowHead; + + }; + ArrowHead m_arrowHeadStart; + ArrowHead m_arrowHeadEnd; + int m_arrowHeadLengthHeight = 20; + int m_arrowHeadHalfWidth = 7; + + bool updateArrowHead(ArrowHead &arrowHead, bool start); + // : ArrowHeads + + // Text on line segments : + struct TextData + { + QString text; + QSharedPointer goText; + }; + + QMap< uint, TextData > m_textDataCont; + + bool updateSegmentText(); + + // : Text on line segments + + +#ifdef SUPPORT_SUBSIDIARY_LINES + QMap m_subsidiaryPositionsCont; +#endif // SUPPORT_SUBSIDIARY_LINES + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + +// Under special circumstances we have to instanciate BasicPathObjectImpl. +// Think of subsidiaryliens at VirtualMines. +// To to so, create 'LocalBasicPathObject' + +// to get access to 'init' +class LocalBasicPathObject final : public BasicPathObjectImpl +{ +public: + using BasicPathObjectImpl::BasicPathObjectImpl; + using BasicPathObjectImpl::init; + using BasicPathObjectImpl::setUniqueId; +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.cpp new file mode 100755 index 000000000..2a8a3c37f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.cpp @@ -0,0 +1,156 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of BiologicalAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "biological_agent_impl.h" +#include +#include +#include +#include + +#include + +// ESRI + + +// Qt + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class BiologicalAgentImpl +//----------------------------------------------------------------------------- +BiologicalAgentImpl::BiologicalAgentImpl(const QString &debugInfo) + : BiologicalAgent() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("BiologicalAgentImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_lineColorFromParty = false; + m_fillColorAlpha = 200; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +BiologicalAgentImpl::~BiologicalAgentImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +QString BiologicalAgentImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + str.append(BasicPathObjectImpl::toString()); + return str; +} + + +Point3D BiologicalAgentImpl::origin() const +{ + return BaseClass::origin(); +} +void BiologicalAgentImpl::setOrigin(const Point3D &origin) +{ + BaseClass::setOrigin(origin); +} +float BiologicalAgentImpl::radius() const +{ + return BaseClass::radius(); +} + +void BiologicalAgentImpl::setRadius(float radius) +{ + BaseClass::setRadius(radius); +} + + +AesEffectState BiologicalAgentImpl::aesState() const +{ + return BaseClass::aesState(); +} +void BiologicalAgentImpl::setAesState(AesEffectState aesState) +{ + BaseClass::setAesState(aesState); +} + +SymbolImage BiologicalAgentImpl::symbolImage() const +{ + SymbolImage symImage = m_tacticalAppearance->symbolImage(); + return symImage; +} + + + +bool BiologicalAgentImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " origin =" << this->origin() //.x() << ", " << this->origin().y() + << " radius =" << this->radius() + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool BiologicalAgentImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500002717000000"); // SymbolSet = 25 271700 + // ^^ ^^^^^^ + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + // m_symbol.refValue().m_sidc = QLatin1String("GUMPNB----"); // 0953 GUMPNB---- -> Polygon -> Image WITH Area // small icon and big border + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFMPNEB---"); // 0081 GFMPNEB--- -> Point -> Image WITHOUT Area // not 'centered' + break; + case AdvancedSymbology::SymbologyType::APP6B: + // m_symbol.refValue().m_sidc = QLatin1String("GHCPBWC---"); // APP6B: 3937 GHCPBWC--- -> Polygon + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUCPBWK---"); // APP6B: 2875 GUCPBWK--- -> Point + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + + } // if (mapImpl) + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + setLineStyle(LineStyle(Qt::yellow, 2)); + setFillStyle(FillStyle(Qt::yellow, FillPattern::Null)); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.h new file mode 100755 index 000000000..e6a34cc52 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/biological_agent_impl.h @@ -0,0 +1,83 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of BiologicalAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The BiologicalAgentImpl class can be described by an origin and a radius. + * \note Maybe this will be changed in future to be consistent to the ChemicalAgent. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath/Polyline, displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT BiologicalAgentImpl : public BasicPathObjectImpl, public virtual BiologicalAgent +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit BiologicalAgentImpl(const QString &debugInfo = QString()); + virtual ~BiologicalAgentImpl(); + +public: + BiologicalAgentImpl(const BiologicalAgentImpl &other) = delete; + BiologicalAgentImpl &operator=(BiologicalAgentImpl &other) = delete; + + QString toString() const override; + + + // some set/get methods: + Point3D origin() const override; + void setOrigin(const Point3D &origin) override; + float radius() const override; + void setRadius(float radius) override; + + + // Delegate to BaseClass: + AesEffectState aesState() const override; + void setAesState(AesEffectState aesState) override; + + SymbolImage symbolImage() const override; + + +protected: + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.cpp new file mode 100755 index 000000000..bb76b4827 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.cpp @@ -0,0 +1,269 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of BuildingOutline + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "building_outline_impl.h" +#include +#include +#include + +#include "Map/object_factory.h" + +#include + +// ESRI + + +// Qt + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class BuildingOutlineImpl +//----------------------------------------------------------------------------- +BuildingOutlineImpl::BuildingOutlineImpl(const QString &debugInfo) + : BuildingOutline() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("BuildingOutlineImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_markerOrigin.setMapObjectImpl(this); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +BuildingOutlineImpl::~BuildingOutlineImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete m_marker; + delete m_modelSceneMarker; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +PositionCont BuildingOutlineImpl::positions() const +{ + return BaseClass::positions(); +} + +bool BuildingOutlineImpl::setPositions(const PositionCont &posCont) +{ + return BaseClass::setPositions(posCont); +} + +QColor BuildingOutlineImpl::majorColor() const +{ + if (m_forcedMajorColor.isValid()) + return m_forcedMajorColor; + // use fillColor here: + return (m_mainGraphicalObject) ? m_mainGraphicalObject->fillStyle().fillColor() : BaseClass::majorColor(); +} + + + +LineStyle BuildingOutlineImpl::lineStyle() const +{ + return BaseClass::lineStyle(); +} +LineStyle &BuildingOutlineImpl::rLineStyle() +{ + return BaseClass::rLineStyle(); +} +void BuildingOutlineImpl::setLineStyle(const LineStyle &lineStyle) +{ + BaseClass::setLineStyle(lineStyle); +} +FillStyle BuildingOutlineImpl::fillStyle() const +{ + return BaseClass::fillStyle(); +} +FillStyle &BuildingOutlineImpl::rFillStyle() +{ + return BaseClass::rFillStyle(); +} +void BuildingOutlineImpl::setFillStyle(const FillStyle &fillStyle) +{ + BaseClass::setFillStyle(fillStyle); +} + +void BuildingOutlineImpl::setMarker(const Point3D &markerOrigin) +{ + m_markerOrigin.setOrigin(markerOrigin); +} + +BuildingOutline::ModelSceneSymbolData BuildingOutlineImpl::getModelSceneSymbolData() +{ + return m_modelSceneSymolData; +} + +void BuildingOutlineImpl::setModelSceneSymbolData(const BuildingOutline::ModelSceneSymbolData &modelSceneSymbolData) +{ + m_modelSceneSymolData = modelSceneSymbolData; +} + +bool BuildingOutlineImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical + << " scale=" << m_mainGraphicalObject->scale() + << " posCont=" << m_positions.positions() + << " url=" << m_modelSceneSymolData.url.toString() + << " scale=" << m_modelSceneSymolData.scale + << " m_heading=" << m_modelSceneSymolData.heading; + + bool success = BaseClass::updateViewImpl(); + updateViewImplMarker(); + + if (m_mapImpl->mode() == Mode::Mode_3D) + updateModelSceneSymbol(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool BuildingOutlineImpl::updateViewImplMarker() +{ + if (m_markerOrigin.isEmpty()) + { + delete m_marker; + m_marker = nullptr; + } + else + { + if (!m_marker) + { + m_marker = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#Marker"), + m_uniqueMapId); + } + const float scale = m_mainGraphicalObject->scale() * m_mainGraphicalObject->lineStyle().lineWidth() * 0.5f; + + m_marker->setMarkerSymbol(MarkerSymbolType::Triangle, qRound(5.0f * scale), m_mainGraphicalObject->rLineStyle().lineColor()); + m_marker->setPickable(m_mainGraphicalObject->pickable()); + m_marker->setDrawOrder(m_internalDrawOrder+1); + m_marker->setOrigin(m_markerOrigin.originTranslated()); + m_marker->setVisibility(m_visibility && m_visibilityTechnical); + + if (!m_marker->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" + << " updateView failed"; + } + + return true; +} + +bool BuildingOutlineImpl::updateModelSceneSymbol() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << m_debugInfo << "'" + << " url=" << m_modelSceneSymolData.url.toString() + << " scale=" << m_modelSceneSymolData.scale + << " m_heading=" << m_modelSceneSymolData.heading; + + bool success = false; + + if (m_markerOrigin.isEmpty()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no markerOrigin" + << " debugInfo='" << m_debugInfo << "'"; + return false; + } + + if (m_modelSceneSymolData.url.isEmpty()) + { + delete m_modelSceneMarker; + m_modelSceneMarker= nullptr; + } else { + if (!m_modelSceneMarker) + m_modelSceneMarker = new GraphicalObjectModelSceneMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#ModelSceneMarker"), + m_uniqueMapId); + // Symbol: + m_modelSceneMarker->setUrl(m_modelSceneSymolData.url); + m_modelSceneMarker->setOrigin(m_markerOrigin.originTranslated()); + m_modelSceneMarker->setHeading(m_modelSceneSymolData.heading); + m_modelSceneMarker->setScale(m_modelSceneSymolData.scale); // use scale of m_modelSceneSymolData, not of m_mainGraphicalObject + m_modelSceneMarker->setVisibility(m_visibility && m_visibilityTechnical); + success = m_modelSceneMarker->updateView(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed" + << " debugInfo='" << m_debugInfo << "'"; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << m_debugInfo << "'"; + return success; +} + + +bool BuildingOutlineImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10042500002701000000"); // SymbolSet = 25 270100 + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "G*MPOGZ---****X"); + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFCPBOGB--"); // APP6B: 3845: GFCPBOGB-- -> Polygon + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::black, 2)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +void BuildingOutlineImpl::updateIdentifierPosition() +{ + if (m_positions.isEmpty()) + return; + + BoundingBox bbox; + this->boundingBox(bbox); + + if (!bbox.isValid()) + return BaseClass::updateIdentifierPosition(); + + OriginTranslated objCenterPoint; + objCenterPoint.setMapObjectImpl(this); + objCenterPoint.setOrigin(bbox.center()); + + m_identifierPositionCandidateTranslated = objCenterPoint.originTranslated(); +} + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.h new file mode 100755 index 000000000..6ce1276fd --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/building_outline_impl.h @@ -0,0 +1,94 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of BuildingOutline + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "basic_path_object_impl.h" +#include // Interface +#include + +// Qt +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The BuildingOutlineImpl class displays the outline of a building. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT BuildingOutlineImpl : public BasicPathObjectImpl, public virtual BuildingOutline +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit BuildingOutlineImpl(const QString &debugInfo = QString()); + virtual ~BuildingOutlineImpl(); + +public: + BuildingOutlineImpl(const BuildingOutlineImpl &other) = delete; + BuildingOutlineImpl &operator=(BuildingOutlineImpl &other) = delete; + + PositionCont positions() const override; + bool setPositions(const PositionCont &posCont) override; + + QColor majorColor() const override; + + LineStyle lineStyle() const override; + LineStyle &rLineStyle() override; + void setLineStyle(const LineStyle &lineStyle) override; + + FillStyle fillStyle() const override; + FillStyle &rFillStyle() override; + void setFillStyle(const FillStyle &fillStyle) override; + + void setMarker(const Point3D &markerOrigin) override; + + ModelSceneSymbolData getModelSceneSymbolData() override; + void setModelSceneSymbolData(const ModelSceneSymbolData &modelSceneSymbolData) override; + +protected: + bool updateViewImpl() override; + bool updateViewImplMarker(); + bool updateModelSceneSymbol(); + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + void updateIdentifierPosition() override; + + // logical + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + GraphicalObjectPictureMarker *m_marker{nullptr}; + OriginTranslated m_markerOrigin; + + ModelSceneSymbolData m_modelSceneSymolData; + GraphicalObjectModelSceneMarker *m_modelSceneMarker{nullptr}; +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.cpp new file mode 100755 index 000000000..b46a8c293 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.cpp @@ -0,0 +1,129 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of ChemicalAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "chemical_agent_impl.h" +#include +#include +#include + +#include + +// ESRI + + +// Qt + + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class ChemicalAgentImpl +//----------------------------------------------------------------------------- +ChemicalAgentImpl::ChemicalAgentImpl(const QString &debugInfo) + : ChemicalAgent() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("ChemicalAgentImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_lineColorFromParty = false; + m_fillColorAlpha = 200; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +ChemicalAgentImpl::~ChemicalAgentImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +Point3D ChemicalAgentImpl::origin() const +{ + return BaseClass::origin(); +} +void ChemicalAgentImpl::setOrigin(const Point3D &origin) +{ + BaseClass::setOrigin(origin); +} +float ChemicalAgentImpl::radius() const +{ + return BaseClass::radius(); +} + +void ChemicalAgentImpl::setRadius(float radius, unsigned int maxPointCount) +{ + BaseClass::setRadius(radius, maxPointCount); +} + + +bool ChemicalAgentImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool ChemicalAgentImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500002718000000"); // SymbolSet = 25 271800 + // ^^ ^^^^^^ + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUMPNC----"); // 0985 GUMPNC---- -> Polygon + // m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GFMPNEC---"); // MIL2525D: 0115: GFMPNEC--- "Chemical F"s + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUCPBWH---"); // APP6B: 3943 GUCPBWH--- -> Polygon + // m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUCPBWK---"); // APP6B: 2879: GUCPBWK--- -> Point "Chemical Attack, Release Event U" + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::yellow, 2)); + setFillStyle(FillStyle(Qt::yellow, FillPattern::Null)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.h new file mode 100755 index 000000000..535844fa8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/chemical_agent_impl.h @@ -0,0 +1,78 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of ChemicalAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include +#include + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The ChemicalAgentImpl class can be described by an unlimited number of positions (PositionCont). + * For convenience an origin and radius can also be parametrized (inherented from BasicPathObjectImpl) + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + * - an infinite number indication objects for displaying impacts (inherented from BasicPathObjectImpl). + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT ChemicalAgentImpl : public BasicPathObjectImpl, public virtual ChemicalAgent +{ + using BaseClass = SituationMap::BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit ChemicalAgentImpl(const QString &debugInfo = QString()); + virtual ~ChemicalAgentImpl(); + +public: + ChemicalAgentImpl(const ChemicalAgentImpl &other) = delete; + ChemicalAgentImpl &operator=(ChemicalAgentImpl &other) = delete; + + Point3D origin() const override; + void setOrigin(const Point3D &origin) override; + float radius() const override; + void setRadius(float radius, unsigned int maxPointCount = 0) override; + + + +protected: + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + + +private: + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.cpp new file mode 100755 index 000000000..d3dd02397 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.cpp @@ -0,0 +1,166 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "cube_impl.h" +#include +#include +#include +#include + + + +// ESRI +#include +#include +#include + + +// Qt + + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class CubeImpl +//----------------------------------------------------------------------------- +CubeImpl::CubeImpl(const QString &debugInfo) + : Cube() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("CubeImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +CubeImpl::~CubeImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +double CubeImpl::height() const +{ + return m_height; +} +void CubeImpl::setHeight(double height) +{ + m_height = height; +} + + +bool CubeImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + // pre-condition + Esri::ArcGISRuntime::Renderer *curRenderer = nullptr; + if (m_graphicsLayer) + curRenderer = m_graphicsLayer->renderer(); + if (!curRenderer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no ArcGISRuntime::Renderer, layername='" << m_logicalLayerName << "'"; + return false; + } + + + // Destroy if invisible : + // 13.09.2018, Norbert Thoden + // Regarding ESRI Case #02170796 - [BOS] Crash with Destruction of 3D Scene + // To reduce likelihood of crash: + // Destroy GraphicalObject (GO, m_mainGraphicalObject) if it becomes invisible + // With V100.3.0 the behaviour does not change. + // (switching cubes for 180 bldgs on and off) + // + if (m_visibility) + { + if (!m_mainGraphicalObject) + { + m_mainGraphicalObject = new GraphicalObjectCube(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QStringLiteral("#GOCube"), + m_uniqueMapId); + } + } else { + delete m_mainGraphicalObject; + m_mainGraphicalObject = nullptr; + return true; + } + // : Destroy if invisible + + + GraphicalObjectCube *goCube = static_cast(m_mainGraphicalObject); + if (goCube) + { + // in former times: partyName was used + // it´s replaced by more flexible ExtrusionColorName: + // const QString &partyName = m_mapImpl->partyIdToColorFunctor().partyIdToName(m_party.partyId.value()); + // goCube->setPartyName(partyName); + + const QColor &partyColor= m_mapImpl->partyIdToColorFunctor().partyIdToColor(m_party.partyId.value()); + if (m_party.partyId.value() > -2) //Use Default: White or TeamColor + { + goCube->setExtrusionColorName( partyColor.name() ); + } + else // Use given Color //need to be prepared in layerfactory! + { + goCube->setExtrusionColorName( majorColor().name() ); + } + + goCube->setPositions(m_positions.positionsTranslated()); + goCube->setHeight(m_height); + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_mainGraphicalObject"; + return false; + } + + + bool success = BaseClass::updateViewImpl(); + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool CubeImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + // create underlying object (to save most attributes only there) + if (!m_mainGraphicalObject) + { + m_mainGraphicalObject = new GraphicalObjectCube(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QStringLiteral("#GOCube"), + m_uniqueMapId); + //this MapObject is not very typical, therefore set some attributes: + this->setPickable(false); + this->setScaleEnable(false); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return success; +} + + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.h new file mode 100755 index 000000000..e2d1b4c51 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/cube_impl.h @@ -0,0 +1,76 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// ESRI +#include + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The CubeImpl class a cube if the UniqueValueRenderer is set. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance, the cube) + * - mainGraphicalObjectText not used + * - tacticalAppearance not used + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT CubeImpl : public AbstractMultiPointMapObjectImpl, public virtual Cube +{ + using BaseClass = AbstractMultiPointMapObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit CubeImpl(const QString &debugInfo = QString()); + virtual ~CubeImpl(); + +public: + CubeImpl(const CubeImpl &other) = delete; + CubeImpl &operator=(CubeImpl &other) = delete; + + double height() const override; + void setHeight(double height) override; + + +protected: + bool updateViewImpl() override; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + + // logical + + +private: + double m_height; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.cpp new file mode 100755 index 000000000..cf0d6042f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.cpp @@ -0,0 +1,105 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "danger_area_impl.h" +#include +#include +#include + +#include + +// ESRI + + +// Qt + + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class DangerAreaImpl +//----------------------------------------------------------------------------- +DangerAreaImpl::DangerAreaImpl(const QString &debugInfo) + : DangerArea() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("DangerAreaImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +DangerAreaImpl::~DangerAreaImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool DangerAreaImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool DangerAreaImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500001511000000"); // SymbolSet = 25 151100 + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUMPNC----"); // Image WITH Area // small icon and big border + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUCPMGASM-"); // APP6B: 3556: GUCPMGASM- -> Polygon +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUCPBWC---"); // APP6B: 3939: GUCPBWC---- -> Polygon + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::yellow, 2)); + setFillStyle(FillStyle(Qt::yellow, FillPattern::Null)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.h new file mode 100755 index 000000000..12d7a7c76 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/danger_area_impl.h @@ -0,0 +1,68 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The DangerAreaImpl class displays an area of undefined danger. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT DangerAreaImpl : public BasicPathObjectImpl, public virtual DangerArea +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit DangerAreaImpl(const QString &debugInfo = QString()); + virtual ~DangerAreaImpl(); + +public: + DangerAreaImpl(const DangerAreaImpl &other) = delete; + DangerAreaImpl &operator=(DangerAreaImpl &other) = delete; + +protected: + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + + +private: + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.cpp new file mode 100755 index 000000000..e96844ff7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.cpp @@ -0,0 +1,179 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of FunctionalArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "functional_area_impl.h" +#include +#include +#include +#include + + +// ESRI + + +// Qt + + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class FunctionalAreaImpl +//----------------------------------------------------------------------------- +FunctionalAreaImpl::FunctionalAreaImpl(const QString &debugInfo) + : FunctionalArea() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("FunctionalAreaImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_pointSymbolData.origin.setMapObjectImpl(this); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +FunctionalAreaImpl::~FunctionalAreaImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete m_pointSymbol; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void FunctionalAreaImpl::reRenderSymbol() +{ + BaseClass::reRenderSymbol(); + if (m_pointSymbol) + m_pointSymbol->reRenderSymbol(); +} + +Point3D FunctionalAreaImpl::pointSymbolOrigin() const +{ + return m_pointSymbolData.origin.origin(); +} +void FunctionalAreaImpl::setPointSymbolOrigin(const Point3D &pointSymbolOrigin) +{ + m_pointSymbolData.origin.setOrigin(pointSymbolOrigin); +} + +const QString &FunctionalAreaImpl::pointSymbolSidc() const +{ + return m_pointSymbolData.symbolData.sidc; +} +void FunctionalAreaImpl::setPointSymbolSidc(const QString &pointSymbolOriginSidc) +{ + m_pointSymbolData.symbolData.sidc = pointSymbolOriginSidc; +} + + +bool FunctionalAreaImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + + if (!updateViewImplPointSymbolData()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImplPointSymbolData failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool FunctionalAreaImpl::updateViewImplPointSymbolData() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // sidc empty? + if (m_pointSymbolData.symbolData.sidc.isEmpty()) + { + delete m_pointSymbol; + m_pointSymbol = nullptr; + return true; + } + + // ok, sidc available: + if (!m_pointSymbol) + { + m_pointSymbol = new ParticipantImpl(m_debugInfo + QStringLiteral("#PntSymblTactApp")); + m_pointSymbol->init(m_mapImpl, m_logicalLayerName); // use same layer as mainGraphicalObj/TECHN. Appearance + m_pointSymbol->setUniqueId(m_uniqueMapId); + } + + if (m_party.partyId.changed()) + m_pointSymbol->setPartyIdChanged(m_party.partyId.value()); + + + // The drawOrder should be above the m_mainGraphicalObject (==Path) + m_pointSymbol->setDrawOrder(this->drawOrder() + 1); // since m_pointSymbol is a participant, use drawOrder instead of m_internalDrawOrder + + AbstractMapObjectImpl *amoImp = static_cast(m_pointSymbol); + amoImp->setVisibility((m_mainGraphicalObject) ? m_mainGraphicalObject->visibility() : false); + + m_pointSymbol->setOrigin(m_pointSymbolData.origin.origin()); + + m_pointSymbol->setPartyId(this->partyId()); + +// m_pointSymbolData.symbolData.color = this->partyColor(); + +// return m_mapImpl->partyIdToColorFunctor().partyIdToRole(m_party.partyId.value(), identityAssumed); + +// AdvancedSymbology::Role role = this->partyRole(); +// SymbolData symbolData = m_pointSymbolData.symbolData; +// symbolData = m_pointSymbolData.symbolData; + + m_pointSymbol->setSymbol(m_pointSymbolData.symbolData); + + m_pointSymbol->setScale(this->scale()); + + // add m_userSymbolOpacity??? + + bool success = m_pointSymbol->updateView(); + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed, m_debugInfo =" << m_debugInfo; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool FunctionalAreaImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + +// if (mapImpl) +// { +// switch (mapImpl->symbologyType()) { +// case AdvancedSymbology::SymbologyType::MIL2525C: +// m_symbol.refValue().sidc = QLatin1String("GUMPNC----"); // Image WITH Area // small icon and big border +// break; +// case AdvancedSymbology::SymbologyType::APP6B: +// m_symbol.refValue().sidc = QLatin1String("GUCPMGASM-"); // APP6B: 3556: GUCPMGASM- -> Polygon +// break; +// } // switch +// } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::black, 2)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.h new file mode 100755 index 000000000..74424ea53 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/functional_area_impl.h @@ -0,0 +1,97 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of FunctionalArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward: +class ParticipantImpl; + + +/** + * @brief The FunctionalAreaImpl class displays an functional area (e.g. medical area). + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT FunctionalAreaImpl : public BasicPathObjectImpl, public virtual FunctionalArea +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit FunctionalAreaImpl(const QString &debugInfo = QString()); + virtual ~FunctionalAreaImpl(); + +public: + FunctionalAreaImpl(const FunctionalAreaImpl &other) = delete; + FunctionalAreaImpl &operator=(FunctionalAreaImpl &other) = delete; + + void reRenderSymbol() override; + + /*! \name Point symbol */ + //@{ + Point3D pointSymbolOrigin() const override; + void setPointSymbolOrigin(const Point3D &pointSymbolOrigin) override; + + const QString &pointSymbolSidc() const override; + void setPointSymbolSidc(const QString &pointSymbolOriginSidc) override; + //@} + + +protected: + bool updateViewImpl() override; + + bool updateViewImplPointSymbolData(); + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + + +private: + + // logical + struct PointSymbolData + { + OriginTranslated origin; + SymbolData symbolData; + }; + + PointSymbolData m_pointSymbolData; + ParticipantImpl *m_pointSymbol = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.cpp new file mode 100755 index 000000000..88ffbf696 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.cpp @@ -0,0 +1,371 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of GeoImage + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "geo_image_impl.h" +#include +#include +#include + + +// ESRI + + +// Qt +#include +#include +#include +#include +#include + + +namespace SituationMap +{ + + +class GeoImageImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + + // Own data: + SimpleValue m_geoImageData; + QSharedPointer m_gopmPin; + + +}; // class GeoImageImpl::Private +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// class GeoImageImpl +//----------------------------------------------------------------------------- +GeoImageImpl::GeoImageImpl(const QString &debugInfo) + : GeoImage() // Interface + , BaseClass(debugInfo) // Implementation + , d(new Private) + , m_logger(getLogger("GeoImageImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +GeoImageImpl::~GeoImageImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +QString GeoImageImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + str.append(d->m_geoImageData.value().toString()); + return str; +} + +GeoImage::GeoImageData GeoImageImpl::geoImageData() const { return d->m_geoImageData.value(); } +void GeoImageImpl::setGeoImageData(const GeoImageData &geoImageData) { d->m_geoImageData = geoImageData; } + +MarkerSymbolType GeoImageImpl::proxyMarker() const { return d->m_geoImageData.value().proxyMarker; } + +void GeoImageImpl::setProxyMarker(const MarkerSymbolType &proxyMarker) +{ + if (d->m_geoImageData.value().proxyMarker != proxyMarker) + d->m_geoImageData.refValue().proxyMarker = proxyMarker; +} + +int GeoImageImpl::proxyMarkerSize() const { return d->m_geoImageData.value().proxyMarkerSize; } +void GeoImageImpl::setProxyMarkerSize(int proxyMarkerSize) +{ + if (d->m_geoImageData.value().proxyMarkerSize != proxyMarkerSize) + d->m_geoImageData.refValue().proxyMarkerSize = proxyMarkerSize; +} + +QColor GeoImageImpl::proxyMarkerColor() const { return d->m_geoImageData.value().proxyMarkerColor; } +void GeoImageImpl::setProxyMarkerColor(const QColor &proxyMarkerColor) +{ + if (d->m_geoImageData.value().proxyMarkerColor != proxyMarkerColor) + d->m_geoImageData.refValue().proxyMarkerColor = proxyMarkerColor; +} + + +QImage GeoImageImpl::proxyImage() const { return d->m_geoImageData.value().proxyImage; } +void GeoImageImpl::setProxyImage(const QImage &proxyImage, const QSize &proxyImageDefaultSize) +{ + if (d->m_geoImageData.value().proxyImage != proxyImage) + d->m_geoImageData.refValue().proxyImage = proxyImage; + + if (d->m_geoImageData.value().proxyImageDefaultSize != proxyImageDefaultSize) + d->m_geoImageData.refValue().proxyImageDefaultSize = proxyImageDefaultSize; +} +QSize GeoImageImpl::proxyImageDefaultSize() const { return d->m_geoImageData.value().proxyImageDefaultSize; } + + +QImage GeoImageImpl::mainImage() const { return d->m_geoImageData.value().mainImage; } + +void GeoImageImpl::setMainImage(const QImage &mainImage) +{ + if (d->m_geoImageData.value().mainImage != mainImage) + d->m_geoImageData.refValue().mainImage = mainImage; +} + +QImage GeoImageImpl::previewImageTypical() const +{ + if (!d->m_geoImageData.value().proxyImage.isNull()) + return d->m_geoImageData.value().proxyImage; + if (!d->m_geoImageData.value().mainImage.isNull()) + return d->m_geoImageData.value().mainImage; + return QImage(); +} + +QString GeoImageImpl::description() const { return d->m_geoImageData.value().description; } + +void GeoImageImpl::setDescription(const QString &description) +{ + if (d->m_geoImageData.value().description != description) + d->m_geoImageData.refValue().description = description; +} + +void GeoImageImpl::setPinEnabled(bool enablePin) +{ + if (d->m_geoImageData.value().pinEnabled != enablePin) + d->m_geoImageData.refValue().pinEnabled = enablePin; +} +bool GeoImageImpl::isPinEnabled() const { return d->m_geoImageData.value().pinEnabled; } + + +QWidget *GeoImageImpl::createWidget(QWidget *parent) const +{ + const GeoImageData &geoImageData = d->m_geoImageData.value(); + + if (geoImageData.mainImage.isNull()) + return nullptr; + + QWidget *widget = new QWidget(parent); + widget->setLayout(new QVBoxLayout()); + + QLabel *img = new QLabel(); + img->setPixmap(QPixmap::fromImage(geoImageData.mainImage)); + widget->layout()->addWidget(img); + + widget->layout()->addWidget(new QLabel(geoImageData.description)); + + + // CloseButton + QPushButton *pbClose = new QPushButton(QLatin1String("Close")); + widget->layout()->addWidget(pbClose); + + pbClose->connect(pbClose, &QPushButton::clicked, widget, [widget]() + { + widget->deleteLater(); + }); + + return widget; +} + + +bool GeoImageImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " origin =" << this->origin() //.x() << ", " << this->origin().y() + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + if (!m_graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no graphicsLayer"; + return false; + } + + if (!m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_mainGraphicalObject"; + return false; + } + + // Marker: + GraphicalObjectPictureMarker *pMarker = static_cast(m_mainGraphicalObject); + + if (d->m_geoImageData.changed()) + { + const GeoImageData &geoImageData = d->m_geoImageData.value(); + + if (geoImageData.proxyImage.isNull()) + { + if (geoImageData.proxyMarker == MarkerSymbolType::Undefined) + pMarker->setMarkerSymbol(MarkerSymbolType::Diamond, 20, Qt::red); + else + pMarker->setMarkerSymbol(geoImageData.proxyMarker, + geoImageData.proxyMarkerSize, + geoImageData.proxyMarkerColor); + } + else + pMarker->setImage(geoImageData.proxyImage, geoImageData.proxyImageDefaultSize); + + // Pin + if (m_mapImpl->mode() == Mode::Mode_3D) + { + if (geoImageData.pinEnabled) + createPin(); + else + resetPin(); + } + } + + updateIdentifierPosition(); + + pMarker->setOrigin(this->originTranslated()); + + + bool success = BaseClass::updateViewImpl(); + + // reset changed: + d->m_geoImageData.reset(); + + onMapExtentChanged(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool GeoImageImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + if (success) + { + // create underlying object (to save most attributes only there) + if (!m_mainGraphicalObject) + m_mainGraphicalObject = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#Image"), + m_uniqueMapId); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool GeoImageImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << " onMapExtentChanged"; + + if (m_graphicalObjectPin && m_mainGraphicalObject) // m_graphicalObjectPin exists only at Mode_3D + { + if (!d->m_gopmPin) + { + d->m_gopmPin = QSharedPointer + (new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , m_debugInfo + QStringLiteral("#PictureMarkerPin") + , m_uniqueMapId) + ); + + GraphicalObjectPictureMarker *pMarker = static_cast(m_mainGraphicalObject); + if (pMarker) + { + const GeoImageData &geoImageData = d->m_geoImageData.value(); + d->m_gopmPin->setImage(pMarker->image(), geoImageData.proxyImageDefaultSize); + } + } + + d->m_gopmPin->setScale(m_mainGraphicalObject->scale()); + + Point3D originTranslated = this->originTranslated(); + DEBUG_LOG(m_logger) << " XXX originTranslatedORG = " << originTranslated; + + // GUEZE-2620 3D Sicht SOS Symbole + // Bisher wurde die Höhe des SOS-Images und -Textes quasi getuned, wobei ja nun + // unter bestimmten Umständen leider zu einem Höhenwert ausserhalb des + // Sichtfensters kam. + // Daher: + // - Pflicht: SOS-Symbol und Text auf den Boden + // - Kuer: Zudem versucht die SW, eine zweite Instanz des Images + // (inkl. Verbindungslinie) zu positionieren. + // Und zwar grob mittig zw. Erdoberfläche und oberen Kante des Sichtbereichs. + // Leider funktioniert des dafür genutzte ESRI-Befehl nicht immer zuverlässig. + // Meist dann, wenn sich der Augenpunkt (ViewPoint) dicht am Objekt befinden... + // Noch ein bisschen Tuning, aber es verbleiben leider Fehlersituationen. + + if (m_mapImpl->scale() > 1500) + { + const double zVal = m_mapImpl->scale() * m_mapImpl->scale() * 0.0001; + DEBUG_LOG(m_logger) << " zVal = " << zVal; + + originTranslated.rz() = zVal; + + // 1 ... 10000 (qBound ~ std::clamp) + originTranslated.rz() = qBound(1.0, originTranslated.rz(), 10000.0); + + // check viewPort: + // Note: unfortunately its dows not work perfect :-( + int cnt = 0; + static int s_counterMax = 10; + while (m_mapImpl->mapCoordinateToViewPortWindowPoint3D(originTranslated).isNull() && (originTranslated.rz() > 1) && (cnt <= s_counterMax)) + { + originTranslated.rz() /= 2.0; + ++cnt; + } + + if (cnt == s_counterMax) + originTranslated.rz() = 0; + else + originTranslated.rz() *= 0.5; // try to show in the middle between baseSurface and upper edge of window + + d->m_gopmPin->setVisibility(true); + + } else { + // GUEZE-3460 - SOS Meldungen werden nicht korrekt angezeigt + if (m_mapImpl->scale() < 500) + originTranslated.rz() = 1.0; // meter + else if (m_mapImpl->scale() < 1000) + originTranslated.rz() = 2.5; // meter + else + originTranslated.rz() = 10.0; // meter + + d->m_gopmPin->setVisibility(true); + } + + DEBUG_LOG(m_logger) << " originTranslated = " << originTranslated; + + d->m_gopmPin->setOrigin(originTranslated); + if (!d->m_gopmPin->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed" << m_mainGraphicalObject->debugInfo(); + + // update m_identifierPositionCandidateTranslated , + // it will be transfered to m_mainGraphicalObject via BaseClass::onMapExtentChanged() + // m_identifierPositionCandidateTranslated = originTranslated; + + Point3D centerEarth = originTranslated; + centerEarth.rz() = -100; // hopefully lower than lowest level of training area + m_graphicalObjectPin->setPositions({originTranslated, centerEarth}); + + m_graphicalObjectPin->setVisibility(d->m_gopmPin->visibility()); + + if (!m_graphicalObjectPin->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateView failed" << m_graphicalObjectPin->debugInfo(); + + } // if (m_graphicalObjectPin && m_mainGraphicalObject) + + return BaseClass::onMapExtentChanged(); +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.h new file mode 100755 index 000000000..082cd59d1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/geo_image_impl.h @@ -0,0 +1,101 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of GeoImage + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_single_point_map_object_impl.h" +#include // Interface + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT GeoImageImpl : public AbstractSinglePointMapObjectImpl, public virtual GeoImage +{ + using BaseClass = AbstractSinglePointMapObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit GeoImageImpl(const QString &debugInfo = QString()); + virtual ~GeoImageImpl(); + +public: + + GeoImageImpl(const GeoImageImpl &other) = delete; + GeoImageImpl &operator=(GeoImageImpl &other) = delete; + + QString toString() const override; + + + GeoImageData geoImageData() const override; + void setGeoImageData(const GeoImageData &geoImageData) override; + + + MarkerSymbolType proxyMarker() const override; + void setProxyMarker(const MarkerSymbolType &proxyMarker) override; + + int proxyMarkerSize() const override; + void setProxyMarkerSize(int proxyMarkerSize) override; + + QColor proxyMarkerColor() const override; + void setProxyMarkerColor(const QColor &proxyMarkerColor) override; + + QImage proxyImage() const override; + QSize proxyImageDefaultSize() const override; + + void setProxyImage(const QImage &proxyImage, const QSize &proxyImageDefaultSize) override; + + + QImage mainImage() const override; + void setMainImage(const QImage &mainImage) override; + + QImage previewImageTypical() const override; + + + QString description() const override; + void setDescription(const QString &description) override; + + void setPinEnabled(bool enablePin) override; + bool isPinEnabled() const override; + + QWidget *createWidget(QWidget *parent = nullptr) const override; + + +protected: + virtual bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + bool onMapExtentChanged() override; + + // internal/admin + + // logical + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.cpp new file mode 100755 index 000000000..e067047a5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.cpp @@ -0,0 +1,177 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of HitLine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "hit_line_impl.h" +#include +#include +#include + +#include + +// ESRI + + +// Qt + + +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class HitLineImpl +//----------------------------------------------------------------------------- +HitLineImpl::HitLineImpl(const QString &debugInfo) + : HitLine() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("HitLineImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo=" << m_debugInfo; + + setArrowHeadStart(true); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo=" << m_debugInfo; +} + +HitLineImpl::~HitLineImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo=" << m_debugInfo; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo=" << m_debugInfo; +} + +QString HitLineImpl::identifier() const +{ + return this->textOnSegment(0); +} +void HitLineImpl::setIdentifier(const QString &identifier) +{ + this->setTextOnSegment(0, identifier); +} + +Point3D HitLineImpl::startPoint() const +{ + // !!! see setStartAndEndPoint() !!! + + const PositionCont &pos = m_positions.positions(); + if (pos.size() < 2) + return Point3D(); + else + return pos[1]; // !!! see setStartAndEndPoint() !!! +} +Point3D HitLineImpl::endPoint() const +{ + // !!! see setStartAndEndPoint() !!! + + const PositionCont &pos = m_positions.positions(); + if (pos.size() < 2) + return Point3D(); + else + return pos[0]; // !!! see setStartAndEndPoint() !!! +} + + +void HitLineImpl::setStartAndEndPoint(const Point3D &start, const Point3D &end) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " start=" << start + << " end=" << end + << ", debugInfo=" << m_debugInfo; + + // According MIL2525 a line (like attack) starts with an arrow. + // But in this context (HitLine) + // start == shooter -> no arrowHead + // end == target -> with arrowHead + + // Therefore we have to add the 'end'/target point first: + m_positions.setPositions(PositionCont() << end << start); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool HitLineImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical + << ", debugInfo=" << m_debugInfo; + + + setArrowHeadsSelected(m_selected); + + bool success = BaseClass::updateViewImpl(); + + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool HitLineImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10032500001406030000"); // SymbolSet = 25 140602 + // ^^ ^^^^^^ + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFGPOLKGS-"); // 1047 - Direction Of Attack Ground Supporting Attack F +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GHGPOLKA--"); // +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GHGPOLKGM-"); // + + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFCPMOLDS-"); // APP6B-3733 GFCPMOLDS- -> Direction Of Attack Ground Supporting Attack F + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + double maxSegmentLength = -1; // -> GeometryEngine::densifyGeodetic + if (mapImpl && (mapImpl->mode() == Mode::Mode_3D)) + maxSegmentLength = 10; // -> GeometryEngine::densifyGeodetic + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polyline, maxSegmentLength); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + m_mainGraphicalObject->rLineStyle().setLinePattern(LinePattern::Dash); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +bool HitLineImpl::setPositions(const PositionCont &) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " DO NOT CALL!!!"; + assert(false); + return false; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.h new file mode 100755 index 000000000..2883adac6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/hit_line_impl.h @@ -0,0 +1,82 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of HitLine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include +#include + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The HitLineImpl class displays shot from a startpoint to an endpoint. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name/identifier) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT HitLineImpl : public BasicPathObjectImpl, public virtual HitLine +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit HitLineImpl(const QString &debugInfo = QString()); + virtual ~HitLineImpl(); + +public: + HitLineImpl(const HitLineImpl &other) = delete; + HitLineImpl &operator=(HitLineImpl &other) = delete; + + QString identifier() const override; + void setIdentifier(const QString &identifier) override; + + Point3D startPoint() const override; + Point3D endPoint() const override; + + void setStartAndEndPoint(const Point3D &start, const Point3D &end) override; + + +protected: + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + +private: + bool setPositions(const PositionCont &) override; //SHOULD NEVER BE CALLED + +private: + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.cpp new file mode 100755 index 000000000..b48492442 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.cpp @@ -0,0 +1,575 @@ +/** + * @RDE_HEADER@ + * IndicationContainer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "indication_container.h" +#include +#include +#include + + +// ESRI +#include +#include + +// Qt +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class IndicationData +//----------------------------------------------------------------------------- +class IndicationData +{ +public: + const QImage &image() const { return m_image;} + void setImage(const QImage &image) { if (m_image != image) m_changed = true; m_image = image; } + + MarkerSymbolType markerSymbolType() const { return m_markerSymbolType; } + void setMarkerSymbolType(const MarkerSymbolType &markerSymbolType) { if (m_markerSymbolType != markerSymbolType) m_changed = true; m_markerSymbolType = markerSymbolType; } + + const QColor &markerColor() const { return m_markerColor; } + void setMarkerColor(const QColor &markerColor) { if (m_markerColor != markerColor) m_changed = true; m_markerColor = markerColor; } + + int markerSize() const { return m_markerSize; } + void setMarkerSize(int markerSize) { if (m_markerSize != markerSize) m_changed = true; m_markerSize = markerSize; } + + bool markerSelected() const { return m_markerSelected; } + void setMarkerSelected(bool markerSelected) { if (m_markerSelected != markerSelected) m_changed = true; m_markerSelected = markerSelected; } + + bool markerPickable() const { return m_markerPickable; } + void setMarkerPickable(bool markerPickable) { if (m_markerPickable != markerPickable) m_changed = true; m_markerPickable = markerPickable; } + + PositionsTranslated &positionsTrans() { return m_positions; } + void setPositions(const PositionCont &positions) { if (m_positions.positions() != positions) m_changed = true; m_positions.setPositions(positions); } + + bool markerVisible() const { return m_markerVisible; } + void setMarkerVisible(bool markerVisible) { if (m_markerVisible != markerVisible) m_changed = true; m_markerVisible = markerVisible; } + + + + bool changed() const { return m_changed; } + void resetChanged() { m_changed = false; } + + IndicationData() + {} + + ~IndicationData() + { + m_pictureMarker.clear(); + } + +public: + QSharedPointer m_coordTranslationThread; + QSharedPointer m_pictureMarker; + +private: + QImage m_image; + + PositionsTranslated m_positions; + MarkerSymbolType m_markerSymbolType = MarkerSymbolType::Undefined; + int m_markerSize = 20; + QColor m_markerColor = Qt::black; + bool m_markerVisible = true; + bool m_markerSelected = false; + bool m_markerPickable = true; + bool m_changed = true; +}; + + +class IndicationContainer::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + Esri::ArcGISRuntime::GeoView *m_geoView = nullptr; + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + + // Own data: + CoordTranslationFunctor *m_coordTransFunctor = nullptr; + QString m_debugInfo; // will be appended by consecutive number + UniqueMapId m_uniqueMapId; + + SimpleValue m_drawOrder = 0; + +private: + // TODO: use sorted vector - only max. 10 elements in there, so qhash/qmap is overkill + QHash> m_map; + +public: + QSharedPointer getIndicationData(int key) + { + auto it = m_map.find(key); + if (it != m_map.end()) + return it.value(); + auto ret = QSharedPointer(new IndicationData()); + m_map[key] = ret; + return ret; + } + + void remove(int key) // attention, will remove the provides image/marker... + { + auto it = m_map.find(key); + if (it == m_map.end()) + return; + const QSharedPointer &indicationData = it.value(); + if (indicationData && indicationData->m_coordTranslationThread) + indicationData->m_coordTranslationThread->wait(); + m_map.erase(it); + } + void clear() { m_map.clear(); } + int size() const { return m_map.size(); } + QList keys() const { return m_map.keys(); } + bool contains(int key) const { return m_map.contains(key); } + const QHash> &container() const { return m_map; } + +}; // class IndicationContainer::Private +//----------------------------------------------------------------------------- + + + +//----------------------------------------------------------------------------- +// class IndicationContainer +//----------------------------------------------------------------------------- +IndicationContainer::IndicationContainer(const Esri::ArcGISRuntime::GeoView *geoView, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + CoordTranslationFunctor *coordTransFunctor, + const UniqueMapId &uniqueMapId) + : QObject() + , m_logger(getLogger("IndicationContainer")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_geoView = const_cast(geoView); + d->m_graphicsLayer = graphicsLayer; + d->m_coordTransFunctor = coordTransFunctor; + d->m_uniqueMapId = uniqueMapId; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +IndicationContainer::~IndicationContainer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // Thread::wait(): + for (const QSharedPointer &id : d->container()) + { + if (id && id->m_coordTranslationThread) + { + id->m_coordTranslationThread->quit(); + id->m_coordTranslationThread->wait(); + } + } // for + + + d->clear(); + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString IndicationContainer::debugInfo() const +{ + return d->m_debugInfo; +} +void IndicationContainer::setDebugInfo(const QString &debugInfo) +{ + d->m_debugInfo = debugInfo; +} + + +PositionCont IndicationContainer::positions(int idx) const +{ + return d->getIndicationData(idx)->positionsTrans().positions(); +} +void IndicationContainer::setPositions(int idx, const PositionCont &positions) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", debugInfo='" << d->m_debugInfo << "'" + << ", m_uniqueMapId = " << d->m_uniqueMapId + << ", idx=" << idx + << ", positions.size=" << positions.size(); + + d->getIndicationData(idx)->setPositions(positions); + + // // d->remove(idx); // attention: remove will implicit rmove the provided image... + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void IndicationContainer::setMarker(int idx, MarkerSymbolType mst, int size, const QColor &color) +{ + d->getIndicationData(idx)->setMarkerSymbolType(mst); + d->getIndicationData(idx)->setMarkerSize(size); + d->getIndicationData(idx)->setMarkerColor(color); +} + +void IndicationContainer::setMarker(int idx, int size) +{ + d->getIndicationData(idx)->setMarkerSize(size); +} + +void IndicationContainer::setMarker(int idx, const QColor &color) +{ + d->getIndicationData(idx)->setMarkerColor(color); +} + + +const QImage &IndicationContainer::image(int idx) const +{ + return d->getIndicationData(idx)->image(); +} +void IndicationContainer::setImage(int idx, const QImage &image) +{ + auto id = d->getIndicationData(idx); + + if (id->image() == image) + return; + id->setImage(image); +} + +void IndicationContainer::clear(int idx) +{ + d->remove(idx); +} + +void IndicationContainer::clearAll() +{ + d->clear(); +} + +int IndicationContainer::drawOrder() const +{ + return d->m_drawOrder.value(); +} +void IndicationContainer::setDrawOrder(int drawOrder) +{ + d->m_drawOrder = drawOrder; +} + +bool IndicationContainer::visible(int idx) const +{ + return d->getIndicationData(idx)->markerVisible(); +} +void IndicationContainer::setVisible(int idx, bool visible) +{ + d->getIndicationData(idx)->setMarkerVisible(visible); +} + +bool IndicationContainer::selected(int idx) const +{ + return d->getIndicationData(idx)->markerSelected(); +} +void IndicationContainer::setSelected(int idx, bool selected) +{ + d->getIndicationData(idx)->setMarkerSelected(selected); +} + +bool IndicationContainer::pickable(int idx) const +{ + return d->getIndicationData(idx)->markerPickable(); +} + +void IndicationContainer::setPickable(int idx, bool pickable) +{ + d->getIndicationData(idx)->setMarkerPickable(pickable); +} + + + +int IndicationContainer::getIndicationCount() const +{ + return d->size(); +} + +QList IndicationContainer::getIndicationIndexList() const +{ + return d->keys(); +} + + +bool IndicationContainer::updateView() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", debugInfo='" << d->m_debugInfo << "'" + << ", m_uniqueMapId = " << d->m_uniqueMapId + << " , d->m_map.size()=" << d->size(); + bool success = true; + + +// int outterSize = d->m_map.size(); + + for (auto it = d->container().begin(); it != d->container().end(); ++it) + { + const QSharedPointer &indicationData = it.value(); + // per single dimension: + + // nothing changed? + if ((!indicationData->changed()) && (!d->m_drawOrder.changed())) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " no change -> continue... " + << ", debugInfo='" << d->m_debugInfo << "'"; + continue; + } + + // set + indicationData->positionsTrans().setCoordTransFunctor(d->m_coordTransFunctor); + + int idx = it.key(); + +#define SYNC +#ifdef SYNC + bool updated = updateIndicationContainer(idx); + success = success && updated; + +#else + + // skip thread and do call sync if: + if (indicationData->positionsTrans().isTranslated() + || ! indicationData->markerVisible() + ) + { + // call sync: + updateIndicationContainer(idx); // update of invisible marker is cheap :-) + } + else + { + + // ok, thread is requested: + + // another thread prepared/running or finished/deleted? + if (indicationData->m_coordTranslationThread.isNull()) + { + // move time consuming work to worker thread: + // apply Deleter to SharedPointer (to avoid simple 'delete') + indicationData->m_coordTranslationThread = + QSharedPointer (new CoordTranslationThread(d->m_debugInfo), &QObject::deleteLater); + + indicationData->m_coordTranslationThread->setUserData(idx); + indicationData->m_coordTranslationThread->setPositions(& (indicationData->positionsTrans())); + + // QObject::connect(indicationData.m_coordTranslationThread.data(), &CoordTranslationThread::finished, indicationData.m_coordTranslationThread.data(), &QObject::deleteLater); + QObject::connect(indicationData->m_coordTranslationThread.data(), &CoordTranslationThread::finished, this, &IndicationContainer::onThreadFinished); + QObject::connect(indicationData->m_coordTranslationThread.data(), &CoordTranslationThread::resultReady, this, &IndicationContainer::onResultReady); + + qWarning() << " start workerThread, idx=" << idx; + indicationData->m_coordTranslationThread->start(); + + } + else + { + FATAL_LOG(m_logger) << LOGFUNCTION + << " new Translation via Thread requested, but still exists..."; + } + + } +#endif // SYNC + + indicationData->resetChanged(); + + } // while + d->m_drawOrder.reset(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'" + << " success=" << success; + return success; +} + +bool IndicationContainer::onResultReady(const QVariant &userData) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << d->m_debugInfo << "'" + << "threadId: " << QThread::currentThreadId(); + + bool success = false; + + bool bOk = false; + int idx = userData.toInt(&bOk); + if (bOk) + { + if (d->contains(idx)) + { + QSharedPointer indicationData = d->getIndicationData(idx); + if (! indicationData->positionsTrans().isTranslated()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " NOT TRANSLATED, should not happen!!!" + << " debugInfo='" << d->m_debugInfo << "'" + << " idx=" << idx; + } + } + + success = updateIndicationContainer(idx); + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " QVariant toInt failed!!!"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'" + << " success = " << success; + return success; +} + +void IndicationContainer::onThreadFinished(const QVariant &userData) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << d->m_debugInfo << "'"; + bool bOk = false; + int idx = userData.toInt(&bOk); + if (bOk) + { + if (d->contains(idx)) + d->getIndicationData(idx)->m_coordTranslationThread.clear(); // will call the Deleter which is 'deleteLater' + } + else + qWarning() << "resultReady: QVariant toInt failed!!!"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'"; +} + + +bool IndicationContainer::updateIndicationContainer(int idx) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << d->m_debugInfo << "'" + << " idx=" << idx; + + QElapsedTimer timer; + + if (m_logger.isInfoEnabled()) + timer.start(); + + if (! d->contains(idx)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " idx does not exist: idx=" << idx; + return false; + } + + bool successPM = true; + QSharedPointer indicationData = d->getIndicationData(idx); + + // do we have positions or is PositionCont empty? + const PositionsTranslated &posTransRef = indicationData->positionsTrans(); + int numberOfPositions = posTransRef.size(); + if (posTransRef.isEmpty()) + { + QSharedPointer &picMarker = indicationData->m_pictureMarker; + picMarker.clear(); + } + else + { + successPM = updateIndicationContainerPictureMarker(idx); + } + + bool success = successPM; + + INFO_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'" + << " success=" << success + << " successPM=" << successPM + << " numberOfPositions = " << numberOfPositions + << " took[ms] " << timer.elapsed(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'" + << " numberOfPositions = " << numberOfPositions + << " successPM=" << successPM + << " success=" << success; + return success; +} + +bool IndicationContainer::updateIndicationContainerPictureMarker(int idx) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " debugInfo='" << d->m_debugInfo << "'" + << " idx=" << idx; + QElapsedTimer timer; + + if (m_logger.isInfoEnabled()) + timer.start(); + + bool success = true; + int numberOfPositions = -1; + + QSharedPointer indicationData = d->getIndicationData(idx); + + QSharedPointer &picMarker = indicationData->m_pictureMarker; + + const MarkerSymbolType &mst = indicationData->markerSymbolType(); + const bool bPicMarkerNeeded = (mst != MarkerSymbolType::Undefined) || !indicationData->image().isNull(); + + if (bPicMarkerNeeded) + { + if (picMarker.isNull()) + { + QString debugInfo = QStringLiteral("%1#Indication_idx(%2)_PM").arg(d->m_debugInfo).arg(idx); + DEBUG_LOG(m_logger) << LOGFUNCTION << " create new Object: " << debugInfo; + picMarker = QSharedPointer + (new GraphicalObjectPictureMarker(d->m_geoView, + d->m_graphicsLayer, + debugInfo, + d->m_uniqueMapId)); + } + + // set parameter: + + // [GUEZE-496] RPL Nr 770: 3D Lagedarstellung Einfrieren der SW Appl. + // GraphicalObjectPictureMarker supports multipoint geometry now + + // do we have an mst or image? + if (mst != MarkerSymbolType::Undefined) + { + // Marker: + picMarker->setMarkerSymbol(mst, + indicationData->markerSize(), + indicationData->markerColor()); + } + else + { + // Image: + picMarker->setImage(indicationData->image()); + } + + bool visible = indicationData->markerVisible(); + picMarker->setDrawOrder(d->m_drawOrder.value()); + picMarker->setVisibility(visible); + picMarker->setSelected(indicationData->markerSelected()); + picMarker->setPickable(indicationData->markerPickable()); + + // Geometry/Positions + const PositionCont &positionsTrans = indicationData->positionsTrans().positionsTranslated(); + picMarker->setPositions(positionsTrans); + numberOfPositions = positionsTrans.size(); + + success = picMarker->updateView(); + } else { + picMarker.clear(); + } + + INFO_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'" + << " success=" << success + << " numberOfPositions = " << numberOfPositions + << " took[ms] " << timer.elapsed(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'" + << " numberOfPositions = " << numberOfPositions + << " success=" << success; + return success; +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.h new file mode 100755 index 000000000..56b19e82e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/indication_container.h @@ -0,0 +1,115 @@ +/** + * @RDE_HEADER@ + * IndicationContainer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include + + +// Qt +#include +#include +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class GeoView; +class GraphicsOverlay; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// Forward: +class CoordTranslationFunctor; +class IndicationData; + + +class IndicationContainer : public QObject +{ + Q_OBJECT + +public: + explicit IndicationContainer(const Esri::ArcGISRuntime::GeoView *geoView, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + CoordTranslationFunctor *coordTransFunctor, + const UniqueMapId &uniqueMapId); + virtual ~IndicationContainer(); + + IndicationContainer(const IndicationContainer &other) = delete; + IndicationContainer &operator=(IndicationContainer &other) = delete; + + QString debugInfo() const; + void setDebugInfo(const QString &debugInfo); + + // Marker : + + virtual PositionCont positions(int idx) const; + virtual void setPositions(int idx, const PositionCont &positions); + + virtual void setMarker(int idx, MarkerSymbolType mst, int size, const QColor &color); + virtual void setMarker(int idx, int size); + virtual void setMarker(int idx, const QColor &color); + + virtual const QImage &image(int idx) const; + virtual void setImage(int idx, const QImage &image); + + virtual void clear(int idx); + virtual void clearAll(); + + virtual int drawOrder() const; + virtual void setDrawOrder(int drawOrder); + + virtual bool visible(int idx) const; + virtual void setVisible(int idx, bool visible); + + virtual bool selected(int idx) const; + virtual void setSelected(int idx, bool selected); + + virtual bool pickable(int idx) const; + virtual void setPickable(int idx, bool pickable); + + + virtual int getIndicationCount() const; + virtual QList getIndicationIndexList() const; + + + virtual bool updateView(); + virtual bool updateIndicationContainer(int idx); + virtual bool updateIndicationContainerPictureMarker(int idx); +// virtual bool updateIndicationContainerSceneMarker(int idx); + +public slots: + virtual void onThreadFinished(const QVariant &userData); + virtual bool onResultReady(const QVariant &userData); + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.cpp new file mode 100755 index 000000000..2536710d1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.cpp @@ -0,0 +1,205 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of HitLine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "line_of_sight_impl.h" +#include +#include +#include + +#include + +// ESRI + + +// Qt + + +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class LineOfSightImpl +//----------------------------------------------------------------------------- +LineOfSightImpl::LineOfSightImpl(const QString &debugInfo) + : LineOfSight() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("LineOfSightImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_extensionLineCoordinate.setMapObjectImpl(this); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +LineOfSightImpl::~LineOfSightImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete m_extensionLine; + m_extensionLine = nullptr; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString LineOfSightImpl::identifier() const +{ + return this->textOnSegment(0); +} +void LineOfSightImpl::setIdentifier(const QString &identifier) +{ + this->setTextOnSegment(0, identifier); +} + +void LineOfSightImpl::setPoints(const Point3D &observer, + const Point3D &obstacle, + const Point3D &target, + const QColor &visibleColor, // green, + const QColor &obstructedColor) // red +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " observer=" << observer + << " obstacle=" << obstacle + << " target=" << target + << " visibleColor=" << visibleColor + << " obstructedColor=" << obstructedColor; + + // some checks + if (observer.isNull() || target.isNull()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " start of end is null"; + return; + } + + if (obstacle.isNull()) + { + // no obstacle -> just a single (green) line: + m_positions.setPositions(PositionCont() << observer << target); + m_extensionLineCoordinate.clear(); + } + else + { + // obstacle -> first (green) line: + m_positions.setPositions(PositionCont() << observer << obstacle); + m_extensionLineCoordinate.setOrigin(target); + } + + // handle color: + if (visibleColor.isValid()) + { + LineStyle ls = this->lineStyle(); + ls.setLineColor(visibleColor); + this->setLineStyle(ls); + } + + if (obstructedColor.isValid()) + m_extensionLineColor = obstructedColor; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool LineOfSightImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical + << " m_positions=" << m_positions.positions() + << " m_additionalCoordinate=" << m_extensionLineCoordinate.origin(); + + // + // + // maybe the line(s) are densified @ GraphicalObjectPath::updateViewImpl() + // to use more vertices (for 3D) !? + // + // + + + bool success = BaseClass::updateViewImpl(); + + updateExtensionLine(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +void LineOfSightImpl::updateExtensionLine() +{ + if (!m_extensionLineCoordinate.isEmpty()) + { + if (!m_extensionLine) + { + QString debugInfo = QStringLiteral("%1#ExtensionLine").arg(m_debugInfo); + m_extensionLine = new GraphicalObjectPath(GraphicalObjectPathType::Polyline, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + debugInfo, + m_uniqueMapId); + } + + m_extensionLine->setVisibility(m_visibility && m_visibilityTechnical); + m_extensionLine->setScale(m_mainGraphicalObject->scale()); + m_extensionLine->setDrawOrder(m_mainGraphicalObject->drawOrder()); + + LineStyle ls = m_mainGraphicalObject->lineStyle(); + ls.setLineColor(m_extensionLineColor); + m_extensionLine->setLineStyle(ls); + + // positions: + PositionsTranslated tempPosTrans; + tempPosTrans.setPositionsTranslated(PositionCont() << + m_extensionLineCoordinate.originTranslated() // obstaclePosition + << m_positions.positionsTranslated()[1]); // end/targetPosition + + m_extensionLine->setPositions(tempPosTrans.positionsTranslated()); + + m_extensionLine->updateView(); + + } + else + { + // no m_extensionLineCoordinate -> remove extensionLine + delete m_extensionLine; + m_extensionLine = nullptr; + } +} + +bool LineOfSightImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polyline); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + // default: green, 3px + LineStyle ls = this->lineStyle(); + ls.setLineColor(Qt::green); + ls.setLineWidth(3); + this->setLineStyle(ls); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +bool LineOfSightImpl::setPositions(const PositionCont &) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " DO NOT CALL!!!"; + assert(false); + return false; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.h new file mode 100755 index 000000000..879f97fd9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/line_of_sight_impl.h @@ -0,0 +1,82 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of HitLine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface +#include + +// Qt +#include +#include + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The LineOfSightImpl class supports a "single" line with two colors. + * The method setPositions() should never be called. + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name/identifier) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT LineOfSightImpl : public BasicPathObjectImpl, public virtual LineOfSight +{ + using BaseClass = SituationMap::BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit LineOfSightImpl(const QString &debugInfo = QString()); + virtual ~LineOfSightImpl(); + +public: + QString identifier() const override; + void setIdentifier(const QString &identifier) override; + + void setPoints(const Point3D &observer, + const Point3D &obstacle, + const Point3D &target, + const QColor &visibleColor = Qt::green, + const QColor &obstructedColor = Qt::red) override; + +protected: + bool updateViewImpl() override; + void updateExtensionLine(); + + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + OriginTranslated m_extensionLineCoordinate; + GraphicalObjectPath *m_extensionLine = nullptr; + QColor m_extensionLineColor = Qt::red; + + // logical + +private: + bool setPositions(const PositionCont &) override; //SHOULD NEVER BE CALLED + +private: + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.cpp new file mode 100755 index 000000000..82c10a9cd --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.cpp @@ -0,0 +1,111 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "map_object_attributes_impl.h" +#include +#include + +#include + +// ESRI + + +// Qt + + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class MapObjectAttributesImpl +//----------------------------------------------------------------------------- +MapObjectAttributesImpl::MapObjectAttributesImpl(const QString &debugInfo) + : MapObjectAttributes() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("MapObjectAttributesImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +MapObjectAttributesImpl::~MapObjectAttributesImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QVariantMap MapObjectAttributesImpl::attributes() const +{ + if (!m_mainGraphicalObject) + return {}; + GraphicalObjectAttributes *goa = static_cast(m_mainGraphicalObject); + if (!goa) + return {}; + return goa->attributes(); +} + +void MapObjectAttributesImpl::setAttributes(const QVariantMap &attributes) +{ + if (!m_mainGraphicalObject) + return; + + GraphicalObjectAttributes *goa = static_cast(m_mainGraphicalObject); + if (!goa) + return; + goa->setAttributes(attributes); +} + +bool MapObjectAttributesImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + m_mainGraphicalObject->setOrigin(this->originTranslated()); + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool MapObjectAttributesImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + if (success) + { + // create underlying object (to save most attributes only there) + if (!m_mainGraphicalObject) + m_mainGraphicalObject = new GraphicalObjectAttributes(m_mapImpl, + m_graphicsLayer, + m_debugInfo + QLatin1String("#GOAttributes"), + m_uniqueMapId); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.h new file mode 100755 index 000000000..c64326112 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_attributes_impl.h @@ -0,0 +1,65 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of DangerArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "situation_map_objects_export.h" +#include "abstract_single_point_map_object_impl.h" +#include +#include // Interface + + +// Qt +#include +#include + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT MapObjectAttributesImpl : public AbstractSinglePointMapObjectImpl, public virtual MapObjectAttributes +{ + using BaseClass = AbstractSinglePointMapObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit MapObjectAttributesImpl(const QString &debugInfo = QString()); + virtual ~MapObjectAttributesImpl(); + +public: + MapObjectAttributesImpl(const MapObjectAttributesImpl &other) = delete; + MapObjectAttributesImpl &operator=(MapObjectAttributesImpl &other) = delete; + + QVariantMap attributes() const override; + void setAttributes(const QVariantMap &attributes) override; + + +protected: + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + + +private: + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.cpp new file mode 100755 index 000000000..b45f9a6b2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.cpp @@ -0,0 +1,289 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of MapObjectIndicator + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "map_object_indicator_impl.h" +#include +#include +#include +#include "abstract_single_point_map_object_impl.h" +#include +#include + + +// ESRI + + +// Qt + +#include + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class MapObjectIndicatorImpl +//----------------------------------------------------------------------------- +MapObjectIndicatorImpl::MapObjectIndicatorImpl(const QString &debugInfo) + : MapObjectIndicator() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("MapObjectIndicatorImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + setPickable(false); + + setArrowHeadStart(true); + + m_arrowHeadLengthHeight = 25; // size of arrow + m_arrowHeadHalfWidth = 7; // size of arrow + + m_pictureMarkerSize = 10; // size of filled circle at the origin (indicating the center of the map) + m_lineWidth = 2; // lineWidth of (dahed) line from origin to target + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +MapObjectIndicatorImpl::~MapObjectIndicatorImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // disconnect lambda!!! Otherwise it will be called with a dangling 'this'pointer. + QObject::disconnect(m_extentChangedConnection); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +Point3D MapObjectIndicatorImpl::startPoint() const +{ + // !!! see setStartAndEndPoint() !!! + + const PositionCont &pos = m_positions.positions(); + if (pos.size() < 2) + return Point3D(); + else + return pos[1]; // !!! see setStartAndEndPoint() !!! +} +Point3D MapObjectIndicatorImpl::endPoint() const +{ + // !!! see setStartAndEndPoint() !!! + + const PositionCont &pos = m_positions.positions(); + if (pos.size() < 2) + return Point3D(); + else + return pos[0]; // !!! see setStartAndEndPoint() !!! +} + + +void MapObjectIndicatorImpl::setStartAndEndPoint(const Point3D &start, const Point3D &end) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " start=" << start + << " end=" << end; + + // According MIL2525 a line (like attack) starts with an arrow. + // But in this context (HitLine) + // start == shooter -> no arrowHead + // end == target -> with arrowHead + + // Therefore we have to add the 'end'/target point first: + m_positions.setPositions(PositionCont() << end << start); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +UniqueMapId MapObjectIndicatorImpl::mapObjectToPointTo() +{ + return m_uniqueIdToPointTo; +} + +void MapObjectIndicatorImpl::setMapObjectToPointTo(UniqueMapId uniqueId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, uniqueId=" << uniqueId; + + m_uniqueIdToPointTo = uniqueId; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, uniqueId=" << uniqueId; +} + + +bool MapObjectIndicatorImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + // create PictureMarker here, to have m_uniqueMapId already set ( compared to init() ) + if (m_pictureMarker.isNull()) + { + QString debugInfo = QStringLiteral("%1#CenterMarker").arg(m_debugInfo); + m_pictureMarker = QSharedPointer + (new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId)); + } + + + setArrowHeadsSelected(m_selected); + + + // real work: + + // + // point to desired obj now: + // + RefPointer objToPointTo; + if (SituationMap::isUniqueMapIdValid(m_uniqueIdToPointTo)) + objToPointTo = m_mapImpl->objectFactory().get(m_uniqueIdToPointTo); + else + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_uniqueIdToPointTo invalid: " << m_uniqueIdToPointTo; + + // retrive values from objToPointTo: + PositionCont line; + QColor color; + + if (objToPointTo.valid()) + { + const QString &objToPointToDebugInfo = objToPointTo->debugInfo(); + DEBUG_LOG(m_logger) << LOGFUNCTION + << " objToPointTo is valid, uniqueIdToPointTo=" << m_uniqueIdToPointTo + << " (objToPointToDebugInfo=" << objToPointToDebugInfo << ")"; + // 1 + AbstractMapObjectImpl *impl = dynamic_cast(objToPointTo.get()); + if (impl) + { + color = impl->majorColor(); +// color.setAlpha(255); // no transperency + this->setDrawOrder(std::max(this->drawOrder(), impl->drawOrder())); + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION + << " dynamic_cast failed, uniqueIdToPointTo=" << m_uniqueIdToPointTo + << " (objToPointToDebugInfo=" << objToPointToDebugInfo << ")"; + } + + // get target position: + BoundingBox bbox; + objToPointTo->boundingBox(bbox); + if (!bbox.isValid()) + WARNING_LOG(m_logger) << LOGFUNCTION << " boundingBox not valid: bbox=" << bbox; + line.append(bbox.center()); + + const QPointF &curCenterPoint = m_mapImpl->centerPoint(); + const Point3D curCenterPoint3D(curCenterPoint.x(), curCenterPoint.y(), .0); + line.append(curCenterPoint3D); + + + // Color: + LineStyle ls = this->lineStyle(); + ls.setLineColor(color); + this->setLineStyle(ls); + + } + else // if (objToPointTo.valid()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " objToPointTo not found"; + + const QPointF &curCenterPoint = m_mapImpl->centerPoint(); + const Point3D curCenterPoint3D(curCenterPoint.x(), curCenterPoint.y(), .0); + line << curCenterPoint3D << curCenterPoint3D; // use curCenterPoint twice (to show a very 'short line'); + color = Qt::magenta; + } + + // + // transfer values: + // + if (line.size() == 2) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " setStartAndEndPoint, uniqueIdToPointTo=" << m_uniqueIdToPointTo + << " line=" << line; + m_positions.setPositions(line); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " size insufficiant, " << " objToPointTo is valid, uniqueIdToPointTo=" << m_uniqueIdToPointTo; + + // update marker values: + const PositionCont &positionsTranslated = m_positions.positionsTranslated(); + if (positionsTranslated.size() >= 2) + m_pictureMarker->setOrigin(m_positions.positionsTranslated()[1]); + color.setAlpha(255); + m_pictureMarker->setMarkerSymbol(MarkerSymbolType::Circle, m_pictureMarkerSize, color); + + bool successBaseClass = BaseClass::updateViewImpl(); + if (!successBaseClass) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + + m_pictureMarker->setSelected(m_selected); + m_pictureMarker->setVisibility((m_mainGraphicalObject) ? m_mainGraphicalObject->visibility() : false); + + bool successPictureMarker = m_pictureMarker->updateView(); + if (!successPictureMarker) + ERROR_LOG(m_logger) << LOGFUNCTION << " m_pictureMarker->updateViewImpl failed"; + + + bool success = successPictureMarker && successBaseClass; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool MapObjectIndicatorImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->setVisibility(ObjectAppearance::Tactical, false); + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polyline); + + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + return false; + } + + if (!m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << "no m_mainGraphicalObject"; + return false; + } + + m_mainGraphicalObject->rLineStyle().setLinePattern(LinePattern::Dash); + m_mainGraphicalObject->rLineStyle().setLineWidth(m_lineWidth); + + // connect to extentChanged... + // ! Must be disconnected in dtor ! + m_extentChangedConnection = QObject::connect(m_mapImpl, &MapImpl::extentChangedDelayed, m_mapImpl, [this]() + // ^^^^ to avoid CLazy warning + { + this->updateView(); + }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +bool MapObjectIndicatorImpl::setPositions(const PositionCont &) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " DO NOT CALL!!!"; + assert(false); + return false; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.h new file mode 100755 index 000000000..b14f83498 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/map_object_indicator_impl.h @@ -0,0 +1,101 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of MapObjectIndicator + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include +#include +#include +#include + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward +class GraphicalObjectPictureMarker; + + +/** + * @brief The MapObjectIndicatorImpl can be used to ease the locating of an object. + * The MapObjectIndicatorImpl can by parametrized with just the UniqueMapId to point to. + * updateView() will do the rest for you: + * - retrieve the object to point to, + * - get the bounding box of that object and + * - create an indicator from the current center of the map to the object. + * \note If the object to point to disappears, you are responsible for removing the MapObjectIndicatorImpl! + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the line with arrowhead) + * - m_pictureMarker of type GraphicalObjectPictureMarker / Image to show circle at the center of the map. + * - mainGraphicalObjectText (displaying the text/name/identifier) + * - tacticalAppearance (irrelevant) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT MapObjectIndicatorImpl : public BasicPathObjectImpl, public virtual MapObjectIndicator +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit MapObjectIndicatorImpl(const QString &debugInfo = QString()); + virtual ~MapObjectIndicatorImpl(); + +public: + MapObjectIndicatorImpl(const MapObjectIndicatorImpl &other) = delete; + MapObjectIndicatorImpl &operator=(MapObjectIndicatorImpl &other) = delete; + + + Point3D startPoint() const override; + Point3D endPoint() const override; + + void setStartAndEndPoint(const Point3D &start, const Point3D &end) override; + + UniqueMapId mapObjectToPointTo() override; + void setMapObjectToPointTo(UniqueMapId uniqueId) override; + + +protected: + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + +private: + bool setPositions(const PositionCont &) override; //SHOULD NEVER BE CALLED + +private: + UniqueMapId m_uniqueIdToPointTo = INVALID_MAP_ID; + QSharedPointer m_pictureMarker; // circle at the center of the map + int m_pictureMarkerSize = 20; + int m_lineWidth = 4; + + QMetaObject::Connection m_extentChangedConnection; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.cpp new file mode 100755 index 000000000..21b18b8ee --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.cpp @@ -0,0 +1,381 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of MineField + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "mine_field_impl.h" +#include +#include "basic_path_object_impl.h" +#include +#include "mine_impl.h" + +#include + +// ESRI + + +// Qt +#include + + + +namespace SituationMap +{ + +// to get access to 'init' +class MineFieldLocalBasicPathObject final : public BasicPathObjectImpl +{ +public: + using BasicPathObjectImpl::BasicPathObjectImpl; + using BasicPathObjectImpl::init; + using BasicPathObjectImpl::setUniqueId; +}; + + + +//----------------------------------------------------------------------------- +class MineFieldImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + + // Own data: + MapImpl *m_mapImpl = nullptr; + QString m_logicalLayer; + QString m_debugInfo; // will be appended by consecutive number + UniqueMapId m_uniqueMapId = INVALID_MAP_ID; + + QMap m_visibilityMines; + + + + QMap > m_cont; + using MAP_IT_T = QMapIterator>; + +// int m_drawOrder = 0; + + bool hasPathObject(int key) const { return m_cont.contains(key); } + QList keysInUse() const { return m_cont.keys(); } + void remove(int key) { m_cont.remove(key); } + void clear() { m_cont.clear(); } + + QSharedPointer getPathObject(int key) + { + auto ret = m_cont.value(key); + if (ret.isNull()) + { + QString debugInfo = QStringLiteral("%1#MineFieldSeg(%2)").arg(m_debugInfo).arg(key); + MineFieldLocalBasicPathObject *temp = new MineFieldLocalBasicPathObject(debugInfo); + bool success = temp->init(m_mapImpl, m_logicalLayer, GraphicalObjectPathType::Polygon); + temp->setUniqueId(m_uniqueMapId); + temp->setFillStyle(FillStyle(FillPattern::Null)); // no fill by default + + if (success) + { + ret = QSharedPointer(static_cast(temp)); + m_cont.insert(key, ret); + } + else + temp->removeMySelf(); + } + return ret; + } +}; // class MineFieldImpl::Private +//----------------------------------------------------------------------------- + + + +//----------------------------------------------------------------------------- +// class MineFieldImpl +//----------------------------------------------------------------------------- +MineFieldImpl::MineFieldImpl(const QString &debugInfo) + : MineField() // Interface + , BaseClass(debugInfo) // Implementation + , d(new Private) + , m_logger(getLogger("MineFieldImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_visibilityMines[static_cast(MineState::Activated)] = true; + d->m_visibilityMines[static_cast(MineState::DeActivated)] = true; + + m_lineColorFromParty = false; + m_fillColorAlpha = 200; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +MineFieldImpl::~MineFieldImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void MineFieldImpl::setUniqueId(const UniqueMapId &uniqueId) +{ + BaseClass::setUniqueId(uniqueId); + d->m_uniqueMapId = m_uniqueMapId; +} + + +PositionCont MineFieldImpl::segment(int key) const +{ + if (d->hasPathObject(key)) + return d->getPathObject(key)->positions(); + else + return PositionCont(); +} + +void MineFieldImpl::setSegment(int key, const PositionCont &positions) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " key=" << key + << " positions.size=" << positions.size(); + + QSharedPointer pathObj = d->getPathObject(key); + if (pathObj) + pathObj->setPositions(positions); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void MineFieldImpl::removeSegment(int key) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->remove(key); +} + +void MineFieldImpl::removeSegments() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->clear(); +} + +QList MineFieldImpl::keysInUse() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->keysInUse(); +} + + +void MineFieldImpl::setPositionsMine(MineField::MineState mineState, const PositionCont &posCont) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " mineState=" << static_cast(mineState) + << " posCont.size()=" << posCont.size(); + + IndicationContainer &mines = this->rIndicationContainer(); + + mines.setDebugInfo(m_debugInfo); + mines.setPositions(static_cast(mineState), posCont); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " mineState=" << static_cast(mineState) + << " posCont.size()=" << posCont.size(); +} + +bool MineFieldImpl::mineVisibility(MineField::MineState mineState) const +{ + return d->m_visibilityMines[static_cast(mineState)]; +} +void MineFieldImpl::setMineVisibility(MineField::MineState mineState, bool visible) +{ + d->m_visibilityMines[static_cast(mineState)] = visible; +} + + +bool MineFieldImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical + << " mineVisibility(MineState::Activated)=" << this->mineVisibility(MineState::Activated) + << " mineVisibility(MineState::DeActivated)=" << this->mineVisibility(MineState::DeActivated) + << " m_positions->positions().count()=" << m_positions.positions().count() + << " m_mainGraphicalObject->positions().count()=" << m_mainGraphicalObject->positions().count() + << " m_mainGraphicalObject->fillStyle().fillPattern()=" << (int)m_mainGraphicalObject->fillStyle().fillPattern(); + + // indication : + if (nullptr == m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_mainGraphicalObject"; + return false; + } + + if (m_indicationCont) + { + const QColor &col = m_mainGraphicalObject->lineStyle().lineColor(); + + const QList idxInUse = m_indicationCont->getIndicationIndexList(); + for (int idx : idxInUse) + { + // handle image on if not already set: + if (m_indicationCont->image(idx).isNull()) + { + bool filled = (idx == static_cast(MineState::DeActivated)); + const QImage &img = MineImpl::renderMineSymbol(col, 20, filled); // filled + m_indicationCont->setImage(idx, img); + } + + m_indicationCont->setSelected(idx, m_selected); + m_indicationCont->setPickable(idx, m_pickable); + } + } + // : indication + + + // m_mainGraphicalObject stores the outline/'bounding box' (techn and tact) + // Those geometry should never be visible. + // The text of should be visible. + this->setVisibilityOfGeometry(false); + + bool success = BaseClass::updateViewImpl(); + + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + + // segment : + + Private::MAP_IT_T mapIt(d->m_cont); + + while (mapIt.hasNext()) + { + mapIt.next(); + QSharedPointer pathObj = mapIt.value(); + if (pathObj) + { + int idx = mapIt.key(); + +// pathObj->setDebugInfo(m_mainGraphicalObject->debugInfo()); // will overwrite complete string -> skip + + pathObj->setLineStyle(m_mainGraphicalObject->lineStyle()); + if (idx >= 0) + pathObj->setFillStyle(m_mainGraphicalObject->fillStyle()); + + pathObj->setVisibility(m_visibility); + pathObj->setVisibility(ObjectAppearance::Technical, m_visibilityTechnical); + + // Do not Transfer symbol. Only m_mainGraphicalObject uses ObjectAppearance::Tactical: +// pathObj->setSymbol(this->symbol()); + pathObj->setVisibility(ObjectAppearance::Tactical, false); + + pathObj->setPickable(m_mainGraphicalObject->pickable()); + + pathObj->setSelected(m_mainGraphicalObject->selected()); + + pathObj->setDrawOrder(this->drawOrder()); // use m_drawOrder instead m_internalDrawOrder. + + if (!pathObj->updateView()) + { + int key = mapIt.key(); + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed, key=" << key; + } + } + } // while + + // : segment + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical + << " mineVisibility(MineState::Activated)=" << this->mineVisibility(MineState::Activated) + << " mineVisibility(MineState::DeActivated)=" << this->mineVisibility(MineState::DeActivated) + << " m_positions->positions().count()=" << m_positions.positions().count() + << " m_mainGraphicalObject->positions().count()=" << m_mainGraphicalObject->positions().count() + << " m_mainGraphicalObject->fillStyle().fillPattern()=" << (int)m_mainGraphicalObject->fillStyle().fillPattern(); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool MineFieldImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"10012500002707060000"); // SymbolSet = 25 270701 , 270705, 270706, 270707 + // Case #01977851 - Runitme SDK for Qt: IdentifyGraphicsOverlays crashes + // BUG-000107707 : IdentifyGraphicsOverlays crashes on military symbol "10012500002707060000"] + // -> use another: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500001511000000"); // SymbolSet = 25 151100 + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFMPOFA---"); // Mil2525C: 0843: GFMPOFA--- -> Polygon (Minefields Mined Area F) + break; + case AdvancedSymbology::SymbologyType::APP6B: +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GFCPBOAIL-"); // APP6B: 3852: GFCPBOAIL- -> Polygon (Minefield - Antipersonnel Mines) +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GFCPBOAIN-"); // APP6B: 3853: GFCPBOAIN- -> Polygon (Minefield - Antitank) + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFCPBOAIM-"); // APP6B: 3857: GFCPBOAIM- -> Polygon (Minefields Mined Area) + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + setLineStyle(LineStyle(Qt::green, 2)); + setFillStyle(FillStyle(Qt::green, FillPattern::Null)); + + // set some values to Private: + d->m_mapImpl = m_mapImpl; + d->m_logicalLayer = m_logicalLayerName; + d->m_debugInfo = m_debugInfo; + d->m_uniqueMapId = m_uniqueMapId; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool MineFieldImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (m_indicationCont) + { + bool visiScale = m_visibilityScaleRange.scaleHasVisibility(m_mapImpl->scale()); + const QList &idxInUse = m_indicationCont->getIndicationIndexList(); // idxInUse is representing all values of 'MineState' + for (int idx : std::as_const(idxInUse)) + { + m_indicationCont->setVisible(idx, m_visibility && m_visibilityTechnical && d->m_visibilityMines.value(idx) && visiScale); + } + } + + bool success = BaseClass::onMapExtentChanged(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.h new file mode 100755 index 000000000..f1bb23bbe --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_field_impl.h @@ -0,0 +1,110 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of MineField + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +enum class MinesState; + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +/** + * @brief The MineFieldImpl class supports MineFields with an unlimited number of segments. + * Additionally the positions of mines can be set. + * MineState::Activated and MineState::DeActivated are supported. + * + * The segments are implemented using a BasicPathObjectImpl. + * The subsidiary lines of the mainGraphicalObject (also of type BasicPathObjectImpl) + * are not used because the geometry part of the mainGraphicalObject is hidden (replaced by the segments.) + * But the envelope of the MineField is necessary to show the TacticalAppearance. + * + * The identifier of the MineFieldImpl is used, not the identifier of the segments. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance (is hidden, see above) + * - mainGraphicalObjectText (displaying the text/name/identifier) + * - tacticalAppearance (displaying the tact. Appearance) + * - Container of BasicPathObjectImpl's to display the single segments. + */ +class SITUATIONMAPOBJECTS_EXPORT MineFieldImpl : public BasicPathObjectImpl, public virtual MineField +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit MineFieldImpl(const QString &debugInfo = QString()); + virtual ~MineFieldImpl(); + +public: + MineFieldImpl(const MineFieldImpl &other) = delete; + MineFieldImpl &operator=(MineFieldImpl &other) = delete; + + void setUniqueId(const UniqueMapId &uniqueId) override; + + + PositionCont segment(int key) const override; + + /** + * @brief Set positions of one segment. + * @param key of segment to be set. + * @param posCont positions of segment. + * @note: is key is negativ, no fill pattern is aplied. + * If key is >=0, the pattern of the segments take over the main pattern. + */ + void setSegment(int key, const PositionCont &positions) override; + + void removeSegment(int key) override; + void removeSegments() override; + QList keysInUse() const override; + + void setPositionsMine(MineState mineState, const PositionCont &posCont) override; + + bool mineVisibility(MineState mineState) const override; + void setMineVisibility(MineState mineState, bool visible) override; + + +protected: + virtual bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + bool onMapExtentChanged() override; + + // internal/admin + + // logical + + +private: + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.cpp new file mode 100755 index 000000000..e83ffefd9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.cpp @@ -0,0 +1,241 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Mine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "mine_impl.h" +#include +#include +#include +#include +#include + +#include + +// ESRI + + +// Qt +#include +#include + + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// constants +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Utils: +//----------------------------------------------------------------------------- + + + + + +//----------------------------------------------------------------------------- +// class MineImpl +//----------------------------------------------------------------------------- +MineImpl::MineImpl(const QString &debugInfo) + : Mine() // Interface + , BaseClass(debugInfo) // Implementation +// , AbstractEffectSimulationImpl() // Secondary implementation + , m_logger(getLogger("MineImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +MineImpl::~MineImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +QString MineImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); +// str.append( AbstractEffectSimulationImpl::toString() ); + return str; +} + + +bool MineImpl::state() const +{ + return m_state.value(); +} +void MineImpl::setState(bool state) +{ + m_state = state; +} + +SymbolImage MineImpl::symbolImage() const +{ + SymbolImage symImage; + GraphicalObjectPictureMarker *pMarker = static_cast(m_mainGraphicalObject); + if (pMarker) + { + symImage.setImage(pMarker->image()); + symImage.setValid(!symImage.image().isNull()); + } + return symImage; +} + + +bool MineImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " origin =" << this->origin() //.x() << ", " << this->origin().y() + << " radius =" << this->radius() + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + if (!m_graphicsLayer) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no graphicsLayer"; + return false; + } + + if (nullptr == m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_mainGraphicalObject"; + return false; + } + + // image: + GraphicalObjectPictureMarker *pMarker = static_cast(m_mainGraphicalObject); + if (m_state.changed()) + { + const QImage &img = renderMineSymbol(partyColor(), + 20, + m_state.value()); + pMarker->setImage(img); + } + pMarker->setOrigin(this->originTranslated()); + + + // tacticalAppearance: + if (m_tacticalAppearance) + { + if (partyChanged()) + m_symbol.refValue().color = partyColor(); + + m_tacticalAppearance->setOrigin(this->originTranslated()); + } + + bool success = BaseClass::updateViewImpl(); + + // reset changed: + m_state.reset(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool MineImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10011500002102000000"); // SymbolSet = 15 210100 ; 280000 -> Crash on Click + break; + case AdvancedSymbology::SymbologyType::MIL2525C: +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GHMPOB----"); // 0754 GHMPOMP--- (Antipersonnel (AP) Mines H) -> Point + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GHMPOB----"); // 0760 GHMPOB---- (Booby Trap H) -> Point + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUOPVY----"); // APP6B: 2549 GUOPVY---- (Booby Trap U) -> Point + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + + } // if (mapImpl) + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + if (success) + { + // create underlying object (to save most attributes only there) + if (!m_mainGraphicalObject) + m_mainGraphicalObject = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QStringLiteral("#Image"), + m_uniqueMapId); + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +QImage MineImpl::renderMineSymbol(const QColor &color, int size, bool filled) +{ + QImage image(size, size, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + QPainter painter(&image); + + QPen pen(color); + pen.setWidth(2); // filled == deactivated == triggered + if (!filled) + pen.setWidth(1); // !filled == active == still armed + painter.setPen(pen); + + const int sizeHalf = size / 2.0; + + QPointF center(sizeHalf, sizeHalf); + + // circle + if (filled) + painter.setBrush(color); + painter.drawEllipse(center, sizeHalf * 0.5, sizeHalf * 0.5); + + // lines + QLine newLine(0, sizeHalf, size / 4.0, sizeHalf); + painter.drawLine(newLine); + + QTransform trans; + trans.translate(sizeHalf, sizeHalf); + trans.rotate(90); + trans.translate(-sizeHalf, -sizeHalf); + + newLine = trans.map(newLine); + painter.drawLine(newLine); + + newLine = trans.map(newLine); + painter.drawLine(newLine); + + newLine = trans.map(newLine); + painter.drawLine(newLine); + + painter.end(); + + return image; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.h new file mode 100755 index 000000000..7f12b0085 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/mine_impl.h @@ -0,0 +1,77 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Mine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_single_point_map_object_impl.h" +//#include "abstract_effect_simulation_impl.h" +#include // Interface + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The MineImpl class displays a mine impact. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPictureMarker / Image, displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT MineImpl : public AbstractSinglePointMapObjectImpl, public virtual Mine +{ + using BaseClass = AbstractSinglePointMapObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit MineImpl(const QString &debugInfo = QString()); + virtual ~MineImpl(); + +public: + MineImpl(const MineImpl &other) = delete; + MineImpl &operator=(MineImpl &other) = delete; + + QString toString() const override; + + bool state() const override; + void setState(bool state) override; + + SymbolImage symbolImage() const override; + + + static QImage renderMineSymbol(const QColor &color, int size, bool filled); + + +protected: + virtual bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + SimpleValue m_state = false; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.cpp new file mode 100755 index 000000000..7499aebd7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.cpp @@ -0,0 +1,388 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of NineLiner + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "nine_liner_impl.h" +#include +#include +#include + +#include +#include + +// ESRI +#include + +// Qt + + +#include + + +namespace SituationMap +{ + +double adaptScale(double scale) +{ + if (scale<1.0) + return scale; + return (0.33 * scale) + 0.66; +} + +//----------------------------------------------------------------------------- +// class NineLinerImpl +//----------------------------------------------------------------------------- +NineLinerImpl::NineLinerImpl(const QString &debugInfo) + : NineLiner() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("NineLinerImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_color = Qt::blue; + + m_otInitialPoint.setMapObjectImpl(this); + m_otTargetPosition.setMapObjectImpl(this); + m_otFriendliesPosition.setMapObjectImpl(this); + +// setArrowHeadStart(true); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +NineLinerImpl::~NineLinerImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString NineLinerImpl::identifier() const +{ + return this->textOnSegment(0); +} +void NineLinerImpl::setIdentifier(const QString &identifier) +{ + this->setTextOnSegment(0, identifier); +} + +void NineLinerImpl::setScale(float scale) +{ + m_ownScale = scale; // to use m_ownScale.changed(); + BaseClass::setScale(scale); +} + +bool NineLinerImpl::updateViewInitialPoint() +{ + bool success = false; + const NineLinerData &data = m_data.value(); + + // InitialPoint / square + const Point3D &ip = data.initialPoint; + if (ip.isNull()) + { + m_pmInitialPoint.clear(); + success = true; + } else { + if(!m_pmInitialPoint) + { + QString debugInfo = QStringLiteral("%1#InitialPointMarker").arg(m_debugInfo); + m_pmInitialPoint = QSharedPointer + (new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId)); + } + + m_pmInitialPoint->setMarkerSymbol(MarkerSymbolType::Square, + 20*adaptScale(m_mainGraphicalObject->scale()), // cover the lines less by reduced growing of pointSymbols + m_color.value()); + m_otInitialPoint.setOrigin(ip); + m_pmInitialPoint->setOrigin(m_otInitialPoint.originTranslated()); + m_pmInitialPoint->setPickable(m_mainGraphicalObject->pickable()); + m_pmInitialPoint->setVisibility(m_mainGraphicalObject->visibility()); + success = m_pmInitialPoint->updateView(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " success=" << success; + return success; +} + +bool NineLinerImpl::updateViewTargetPosition() +{ + bool success = false; + const NineLinerData &data = m_data.value(); + + // TargetPosition / triangle + const Point3D &ip = data.targetPosition; + if (ip.isNull()) + { + m_pmTargetPosition.clear(); + success = true; + } else { + if(!m_pmTargetPosition) + { + QString debugInfo = QStringLiteral("%1#TargetPositionMarker").arg(m_debugInfo); + m_pmTargetPosition = QSharedPointer + (new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId)); + } + + m_pmTargetPosition->setMarkerSymbol(MarkerSymbolType::Triangle, + 20*adaptScale(m_mainGraphicalObject->scale()), // cover the lines less by reduced growing of pointSymbols + m_color.value()); + m_otTargetPosition.setOrigin(ip); + m_pmTargetPosition->setOrigin(m_otTargetPosition.originTranslated()); + m_pmTargetPosition->setPickable(m_mainGraphicalObject->pickable()); + m_pmTargetPosition->setVisibility(m_mainGraphicalObject->visibility()); + success = m_pmTargetPosition->updateView(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " success=" << success; + return success; +} + +Point3D movePoint(const Point3D& point, int length, int angle) +{ + QList pColl; + + constexpr double deg2rad = LtsCoordTrans::deg2rad(); + constexpr double rad2deg = LtsCoordTrans::rad2deg(); + + // to wgs84 / degree + Esri::ArcGISRuntime::Point p(point.x()*rad2deg, point.y()*rad2deg, point.z()*rad2deg, Esri::ArcGISRuntime::SpatialReference::wgs84()); + + pColl = Esri::ArcGISRuntime::GeometryEngine::moveGeodetic({p}, + length, Esri::ArcGISRuntime::LinearUnit::meters(), + angle, Esri::ArcGISRuntime::AngularUnit::degrees(), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + p = pColl.first(); + // back to rad + const Point3D movedPoint (p.x()*deg2rad, p.y()*deg2rad, p.z()*deg2rad); + +// qCritical() << " length = " << length << " angle= " << angle; + + return movedPoint; +} + +Point3D movePointFah(const Point3D& point, int length, int angle) +{ + if (length < 1000) + length = 1000; + + if (length > 100000) + length = 100000; + + return movePoint(point, length, angle+180); +} + +bool NineLinerImpl::updateViewFriendlies() +{ + bool success = false; + const NineLinerData &data = m_data.value(); + + if (data.friendliesDistance < 0) + { + m_pmFriendlies.clear(); + success = true; + } + else + { + const Point3D &tartgetPosition = m_otTargetPosition.origin(); + const Point3D &friendliesOrigin = movePoint(tartgetPosition, static_cast(data.friendliesDistance), data.friendliesBearing); + + if(!m_pmFriendlies) + { + QString debugInfo = QStringLiteral("%1#FriendliesPositionMarker").arg(m_debugInfo); + m_pmFriendlies = QSharedPointer + (new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId)); + } + m_pmFriendlies->setMarkerSymbol(MarkerSymbolType::Circle, + 15*adaptScale(m_mainGraphicalObject->scale()), // cover the lines less by reduced growing of pointSymbols + m_color.value()); + m_otFriendliesPosition.setOrigin(friendliesOrigin); + m_pmFriendlies->setOrigin(m_otFriendliesPosition.originTranslated()); + m_pmFriendlies->setPickable(m_mainGraphicalObject->pickable()); + m_pmFriendlies->setVisibility(m_mainGraphicalObject->visibility()); + success = m_pmFriendlies->updateView(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " success=" << success; + return success; +} + + +bool NineLinerImpl::updateViewFinalAttackHeading(int length) +{ + const NineLinerData &data = m_data.value(); + + const Point3D &tartgetPosition = m_otTargetPosition.origin(); + const Point3D &fahOrigin = movePointFah(tartgetPosition, length, data.fah); + + m_positions.setPositions(PositionCont() << fahOrigin << tartgetPosition); + + return true; +} + +bool NineLinerImpl::updateViewFahSector(QSharedPointer &path, const QString &debugInfo, int length, int angle) +{ + const Point3D &tartgetPosition = m_otTargetPosition.origin(); + const Point3D &origin = movePointFah(tartgetPosition, length, angle); + + bool success = false; + + if (angle < 0) + { + path.clear(); + success = true; + } else { + if(!path) + { + path = QSharedPointer + (new GraphicalObjectPath(GraphicalObjectPathType::Polyline, + m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId)); + path->setDrawOrder(m_internalDrawOrder - 1); + } + path->setLineStyle(LineStyle(m_color.value(), 3, LinePattern::Dash)); + + PositionsTranslated positions; + positions.setMapObjectImpl(this); + positions.addSinglePosition(tartgetPosition); + positions.addSinglePosition(origin); + + path->setPositions(positions.positionsTranslated()); + path->setScale(m_mainGraphicalObject->scale()); + path->setPickable(m_mainGraphicalObject->pickable()); + path->setVisibility(m_mainGraphicalObject->visibility()); + success = path->updateView(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " success=" << success; + return success; +} + +bool NineLinerImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_data=" << m_data.value().toString() + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + BaseClass::updateViewImpl(); + + m_color = partyColor(); + +// if (m_data.changed() || m_color.changed() || m_ownScale.changed()) + { + updateViewInitialPoint(); + updateViewTargetPosition(); + updateViewFriendlies(); + + const int length = static_cast(m_mapImpl->pixelSizeMeter() * 100); + updateViewFinalAttackHeading(length); + updateViewFahSector(m_pathFahSectorLow, QStringLiteral("%1#FahSectorLow").arg(m_debugInfo), length, m_data.value().fahLow); + updateViewFahSector(m_pathFahSectorHigh, QStringLiteral("%1#FahSectorHigh").arg(m_debugInfo), length, m_data.value().fahHigh); + } + + setArrowHeadsSelected(m_selected); + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + // reset changed flag -> updateViewImplPost + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool NineLinerImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::onMapExtentChanged(); + + // to handle unchanged pixelSizeMeter + const int newFahLength = static_cast(m_mapImpl->pixelSizeMeter() * 100); + + if (m_fahLength == newFahLength) + return true; + + // store new value + m_fahLength = newFahLength; + + updateViewFinalAttackHeading(m_fahLength); + m_mainGraphicalObject->setPositions(m_positions.positionsTranslated()); + if(!m_mainGraphicalObject->updateView()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed (m_mainGraphicalObject)"; + + if (!updateViewFahSector(m_pathFahSectorLow, QStringLiteral("%1#FahSectorLow").arg(m_debugInfo), m_fahLength, m_data.value().fahLow)) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed (m_pathFahSectorLow)"; + + if (!updateViewFahSector(m_pathFahSectorHigh, QStringLiteral("%1#FahSectorHigh").arg(m_debugInfo), m_fahLength, m_data.value().fahHigh)) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed (m_pathFahSectorHigh)"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return success; +} + +bool NineLinerImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10032500001406030000"); // SymbolSet = 25 140602 + // ^^ ^^^^^^ + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFGPOLKGS-"); // 1047 - Direction Of Attack Ground Supporting Attack F +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GHGPOLKA--"); // +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GHGPOLKGM-"); // + + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GFCPMOLDS-"); // APP6B-3733 GFCPMOLDS- -> Direction Of Attack Ground Supporting Attack F + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polyline); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + m_mainGraphicalObject->setLineStyle(LineStyle(Qt::cyan, 2, LinePattern::Solid)); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +bool NineLinerImpl::setPositions(const PositionCont &) +{ + FATAL_LOG(m_logger) << LOGFUNCTION << " DO NOT CALL!!!"; + assert(false); + return false; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.h new file mode 100755 index 000000000..d7bae0236 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nine_liner_impl.h @@ -0,0 +1,108 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of NineLiner + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include +#include + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT NineLinerImpl : public BasicPathObjectImpl, public virtual NineLiner +{ + using BaseClass = SituationMap::BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit NineLinerImpl(const QString &debugInfo = QString()); + virtual ~NineLinerImpl(); + +public: + NineLinerImpl(const NineLinerImpl &other) = delete; + NineLinerImpl &operator=(NineLinerImpl &other) = delete; + + QString identifier() const override; + void setIdentifier(const QString &identifier) override; + + void setNineLinerData(const NineLinerData& data) override { m_data = data; } + + void setScale(float scale) override; + +protected: + bool updateViewInitialPoint(); + bool updateViewTargetPosition(); + + bool updateViewFriendlies(); + + bool updateViewFinalAttackHeading(int length); + bool updateViewFahSector(QSharedPointer &path, const QString &debugInfo, int length, int angle); + + + bool updateViewImpl() override; + bool onMapExtentChanged() override; + + bool updateViewImplPost() override + { + BaseClass::updateViewImplPost(); + m_data.reset(); + m_color.reset(); + m_ownScale.reset(); + return true; + } + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + +private: + bool setPositions(const PositionCont &) override; //SHOULD NEVER BE CALLED + +private: + // input: + SimpleValue m_data; + + OriginTranslated m_otInitialPoint; + OriginTranslated m_otTargetPosition; + OriginTranslated m_otFriendliesPosition; + int m_fahLength = 1; + + // display: + SimpleValue m_ownScale; + SimpleValue m_color; + QSharedPointer m_pmInitialPoint; // square + QSharedPointer m_pmTargetPosition; // triangle + + QSharedPointer m_pathFahSectorLow; + QSharedPointer m_pathFahSectorHigh; + + QSharedPointer m_pmFriendlies; // circle + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.cpp new file mode 100755 index 000000000..4a665aaa7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.cpp @@ -0,0 +1,428 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of NuclearAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "nuclear_agent_impl.h" +#include +#include +#include + +#include + + +// ESRI + + +// Qt +#include +#include + + +namespace SituationMap +{ + +// to get access to 'init' +using NAgentLocalBasicPathObject = LocalBasicPathObject; + +QImage renderImpactSymbol(const QColor &color, int size, bool filled) +{ + QImage image(size, size, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + QPainter painter(&image); + + QPen pen(color); + int penWidth = size/10; + if (penWidth < 2) + penWidth = 2; + pen.setWidth(penWidth); + painter.setPen(pen); + + const int sizeHalf = size / 2; + + QPointF center(sizeHalf, sizeHalf); + + // circle + if (filled) + painter.setBrush(color); + painter.drawEllipse(center, sizeHalf * 0.5, sizeHalf * 0.5); + + // lines + QLine newLine(0, sizeHalf, size / 4.0, sizeHalf); + painter.drawLine(newLine); + + QTransform trans; + trans.translate(sizeHalf, sizeHalf); + trans.rotate(45); + trans.translate(-sizeHalf, -sizeHalf); + + // 8 are required, one is already draw, so 7: + for (int i=0; i<7; ++i) + { + newLine = trans.map(newLine); + painter.drawLine(newLine); + } + + painter.end(); + + return image; +} + + +class NuclearAgentImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: +// Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + + // Own data: + MapImpl *m_mapImpl{nullptr}; + QString m_logicalLayer; + QString m_debugInfo; // will be appended by consecutive number + UniqueMapId m_uniqueMapId{INVALID_MAP_ID}; + + GraphicalObjectPictureMarker *m_centerImage{nullptr}; + + + struct ValueDataNA + { + QSharedPointer basicPathObj; + NuclearAgent::OptionalData optData; + }; + + QMap m_cont; /// one polyline per subsidiary line + using MAP_IT_T = QMapIterator; +// int m_drawOrder = 0; + + bool hasPathObject(int key) const { return m_cont.contains(key); } + QList keysInUse() const { return m_cont.keys(); } + void remove(int key) { m_cont.remove(key); } + void clear() { m_cont.clear(); } + + ValueDataNA getPathObject(int key, const OptionalData &optData = OptionalData()) + { + auto ret = m_cont.value(key); // copy + if (ret.basicPathObj.isNull()) + { + QString debugInfo = QStringLiteral("%1#SubsidiaryLine(%2)").arg(m_debugInfo).arg(key); + NAgentLocalBasicPathObject *temp = new NAgentLocalBasicPathObject(debugInfo); + GraphicalObjectPathType pathType = GraphicalObjectPathType::Polyline; + if (optData.closed) + pathType = GraphicalObjectPathType::Polygon; + bool success = temp->init(m_mapImpl, m_logicalLayer, pathType); + temp->setUniqueId(m_uniqueMapId); + + if (success) + ret.basicPathObj = QSharedPointer(static_cast(temp)); + else + temp->removeMySelf(); + + ret.optData.closed = optData.closed; + } + // transfer optional data (line|fill) to ret and then to container _if_ set: + if (optData.lineStyle) + ret.optData.lineStyle = optData.lineStyle; + if (optData.fillStyle) + ret.optData.fillStyle = optData.fillStyle; + m_cont.insert(key, ret); + + return ret; + } +}; // class NuclearAgentImpl::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class NuclearAgentImpl +//----------------------------------------------------------------------------- +NuclearAgentImpl::NuclearAgentImpl(const QString &debugInfo) + : NuclearAgent() // Interface + , BaseClass(debugInfo) // Implementation + , d(new Private) + , m_logger(getLogger("NuclearAgentImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_fillColorAlpha = 200; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +NuclearAgentImpl::~NuclearAgentImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d->m_centerImage; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void NuclearAgentImpl::setUniqueId(const UniqueMapId &uniqueId) +{ + BaseClass::setUniqueId(uniqueId); + d->m_uniqueMapId = m_uniqueMapId; +} + + +PositionCont NuclearAgentImpl::subsidiaryLine(int key) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->hasPathObject(key)) + return d->getPathObject(key).basicPathObj->positions(); + else + return PositionCont(); +} + +void NuclearAgentImpl::setSubsidiaryLine(int key, const PositionCont &positions, const OptionalData &optData) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", key=" << key + << ", closed=" << optData.closed + << ", positions.size=" << positions.size(); + + Private::ValueDataNA vd = d->getPathObject(key, optData); + QSharedPointer pathObj = vd.basicPathObj; + if (pathObj) + pathObj->setPositions(positions); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QList NuclearAgentImpl::subsidiaryKeysInUse() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->keysInUse(); +} + +void NuclearAgentImpl::removeSubsidiaryLine(int key) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->remove(key); +} + +void NuclearAgentImpl::removeSubsidiaryLines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->clear(); +} + +Point3D NuclearAgentImpl::impactOrigin() const +{ + return BaseClass::tacticalOrigin(); +} + +void NuclearAgentImpl::setImpactOrigin(const Point3D &impactOrigin) +{ + BaseClass::setTacticalOrigin(impactOrigin); +} + +void NuclearAgentImpl::setDetonation(const Point3D &origin, double radius) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + BaseClass::setDetonation(origin, radius); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool NuclearAgentImpl::hasDetonation() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return BaseClass::hasDetonation(); +} + +void NuclearAgentImpl::resetDetonation() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + BaseClass::resetDetonation(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool NuclearAgentImpl::updateViewImpl() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + // centerImage: + if (!d->m_centerImage) + { + d->m_centerImage = + new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#CenterImage"), + m_uniqueMapId); + } +// if (partyChanged()) + { + // QImage img = renderImpactSymbol(partyColor(), 20, true); // filled + QImage img = renderImpactSymbol(m_mainGraphicalObject->lineStyle().lineColor(), 20, true); // filled + d->m_centerImage->setImage(img); + } + +// m_centerImage->setScale(m_mainGraphicalObject->scale()); // do not scale centerImage, equal to mine of MineArea + + d->m_centerImage->setOrigin(m_tacticalOrigin.value().originTranslated()); + d->m_centerImage->setVisibility(m_visibility && m_visibilityTechnical); + + + + bool success = BaseClass::updateViewImpl(); + + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + return false; + } + + if (nullptr == m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_mainGraphicalObject"; + return false; + } + + + + + // subsidiary : + + Private::MAP_IT_T mapIt(d->m_cont); + while (mapIt.hasNext()) + { + mapIt.next(); + const Private::ValueDataNA &vd = mapIt.value(); + QSharedPointer pathObj = vd.basicPathObj; + const OptionalData &optData = vd.optData; + if (pathObj) + { +// pathObj->setDebugInfo(m_mainGraphicalObject->debugInfo()); // will overwrite complete string -> skip + + if (optData.lineStyle) + pathObj->setLineStyle((*optData.lineStyle.data())); + else + pathObj->setLineStyle(m_mainGraphicalObject->lineStyle()); + + // transfer fillStyle only if relevant: + if (pathObj->pathType() == GraphicalObjectPathType::Polygon) + { + if (optData.fillStyle) + pathObj->setFillStyle((*optData.fillStyle.data())); + else + pathObj->setFillStyle(m_mainGraphicalObject->fillStyle()); + } + + pathObj->setVisibility(m_visibility); + pathObj->setVisibility(ObjectAppearance::Technical, m_visibilityTechnical); + + // Do not Transfer symbol. Only m_mainGraphicalObject uses ObjectAppearance::Tactical: +// pathObj->setSymbol(this->symbol()); + + + pathObj->setVisibility(ObjectAppearance::Tactical, false); + + pathObj->setPickable(m_mainGraphicalObject->pickable()); + + pathObj->setSelected(m_mainGraphicalObject->selected()); + + pathObj->setDrawOrder(m_internalDrawOrder); + + success = pathObj->updateView(); + if (!success) + { + int key = mapIt.key(); + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed, key=" << key; + } + } + } // while + + // : subsidiary + + + // singleSceneMarker : + updateSingleSceneMarker(m_visibility); // inclusive updateView() + // : singleSceneMarker + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool NuclearAgentImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500002719000000"); // SymbolSet = 25 271900 + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUMPNR----"); // MIL2525D: 1388 GUMPNR---- -> Polygon (Radioactive Area U) +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUMPNZ----"); // MIL2525D: 0359 GUMPNZ---- -> Point (Nuclear Detonations Ground Zero U) + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUCPBWR---"); // APP6B: 3935 GUCPBWR--- -> Polygon +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUCPBWP---"); // APP6B: 2871 GUCPBWP--- -> Point (Fallout Producing U) + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::yellow, 2)); + setFillStyle(FillStyle(Qt::yellow, FillPattern::Null)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + // set some values to Private: + d->m_mapImpl = m_mapImpl; + d->m_logicalLayer = m_logicalLayerName; + d->m_debugInfo = m_debugInfo; + d->m_uniqueMapId = m_uniqueMapId; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool NuclearAgentImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool successCenterImage = true; + if (d->m_centerImage) + { + bool visiScale = m_visibilityScaleRange.scaleHasVisibility(m_mapImpl->scale()); + d->m_centerImage->setVisibility(visiScale && m_visibility && m_visibilityTechnical); + successCenterImage = d->m_centerImage->updateView(); + } + + bool success = BaseClass::onMapExtentChanged() && successCenterImage; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.h new file mode 100755 index 000000000..ebae69f6d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/nuclear_agent_impl.h @@ -0,0 +1,106 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of NuclearAgent + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The NuclearAgentImpl class displays a nuclear area. The outline is displayed as well a subsidiary lines. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance / outline) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + * - Container of BasicPathObjectImpl's to display the subsidiary lines. + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT NuclearAgentImpl : public BasicPathObjectImpl, public virtual NuclearAgent +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit NuclearAgentImpl(const QString &debugInfo = QString()); + virtual ~NuclearAgentImpl(); + +public: + NuclearAgentImpl(const NuclearAgentImpl &other) = delete; + NuclearAgentImpl &operator=(NuclearAgentImpl &other) = delete; + + void setUniqueId(const UniqueMapId &uniqueId) override; + + /*! \name Subsidiary polylines, identified by an arbitrary choose key.*/ + //@{ + PositionCont subsidiaryLine(int key) const override; + void setSubsidiaryLine(int key, const PositionCont &positions, const OptionalData &optData = OptionalData()) override; + + QList subsidiaryKeysInUse() override; + + void removeSubsidiaryLine(int key) override; + void removeSubsidiaryLines() override; + //@} + + /*! \name Impact*/ + //@{ + Point3D impactOrigin() const override; + void setImpactOrigin(const Point3D &impactOrigin) override; + //@} + + + /*! \name Single Detonation */ + //@{ + void setDetonation(const Point3D &origin, double radius) override; + bool hasDetonation() const override; + void resetDetonation() override; + //@} + + +protected: + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + bool onMapExtentChanged() override; + + // internal/admin + + + // logical + + +private: + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.cpp new file mode 100755 index 000000000..400d4a08c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.cpp @@ -0,0 +1,1237 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Participant + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "participant_impl.h" +#include "basic_path_object_impl.h" +#include +#include +#include +#include +#include // for renderExtensions +#include // +#include + + +// ESRI +#include + + +// Qt +#include +#include +#include +#include + +#ifdef LTS_DEBUG +# include +#endif + +#include + + +namespace SituationMap +{ + +// to get access to 'init' +class DesignationLocalBasicPathObject final : public BasicPathObjectImpl +{ + DesignationLocalBasicPathObject() {} + virtual ~DesignationLocalBasicPathObject() {} +public: + using BasicPathObjectImpl::BasicPathObjectImpl; + using BasicPathObjectImpl::init; + using BasicPathObjectImpl::setUniqueId; +}; + +//----------------------------------------------------------------------------- +// class ParticipantImpl +//----------------------------------------------------------------------------- +ParticipantImpl::ParticipantImpl(const QString &debugInfo) + : Participant() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("ParticipantImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_origin.setMapObjectImpl(this); + initTextModifierData(); + + m_movementTrackCoords.setMapObjectImpl(this); + + // m_symbolResultWatcher is canceled on destruction. + // So it`s safe that no signal will be fired afterwards. + // To avoid CLang warnings use m_symbolResultWatcher as context (3rd para), although it´s not intended. + // Better idea? + QObject::connect(&m_symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady, + &m_symbolResultWatcher, std::bind(&ParticipantImpl::onSymbolResultReady, this)); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void ParticipantImpl::onSymbolResultReady() +{ + QSharedPointer symResult = m_symbolResultWatcher.symbolResult(); + + if (AdvancedSymbology::SymbolDictionary::doParanoiaChecks() && (! symResult)) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " no symResult..." + << " m_symDataToRender= '" << m_symDataToRender.toString(); + abort(); + } + + + if (! symResult) + return; // should NEVER happen! (Which object should have triggered the signal???) + + const SymbolImage &symImage = symResult->symbolImage(); + + if (AdvancedSymbology::SymbolDictionary::doParanoiaChecks()) + { + if (m_symDataToRender.sidc != symResult->symbolDataToRender().sidc || + m_symDataToRender.role != symResult->symbolDataToRender().role) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " resultReady...sidc or role mismatch..." + << " m_symDataToRender= '" << m_symDataToRender.toString() + << " symResult->symbolDataToRender()= '" << symResult->symbolDataToRender().toString(); + abort(); + } + + if (symImage.image().isNull()) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " image is null"; + abort(); + } + } + + // For preview purpose: + m_symbolImage.setValid(true); + m_symbolImage.setImage(symImage.image()); + + if (m_mainGraphicalObject) + { + GraphicalObjectPictureMarker *goPicture = + static_cast(m_mainGraphicalObject); + + goPicture->setImage(symImage.image()); + if (!goPicture->updateView()) + FATAL_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " updateView failed"; + + + // Note: + // XML-Symbols have their own/symbol-specific scale. + // We don´t have that value directly here, so we have to 'reinterpret' + // the final image: + // do this calculation only for Xml symbols, + // otherwise the other symbols will scale in two steps if you modify the slider + if (AdvancedSymbology::SidcCodec::decodeSidcType(symResult->symbolDataToRender().sidc) == AdvancedSymbology::SidcCodec::SidcType::CivilXml) + { + int imageSize = actualImage().size().width(); // width and heigth are equal + if (imageSize > 0) + m_scale4XmlSymbolWithSymbolSpecificScale = 0.7 * imageSize / (51 * m_scale.value() * m_symbol.value().size); // 51 == EsriSymbolSize + } + } else { + if (AdvancedSymbology::SymbolDictionary::doParanoiaChecks()) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " no m_mainGraphicalObject"; + abort(); + } + } +} + + +ParticipantImpl::~ParticipantImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // clear all LaserDesignations: + m_designationData.reset(); + + for (const auto &mod : m_textModifierCont) + delete mod.goText; + + // abort pending call: + m_symbolResultWatcher.cancel(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +AdvancedSymbology::SymbologyType ParticipantImpl::symbologyType() const +{ + return (m_dictionary) ? m_dictionary->symbologyType() : AdvancedSymbology::SymbolDictionary::defaultSymbologyType(); +} // AdvancedSymbology::SymbologyType ParticipantImpl::symbologyType() const + + +void ParticipantImpl::initTextModifierData() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + TextModifierData data; + data.verticalAlignment = VerticalAlignment::Bottom; + + // right side: + data.horizontalAlignment = HorizontalAlignment::Left; + + int xOffset = 0; // pixel + int yOffset = -9; // pixel + + const int x = 27; + + // right side: + + data.offset = QPoint(x + xOffset, 30 + yOffset); + m_textModifierCont[ static_cast(TextModifier::EF_ReinforcedReduced) ] = data; + + data.offset = QPoint(x + xOffset, 15 + yOffset); + m_textModifierCont[ static_cast(TextModifier::G_StaffComments) ] = data; + + data.offset = QPoint(x + xOffset, yOffset); + m_textModifierCont[ static_cast(TextModifier::H_AdditionalInformation) ] = data; + + data.offset = QPoint(x + xOffset, -15 + yOffset); + m_textModifierCont[ static_cast(TextModifier::M_HigherFormation) ] = data; + + data.offset = QPoint(x + xOffset, -30 + yOffset); + m_textModifierCont[ static_cast(TextModifier::JKLNP) ] = data; + // left side: + data.horizontalAlignment = HorizontalAlignment::Right; + + data.offset = QPoint(-x + xOffset, 30 + yOffset); + m_textModifierCont[ static_cast(TextModifier::W_DateTimeGroup) ] = data; + + data.offset = QPoint(-x + xOffset, 15 + yOffset); + m_textModifierCont[ static_cast(TextModifier::XY_AltitudeLocation) ] = data; + + data.offset = QPoint(-x + xOffset, yOffset); + m_textModifierCont[ static_cast(TextModifier::V_Type) ] = data; + + data.offset = QPoint(-x + xOffset, -15 + yOffset); + m_textModifierCont[ static_cast(TextModifier::T_UniqueDesignation) ] = data; + + data.offset = QPoint(-x + xOffset, -30 + yOffset); + m_textModifierCont[ static_cast(TextModifier::Z_Speed) ] = data; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void ParticipantImpl::setDebugInfo(const QString &debugInfo) +{ + // Do NOT call the baseclass, + // ParticipantImpl will create m_mainGraphicalObject only on demand. + m_debugInfo = debugInfo; +} + + +QString ParticipantImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + str.append(QStringLiteral(" symbol='%1'").arg(m_symbol.value().toString())); + return str; +} + +Point3D ParticipantImpl::origin() const +{ + return m_origin.origin(); +} +bool ParticipantImpl::setOrigin(const Point3D &origin) +{ + m_updateObjectIndicator = m_origin.setOrigin(origin) || m_updateObjectIndicator; + return m_updateObjectIndicator; +} + + +// Just to enable using as AES item (template@AdapterDds) +void ParticipantImpl::setPositions(const PositionCont &posCont) +{ + Q_UNUSED(posCont); + // Should not be called +} + + +void ParticipantImpl::boundingBox(BoundingBox &bbox) const +{ + GraphicalObjectPictureMarker *goPicture = + static_cast(m_mainGraphicalObject); + + if (goPicture && !goPicture->image().isNull()) + { + const QSize &imgSize = goPicture->image().size(); // maybe a HotSpot has to be honored!? + + bbox = BoundingBox(BoundingBox::ExtentType::Pixel, + m_origin.origin(), // Sim + imgSize.width(), imgSize.height() // pixel + ); + } +} + +QColor ParticipantImpl::majorColor() const +{ + const QColor &partyCol = partyColor(); + return partyCol; +} + + +float ParticipantImpl::userSymbolOpacity() const +{ + return m_userSymbolOpacity.value(); +} +void ParticipantImpl::setUserSymbolOpacity(float userSymbolOpacity) +{ + m_userSymbolOpacity = userSymbolOpacity; +} + +SymbolDataToRender ParticipantImpl::symbolDataToRender(bool ignoreForcedColor) const +{ + if (ignoreForcedColor && m_party.forcedPartyColor.value().isValid()) + { + SymbolDataToRender copy = m_symDataToRender; + copy.fillColor = m_party.partyColor; + return copy; + } + return m_symDataToRender; +} + +QImage ParticipantImpl::actualImage() const +{ + if (! m_mainGraphicalObject) + return QImage(); + + GraphicalObjectPictureMarker *goPicture = + static_cast(m_mainGraphicalObject); + + return goPicture->image(); +} + +float ParticipantImpl::scale() const +{ + return m_scale.value(); +} +void ParticipantImpl::setScale(float scale) +{ + m_scale = scale; +} + + +bool ParticipantImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_debugInfo=" << m_debugInfo + << " m_party.partyId=" << m_party.partyId + << " m_party.changed()=" << m_party.changed() + << " m_origin()=" << m_origin.origin() + << " m_height=" << m_height + << " m_origin.originTranslated()=" << m_origin.originTranslated() + << " visibility=" << m_visibility + << " m_symbol.changed=" << m_symbol.changed() + << " m_symbol=" << m_symbol.value().toString() + << " extensionsChanged=" << m_extensionsChanged + << " m_movementTrackCoords= " << m_movementTrackCoords.positions(); + + // Prepare / create : + if (!m_mainGraphicalObject) + { + m_mainGraphicalObject = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#Image"), + m_uniqueMapId); + // to avoid incorrect number of positions, set them already here: + m_mainGraphicalObject->setOrigin(m_origin.originTranslated()); + } + // : Prepare / create + + TRACE_LOG(m_logger) << LOGFUNCTION << " START2" + << " m_debugInfo=" << m_debugInfo + << " m_scale=" << m_scale.value() + << " m_scaleChanged=" << m_scale.changed(); + + bool successPicture = true; + const bool visiSwitchedChanged = m_mainGraphicalObject->visibility() != m_visibility; + + // scenario: object invisible, partyId changes, object becomes visible: + // So we have to force an update of the cached partyColor: + if (visiSwitchedChanged && m_visibility) + { + // enforce following block (fetchSymbol) being proccessed + // Szenario: + // 'Organize Participants': add template to Pool (ParticipantImpl are created) + // Move them to Attached (partyId is updated, invisible) + // On 'Move Participants' the become visible, but partyId does NOT change. + // So the symbol was still ocker + m_party.partyId.setChanged(); + m_party.partyColor = QColor(); // set invalid to force cache update + + // NLDEVCTRL: Ausfallkreuz Teilnehmersymbol + // Auch diese Farbe zurücksetzen: + m_injuryData.resetChanged(); + } + + if (m_visibility) + { + if (m_mainGraphicalObject) + { + GraphicalObjectPictureMarker *goPicture = + static_cast(m_mainGraphicalObject); + + goPicture->setOrigin(m_origin.originTranslated()); + + + if (m_scale.changed() || + m_symbol.changed() || + m_userSymbolOpacity.changed() || + m_marked.changed() || + m_party.changed() || + m_extensionsChanged || + m_injuryData.changed) + { + m_symDataToRender = m_symbol.value().getDefaultRenderData(); + + bool isIdentityAssumed = false; + + if (!m_symDataToRender.sidc.isEmpty()) + { + AdvancedSymbology::SidcBuilder sidcBuilder(m_dictionary); + if (sidcBuilder.setSidc(m_symDataToRender.sidc)) + isIdentityAssumed = sidcBuilder.isIdentityAssumed(); + else + DEBUG_LOG(m_logger) << LOGFUNCTION << " sidcBuilder.setSidc failed" + << " m_debugInfo=" << m_debugInfo; + } + + + m_symDataToRender.role = this->partyRole(isIdentityAssumed); + + int widthPixel = SituationMap::SymbolSizeLdefault; // * m_mapImpl->symbologyScaleFactor(); + widthPixel *= m_symbol.value().size * m_scale.value(); + m_symDataToRender.sizeL = widthPixel; + + // main symbol color + const QColor &partyCol = partyColor(); + m_symDataToRender.fillColor = partyCol; + // use userSymbolOpacity only for fillColor: + m_symDataToRender.fillColor.setAlpha(m_userSymbolOpacity.value() * 255); + m_symDataToRender.fillColorReplace = (m_party.partyId() >= 0); + + // add other data + m_symDataToRender.injury = m_injuryData.injury; + m_symDataToRender.injuryColor = injuryColorInternal(); + m_symDataToRender.marking = m_marked.value(); + m_symDataToRender.additionalSymbols = m_additionalSymbols; + + if (goPicture->image().isNull()) + goPicture->setImage(m_dictionary->temporaryImage(m_symDataToRender.sizeL)); + + successPicture = true; + + // until the result is delivered: + m_scale4XmlSymbolWithSymbolSpecificScale = 1.0; + + // you expect findImage - correct + // But findImage handles multiPoint object special, which is not relevant here. + // So, in this case (for pointSymbols) use fetchSymbol: + m_symbolResultWatcher.setSymbolResult(m_dictionary->fetchSymbol(m_symDataToRender)); + } + + } // if (m_mainGraphicalObject) + + } + + // + // MovementTrack : + // + // MovementTrackData: + + TRACE_LOG(m_logger) << LOGFUNCTION + << " m_debugInfo=" << m_debugInfo + << " m_movementTrackEnabled=" << m_movementTrackEnabled + << ", m_movementTrackCoords.size=" << m_movementTrackCoords.size() + << ", m_movementTrackVisibility=" << m_movementTrackVisibility; + + bool successMovementTrack = true; + if (!m_movementTrackEnabled) + { + m_movementTrackCoords.clear(); + m_movementTrack.clear(); // destroy GraphicalObjectPath + } // if (m_movementTrackEnabled) + + // Display: + if (m_movementTrackCoords.size() >= 2) + { + if (! m_movementTrack) + { + m_movementTrack = + QSharedPointer(new GraphicalObjectPath(GraphicalObjectPathType::Polyline, + m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QStringLiteral("#Track"), + m_uniqueMapId)); + m_movementTrack->setLineStyle(LineStyle(partyColor(), 2, LinePattern::Solid)); + } + // move one layer backwards to put the Track BEHIND the image (drawOrder==0): + m_movementTrack->setDrawOrder(m_internalDrawOrder - 1); + + m_movementTrack->setPositions(m_movementTrackCoords.positionsTranslated()); + + if (m_party.changed()) + m_movementTrack->rLineStyle().setLineColor(partyColor()); + + if (m_scale.changed()) + m_movementTrack->setScale(m_scale.value()>1.0f ? m_scale.value() : 1.0f); + + m_movementTrack->setVisibility(m_visibility && m_visibilityTechnical && m_movementTrackVisibility); + successMovementTrack = m_movementTrack->updateView(); + } + else + m_movementTrack.clear(); // destroy GraphicalObjectPath + // : MovementTrack + + bool successTextModifier = updateViewImplTextModifier(); + bool successRanges = updateViewImplRanges(); + bool successDesignation = updateViewImplDesignation(); + + bool successDirectionIndicators = updateViewImplDirectionIndicators(); + + bool success = BaseClass::updateViewImpl() && + successPicture && + successTextModifier && + successRanges && + successDesignation && + successDirectionIndicators && + successMovementTrack; + + // reset changed flags is done via updateViewImplPost + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", success=" << success; + return success; +} + +bool ParticipantImpl::updateViewImplTextModifier() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = true; + + const int size = static_cast(TextModifier::END); + for (int i = 0; i < size; ++i) + { + TextModifierData &tmData = m_textModifierCont[i]; + + if (tmData.text.isEmpty()) + { + // text empty + delete tmData.goText; + tmData.goText = nullptr; + } + else + { + // text filled: + if (!tmData.goText) + { + QString debugInfo = QStringLiteral("%1#textMod(%2)").arg(m_debugInfo).arg(i); + + tmData.goText = new GraphicalObjectText(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + debugInfo, + m_uniqueMapId); + } + } + + if (tmData.goText) + { + const bool bNewVisibility = tmData.visible && m_visibility; + tmData.goText->setVisibility(bNewVisibility); + + // save following fo invisble object + if (bNewVisibility) + { + tmData.goText->setText(tmData.text); + // move one layer topwards to let the GraphicalObjectRange stay behind: + tmData.goText->setDrawOrder(m_internalDrawOrder + 1); + + TextStyle ts; + const bool bigger = (i == static_cast(TextModifier::EF_ReinforcedReduced) && + tmData.text.compare(QLatin1String("+")) == 0); + + const float finalScale = m_scale.value() * m_symbol.value().size * m_scale4XmlSymbolWithSymbolSpecificScale; + ts.setSize(finalScale * (bigger ? 22.5 : 15)); + + if (m_party.partyId() < 0) + ts.setTextColor(QColor(Qt::black)); + else + ts.setTextColor(partyColor()); + + // Alignment: + ts.setHorizontalAlignment(tmData.horizontalAlignment); + ts.setVerticalAlignment(tmData.verticalAlignment); + tmData.goText->setTextStyle(ts); + + // Offset: + tmData.goText->setXOffset(finalScale * static_cast(tmData.offset.x())); + tmData.goText->setYOffset(finalScale * static_cast(tmData.offset.y())); + } + + // origin is required for updateView (otherwise 'incorrect number of positions'): + // (originTranslated is cached) + tmData.goText->setOrigin(m_origin.originTranslated()); + + success = success && tmData.goText->updateView(); + } + + } // for + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", success=" << success; + return success; +} + +QImage generateDirectionIndicatorImage(int sizeL, + const QColor &defaultColor, + const QMap &directionIndicatorCont) +{ + const int fullSize = sizeL * 1.7 * 2.0; // try and error: '1.7'; + + QImage fullImage(fullSize, fullSize, QImage::Format_ARGB32); + fullImage.fill(Qt::transparent); + +#ifdef NEVER + // for debugging : + if (1) + { + QPainter painter(&fullImage); + QPen pen(Qt::black); + pen.setWidth(2.0); + painter.setPen(pen); + painter.drawRect(fullImage.rect()); + } + // : for debugging +#endif + + QPainter painter(&fullImage); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + + const QPointF center((fullImage.width() / 2), (fullImage.height() / 2)); + const QPointF dest(center.x(), 0); + const int width = fullImage.size().width(); + const int height = fullImage.size().height(); + + for (const auto &item : directionIndicatorCont) + { + QColor finalColor = item.color.isValid() ? item.color : defaultColor; + + QPen pen(finalColor); + pen.setWidthF(item.lineWidth * double(sizeL)/SituationMap::SymbolSizeLdefault); + pen.setStyle(item.lineStyle); + painter.setPen(pen); + + painter.save(); + + painter.translate(width / 2, height / 2); + painter.rotate(item.directionDegree); + painter.translate(-width / 2, -height / 2); + + // line + painter.drawLine(center, dest); // | to top + + // arrow + QPen penArrow(finalColor); + penArrow.setWidthF(item.lineWidth * double(sizeL)/SituationMap::SymbolSizeLdefault); + painter.setPen(penArrow); + + QPolygon poly; + poly.reserve(3); + + int aLength = 6 * pen.width(); + int aWidth = 2 * pen.width(); + + // 2 || (dest) + // | | + // 1 | | 3 + + poly.append(dest.toPoint() + QPoint(-aWidth, aLength) ); + poly.append(dest.toPoint()); + poly.append(dest.toPoint() + QPoint(+aWidth, aLength)); + + painter.drawPolyline(poly); + + painter.restore(); + } // for + + return fullImage; +} + + +bool ParticipantImpl::updateViewImplDirectionIndicators() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_directionIndicatorCont.count()=" << m_directionIndicatorCont.count(); + + bool success = true; + + if (m_directionIndicatorCont.isEmpty()) + m_directionIndicatorPms.clear(); // destroy object + else + { + // needs to be created? + if (!m_directionIndicatorPms) + { + m_directionIndicatorPms = QSharedPointer(new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#DirectionIndImage"), + m_uniqueMapId)); + } + const QImage &image = generateDirectionIndicatorImage(m_symDataToRender.sizeL, majorColor(), m_directionIndicatorCont); + m_directionIndicatorPms->setImage(image); + m_directionIndicatorPms->setOrigin(m_origin.originTranslated()); + m_directionIndicatorPms->setVisibility(m_mainGraphicalObject->visibility()); + m_directionIndicatorPms->setSelected(m_selected); + + // move one level backwards to put the GraphicalObjectRange BEHIND the image (drawOrder==0), but on top of WaeaponRanges: + m_directionIndicatorPms->setDrawOrder(m_internalDrawOrder - 1); + + success = m_directionIndicatorPms->updateView(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", success=" << success; + return success; +} + +bool ParticipantImpl::updateViewImplRanges() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " number Ranges=" << m_ranges.value().count(); + + bool success = true; + + const QVector &ranges = m_ranges.value(); + int size = ranges.size(); + m_graphObjRanges.resize(size); + + for (int i = 0; i < size; ++i) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " i=" << i; + + // get some values/references: + int range = ranges[i]; + QSharedPointer &goRange = m_graphObjRanges[i]; + const bool visi = m_visibility && m_rangesVisibility; + + if (!visi) + { + goRange.clear(); // destroy object + } + else + { + if (goRange.isNull()) + { + goRange = QSharedPointer + (new GraphicalObjectRange(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , QStringLiteral("%1#Range_i(%2)_r(%3)").arg(m_debugInfo).arg(i).arg(range) + , m_uniqueMapId) + ); + } + + // move two level backwards to put the GraphicalObjectRange BEHIND the image (drawOrder==0): + goRange->setVisibility(visi); + goRange->setDrawOrder(m_internalDrawOrder - 2); + goRange->setOrigin(m_origin.originTranslated()); + goRange->setRange(range, i * 15); + goRange->setLineStyle(LineStyle(partyColor(), 3, LinePattern::Solid)); + goRange->setFillStyle(FillStyle(Qt::black, FillPattern::Null)); + + bool updated = goRange->updateView(); + success = success && updated; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " updated=" << updated; + } // if (!visi) + + }// for + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", success=" << success; + return success; +} + +bool ParticipantImpl::updateViewImplDesignation() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", m_debugInfo=" << m_debugInfo + << ", m_designationData.value().size()=" << m_designationData.value().size(); + bool success = true; + + const QVector &designationDataVec = m_designationData.value(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " START 11" << ", m_debugInfo=" << m_debugInfo + << " m_designationMapObjects.size() = " << m_designationMapObjects.size() + << " m_designationData.value().size() = " << designationDataVec.size(); + + m_designationMapObjects.resize(designationDataVec.size()); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " START 22" << ", m_debugInfo=" << m_debugInfo + << " m_designationMapObjects.size() = " << m_designationMapObjects.size() + << " m_designationData.value().size() = " << designationDataVec.size(); + + int idx = -1; + for (const Participant::DesignationData &designationData : std::as_const(designationDataVec)) + { + ++idx; + + QSharedPointer &bpoRef = m_designationMapObjects[idx]; + if (bpoRef.isNull()) + { + QString debugInfo = QString(QStringLiteral("%1#Designation_code_%2")).arg(m_debugInfo).arg(idx); + DesignationLocalBasicPathObject *designationDPO = new DesignationLocalBasicPathObject(debugInfo); + + success = designationDPO->init(m_mapImpl, m_logicalLayerName, GraphicalObjectPathType::Polyline); + designationDPO->setUniqueId(m_uniqueMapId); + designationDPO->setArrowHeadEnd(true); + if (success) + { + bpoRef = QSharedPointer(static_cast(designationDPO)); + } else { + ERROR_LOG(m_logger) << LOGFUNCTION << ", m_debugInfo=" << m_debugInfo + << " designationPO->init failed"; + designationDPO->removeMySelf(); + continue; + } + } + + // since DesignationLocalBasicPathObject is a MapObject (and not GraphicalObject) + // untranslated coords have to be used here: + bpoRef->setPositions({ this->origin(), designationData.spotLocation }); + bpoRef->setTextOnSegment(0, QObject::tr("Code: %1").arg(designationData.code)); + bpoRef->setSelected(m_selected); + bpoRef->setLineStyle(LineStyle(partyColor(), 3, LinePattern::DashDot)); // partyColor may change + bpoRef->setVisibility(m_visibility); + + const bool updated = bpoRef->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" << ", m_debugInfo=" << m_debugInfo << " updated= " << updated; + + success = success && updated; + + DEBUG_LOG(m_logger) << LOGFUNCTION << ", m_debugInfo=" << m_debugInfo + << ", updated= " << updated + << ", success= " << success; + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", m_debugInfo=" << m_debugInfo + << ", success=" << success; + return success; +} + +bool ParticipantImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + if (mapImpl) + m_dictionary = &(m_mapImpl->symbolDictionary()); + + success = success && (m_dictionary); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", success=" << success; + return success; +} + +const QColor &ParticipantImpl::partyColor() const +{ + if (m_party.forcedPartyColor.value().isValid()) + return m_party.forcedPartyColor.value(); + + if ((m_party.partyId.value() == -1) && m_symbol.value().color.isValid()) + return m_symbol.value().color; + + if (m_party.partyId.changed() || m_party.useLightColor.changed() || !m_party.partyColor.isValid()) + { + m_party.partyColor = + m_mapImpl->partyIdToColorFunctor().partyIdToColor(m_party.partyId.value(), + m_party.useLightColor.value()); + } + return m_party.partyColor; +} + +QColor ParticipantImpl::partyColor(int partyId, bool isOutOfDate) const +{ + return BaseClass::partyColor(partyId, isOutOfDate); +} + + +//#define NEVER +#ifdef NEVER +QStringList sl; +sl << "uniquedesignation"; +sl << "type"; +sl << "datetimevalid"; +sl << "quantity"; + +sl << "speed"; +sl << "staffcomment"; +sl << "z"; +sl << "x"; +sl << "z/x"; +sl << "y"; +sl << "combateffectiveness"; +sl << "iff_sif"; + + +for (const QString &item : sl) + m_message->setProperty(item, QString("%1_V").arg(item)); + + +// toJson: +// "{"type":"esriMultiPartCIMSymbol","parts":[{"id":"GHMPNZ----_mil2525c"," +// content":[{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}}]},"horizontalAlignment":"Left","verticalAlignment":"Baseline","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text": +// +// [uniquedesignation]","textPlacement":{"type":"point","method":"southwest","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}}]},"horizontalAlignment":"Left","verticalAlignment":"Baseline","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text": +// +// [type]","textPlacement":{"type":"point","method":"west","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1, +// pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}}]},"horizontalAlignment":"Left","verticalAlignment":"Baseline","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":"" + +// [datetimevalid]","textPlacement":{"type":"point","method":"northwest","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers": +// [{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}}]},"horizontalAlignment":"Left","verticalAlignment":"Baseline","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100, +// "text":"[x],[y]","textPlacement": +// {"type":"point","method":"south","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round", +// "joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}}]},"horizontalAlignment":"Left","verticalAlignment":"Baseline","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":" + +// [quantity]","textPlacement":{"type":"point","method":"north","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer", +// "fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1, +// "pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10, +// "alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}}]},"horizontalAlignment":"Left","verticalAlignment":"Baseline","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":" + +// [additionalinformation]", "textPlacement":{"type":"point","method":"northeast","offset":1}}]},{"type":"CIMPointSymbol", +// "symbolLayers":[{"type":"CIMVectorMarker","size":25,"anchorPoint":{"x":0,"y":0},"anchorPointUnits":"Relative","scaleStrokesAndFills":true,"offsetY":9,"dominantSizeAxis3D":"Z","frame":{"xmin":0,"ymin":0,"xmax":515,"ymax":515},"markerGraphics":[{"type":"CIMMarkerGraphic", +// "geometry":{"rings":[[[209.8,357.1],[305.3,357.1],[305.3,91.8],[209.8,91.8],[209.8,357.1]]]},"symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":6.432937632884479, +// "pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]},"capStyle": +// "Butt","joinStyle":"Miter","miterLimit":112,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true}, +// {"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,255,128,255]}}]}}, +// {"type":"CIMMarkerGraphic","geometry":{"rings":[[[232.1,197.9],[232.1,258.6],[240.4,258.6],[272.3,210.9],[272.3,258.6],[280,258.6],[280,197.9],[271.8,197.9],[239.8,245.6],[239.8,197.9],[232.1,197.9]]]}," +// symbol":{"type":"CIMPolygonSymbol","symbolLayers": +// [{"type":"CIMFilledStroke","width":1.2774627923458541,"pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}, +// "capStyle":"Butt","joinStyle":"Miter","miterLimit":112,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true}, +// {"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}}]}}, +// {"type":"CIMMarkerGraphic","geometry":{"rings":[[[257.5,357.1],[124.9,357.1],[128.3,385.1],[136.5,412.4],[148.5,435.9],[164.2,455.3],[182.9,470.3],[204.7,481.1],[229.6,487.6],[257.5,489.7],[285.4,487.6],[310.3,481.1],[332.2,470.3],[350.9,455.3],[366.5,435.9],[378.5,412.4],[386.7,385.1],[390.2,357.1],[257.5,357.1]]]}, +// "symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":6.432937632884479,"pattern":{"type":"CIMSolidPattern","color":[255,0,0,255]}, +// "capStyle":"Butt","joinStyle":"Miter","miterLimit":112,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true}, +// {"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[255,255,128,255]}}]}}]}]}]}]}" (ication/ngl_qt_msg_handler.inc:21) + +// Second: +// 2015-05-06 09:39:18,903 INFO SituationMap.MapImpl |#| SituationMap::MapImpl::onMouseRelease onMouseRelease for left mouse button gId= 4 symbol.toJson= '{"type":"esriMultiPartCIMSymbol","parts":[{"id":"SFGPUCD---_mil2525c","content":[{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[0,0,0,255]}}]},"horizontalAlignment":"Left","verticalAlignment":"Baseline","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":"[uniquedesignation] +// [speed]","textPlacement":{"type":"point","method":"southwest","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[0,0,0,255]}}]},"horizontalAlignment":"Center","verticalAlignment":"Center","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":"[datetimevalid] +// [z]/[x],[y]","textPlacement":{"type":"point","method":"northwest","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[0,0,0,255]}}]},"horizontalAlignment":"Center","verticalAlignment":"Center","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":"[reinforced] +// [staffcomment]","textPlacement":{"type":"point","method":"northeast","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[0,0,0,255]}}]},"horizontalAlignment":"Center","verticalAlignment":"Center","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":"[additionalinformation]","textPlacement":{"type":"point","method":"east","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMTextLayer","fontFamilyName":"Arial","fontStyleName":"Bold","symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":1,"pattern":{"type":"CIMSolidPattern","color":[255,255,255,255]},"capStyle":"Round","joinStyle":"Round","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[0,0,0,255]}}]},"horizontalAlignment":"Center","verticalAlignment":"Center","textCase":"Normal","textDirection":"LTR","height":8.25,"letterWidth":100,"wordSpacing":100,"text":"[higherformation] +// [combateffectiveness]/[iff_sif]","textPlacement":{"type":"point","method":"southeast","offset":1}}]},{"type":"CIMPointSymbol","symbolLayers":[{"type":"CIMVectorMarker","size":25,"anchorPoint":{"x":0,"y":0},"anchorPointUnits":"Relative","scaleStrokesAndFills":true,"dominantSizeAxis3D":"Z","frame":{"xmin":0,"ymin":0,"xmax":710,"ymax":530},"markerGraphics":[{"type":"CIMMarkerGraphic","geometry":{"rings":[[[85,445],[85,85],[625,85],[625,445],[85,445]]]},"symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":15,"pattern":{"type":"CIMSolidPattern","color":[0,0,0,255]},"capStyle":"Butt","joinStyle":"Miter","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[128,224,255,255]}}]}},{"type":"CIMMarkerGraphic","geometry":{"rings":[[[91,85],[113,120],[138,151],[168,178],[201,200],[237,219],[275,232],[314,240],[355,243],[396,240],[435,232],[473,219],[509,200],[542,178],[571,151],[597,120],[619,85],[91,85]]]},"symbol":{"type":"CIMPolygonSymbol","symbolLayers":[{"type":"CIMFilledStroke","width":15,"pattern":{"type":"CIMSolidPattern","color":[0,0,0,255]},"capStyle":"Butt","joinStyle":"Miter","miterLimit":10,"alignment":"Center","lineStyle3D":"Strip","patternFollowsStroke":true},{"type":"CIMFill","pattern":{"type":"CIMSolidPattern","color":[128,224,255,255]}}]}}]}]}]}]}' (t/Legatus/Plugins/SituationMap/MapImpl/MapImpl.cpp:1019) + +#endif + + + + +QString ParticipantImpl::textModifier(TextModifier textModifier) const +{ + return m_textModifierCont[ static_cast(textModifier) ].text; +} +void ParticipantImpl::setTextModifier(TextModifier textModifier, const QString &text) +{ + m_textModifierCont[ static_cast(textModifier) ].text = text; +} + +bool ParticipantImpl::textModifierVisibility(TextModifier textModifier) const +{ + return m_textModifierCont[ static_cast(textModifier) ].visible; +} +void ParticipantImpl::setTextModifierVisibility(TextModifier textModifier, bool visible) +{ + m_textModifierCont[ static_cast(textModifier) ].visible = visible; +} + + +QColor ParticipantImpl::injuryColor() const +{ + return m_injuryData.injuryColor; +} +Injury ParticipantImpl::injury() const +{ + return m_injuryData.injury; +} +bool ParticipantImpl::setInjury(Injury injury, + const QColor &injuryColor) +{ + bool changed = false; + + if (m_injuryData.injury != injury) + { + m_injuryData.injury = injury; + m_injuryData.changed = true; + changed = true; + } + + if (m_injuryData.injuryColor != injuryColor) + { + m_injuryData.injuryColor = injuryColor; + m_injuryData.changed = true; + changed = true; + } + + // switch from partyId to color: + m_injuryData.injuryPartyId = -1; + return changed; +} + +int ParticipantImpl::injuryPartyId() const +{ + return m_injuryData.injuryPartyId; +} +bool ParticipantImpl::setInjury(Injury injury, int partyId) +{ + bool changed = false; + + if (m_injuryData.injury != injury) + { + m_injuryData.injury = injury; + m_injuryData.changed = true; + changed = true; + } + + if (m_injuryData.injuryPartyId != partyId) + { + m_injuryData.injuryPartyId = partyId; + m_injuryData.changed = true; + changed = true; + } + return changed; +} + +bool ParticipantImpl::setPartyIdChanged(int partyId) +{ + bool changed = BaseClass::setPartyIdChanged(partyId); + if ((partyId < 0) || (m_injuryData.injuryPartyId == partyId)) + { + m_injuryData.changed = true; + changed = true; + } + return changed; +} + + +const QColor &ParticipantImpl::injuryColorInternal() +{ + if (m_injuryData.changed) + { + if (m_injuryData.partyIdValid()) + m_injuryData.injuryColor = this->partyColor(m_injuryData.injuryPartyId); + else + m_injuryData.injuryColor = QColor(100, 100, 0); // 100, 100, 0 -> ocker; same as ASB oreignInterfaceImpl::findImage() + } + return m_injuryData.injuryColor; +} + + + + + +bool ParticipantImpl::additionalSymbol(AdditionalSymbolType additionalSymbolType) const +{ + return (m_additionalSymbols.contains(additionalSymbolType)); +} + +bool ParticipantImpl::setAdditionalSymbol(AdditionalSymbolType additionalSymbolType, bool newState) +{ + bool changed = false; + + if (newState) + { + if (!m_additionalSymbols.contains(additionalSymbolType)) + { + changed = true; + m_extensionsChanged = true; + m_additionalSymbols.insert(additionalSymbolType); + } + } + else + { + if (m_additionalSymbols.contains(additionalSymbolType)) + { + changed = true; + m_extensionsChanged = true; + m_additionalSymbols.remove(additionalSymbolType); + } + } + return changed; +} + +bool SituationMap::ParticipantImpl::isMarked() const +{ + return m_marked.value(); +} + +bool ParticipantImpl::setMarked(bool marked) +{ + bool changed = m_marked.value() != marked; + m_marked = marked; + return changed; +} + +void ParticipantImpl::setDirectionIndicator(int key, const DirectionIndicatorData &data) +{ + m_directionIndicatorCont[key] = data; +} + +void ParticipantImpl::removeDirectionIndicator(int key) +{ + if (key < 0) + m_directionIndicatorCont.clear(); + else + m_directionIndicatorCont.remove(key); +} + +bool ParticipantImpl::isDirectionIndicatorsVisible() const +{ + return m_directionIndicatorsVisibility.value(); +} + +void ParticipantImpl::setDirectionIndicatorsVisibility(bool visible) +{ + m_directionIndicatorsVisibility = visible; +} + +QVector ParticipantImpl::ranges() const +{ + return m_ranges.value(); +} +bool ParticipantImpl::setRanges(const QVector &ranges) +{ + bool changed = m_ranges.value() != ranges; + m_ranges = ranges; + return changed; +} + +bool ParticipantImpl::isRangesVisible() const +{ + return m_rangesVisibility; +} +void ParticipantImpl::setRangesVisibility(bool visible) +{ + m_rangesVisibility = visible; +} + +bool ParticipantImpl::setDesignations(const QVector &designations) +{ + m_designationData = designations; + return m_designationData.changed(); +} + +//bool ParticipantImpl::addUpdateDesignation(const DesignationData &designationData) +//{ +// // try the keep the m_designationData clean +// if (designationData.isNull()) +// return removeDesignation(designationData.code); + +// bool changed = m_designationData.value(designationData.code) != designationData; +// if (changed) +// m_designationData[designationData.code] = designationData; +// return changed; +//} + +//bool ParticipantImpl::removeDesignation(int code) +//{ +// return (m_designationData.remove(code) > 0); +//} + +bool ParticipantImpl::isMovementTrackEnabled() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_movementTrackEnabled=" << m_movementTrackEnabled; + return m_movementTrackEnabled; +} + +void ParticipantImpl::setMovementTrackEnabled(bool enable) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_movementTrackEnabled=" << m_movementTrackEnabled; + m_movementTrackEnabled = enable; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_movementTrackEnabled=" << m_movementTrackEnabled; +} + +void ParticipantImpl::setMovementTrack(const PositionCont &movementTrack) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_debugInfo=" << m_debugInfo + << ", movementTrack=" << movementTrack; + m_movementTrackCoords.setPositions(movementTrack); + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_debugInfo=" << m_debugInfo + << ", movementTrack=" << movementTrack; +} + +void ParticipantImpl::addToMovementTrack(const Point3D &addToMovementTrack) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_movementTrackCoords.addSinglePosition(addToMovementTrack); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PositionCont ParticipantImpl::movementTrack() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " movementTrackSize=" << m_movementTrackCoords.size(); + return m_movementTrackCoords.positions(); +} + +int ParticipantImpl::sizeOfMovementTrack() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " movementTrackSize=" << m_movementTrackCoords.size(); + return m_movementTrackCoords.size(); +} + +bool ParticipantImpl::isMovementTrackVisible() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_movementTrackVisibility=" << m_movementTrackVisibility; + return m_movementTrackVisibility; +} + +void ParticipantImpl::setMovementTrackVisibility(bool enable) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_movementTrackVisibility = enable; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " m_movementTrackVisibility=" << m_movementTrackVisibility; +} + +int ParticipantImpl::numberOfValuesChanged() +{ + int count = BaseClass::numberOfValuesChanged(); + + if (m_userSymbolOpacity.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; count++; } + if (m_scale.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; count++; } + + if (m_extensionsChanged) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; count++; } + + if (m_marked.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; count++; } + if (m_ranges.changed()) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; count++; } + if (m_injuryData.changed) { WARNING_LOG(m_logger) << LOGFUNCTION << " START "; count++; } + + return count; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.h new file mode 100755 index 000000000..d0cf3e16d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/participant_impl.h @@ -0,0 +1,293 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Participant + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include // Interface +#include +#include +#include +#include +#include +#include +#include +#include "situation_map_objects_export.h" + + +// Qt +#include +#include +#include +#include + + +// forward: + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward: +class BasicPathObjectImpl; +class ParticipantImplMessage; + + +struct TextModifierData +{ + QString text; + GraphicalObjectText *goText = nullptr; + QPoint offset; + + HorizontalAlignment horizontalAlignment = HorizontalAlignment::Left; + VerticalAlignment verticalAlignment = VerticalAlignment::Bottom; + + bool visible = true; +}; + + + +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface + + +/** + * @brief The ParticipantImpl class implements the Participant. + * + * @internal Internal: Though this class derives from AbstractMapObject like many others this class is special. + * In contrast to most other sub classes the Participant does NOT call some baseclass methods (like updateView): + */ +class SITUATIONMAPOBJECTS_EXPORT ParticipantImpl : public AbstractMapObjectImpl, public virtual Participant +{ + using BaseClass = AbstractMapObjectImpl; + + template friend class GenericObjectFactory; + friend class FunctionalAreaImpl; +protected: + explicit ParticipantImpl(const QString &debugInfo = QString()); + virtual ~ParticipantImpl(); + +public: + void initTextModifierData(); + + + ParticipantImpl(const ParticipantImpl &other) = delete; + ParticipantImpl &operator=(ParticipantImpl &other) = delete; + + void setDebugInfo(const QString &debugInfo) override; + + QString toString() const override; + + + // Data access: + Point3D origin() const override; + bool setOrigin(const Point3D &origin) override; + + /*! Just to enable using as AES item (template@AdapterDds) !*/ + void setPositions(const PositionCont &posCont) override; + + void boundingBox(BoundingBox &bbox) const override; + QColor majorColor() const override; + + + float userSymbolOpacity() const override; + void setUserSymbolOpacity(float userSymbolOpacity) override; + + SymbolDataToRender symbolDataToRender(bool ignoreForcedColor = true) const override; + QImage actualImage() const override; + + AdvancedSymbology::SymbologyType symbologyType() const override; + + // Scale : ('overload' AbstractMapObjectImpl) + float scale() const override; + void setScale(float scale) override; + // : Scale + + + QString textModifier(TextModifier textModifier) const override; + void setTextModifier(TextModifier textModifier, const QString &text) override; + bool textModifierVisibility(TextModifier textModifier) const override; + void setTextModifierVisibility(TextModifier textModifier, bool visible) override; + + Injury injury() const override; + QColor injuryColor() const override; + bool setInjury(Injury injury, const QColor &injuryColor) override; ///< Attention: 'Qt::red' is an int + + int injuryPartyId() const override; + bool setInjury(Injury injury, int partyId) override; ///< Attention: 'Qt::red' is an int + + bool setPartyIdChanged(int partyId = -1) override; + + bool additionalSymbol(AdditionalSymbolType additionalSymbolType) const override; + bool setAdditionalSymbol(AdditionalSymbolType additionalSymbolType, bool newState) override; + + bool isMarked() const override; + bool setMarked(bool marked) override; + + /*! \name DirectionIndicator like Turret Alignment*/ + //@{ + void setDirectionIndicator(int key, const DirectionIndicatorData &data) override; + void removeDirectionIndicator(int key = -1) override; + + bool isDirectionIndicatorsVisible() const override; + void setDirectionIndicatorsVisibility(bool visible) override; + //@} + + QVector ranges() const override; + bool setRanges(const QVector &ranges) override; + bool isRangesVisible() const override; + void setRangesVisibility(bool visible) override; + + bool setDesignations(const QVector &designations) override; + + + bool isMovementTrackEnabled() const override; + void setMovementTrackEnabled(bool enable) override; + + void setMovementTrack(const PositionCont &movementTrack) override; + void addToMovementTrack(const Point3D &addToMovementTrack) override; + PositionCont movementTrack() const override; + int sizeOfMovementTrack() const override; + + bool isMovementTrackVisible() const override; + void setMovementTrackVisibility(bool enable) override; + + + // CHECK : + QString identifier() const override { return QString();} + virtual void setIdentifier(const QString &identifier) override { Q_UNUSED(identifier); } + + void setVisibility(ObjectAppearance appearance, bool visibility) override { Q_UNUSED(appearance); Q_UNUSED(visibility);} + // : CHECK + + + int numberOfValuesChanged(); ///< For Unit/Module Test + + +protected: + friend class MapImpl; + +private: + + +protected: + bool updateViewImplPre() override { return true; } // no tactical Appearance is necessary + bool updateViewImpl() override; + bool updateViewImplTextModifier(); + + bool updateViewImplDirectionIndicators(); + bool updateViewImplRanges(); + bool updateViewImplDesignation(); + + bool updateViewImplPost() override + { + // reset changed flags: + BaseClass::updateViewImplPost(); + m_userSymbolOpacity.reset(); + m_scale.reset(); + m_injuryData.changed = false; + m_marked.reset(); + m_directionIndicatorsVisibility.reset(); + m_ranges.reset(); + m_extensionsChanged = false; + m_designationData.reset(); + return true; + } + + + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + void onSymbolResultReady(); + + + // logical + OriginTranslated m_origin; + double m_height = 0; + + SimpleValue m_userSymbolOpacity = 1.0; // 0..1 + + SimpleValue m_scale = 1.0; // 'overload' AbstractMapObjectImpl + +public: + const QColor &partyColor() const override; + QColor partyColor(int partyId, bool isOutOfDate = false) const override; + +protected: + TextModifierData m_textModifierCont[static_cast(TextModifier::END)]; + + // Extensions : + struct InjuryData + { + SituationMap::Injury injury = SituationMap::Injury::NoInjury; + QColor injuryColor; + int injuryPartyId = -1; + + bool changed = true; + + void resetChanged() { changed = true; } + + bool partyIdValid() const + { + return injuryPartyId >= 0; + } + }; + + InjuryData m_injuryData; + const QColor &injuryColorInternal(); + + QSet m_additionalSymbols; + + SimpleValue m_marked = false; + + SymbolDataToRender m_symDataToRender; + float m_scale4XmlSymbolWithSymbolSpecificScale = 0; + + QMap m_directionIndicatorCont; + QSharedPointer m_directionIndicatorPms; + SimpleValue m_directionIndicatorsVisibility = true; + + SimpleValue< QVector > m_ranges; + bool m_rangesVisibility = false; + QVector< QSharedPointer > m_graphObjRanges; + + + SimpleValue< QVector > m_designationData; // Key = PRF Code + QVector> m_designationMapObjects; + + + PositionsTranslated m_movementTrackCoords; + bool m_movementTrackEnabled = false; + bool m_movementTrackVisibility = true; + QSharedPointer m_movementTrack; + + bool m_extensionsChanged = true; + // : Extensions + + AdvancedSymbology::SymbolDictionary *m_dictionary = nullptr; + AdvancedSymbology::SymbolResultWatcher m_symbolResultWatcher; + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + // Forbid some overloaded methods without effects of this object: +// virtual QString identifier() const { return QString();}; +// virtual void setIdentifier(const QString& identifier) { Q_UNUSED(identifier); }; + +// virtual bool visibility(ObjectAppearance appearance) const { Q_UNUSED(appearance); return true;}; +// virtual void setVisibility(ObjectAppearance appearance, bool visibility) { Q_UNUSED(appearance); Q_UNUSED(appearance);}; + +}; + + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.cpp new file mode 100755 index 000000000..6d570ffef --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.cpp @@ -0,0 +1,370 @@ +/** + * @RDE_HEADER@ + * PathContainer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "path_container.h" +#include +#include +#include + + +// ESRI +#include +#include + +// Qt +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class PathData +//----------------------------------------------------------------------------- +class PathData +{ +public: + PathData() = default; + + PositionsTranslated &pathTrans() { return m_path; } + bool setPath(const PositionCont &path) + { + if (m_path.positions() != path) + m_changed = true; + m_path.setPositions(path); + return m_changed; + } + + bool visibility() const { return m_visibility.value(); } + void setVisibility(bool onOff) { m_visibility = onOff; } + + bool changed() const { return m_changed || m_visibility.changed(); } + void resetChanged() { m_changed = false; m_visibility.reset(); } + + + ~PathData() + { + } + +public: + +private: + PositionsTranslated m_path; + SimpleValue m_visibility{true}; + bool m_changed{true}; +}; + + +class PathContainer::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + Esri::ArcGISRuntime::GeoView *m_geoView{nullptr}; + Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer{nullptr}; + + + // Own data: + CoordTranslationFunctor *m_coordTransFunctor{nullptr}; + QString m_debugInfo; // will be appended by consecutive number + UniqueMapId m_uniqueMapId; + + SimpleValue m_lineStyle; + SimpleValue m_fillStyle; + SimpleValue m_drawOrder{0}; + SimpleValue m_selected{false}; + QSharedPointer m_multiPath; + +#ifdef LTS_DEBUG + static long m_counter; +#endif + +private: + QHash> m_map; + +public: + QSharedPointer getPathData(int key) + { + auto it = m_map.find(key); + if (it != m_map.end()) + return it.value(); + auto ret = QSharedPointer(new PathData()); + m_map[key] = ret; + return ret; + } + + bool remove(int key) + { + auto it = m_map.find(key); + if (it == m_map.end()) + return false; + m_map.erase(it); + return true; + } + void clear() { m_map.clear(); } + int size() const { return m_map.size(); } + QList keys() const { return m_map.keys(); } + bool contains(int key) const { return m_map.contains(key); } + const QHash> &container() const { return m_map; } + +}; // class PathContainer::Private +#ifdef LTS_DEBUG +long PathContainer::Private::m_counter = 0; +#endif +//----------------------------------------------------------------------------- + + + +//----------------------------------------------------------------------------- +// class PathContainer +//----------------------------------------------------------------------------- +PathContainer::PathContainer(const Esri::ArcGISRuntime::GeoView *geoView, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + CoordTranslationFunctor *coordTransFunctor, + const UniqueMapId &uniqueMapId) + : QObject() + , m_logger(getLogger("PathContainer")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_geoView = const_cast(geoView); + d->m_graphicsLayer = graphicsLayer; + d->m_coordTransFunctor = coordTransFunctor; + d->m_uniqueMapId = uniqueMapId; + + QString debugInfo = QStringLiteral("%1#PathContainerPolyline").arg(d->m_debugInfo); + DEBUG_LOG(m_logger) << LOGFUNCTION << " create new Object: " << debugInfo; + d->m_multiPath = QSharedPointer + (new GraphicalObjectMultiPath(GraphicalObjectPathType::Polyline, + d->m_geoView, + d->m_graphicsLayer, + debugInfo, + d->m_uniqueMapId)); + +#ifdef LTS_DEBUG + INFO_LOG(m_logger) << " ctor PathContainer-Polyline counter = " << d->m_counter++ + << ", debugInfo = " << debugInfo; +#endif // LTS_DEBUG + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} +PathContainer::PathContainer(const GraphicalObjectPathType pathType, + const Esri::ArcGISRuntime::GeoView *geoView, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + CoordTranslationFunctor *coordTransFunctor, + const UniqueMapId &uniqueMapId) + : QObject() + , m_logger(getLogger("PathContainer")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_geoView = const_cast(geoView); + d->m_graphicsLayer = graphicsLayer; + d->m_coordTransFunctor = coordTransFunctor; + d->m_uniqueMapId = uniqueMapId; + + QString debugInfo = QStringLiteral("%1#PathContainerPolygon").arg(d->m_debugInfo); + DEBUG_LOG(m_logger) << LOGFUNCTION << " create new Object: " << debugInfo; + d->m_multiPath = QSharedPointer + (new GraphicalObjectMultiPath(pathType, + d->m_geoView, + d->m_graphicsLayer, + debugInfo, + d->m_uniqueMapId)); +#ifdef LTS_DEBUG + INFO_LOG(m_logger) << " ctor PathContainer-Polygon counter = " << d->m_counter++ + << ", debugInfo = " << debugInfo; +#endif // LTS_DEBUG + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PathContainer::~PathContainer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->clear(); + delete d; + +#ifdef LTS_DEBUG + INFO_LOG(m_logger) << " dtor PathContainer counter =" << --d->m_counter; +#endif // LTS_DEBUG + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString PathContainer::debugInfo() const +{ + return d->m_debugInfo; +} +void PathContainer::setDebugInfo(const QString &debugInfo) +{ + d->m_debugInfo = debugInfo; + d->m_multiPath->setDebugInfo(QStringLiteral("%1#PathContainer").arg(d->m_debugInfo)); +} + + +PositionCont PathContainer::path(int idx) const +{ + return d->getPathData(idx)->pathTrans().positions(); +} +bool PathContainer::setPath(int idx, const PositionCont &path) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " idx=" << idx + << " path.size=" << path.size(); + + bool hasChanged = false; + + if (path.isEmpty()) + hasChanged = d->remove(idx); + else + hasChanged = d->getPathData(idx)->setPath(path); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return hasChanged; +} + +void PathContainer::clear(int idx) +{ + d->remove(idx); +} + +void PathContainer::clearAll() +{ + d->clear(); +} + +LineStyle PathContainer::lineStyle() const +{ + return d->m_lineStyle.value(); +} +void PathContainer::setLineStyle(const LineStyle &lineStyle) +{ + d->m_lineStyle = lineStyle; +} + +FillStyle PathContainer::fillStyle() const +{ + return d->m_fillStyle.value(); +} + +void PathContainer::setFillStyle(const FillStyle &fillStyle) +{ + d->m_fillStyle = fillStyle; +} + +int PathContainer::drawOrder() const +{ + return d->m_drawOrder.value(); +} +void PathContainer::setDrawOrder(int drawOrder) +{ + d->m_drawOrder = drawOrder; +} + +bool PathContainer::selected() const +{ + return d->m_selected.value(); +} +void PathContainer::setSelected(bool selected) +{ + d->m_selected = selected; +} + +bool PathContainer::visibility(int idx) const +{ + return d->getPathData(idx)->visibility(); +} + +void PathContainer::setVisibility(int idx, bool onOff) +{ + d->getPathData(idx)->setVisibility(onOff); +} + +int PathContainer::getPathCount() const +{ + return d->size(); +} + +QList PathContainer::getIndicationIndexList() const +{ + return d->keys(); +} + + +bool PathContainer::updateView() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", debugInfo='" << d->m_debugInfo << "'" + << ", d->m_map.size()= " << d->size(); + bool success = true; + + bool bDoUpdateView = false; + if (d->m_lineStyle.changed() || d->m_fillStyle.changed() || d->m_drawOrder.changed() || d->m_selected.changed()) + { + d->m_multiPath->setLineStyle(d->m_lineStyle.value()); + d->m_multiPath->setFillStyle(d->m_fillStyle.value()); + d->m_multiPath->setDrawOrder(d->m_drawOrder.value()); + d->m_multiPath->setSelected(d->m_selected.value()); + bDoUpdateView = true; + } + + // is something removed? + if (d->m_multiPath->pathIndexInUse() != d->keys()) + { + d->m_multiPath->reduceTo(d->keys()); + bDoUpdateView = true; + } + + for (auto it = d->container().begin(); it != d->container().end(); ++it) + { + QSharedPointer pathData = it.value(); + if (pathData->changed()) + { + const int idx = it.key(); + pathData->pathTrans().setCoordTransFunctor(d->m_coordTransFunctor); + + if (pathData->visibility()) + { + const PositionCont &pathTrans = pathData->pathTrans().positionsTranslated(); + d->m_multiPath->setPath(idx, pathTrans); + } else { + // a cleared path will not be transfered to ESRI::Graphic, see GraphicalObjectMultiPath + // So clearPath is sufficiant + d->m_multiPath->clearPath(idx); + } + bDoUpdateView = true; + pathData->resetChanged(); + } + } // for + + if (bDoUpdateView) + { + bool updated = d->m_multiPath->updateView(); + success = updated; + } + + // reset + d->m_lineStyle.reset(); + d->m_fillStyle.reset(); + d->m_drawOrder.reset(); + d->m_selected.reset(); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << " debugInfo='" << d->m_debugInfo << "'" + << " success=" << success; + return success; +} + + +} +// namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.h new file mode 100755 index 000000000..cebcb29c2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/path_container.h @@ -0,0 +1,110 @@ +/** + * @RDE_HEADER@ + * PathContainer + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include +#include +#include +#include + +// Qt +#include +#include +#include + + +// forward: +namespace Esri +{ +namespace ArcGISRuntime +{ +class GeoView; +class GraphicsOverlay; +} +} + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// Forward: +class CoordTranslationFunctor; +class PathData; +enum class GraphicalObjectPathType; + + +class PathContainer : public QObject +{ + Q_OBJECT + +public: + explicit PathContainer(const Esri::ArcGISRuntime::GeoView *geoView, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + CoordTranslationFunctor *coordTransFunctor, + const UniqueMapId &uniqueMapId); + + explicit PathContainer(const GraphicalObjectPathType pathType, + const Esri::ArcGISRuntime::GeoView *geoView, + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, + CoordTranslationFunctor *coordTransFunctor, + const UniqueMapId &uniqueMapId); + virtual ~PathContainer(); + + PathContainer(const PathContainer &other) = delete; + PathContainer &operator=(PathContainer &other) = delete; + + QString debugInfo() const; + void setDebugInfo(const QString &debugInfo); + + // Marker : + + virtual PositionCont path(int idx) const; + virtual bool setPath(int idx, const PositionCont &path); + + virtual void clear(int idx); + virtual void clearAll(); + + virtual LineStyle lineStyle() const; + virtual void setLineStyle(const LineStyle &lineStyle); + + virtual FillStyle fillStyle() const; + virtual void setFillStyle(const FillStyle &fillStyle); + + virtual int drawOrder() const; + virtual void setDrawOrder(int drawOrder); + + virtual bool selected() const; + virtual void setSelected(bool selected); + + virtual bool visibility(int idx) const; + virtual void setVisibility(int idx, bool onOff); + + virtual int getPathCount() const; + virtual QList getIndicationIndexList() const; + + + virtual bool updateView(); + +public slots: + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.cpp new file mode 100755 index 000000000..efa989d23 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.cpp @@ -0,0 +1,75 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Polygon + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "polygon_impl.h" +#include +#include + + +// ESRI + + +// Qt + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class PolygonImpl +//----------------------------------------------------------------------------- +PolygonImpl::PolygonImpl(const QString &debugInfo) + : Polygon() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("PolygonImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PolygonImpl::~PolygonImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool PolygonImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool PolygonImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.h new file mode 100755 index 000000000..91058d417 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polygon_impl.h @@ -0,0 +1,69 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Polygon + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The PolygonImpl class displays a polygon. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the line) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT PolygonImpl : public BasicPathObjectImpl, public virtual Polygon +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit PolygonImpl(const QString &debugInfo = QString()); + virtual ~PolygonImpl(); + +public: + PolygonImpl(const PolygonImpl &other) = delete; + PolygonImpl &operator=(PolygonImpl &other) = delete; + +// void setScale(float) override {} + + +protected: + virtual bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.cpp new file mode 100755 index 000000000..fb158144b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.cpp @@ -0,0 +1,115 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Polyline + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "polyline_impl.h" +#include +#include + + +// ESRI + + +// Qt + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class PolylineImpl +//----------------------------------------------------------------------------- +PolylineImpl::PolylineImpl(const QString &debugInfo) + : Polyline() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("PolylineImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +PolylineImpl::~PolylineImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool PolylineImpl::arrowHeadStart() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return BaseClass::arrowHeadStart(); +} +void PolylineImpl::setArrowHeadStart(bool enable) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + BaseClass::setArrowHeadStart(enable); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool PolylineImpl::arrowHeadEnd() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return BaseClass::arrowHeadEnd(); +} +void PolylineImpl::setArrowHeadEnd(bool enable) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + BaseClass::setArrowHeadEnd(enable); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString PolylineImpl::textOnSegment(uint segment) const +{ + return BaseClass::textOnSegment(segment); +} +void PolylineImpl::setTextOnSegment(uint segment, const QString &segmentText) +{ + BaseClass::setTextOnSegment(segment, segmentText); +} +void PolylineImpl::clearTextOnSegment(uint segment) +{ + BaseClass::clearTextOnSegment(segment); +} + + +bool PolylineImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool PolylineImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polyline); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.h new file mode 100755 index 000000000..6d16bf342 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/polyline_impl.h @@ -0,0 +1,81 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Polyline + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The PolylineImpl class displays a polyline. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the line) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT PolylineImpl : public BasicPathObjectImpl, public virtual Polyline +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit PolylineImpl(const QString &debugInfo = QString()); + virtual ~PolylineImpl(); + +public: + PolylineImpl(const PolylineImpl &other) = delete; + PolylineImpl &operator=(PolylineImpl &other) = delete; + +// void setScale(float) override {} + + + /*! \name ArrowHeads */ + //@{ + bool arrowHeadStart() const override; + void setArrowHeadStart(bool enable) override; + + bool arrowHeadEnd() const override; + void setArrowHeadEnd(bool enable) override; + //@} + + QString textOnSegment(uint segment) const override; + void setTextOnSegment(uint segment, const QString &segmentText) override; + void clearTextOnSegment(uint segment) override; + +protected: + virtual bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/position_marker.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/position_marker.xpm new file mode 100755 index 000000000..981c6d979 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/position_marker.xpm @@ -0,0 +1,22 @@ +/* XPM */ +static const char * position_marker_xpm[] = +{ + "15 15 2 1", + " c None", + ". c #FB0000", + " . ", + " . ", + " . ", + " . ", + " . ", + " . ", + " . ", + "...............", + " . ", + " . ", + " . ", + " . ", + " . ", + " . ", + " . " +}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.cpp new file mode 100755 index 000000000..85bee45d0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.cpp @@ -0,0 +1,323 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of SingleFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "single_fire_impl.h" +#include +#include +#include +#include + + +#include + +// ESRI + + +// Qt +#include +#include + + +namespace SituationMap +{ + +static int s_gunToTargetLine3DKEY = -1; +static int s_gunToTargetLineSurfaceKEY = 0; +static int s_nonLethalAreaKEY = 1; +//----------------------------------------------------------------------------- +// class SingleFireImpl +//----------------------------------------------------------------------------- +SingleFireImpl::SingleFireImpl(const QString &debugInfo) + : SingleFire() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("SingleFireImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_fillColorAlpha = 200; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SingleFireImpl::~SingleFireImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete m_centerImage; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +QString SingleFireImpl::toString() const +{ + QString str; + str.append(BaseClass::toString()); + str.append(BasicPathObjectImpl::toString()); + return str; +} + +Point3D SingleFireImpl::impactOrigin() const +{ + return BaseClass::tacticalOrigin(); +} +void SingleFireImpl::setImpactOrigin(const Point3D &impactOrigin) +{ + BaseClass::setTacticalOrigin(impactOrigin); +} + +SingleFire::ImpactType SingleFireImpl::impactType() const +{ + return m_impactType.value(); +} + +void SingleFireImpl::setImpactType(SingleFire::ImpactType impactType) +{ + m_impactType = impactType; +} + +Point3D SingleFireImpl::origin() const +{ + return BaseClass::origin(); +} +void SingleFireImpl::setOrigin(const Point3D &origin) +{ + BaseClass::setOrigin(origin); +} +float SingleFireImpl::radius() const +{ + return BaseClass::radius(); +} + +void SingleFireImpl::setRadius(float radius) +{ + BaseClass::setRadius(radius); +} + +void SingleFireImpl::setOriginatorToTargetLineSurface(const Point3D &start, const Point3D &end) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", start=" << start << ", end=" << end << ", debugInfo=" << m_debugInfo; + + PathContainer &path = this->rPathContainer(); // should be SP::Draped + path.setDebugInfo(m_debugInfo + QStringLiteral("SpDraped")); + + if (start.isNull() && end.isNull()) + { + path.clear(s_gunToTargetLineSurfaceKEY); + } else { + LineStyle ls = m_mainGraphicalObject->lineStyle(); + ls.setLinePattern(LinePattern::Dash); + ls.setLineWidth(2); + path.setLineStyle(ls); + + path.setPath(s_gunToTargetLineSurfaceKEY, {start, end}); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", start=" << start << ", end=" << end << ", debugInfo=" << m_debugInfo; + +} + +AesEffectState SingleFireImpl::aesState() const +{ + return BaseClass::aesState(); +} +void SingleFireImpl::setAesState(AesEffectState aesState) +{ + BaseClass::setAesState(aesState); +} + +SymbolImage SingleFireImpl::symbolImage() const +{ + SymbolImage symImage = m_tacticalAppearance->symbolImage(); + return symImage; +} + + + +bool SingleFireImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " origin =" << this->origin() //.x() << ", " << this->origin().y() + << " radius =" << this->radius() + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + if (m_tacticalAppearance) + { + if (partyChanged()) + m_symbol.refValue().color = partyColor(); + } + + // centerImage: + if (!m_centerImage) + { + m_centerImage = + new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#CenterImage"), + m_uniqueMapId); + m_impactType.setChanged(); + } + + if (m_impactType.changed() || partyChanged()) + { + QImage img; + switch(m_impactType.value()) + { + case SingleFire::ImpactType::normalImpact: + img = renderImpactSymbol(partyColor(), 20, true); // filled + break; + case SingleFire::ImpactType::doubleImpact: + img = renderDoubleImpactSymbol(partyColor(), 20, true); // filled + break; + } // switch + m_centerImage->setImage(img); + } + +// m_centerImage->setScale(m_mainGraphicalObject->scale()); // do not scale centerImage, equal to mine of MineArea + + m_centerImage->setOrigin(m_tacticalOrigin.value().originTranslated()); + m_centerImage->setVisibility(m_visibility && m_visibilityTechnical); + + bool success = BaseClass::updateViewImpl(); // will call onMapExtentChanged and therefore m_centerImage->updateView + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool SingleFireImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + + switch (mapImpl->symbologyType()) + { + + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500002108000000"); // SymbolSet = 21 270800 , 270900 + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + // m_symbol.refValue().m_sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUGPGPWP--"); // 0431 GUGPGPWP-- (Predicted Impact Point U) -> Point + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUGPGPWI--"); // 0293 GHGPGPWI-- (Impact Point H) -> Point + break; + case AdvancedSymbology::SymbologyType::APP6B: + //m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUOPVMM---"); // APP6B: 2564 GUOPVMM--- -> Point + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUOPVI----"); // APP6B: 2560 GUOPVI---- -> Point + break; + case AdvancedSymbology::SymbologyType::APP6D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500002108000000"); // SymbolSet = 21 270800 , 270900 + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + + } // if (mapImpl) + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool SingleFireImpl::onMapExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool successCenterImage = true; + if (m_centerImage) + { + bool visiScale = m_visibilityScaleRange.scaleHasVisibility(m_mapImpl->scale()); + m_centerImage->setVisibility(visiScale && m_visibility && m_visibilityTechnical); + successCenterImage = m_centerImage->updateView(); + } + + bool success = BaseClass::onMapExtentChanged() && successCenterImage; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; + +} + +QImage SingleFireImpl::renderDoubleImpactSymbol(const QColor &color, int size, bool filled) +{ + QImage finalImage(2.25*size, size, QImage::Format_ARGB32); + finalImage.fill(Qt::transparent); + + const QImage &singleImg = renderImpactSymbol(color, size, filled); + QRect target = singleImg.rect(); + QPainter painter(&finalImage); + + // left + painter.drawImage(target, singleImg, singleImg.rect()); + + // right + target.moveTo(finalImage.width() - singleImg.width(), 0); + painter.drawImage(target, singleImg, singleImg.rect()); + + painter.end(); + + return finalImage; +} + +QImage SingleFireImpl::renderImpactSymbol(const QColor &color, int size, bool filled) +{ + QImage image(size, size, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + QPainter painter(&image); + + QPen pen(color); + int penWidth = size/10; + if (penWidth < 2) + penWidth = 2; + pen.setWidth(penWidth); + painter.setPen(pen); + + const int sizeHalf = size / 2; + + QPointF center(sizeHalf, sizeHalf); + + // circle + if (filled) + painter.setBrush(color); + painter.drawEllipse(center, sizeHalf * 0.5, sizeHalf * 0.5); + + // lines + QLine newLine(0, sizeHalf, size / 4.0, sizeHalf); + painter.drawLine(newLine); + + QTransform trans; + trans.translate(sizeHalf, sizeHalf); + trans.rotate(45); + trans.translate(-sizeHalf, -sizeHalf); + + // 8 are required, one is already draw, so 7: + for (int i=0; i<7; ++i) + { + newLine = trans.map(newLine); + painter.drawLine(newLine); + } + + painter.end(); + + return image; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.h new file mode 100755 index 000000000..fef90eaff --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/single_fire_impl.h @@ -0,0 +1,106 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of SingleFire + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "basic_path_object_impl.h" +#include // Interface +#include + + +// Qt + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +/** + * @brief The SingleFireImpl class displays a single impact. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPictureMarker / Image, displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT SingleFireImpl : public BasicPathObjectImpl, public virtual SingleFire +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit SingleFireImpl(const QString &debugInfo = QString()); + virtual ~SingleFireImpl(); + +public: + SingleFireImpl(const SingleFireImpl &other) = delete; + SingleFireImpl &operator=(SingleFireImpl &other) = delete; + + QString toString() const override; + + + // some set/get methods: + Point3D impactOrigin() const override; + void setImpactOrigin(const Point3D &impactOrigin) override; + + ImpactType impactType() const override; + void setImpactType(ImpactType impactType = ImpactType::normalImpact) override; + + Point3D origin() const override; + void setOrigin(const Point3D &origin) override; + float radius() const override; + void setRadius(float radius) override; + void setOriginatorToTargetLineSurface(const Point3D &start, const Point3D &end) override; + + + // Delegate to BaseClass: + AesEffectState aesState() const override; + void setAesState(AesEffectState aesState) override; + + SymbolImage symbolImage() const override; + + static QImage renderDoubleImpactSymbol(const QColor &color, int size, bool filled); + static QImage renderImpactSymbol(const QColor &color, int size, bool filled); + + + +protected: + bool updateViewImpl() override; + + bool updateViewImplPost() override + { + BaseClass::updateViewImplPost(); + m_impactType.reset(); + return true; + } + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + bool onMapExtentChanged() override; + + // internal/admin + + // logical + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + + SimpleValue m_impactType = ImpactType::normalImpact; + GraphicalObjectPictureMarker *m_centerImage = nullptr; +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/situation_map_objects_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/situation_map_objects_export.h new file mode 100755 index 000000000..2e4782dd6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/situation_map_objects_export.h @@ -0,0 +1,21 @@ +/** + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef SITUATIONMAPOBJECTS_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define SITUATIONMAPOBJECTS_EXPORT +# else +# if defined(MAKE_SITUATIONMAPOBJECTS_LIB) +# define SITUATIONMAPOBJECTS_EXPORT Q_DECL_EXPORT +# else +# define SITUATIONMAPOBJECTS_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.cpp new file mode 100755 index 000000000..30eecf88c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.cpp @@ -0,0 +1,193 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of FunctionalArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "sound_area_impl.h" +#include +#include +#include +#include + + +// ESRI + + +// Qt +#include + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// class SoundAreaImpl +//----------------------------------------------------------------------------- +SoundAreaImpl::SoundAreaImpl(const QString &debugInfo) + : SoundArea() // Interface + , BaseClass(debugInfo) // Implementation + , m_logger(getLogger("SoundAreaImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_markerOrigin.setMapObjectImpl(this); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SoundAreaImpl::~SoundAreaImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete m_pictureMarker; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//void SoundAreaImpl::reRenderSymbol() +//{ +// BaseClass::reRenderSymbol(); +// if (m_pointSymbol) +// m_pointSymbol->reRenderSymbol(); +//} + +Point3D SoundAreaImpl::markerOrigin() const +{ + return m_markerOrigin.origin(); +} +void SoundAreaImpl::setMarkerOrigin(const Point3D &origin) +{ + m_markerOrigin.setOrigin(origin); +} + +const MarkerData &SoundAreaImpl::markerData() const +{ + return m_markerData.value(); +} +void SoundAreaImpl::setMarkerData(const MarkerData &markerData) +{ + m_markerData = markerData; +} + +QImage SoundAreaImpl::previewImageTypical() const +{ + return m_markerData.value().m_proxyImage; // maybe mainImage if it will be provided (see geo_image_impl) +} + +QImage SoundAreaImpl::previewImageSpecific() const +{ + QImage imageFromForm = BaseClass::previewImageSpecific(); + QImage markerImage = markerData().proxyImage(); + + // most likely bothj are available: + if (!imageFromForm.isNull() && !markerImage.isNull()) + { + // merge marker image on top: + const QSize &outerSize = imageFromForm.size(); + markerImage = markerImage.scaled(outerSize/1.5, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + + QPainter painter(&imageFromForm); + painter.setRenderHints(QPainter::Antialiasing|QPainter::SmoothPixmapTransform); + + QRect r = markerImage.rect(); + + int offsetX = (outerSize.width() - r.width()) / 2; + int offsetY = (outerSize.height() - r.height()) / 2; + + r.moveTo(offsetX, offsetY); + painter.drawImage(r, markerImage, markerImage.rect()); + return imageFromForm; + } else if (imageFromForm.isNull()) + return markerImage; + + return imageFromForm; +} + + +bool SoundAreaImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + + if (!updateViewImplMarkerData()) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImplPointSymbolData failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool SoundAreaImpl::updateViewImplMarkerData() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (m_markerData.changed()) + { + const MarkerData &markerData = m_markerData.value(); + + // empty? + if (markerData.isEmpty()) + { + delete m_pictureMarker; + m_pictureMarker = nullptr; + return true; + } + + // ok, data available: + if (!m_pictureMarker) + { + m_pictureMarker = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + m_debugInfo + QLatin1String("#Marker"), + m_uniqueMapId); + } + if (markerData.hasMarker()) + m_pictureMarker->setMarkerSymbol(markerData.m_proxyMarker, + markerData.m_proxyMarkerSize, + markerData.m_proxyMarkerColor); + else if (markerData.hasProxyImage()) + m_pictureMarker->setImage(markerData.m_proxyImage, + markerData.m_proxyImageDefaultSize); + } // if (m_markerData.changed()) + + m_pictureMarker->setOrigin(m_markerOrigin.originTranslated()); + + m_pictureMarker->setPickable(m_mainGraphicalObject->pickable()); + m_pictureMarker->setSelected(m_mainGraphicalObject->selected()); + m_pictureMarker->setDrawOrder(m_internalDrawOrder + 1); + // m_pictureMarker->setScale(); + m_pictureMarker->setVisibility(m_visibility && m_visibilityTechnical); + + bool success = m_pictureMarker->updateView(); + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed, m_debugInfo =" << m_debugInfo; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool SoundAreaImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::black, 2)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.h new file mode 100755 index 000000000..34d4d2757 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/sound_area_impl.h @@ -0,0 +1,89 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of FunctionalArea + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward: + + +/** + * @brief The SoundAreaImpl class displays a sound area and an image. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance) + * - mainGraphicalObjectText (displaying the text/name) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT SoundAreaImpl : public BasicPathObjectImpl, public virtual SoundArea +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit SoundAreaImpl(const QString &debugInfo = QString()); + virtual ~SoundAreaImpl(); + +public: + SoundAreaImpl(const SoundAreaImpl &other) = delete; + SoundAreaImpl &operator=(SoundAreaImpl &other) = delete; + +// void reRenderSymbol() override; + + /*! \name Marker / Point symbol*/ + //@{ + Point3D markerOrigin() const override; + void setMarkerOrigin(const Point3D &origin) override; + + const MarkerData &markerData() const override; + void setMarkerData(const MarkerData &markerData) override; + //@} + + QImage previewImageTypical() const override; + QImage previewImageSpecific() const override; + + +protected: + bool updateViewImpl() override; + + bool updateViewImplMarkerData(); + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + +private: + // logical + OriginTranslated m_markerOrigin; + SimpleValue m_markerData; + GraphicalObjectPictureMarker *m_pictureMarker{nullptr}; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.cpp new file mode 100755 index 000000000..474606376 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.cpp @@ -0,0 +1,149 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Text + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "text_impl.h" +#include +#include +#include +#include +#include + +// ESRI + + +// Qt +#include + + +#include "position_marker.xpm" + + +namespace SituationMap +{ + +//----------------------------------------------------------------------------- +// constants +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Utils: +//----------------------------------------------------------------------------- + + + + + +//----------------------------------------------------------------------------- +// class TextImpl +//----------------------------------------------------------------------------- +TextImpl::TextImpl(const QString &debugInfo) + : Text() // Interface + , BaseClass(debugInfo) // Implementation + , m_showAnchorMarker(false) + , m_anchorMarker(nullptr) + , m_logger(getLogger("TextImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_visibilityScaleRange.resetToMax(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +TextImpl::~TextImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete m_anchorMarker; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void TextImpl::setShowAnchorMarker(bool on) +{ + if (on == m_showAnchorMarker) + return; + + m_showAnchorMarker = on; + if (on) + { + static QImage markerSymbol; + + if (markerSymbol.isNull()) + markerSymbol = QImage(position_marker_xpm); + + m_anchorMarker = new GraphicalObjectPictureMarker(m_mapImpl->getEsriGeoView(), + m_graphicsLayer, + QLatin1String("AnchorMarker"), + INVALID_MAP_ID); + m_anchorMarker->setImage(markerSymbol); + } + else + { + delete m_anchorMarker; + m_anchorMarker = nullptr; + } +} // TextImpl::setShowAnchorMarker + + +bool TextImpl::showAnchorMarker() const { return m_showAnchorMarker; } + + +bool TextImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " visibility=" << m_visibility; + + // put translated origin to stub. Later GOText will takeover the (translated) origin. + if (m_mainGraphicalObject) m_mainGraphicalObject->setOrigin(originTranslated()); + + bool success = BaseClass::updateViewImpl(); + + if (m_anchorMarker) + { + m_anchorMarker -> setSelected(m_selected); + m_anchorMarker -> setDrawOrder(m_internalDrawOrder); + m_anchorMarker -> setOrigin(m_mainGraphicalObjectText->origin()); + m_anchorMarker -> setVisibility(m_mainGraphicalObjectText->visibility()); + m_anchorMarker -> updateView(); + } + + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool TextImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = BaseClass::init(mapImpl, logicalLayerName); + + if (success) + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Use the StubGraphicalObject, which is doing nothing of the work, because + * the text will be rendered using m_mainGraphicalObjectText + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + m_mainGraphicalObject = new StubGraphicalObject(); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.h new file mode 100755 index 000000000..bd4e65715 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/text_impl.h @@ -0,0 +1,76 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of Text + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_single_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class GraphicalObjectPictureMarker; + +/** + * @brief The TextImpl class displays a text. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (irrelevant) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (irrelevant) + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT TextImpl : public AbstractSinglePointMapObjectImpl, public virtual Text +{ + using BaseClass = AbstractSinglePointMapObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit TextImpl(const QString &debugInfo = QString()); + virtual ~TextImpl(); + +public: + TextImpl(const TextImpl &other) = delete; + TextImpl &operator=(TextImpl &other) = delete; + +// void setScale(float) override {} + + void setShowAnchorMarker(bool on) override; + bool showAnchorMarker() const override; + +protected: + + bool updateViewImpl() override; + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + // logical + bool m_showAnchorMarker; + GraphicalObjectPictureMarker *m_anchorMarker; + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.cpp new file mode 100755 index 000000000..affacbed1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.cpp @@ -0,0 +1,494 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of VirtualMine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "virtual_mine_impl.h" +#include +#include +#include +#include +#include + +#include + + +// ESRI +#include +#include +#include + +// Qt +#include +#include + +namespace SituationMap +{ + +//// to get access to 'init' +//class VMineLocalBasicPathObject final : public BasicPathObjectImpl +//{ +//public: +// using BasicPathObjectImpl::BasicPathObjectImpl; +// using BasicPathObjectImpl::init; +// using BasicPathObjectImpl::setUniqueId; +//}; + +using VMineLocalBasicPathObject = LocalBasicPathObject; + + +class VirtualMineImpl::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: +// Esri::ArcGISRuntime::GraphicsOverlay *m_graphicsLayer = nullptr; + + + // Own data: + MapImpl *m_mapImpl = nullptr; + QString m_logicalLayer; + QString m_debugInfo; // will be appended by consecutive number + UniqueMapId m_uniqueMapId = INVALID_MAP_ID; + + struct MarkerSymbolDataRenderer + { +// std::shared_ptr m_graphic3D; + QSharedPointer m_goRendererAtt; + QVariantMap m_attribMap; + OriginTranslated m_origin; + } m_markerSymbolDataRenderer; + + struct MarkerSymbolData + { + QSharedPointer m_goSceneMarker; + SceneSymbolData m_sceneSymbolData; + OriginTranslated m_origin; + } m_markerSymbolData; + + struct ValueDataVM + { + QSharedPointer basicPathObj; + VirtualMine::OptionalData optData; + }; + + QMap m_cont; /// one polyline per subsidiary line + using MAP_IT_T = QMapIterator; +// int m_drawOrder = 0; + + bool hasPathObject(int key) const { return m_cont.contains(key); } + QList keysInUse() const { return m_cont.keys(); } + void remove(int key) { m_cont.remove(key); } + void clear() { m_cont.clear(); } + + ValueDataVM getPathObject(int key, const OptionalData &optData = OptionalData()) + { + auto ret = m_cont.value(key); // copy + if (ret.basicPathObj.isNull()) + { + QString debugInfo = QStringLiteral("%1#SubsidiaryLine(%2)").arg(m_debugInfo).arg(key); + VMineLocalBasicPathObject *temp = new VMineLocalBasicPathObject(debugInfo); + GraphicalObjectPathType pathType = GraphicalObjectPathType::Polyline; + if (optData.closed) + pathType = GraphicalObjectPathType::Polygon; + bool success = temp->init(m_mapImpl, m_logicalLayer, pathType); + temp->setUniqueId(m_uniqueMapId); + + if (success) + ret.basicPathObj = QSharedPointer(static_cast(temp)); + else + temp->removeMySelf(); + + ret.optData.closed = optData.closed; + } + // transfer optional data (line|fill) to ret and then to container _if_ set: + if (optData.lineStyle) + ret.optData.lineStyle = optData.lineStyle; + if (optData.fillStyle) + ret.optData.fillStyle = optData.fillStyle; + m_cont.insert(key, ret); + + return ret; + } +}; // class VirtualMineImpl::Private +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// class VirtualMineImpl +//----------------------------------------------------------------------------- +VirtualMineImpl::VirtualMineImpl(const QString &debugInfo) + : VirtualMine() // Interface + , BaseClass(debugInfo) // Implementation + , d(new Private) + , m_logger(getLogger("VirtualMineImpl")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_fillColorAlpha = 200; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +VirtualMineImpl::~VirtualMineImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_markerSymbolDataRenderer.m_goRendererAtt.clear(); + d->m_markerSymbolData.m_goSceneMarker.clear(); + +// // approach for V100.15 ; will maybe crash with older versions +// if (d->m_markerSymbolData.m_graphic3D && m_graphicsLayer && m_graphicsLayer->graphics()) +// m_graphicsLayer->graphics()->removeOne(d->m_markerSymbolData.m_graphic3D.get()); +// d->m_markerSymbolData.m_graphic3D.reset(); + + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void VirtualMineImpl::setUniqueId(const UniqueMapId &uniqueId) +{ + BaseClass::setUniqueId(uniqueId); + d->m_uniqueMapId = m_uniqueMapId; +} + + +PositionCont VirtualMineImpl::subsidiaryLine(int key) const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (d->hasPathObject(key)) + return d->getPathObject(key).basicPathObj->positions(); + else + return PositionCont(); +} + +void VirtualMineImpl::setSubsidiaryLine(int key, const PositionCont &positions, const OptionalData &optData) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", key=" << key + << ", closed=" << optData.closed + << ", positions.size=" << positions.size(); + + Private::ValueDataVM vd = d->getPathObject(key, optData); + QSharedPointer pathObj = vd.basicPathObj; + if (pathObj) + pathObj->setPositions(positions); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QList VirtualMineImpl::subsidiaryKeysInUse() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->keysInUse(); +} + +void VirtualMineImpl::removeSubsidiaryLine(int key) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->remove(key); +} + +void VirtualMineImpl::removeSubsidiaryLines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->clear(); +} + +bool VirtualMineImpl::has3DMarkerRenderer() const +{ +// TRACE_LOG(m_logger) << LOGFUNCTION << " START"; +// TRACE_LOG(m_logger) << LOGFUNCTION << " END, isEmpty = " << d->m_markerSymbolData.m_origin.isEmpty(); + return !d->m_markerSymbolDataRenderer.m_origin.isEmpty(); +} + +void VirtualMineImpl::set3DMarkerRenderer(const Point3D &markerOrigin, const QVariantMap &attribMap) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_markerSymbolDataRenderer.m_origin.setOrigin(markerOrigin); + d->m_markerSymbolDataRenderer.m_attribMap = attribMap; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool VirtualMineImpl::has3DMarkerSceneSymbol() const +{ +// TRACE_LOG(m_logger) << LOGFUNCTION << " START"; +// TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return !d->m_markerSymbolData.m_origin.isEmpty(); +} + +void VirtualMineImpl::set3DMarkerSceneSymbol(const Point3D &markerOrigin, const SceneSymbolData &sceneSymbolData) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_markerSymbolData.m_origin.setOrigin(markerOrigin); + d->m_markerSymbolData.m_sceneSymbolData = sceneSymbolData; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool VirtualMineImpl::updateViewImpl() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " m_debugInfo=" << m_debugInfo + << " symbol=" << m_symbol.value().sidc + << " visibility=" << m_visibility + << " visiTechn=" << m_visibilityTechnical + << " visiTact=" << m_visibilityTactical; + + bool success = BaseClass::updateViewImpl(); + + if (!success) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed"; + return false; + } + + if (nullptr == m_mainGraphicalObject) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no m_mainGraphicalObject"; + return false; + } + + // subsidiary : + + Private::MAP_IT_T mapIt(d->m_cont); + while (mapIt.hasNext()) + { + mapIt.next(); + const Private::ValueDataVM &vd = mapIt.value(); + QSharedPointer pathObj = vd.basicPathObj; + const OptionalData &optData = vd.optData; + if (pathObj) + { +// pathObj->setDebugInfo(m_mainGraphicalObject->debugInfo()); // will overwrite complete string -> skip + + if (optData.lineStyle) + pathObj->setLineStyle((*optData.lineStyle.data())); + else + pathObj->setLineStyle(m_mainGraphicalObject->lineStyle()); + + // transfer fillStyle only if relevant: + if (pathObj->pathType() == GraphicalObjectPathType::Polygon) + { + if (optData.fillStyle) + pathObj->setFillStyle((*optData.fillStyle.data())); + else + pathObj->setFillStyle(m_mainGraphicalObject->fillStyle()); + } + + pathObj->setVisibility(m_visibility); + pathObj->setVisibility(ObjectAppearance::Technical, m_visibilityTechnical); + + // Do not Transfer symbol. Only m_mainGraphicalObject uses ObjectAppearance::Tactical: +// pathObj->setSymbol(this->symbol()); + + + pathObj->setVisibility(ObjectAppearance::Tactical, false); + + pathObj->setPickable(m_mainGraphicalObject->pickable()); + + pathObj->setSelected(m_mainGraphicalObject->selected()); + + pathObj->setDrawOrder(m_internalDrawOrder); + + success = pathObj->updateView(); + if (!success) + { + int key = mapIt.key(); + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed, key=" << key + << ", m_debugInfo=" << m_debugInfo; + + } + } + } // while + + // : subsidiary + + + // 3DMarkerSceneSymbol : + if (m_mapImpl->mode() == Mode::Mode_3D) + { + const bool successUpdateRender = updateViewImpl3DMarkerRenderer(); + if (!successUpdateRender) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl3DMarkerRenderer failed" << ", m_debugInfo=" << m_debugInfo; + + const bool successUpdateSceneSymbol = updateViewImpl3DMarkerSceneSymbol(); + if (!successUpdateSceneSymbol) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl3DMarkerSceneSymbol failed" << ", m_debugInfo=" << m_debugInfo; + + success = success && successUpdateRender && successUpdateSceneSymbol; + } + // : 3DMarkerSceneSymbol + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool VirtualMineImpl::updateViewImpl3DMarkerRenderer() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = true; + + // qCritical() << " attribMap " << attribMap; + if (m_mapImpl->mode() == Mode::Mode_3D) + { + if (d->m_markerSymbolDataRenderer.m_origin.isEmpty()) + { + d->m_markerSymbolDataRenderer.m_goRendererAtt.clear(); + } else { + + if (!d->m_markerSymbolDataRenderer.m_goRendererAtt) + { + QString debugInfo = QStringLiteral("%1#goRendererAtt").arg(m_debugInfo); + d->m_markerSymbolDataRenderer.m_goRendererAtt = QSharedPointer + (new GraphicalObjectRendererAttributes(m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId) + ); + } + + d->m_markerSymbolDataRenderer.m_origin.setMapObjectImpl(this); + const Point3D &pos = d->m_markerSymbolDataRenderer.m_origin.originTranslated(); + d->m_markerSymbolDataRenderer.m_goRendererAtt->setOrigin(pos); + + d->m_markerSymbolDataRenderer.m_goRendererAtt->setAttributeMap(d->m_markerSymbolDataRenderer.m_attribMap); + + d->m_markerSymbolDataRenderer.m_goRendererAtt->setVisibility(m_visibility); + d->m_markerSymbolDataRenderer.m_goRendererAtt->setPickable(m_mainGraphicalObject->pickable()); + d->m_markerSymbolDataRenderer.m_goRendererAtt->setSelected(m_mainGraphicalObject->selected()); + d->m_markerSymbolDataRenderer.m_goRendererAtt->setDrawOrder(m_internalDrawOrder); + + // scale does not makes sense :-) + + success = d->m_markerSymbolDataRenderer.m_goRendererAtt->updateView(); + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed, m_goRendererAtt" + << ", m_debugInfo=" << m_debugInfo; + } + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + +bool VirtualMineImpl::updateViewImpl3DMarkerSceneSymbol() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + bool success = true; + + // qCritical() << " attribMap " << attribMap; + if (m_mapImpl->mode() == Mode::Mode_3D) + { + if (d->m_markerSymbolData.m_origin.isEmpty()) + { + d->m_markerSymbolData.m_goSceneMarker.clear(); + } else { + + if (!d->m_markerSymbolData.m_goSceneMarker) + { + SituationMap::SceneMarkerType type = SituationMap::SceneMarkerType::SPHERE; + if (d->m_markerSymbolData.m_sceneSymbolData.cone) + type = SituationMap::SceneMarkerType::CONE; + + QString debugInfo = QStringLiteral("%1#goSceneMarker").arg(m_debugInfo); + d->m_markerSymbolData.m_goSceneMarker = QSharedPointer + (new GraphicalObjectSceneMarker(type, + m_mapImpl->getEsriGeoView() + , m_graphicsLayer + , debugInfo + , m_uniqueMapId) + ); + } + + d->m_markerSymbolData.m_origin.setMapObjectImpl(this); + const Point3D &pos = d->m_markerSymbolData.m_origin.originTranslated(); + d->m_markerSymbolData.m_goSceneMarker->setOrigin(pos); + d->m_markerSymbolData.m_goSceneMarker->setDiameter(d->m_markerSymbolData.m_sceneSymbolData.radius*2.0); + + d->m_markerSymbolData.m_goSceneMarker->setHeading(d->m_markerSymbolData.m_sceneSymbolData.heading); + d->m_markerSymbolData.m_goSceneMarker->setPitch(d->m_markerSymbolData.m_sceneSymbolData.pitch); + d->m_markerSymbolData.m_goSceneMarker->setRoll(d->m_markerSymbolData.m_sceneSymbolData.roll); + + d->m_markerSymbolData.m_goSceneMarker->setVisibility(m_visibility); + d->m_markerSymbolData.m_goSceneMarker->setPickable(m_mainGraphicalObject->pickable()); + d->m_markerSymbolData.m_goSceneMarker->setSelected(m_mainGraphicalObject->selected()); + d->m_markerSymbolData.m_goSceneMarker->setDrawOrder(m_internalDrawOrder); + + QColor col = d->m_markerSymbolData.m_sceneSymbolData.color; + col.setAlphaF(col.alphaF() / 2.0); + d->m_markerSymbolData.m_goSceneMarker->setFillStyle(FillStyle(col, FillPattern::Solid)); + + // scale does not makes sense :-) + + success = d->m_markerSymbolData.m_goSceneMarker->updateView(); + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " updateViewImpl failed, m_goSceneMarker" + << ", m_debugInfo=" << m_debugInfo; + } + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + + +bool VirtualMineImpl::init(MapImpl *mapImpl, const QString &logicalLayerName) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (mapImpl) + { + switch (mapImpl->symbologyType()) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "10012500002719000000"); // SymbolSet = 25 271900 + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUMPNR----"); // MIL2525D: 1388 GUMPNR---- -> Polygon (Radioactive Area U) +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUMPNZ----"); // MIL2525D: 0359 GUMPNZ---- -> Point (Nuclear Detonations Ground Zero U) + break; + case AdvancedSymbology::SymbologyType::APP6B: + m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(), "GUCPBWR---"); // APP6B: 3935 GUCPBWR--- -> Polygon +// m_symbol.refValue().sidc = AdvancedSymbology::SidcCodec::encodeComplete(mapImpl->symbologyType(),"GUCPBWP---"); // APP6B: 2871 GUCPBWP--- -> Point (Fallout Producing U) + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } // switch + } // if (mapImpl) + + + bool success = BaseClass::init(mapImpl, logicalLayerName, GraphicalObjectPathType::Polygon); + + setLineStyle(LineStyle(Qt::yellow, 2)); + setFillStyle(FillStyle(Qt::yellow, FillPattern::Null)); + + if (!success) + ERROR_LOG(m_logger) << LOGFUNCTION << " init failed"; + + // set some values to Private: + d->m_mapImpl = m_mapImpl; + d->m_logicalLayer = m_logicalLayerName; + d->m_debugInfo = m_debugInfo; + d->m_uniqueMapId = m_uniqueMapId; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, success=" << success; + return success; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.h new file mode 100755 index 000000000..a6e860620 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MapObjectsImpl/virtual_mine_impl.h @@ -0,0 +1,102 @@ +/** + * @RDE_HEADER@ + * Interface-Implementation of VirtualMine + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "abstract_multi_point_map_object_impl.h" +#include "basic_path_object_impl.h" +#include // Interface + + +// Qt +#include + + + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +/** + * @brief The VirtualMineImpl class displays a nuclear area. The outline is displayed as well a subsidiary lines. + * + * A class instance consists of the following logical elements: + * - mainGraphicalObject (GraphicalObjectPath displaying the main techn. Appearance / outline) + * - mainGraphicalObjectText (displaying the text/name) + * - tacticalAppearance (displaying the tact. Appearance) + * - Container of BasicPathObjectImpl's to display the subsidiary lines. + */ +// inherent 'public', to get that implementation +// inherent 'public virtual' to get that interface +class SITUATIONMAPOBJECTS_EXPORT VirtualMineImpl : public BasicPathObjectImpl, public virtual VirtualMine +{ + using BaseClass = BasicPathObjectImpl; + + template friend class GenericObjectFactory; +protected: + explicit VirtualMineImpl(const QString &debugInfo = QString()); + virtual ~VirtualMineImpl(); + +public: + VirtualMineImpl(const VirtualMineImpl &other) = delete; + VirtualMineImpl &operator=(VirtualMineImpl &other) = delete; + + void setUniqueId(const UniqueMapId &uniqueId) override; + + /*! \name Subsidiary polylines, identified by an arbitrary choose key.*/ + //@{ + PositionCont subsidiaryLine(int key) const override; + void setSubsidiaryLine(int key, const PositionCont &positions, const OptionalData &optData = OptionalData()) override; + + QList subsidiaryKeysInUse() override; + + void removeSubsidiaryLine(int key) override; + void removeSubsidiaryLines() override; + //@} + + + /*! \name 3D marker / SimpleMarkerSceneSymbol (via renderer) */ + //@{ + bool has3DMarkerRenderer() const override; + void set3DMarkerRenderer(const Point3D &markerOrigin, const QVariantMap &attribMap) override; + //@} + + bool has3DMarkerSceneSymbol() const override; + void set3DMarkerSceneSymbol(const Point3D &markerOrigin, const SceneSymbolData &sceneSymbolData) override; + + +protected: + bool updateViewImpl() override; + bool updateViewImpl3DMarkerRenderer(); + bool updateViewImpl3DMarkerSceneSymbol(); + + friend class MapImpl; + bool init(MapImpl *mapImpl, const QString &logicalLayerName) override; + + // internal/admin + + + // logical + + +private: + class Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.cpp new file mode 100755 index 000000000..d1b156223 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.cpp @@ -0,0 +1,4691 @@ +/** +** +** @RDE_HEADER@ +** Clipping and Offsetting of lines and polygons +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** To use under Boost Software License Vers. 1 +** see also http://www.angusj.com/delphi/clipper/documentation/Docs/_Body.htm +** ----------------------------------------------------------------------------------------------- +*/ + +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.2.1 * +* Date : 31 October 2014 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2014 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "math_const.h" + +namespace ClipperLib { + +static double const pi = MathConst::pi; +static double const two_pi = MathConst::pi_two; +static double const def_arc_tolerance = 0.25; + +enum Direction { dRightToLeft, dLeftToRight }; + +static int const Unassigned = -1; //edge not currently 'owning' a solution +static int const Skip = -2; //edge that would otherwise close a path + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) + +struct TEdge { + IntPoint Bot; + IntPoint Curr; + IntPoint Top; + IntPoint Delta; + double Dx; + PolyType PolyTyp; + EdgeSide Side; + int WindDelta; //1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; //winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; + + TEdge() : Bot() + , Curr() + , Top() + , Delta() + , Dx(0) + , PolyTyp(PolyType::ptClip) + , Side(EdgeSide::esLeft) + , WindDelta(0) + , WindCnt(0) + , WindCnt2(0) + , OutIdx(0) + , Next(nullptr) + , Prev(nullptr) + , NextInLML(nullptr) + , NextInAEL(nullptr) + , PrevInAEL(nullptr) + , NextInSEL(nullptr) + , PrevInSEL(nullptr) {;} + + void init() + { + Bot = IntPoint(); + Curr = IntPoint(); + Top = IntPoint(); + Delta = IntPoint(); + Dx = 0.0; + PolyTyp = PolyType::ptClip; + Side = EdgeSide::esLeft; + WindDelta = 0; + WindCnt = 0; + WindCnt2 = 0; + OutIdx = 0; + Next = nullptr; + Prev = nullptr; + NextInLML = nullptr; + NextInAEL = nullptr; + PrevInAEL = nullptr; + NextInSEL = nullptr; + PrevInSEL = nullptr; + } +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; +}; + +struct LocalMinimum { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; +}; + +struct OutPt; + +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +struct LocMinSorter +{ + inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2) const + { + return locMin2.Y < locMin1.Y; + } +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) +{ + if ((val < 0)) return static_cast(val - 0.5); + else return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return nullptr; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + int result = (int)AllNodes.size(); + //with negative offsets, ignore the hidden outer polygon ... + if (result > 0 && Childs[0] != AllNodes[0]) result--; + return result; +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode() + : Contour() + , Childs() + , Parent(nullptr) + , Index(0) + , m_IsOpen(false) + , m_jointype(JoinType::jtSquare) + , m_endtype(EndType::etClosedPolygon) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return (int)Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = (unsigned)Childs.size(); + Childs.emplace_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return nullptr; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const +{ + return m_IsOpen; +} +//------------------------------------------------------------------------------ + +#ifndef use_int32 + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + ulong64 lo = 0; + long64 hi = 0; + + Int128() = default; + explicit Int128(long64 _lo) + : lo(_lo), hi(_lo < 0 ? -1 : 0) + {} + + explicit Int128(const long64& _hi, const ulong64& _lo) + : lo(_lo), hi(_hi) + {} + + Int128 & operator = (const long64 &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return *this; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi, 0); + else + return Int128(~hi, ~lo + 1); + } + + operator double() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } + +}; +//------------------------------------------------------------------------------ + +Int128 Int128Mul (long64 lhs, long64 rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +}; +#endif + +//------------------------------------------------------------------------------ +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +void Swap(cInt& val1, cInt& val2) +{ + cInt tmp = val1; + val1 = val2; + val2 = tmp; +} +//------------------------------------------------------------------------------ +bool Orientation(const Path &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +double Area(const Path &poly) +{ + int size = (int)poly.size(); + if (size < 3) return 0; + + double a = 0; + for (int i = 0, j = size -1; i < size; ++i) + { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec) +{ + OutPt *op = outRec.Pts; + if (!op) return 0; + double a = 0; + do { + a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + op = op->Next; + } while (op != outRec.Pts); + return a * 0.5; +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (pp2->Pt == Pt) return true; + pp2 = pp2->Next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +int PointInPolygon (const IntPoint &pt, const Path &path) +{ + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + int result = 0; + size_t cnt = path.size(); + if (cnt < 3) return 0; + IntPoint ip = path[0]; + for(size_t i = 1; i <= cnt; ++i) + { + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) + { + if ((ipNext.X == pt.X) || (ip.Y == pt.Y && + ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; + } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) + { + if (ip.X >= pt.X) + { + if (ipNext.X > pt.X) result = 1 - result; + else + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } else + { + if (ipNext.X > pt.X) + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } + } + ip = ipNext; + } + return result; +} +//------------------------------------------------------------------------------ + +int PointInPolygon (const IntPoint &pt, OutPt *op) +{ + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + OutPt* startOp = op; + for(;;) + { + if (op->Next->Pt.Y == pt.Y) + { + if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && + ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; + } + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) + { + if (op->Pt.X >= pt.X) + { + if (op->Next->Pt.X > pt.X) result = 1 - result; + else + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } else + { + if (op->Next->Pt.X > pt.X) + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) break; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) +{ + OutPt* op = OutPt1; + do + { + //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) return res > 0; + op = op->Next; + } + while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(e1.Delta.Y, e2.Delta.X) == Int128Mul(e1.Delta.X, e2.Delta.Y); + else +#endif + return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint & pt1, const IntPoint & pt2, + const IntPoint &pt3, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint & pt1, const IntPoint & pt2, + const IntPoint & pt3, const IntPoint & pt4, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +inline bool IsHorizontal(const TEdge &e) +{ + return e.Delta.Y == 0; +} +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint & pt1, const IntPoint & pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +inline void SetDx(TEdge &e) +{ + e.Delta.X = (e.Top.X - e.Bot.X); + e.Delta.Y = (e.Top.Y - e.Bot.Y); + + if (e.Delta.Y == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Delta.X) / e.Delta.Y; +} +//--------------------------------------------------------------------------- + +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) +{ + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; +} +//------------------------------------------------------------------------------ + +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) +{ + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; +} +//------------------------------------------------------------------------------ + +inline cInt TopX(const TEdge &edge, const cInt currentY) +{ + return ( currentY == edge.Top.Y ) ? + edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); +} +//------------------------------------------------------------------------------ + +void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) +{ +#ifdef use_xyz + ip.Z = 0; +#endif + + double b1, b2; + if (Edge1.Dx == Edge2.Dx) + { + ip.Y = Edge1.Curr.Y; + ip.X = TopX(Edge1, ip.Y); + return; + } + else if (Edge1.Delta.X == 0) + { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else + { + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); + } + } + else if (Edge2.Delta.X == 0) + { + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else + { + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); + } + } + else + { + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); + } + + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) + { + if (Edge1.Top.Y > Edge2.Top.Y) + ip.Y = Edge1.Top.Y; + else + ip.Y = Edge2.Top.Y; + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); + } + //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... + if (ip.Y > Edge1.Curr.Y) + { + ip.Y = Edge1.Curr.Y; + //use the more vertical edge to derive X ... + if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) + ip.X = TopX(Edge2, ip.Y); else + ip.X = TopX(Edge1, ip.Y); + } +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == nullptr) return; + pp->Prev->Next = nullptr; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->Next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) +{ + #ifdef USE_MEMSET + std::memset(e, 0, sizeof(TEdge)); + #else + e->init(); + #endif + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ + +void InitEdge2(TEdge& e, PolyType Pt) +{ + if (e.Curr.Y >= e.Next->Curr.Y) + { + e.Bot = e.Curr; + e.Top = e.Next->Curr; + } else + { + e.Top = e.Curr; + e.Bot = e.Next->Curr; + } + SetDx(e); + e.PolyTyp = Pt; +} +//------------------------------------------------------------------------------ + +TEdge* RemoveEdge(TEdge* e) +{ + //removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge* result = e->Next; + e->Prev = nullptr; //flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) +{ + //swap horizontal edges' Top and Bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + Swap(e.Top.X, e.Bot.X); +#ifdef use_xyz + Swap(e.Top.Z, e.Bot.Z); +#endif +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = nullptr; + OutPt* p = pp->Next; + while (p != pp) + { + if (p->Pt.Y > pp->Pt.Y) + { + pp = p; + dups = nullptr; + } + else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) + { + if (p->Pt.X < pp->Pt.X) + { + dups = nullptr; + pp = p; + } else + { + if (p->Next != pp && p->Prev != pp) dups = p; + } + } + p = p->Next; + } + if (dups) + { + //there appears to be at least 2 vertices at BottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->Next; + while (dups->Pt != pp->Pt) dups = dups->Next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool Pt2IsBetweenPt1AndPt3(const IntPoint & pt1, + const IntPoint & pt2, const IntPoint & pt3) +{ + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); +} +//------------------------------------------------------------------------------ + +bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) +{ + if (seg1a > seg1b) Swap(seg1a, seg1b); + if (seg2a > seg2b) Swap(seg2a, seg2b); + return (seg1a < seg2b) && (seg2a < seg1b); +} + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + + +ClipperBase::ClipperBase() : m_MinimaList(), m_UseFullRange(false), m_edges(), m_PreserveCollinear(false), m_HasOpenPaths(false) +{ + m_CurrentLM = m_MinimaList.begin(); //begin() == end() here +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint& Pt, bool& useFullRange) +{ + if (useFullRange) + { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw "Coordinate outside allowed range"; + } + else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, useFullRange); + } +} +//------------------------------------------------------------------------------ + +TEdge* FindNextLocMin(TEdge* E) +{ + for (;;) + { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; + while (IsHorizontal(*E->Prev)) E = E->Prev; + TEdge* E2 = E; + while (IsHorizontal(*E)) E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) +{ + TEdge *Result = E; + TEdge *Horz = nullptr; + + if (E->OutIdx == Skip) + { + //if edges still remain in the current bound beyond the skip edge then + //create another LocMin and call ProcessBound once more + if (NextIsForward) + { + while (E->Top.Y == E->Next->Bot.Y) E = E->Next; + //don't include top horizontals when parsing a bound a second time, + //they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) E = E->Prev; + } + else + { + while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; + while (E != Result && IsHorizontal(*E)) E = E->Next; + } + + if (E == Result) + { + if (NextIsForward) Result = E->Next; + else Result = E->Prev; + } + else + { + //there are more edges in the bound beyond result starting with E + if (NextIsForward) + E = Result->Next; + else + E = Result->Prev; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = nullptr; + locMin.RightBound = E; + E->WindDelta = 0; + Result = ProcessBound(E, NextIsForward); + m_MinimaList.emplace_back(locMin); + } + return Result; + } + + TEdge *EStart; + + if (IsHorizontal(*E)) + { + //We need to be careful with open paths because this may not be a + //true local minima (ie E may be following a skip edge). + //Also, consecutive horz. edges may start heading left before going right. + if (NextIsForward) + EStart = E->Prev; + else + EStart = E->Next; + if (EStart->OutIdx != Skip) + { + if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge + { + if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) + ReverseHorizontal(*E); + } + else if (EStart->Bot.X != E->Bot.X) + ReverseHorizontal(*E); + } + } + + EStart = E; + if (NextIsForward) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!NextIsForward) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!NextIsForward) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + + return Result; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) +{ +#ifdef use_lines + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() -1; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; + while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; + + //create a new edge array ... + TEdge *edges = new TEdge [highI +1]; + + bool IsFlat = true; + //1. Basic (first) edge initialization ... + try + { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); + } + } + catch(...) + { + delete [] edges; + throw; //range test fails + } + TEdge *eStart = &edges[0]; + + //2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge *E = eStart, *eLoopStop = eStart; + for (;;) + { + //nb: allows matching start and end points when not Closed ... + if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) + { + if (E == E->Next) break; + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E->Prev == E->Next) + break; //only two vertices + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) + { + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; + } + E = E->Next; + if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) + { + delete [] edges; + return false; + } + + if (!Closed) + { + m_HasOpenPaths = true; + eStart->Prev->OutIdx = Skip; + } + + //3. Do second stage of edge initialization ... + E = eStart; + do + { + InitEdge2(*E, PolyTyp); + E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; + } + while (E != eStart); + + //4. Finally, add edge bounds to LocalMinima list ... + + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) + { + if (Closed) + { + delete [] edges; + return false; + } + E->Prev->OutIdx = Skip; + if (E->Prev->Bot.X < E->Prev->Top.X) ReverseHorizontal(*E->Prev); + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = nullptr; + locMin.RightBound = E; + locMin.RightBound->Side = esRight; + locMin.RightBound->WindDelta = 0; + while (E->Next->OutIdx != Skip) + { + E->NextInLML = E->Next; + if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + m_MinimaList.emplace_back(locMin); + m_edges.emplace_back(edges); + return true; + } + + m_edges.emplace_back(edges); + bool leftBoundIsForward; + TEdge* EMin = nullptr; + + //workaround to avoid an endless loop in the while loop below when + //open paths have matching start and end points ... + if (E->Prev->Bot == E->Prev->Top) E = E->Next; + + for (;;) + { + E = FindNextLocMin(E); + if (E == EMin) break; + else if (!EMin) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) + { + locMin.LeftBound = E->Prev; + locMin.RightBound = E; + leftBoundIsForward = false; //Q.nextInLML = Q.prev + } else + { + locMin.LeftBound = E; + locMin.RightBound = E->Prev; + leftBoundIsForward = true; //Q.nextInLML = Q.next + } + locMin.LeftBound->Side = esLeft; + locMin.RightBound->Side = esRight; + + if (!Closed) locMin.LeftBound->WindDelta = 0; + else if (locMin.LeftBound->Next == locMin.RightBound) + locMin.LeftBound->WindDelta = -1; + else locMin.LeftBound->WindDelta = 1; + locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; + + E = ProcessBound(locMin.LeftBound, leftBoundIsForward); + if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); + + TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); + if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); + + if (locMin.LeftBound->OutIdx == Skip) + locMin.LeftBound = nullptr; + else if (locMin.RightBound->OutIdx == Skip) + locMin.RightBound = nullptr; + m_MinimaList.emplace_back(locMin); + if (!leftBoundIsForward) E = E2; + } + return true; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) +{ + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) + { + //for each edge array in turn, find the first used edge and + //check for and remove any hiddenPts in each edge in the array. + TEdge* edges = m_edges[i]; + delete [] edges; + } + m_edges.clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList.begin(); + if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process + std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); + + //reset all edges ... + for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) + { + TEdge* e = lm->LeftBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esLeft; + e->OutIdx = Unassigned; + } + + e = lm->RightBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + m_MinimaList.clear(); + m_CurrentLM = m_MinimaList.begin(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::PopLocalMinima() +{ + if (m_CurrentLM == m_MinimaList.end()) return; + ++m_CurrentLM; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + MinimaList::iterator lm = m_MinimaList.begin(); + if (lm == m_MinimaList.end()) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm != m_MinimaList.end()) + { + result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); + TEdge* e = lm->LeftBound; + for (;;) { + TEdge* bottomE = e; + while (e->NextInLML) + { + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + e = e->NextInLML; + } + result.left = std::min(result.left, e->Bot.X); + result.right = std::max(result.right, e->Bot.X); + result.left = std::min(result.left, e->Top.X); + result.right = std::max(result.right, e->Top.X); + result.top = std::min(result.top, e->Top.Y); + if (bottomE == lm->LeftBound) e = lm->RightBound; + else break; + } + ++lm; + } + return result; +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) + : ClipperBase() + , m_PolyOuts() + , m_Joins() + , m_GhostJoins() + , m_IntersectList() + , m_ClipType(ClipType::ctIntersection) + , m_Scanbeam() + , m_ActiveEdges(nullptr) + , m_SortedEdges(nullptr) + , m_ExecuteLocked(false) + , m_ClipFillType(PolyFillType::pftEvenOdd) + , m_SubjFillType(PolyFillType::pftEvenOdd) + , m_ReverseOutput((initOptions & ioReverseSolution) != 0) + , m_UsingPolyTree(false) + , m_StrictSimple((initOptions & ioStrictlySimple) != 0) +#ifdef use_xyz + , m_ZFill(nullptr) +#endif +{ + m_UseFullRange = false; + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +Clipper::~Clipper() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(ZFillCallback zFillFunc) +{ + m_ZFill = zFillFunc; +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::Reset() +{ + ClipperBase::Reset(); + m_Scanbeam = ScanbeamList(); + m_ActiveEdges = nullptr; + m_SortedEdges = nullptr; + for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) + InsertScanbeam(lm->Y); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + if (m_HasOpenPaths) + throw clipperException("Error: PolyTree struct is need for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && + outrec.FirstLeft->Pts)) return; + + OutRec* orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded = true; + try { + Reset(); + if (m_CurrentLM == m_MinimaList.end()) return true; + cInt botY = PopScanbeam(); + do { + InsertLocalMinimaIntoAEL(botY); + ClearGhostJoins(); + ProcessHorizontals(false); + if (m_Scanbeam.empty()) break; + cInt topY = PopScanbeam(); + succeeded = ProcessIntersections(topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while (!m_Scanbeam.empty() || m_CurrentLM != m_MinimaList.end()); + } + catch(...) + { + succeeded = false; + } + + if (succeeded) + { + //fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) JoinCommonEdges(); + + //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (outRec->Pts && !outRec->IsOpen) + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertScanbeam(const cInt Y) +{ + //if (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) return;// avoid duplicates. + m_Scanbeam.push(Y); +} +//------------------------------------------------------------------------------ + +cInt Clipper::PopScanbeam() +{ + const cInt Y = m_Scanbeam.top(); + m_Scanbeam.pop(); + while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. + return Y; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeAllOutRecs(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = nullptr; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) const +{ + TEdge *e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; + if (!e) + { + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //EvenOdd filling ... + if (edge.WindDelta == 0) + { + //are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != &edge) + { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.PolyTyp == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; + case pftNonZero: + if (Abs(edge.WindCnt) != 1) return false; + break; + case pftPositive: + if (edge.WindCnt != 1) return false; + break; + default: //pftNegative + if (edge.WindCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + break; + case ctDifference: + if (edge.PolyTyp == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + OutPt* result; + TEdge *e, *prevE; + if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) + { + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; + e = e1; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; + else + prevE = e->PrevInAEL; + } else + { + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; + e = e2; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; + else + prevE = e->PrevInAEL; + } + + if (prevE && prevE->OutIdx >= 0 && + (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && + SlopesEqual(*e, *prevE, m_UseFullRange) && + (e->WindDelta != 0) && (prevE->WindDelta != 0)) + { + OutPt* outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + AddOutPt( e1, Pt ); + if (e2->WindDelta == 0) AddOutPt(e2, Pt); + if( e1->OutIdx == e2->OutIdx ) + { + e1->OutIdx = Unassigned; + e2->OutIdx = Unassigned; + } + else if (e1->OutIdx < e2->OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->PrevInSEL = nullptr; + edge->NextInSEL = nullptr; + } + else + { + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = nullptr; + m_SortedEdges->PrevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint & OffPt) +{ + Join* j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.emplace_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearGhostJoins() +{ + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddGhostJoin(OutPt *op, const IntPoint & OffPt) +{ + Join* j = new Join; + j->OutPt1 = op; + j->OutPt2 = nullptr; + j->OffPt = OffPt; + m_GhostJoins.emplace_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) +{ + while (m_CurrentLM != m_MinimaList.end() && (m_CurrentLM->Y == botY)) + { + TEdge* lb = m_CurrentLM->LeftBound; + TEdge* rb = m_CurrentLM->RightBound; + PopLocalMinima(); + OutPt *Op1 = nullptr; + if (!lb) + { + //nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, nullptr); + SetWindingCount(*rb); + if (IsContributing(*rb)) + Op1 = AddOutPt(rb, rb->Bot); + } + else if (!rb) + { + InsertEdgeIntoAEL(lb, nullptr); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + else + { + InsertEdgeIntoAEL(lb, nullptr); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount( *lb ); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + + if (rb) + { + if(IsHorizontal(*rb)) AddEdgeToSEL(rb); + else InsertScanbeam( rb->Top.Y ); + } + + if (!lb || !rb) continue; + + //if any output polygons share an edge, they'll need joining later ... + if (Op1 && IsHorizontal(*rb) && + !m_GhostJoins.empty() && (rb->WindDelta != 0)) + { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + { + Join* jr = m_GhostJoins[i]; + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && + lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if(lb->NextInAEL != rb) + { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge* e = lb->NextInAEL; + if (e) + { + while( e != rb ) + { + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the Right of param2 ABOVE the intersection ... + IntersectEdges(rb , e , lb->Curr); //order important here + e = e->NextInAEL; + } + } + } + + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted + if( AelPrev ) AelPrev->NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if( AelNext ) AelNext->PrevInAEL = AelPrev; + e->NextInAEL = nullptr; + e->PrevInAEL = nullptr; +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->PrevInSEL; + TEdge* SelNext = e->NextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->NextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->PrevInSEL = SelPrev; + e->NextInSEL = nullptr; + e->PrevInSEL = nullptr; +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) +{ + if (pt.Z != 0 || !m_ZFill) return; + else if (pt == e1.Bot) pt.Z = e1.Bot.Z; + else if (pt == e1.Top) pt.Z = e1.Top.Z; + else if (pt == e2.Bot) pt.Z = e2.Bot.Z; + else if (pt == e2.Top) pt.Z = e2.Top.Z; + else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + bool e1Contributing = ( e1->OutIdx >= 0 ); + bool e2Contributing = ( e2->OutIdx >= 0 ); + +#ifdef use_xyz + SetZ(Pt, *e1, *e2); +#endif + +#ifdef use_lines + //if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) return; + + //if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && + e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) + { + if (e1->WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + } + else if (e1->PolyTyp != e2->PolyTyp) + { + //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + return; + } +#endif + + //update winding counts... + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if ( e1->PolyTyp == e2->PolyTyp ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; + } else + { + if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; + else e1->WindCnt += e2->WindDelta; + if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; + else e2->WindCnt -= e1->WindDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; + else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; + else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->PolyTyp == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->PolyTyp == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + cInt e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->WindCnt; break; + case pftNegative: e1Wc = -e1->WindCnt; break; + default: e1Wc = Abs(e1->WindCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->WindCnt; break; + case pftNegative: e2Wc = -e2->WindCnt; break; + default: e2Wc = Abs(e2->WindCnt); + } + + if ( e1Contributing && e2Contributing ) + { + if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) + { + AddLocalMaxPoly(e1, e2, Pt); + } + else + { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides( *e1 , *e2 ); + SwapPolyIndexes( *e1 , *e2 ); + } + } + else if ( e1Contributing ) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( e2Contributing ) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) + { + //neither edge is currently contributing ... + + cInt e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->WindCnt2; break; + case pftNegative : e1Wc2 = -e1->WindCnt2; break; + default: e1Wc2 = Abs(e1->WindCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->WindCnt2; break; + case pftNegative: e2Wc2 = -e2->WindCnt2; break; + default: e2Wc2 = Abs(e2->WindCnt2); + } + + if (e1->PolyTyp != e2->PolyTyp) + { + AddLocalMinPoly(e1, e2, Pt); + } + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctDifference: + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, Pt); + } + else + SwapSides( *e1, *e2 ); + } +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const +{ + bool IsHole = false; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->OutIdx >= 0 && e2->WindDelta != 0) + { + IsHole = !IsHole; + if (! outrec->FirstLeft) + outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; + } + e2 = e2->PrevInAEL; + } + if (IsHole) outrec->IsHole = true; +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; + else if (OutPt1->Next == OutPt1) return outRec2; + else if (OutPt2->Next == OutPt2) return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::GetOutRec(int Idx) +{ + OutRec* outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + + OutRec *holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + //get the start and ends of both output polygons and + //join e2 poly onto e1 poly and delete pointers to e2 ... + + OutPt* p1_lft = outRec1->Pts; + OutPt* p1_rt = p1_lft->Prev; + OutPt* p2_lft = outRec2->Pts; + OutPt* p2_rt = p2_lft->Prev; + + EdgeSide Side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->Side == esLeft ) + { + if( e2->Side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; + } else + { + //x y z a b c + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; + } + Side = esLeft; + } else + { + if( e2->Side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + } else + { + //a b c x y z + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; + } + Side = esRight; + } + + outRec1->BottomPt = nullptr; + if (holeStateRec == outRec2) + { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->IsHole = outRec2->IsHole; + } + outRec2->Pts = nullptr; + outRec2->BottomPt = nullptr; + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->OutIdx == ObsoleteIdx ) + { + e->OutIdx = OKIdx; + e->Side = Side; + break; + } + e = e->NextInAEL; + } + + outRec2->Idx = outRec1->Idx; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::CreateOutRec() +{ + OutRec* result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = nullptr; + result->Pts = nullptr; + result->BottomPt = nullptr; + result->PolyNd = nullptr; + m_PolyOuts.emplace_back(result); + result->Idx = (int)m_PolyOuts.size()-1; + return result; +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + bool ToFront = (e->Side == esLeft); + if( e->OutIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + outRec->IsOpen = (e->WindDelta == 0); + OutPt* newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); + e->OutIdx = outRec->Idx; + return newOp; + } else + { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt* op = outRec->Pts; + + if (ToFront && (pt == op->Pt)) return op; + else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; + + OutPt* newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) outRec->Pts = newOp; + return newOp; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals(bool IsTopOfScanbeam) +{ + TEdge* horzEdge = m_SortedEdges; + while(horzEdge) + { + DeleteFromSEL(horzEdge); + ProcessHorizontal(horzEdge, IsTopOfScanbeam); + horzEdge = m_SortedEdges; + } +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) +{ + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const cInt Y) +{ + return e && e->Top.Y == Y && !e->NextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const cInt Y) +{ + return e->Top.Y == Y && e->NextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + TEdge* result = nullptr; + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + result = e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + result = e->Prev; + + if (result && (result->OutIdx == Skip || + //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ... + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) + return nullptr; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +{ + //check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) return; + + if( Edge1->NextInAEL == Edge2 ) + { + TEdge* Next = Edge2->NextInAEL; + if( Next ) Next->PrevInAEL = Edge1; + TEdge* Prev = Edge1->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } + else if( Edge2->NextInAEL == Edge1 ) + { + TEdge* Next = Edge1->NextInAEL; + if( Next ) Next->PrevInAEL = Edge2; + TEdge* Prev = Edge2->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } + else + { + TEdge* Next = Edge1->NextInAEL; + TEdge* Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if( Edge1->NextInAEL ) Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1; + else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) +{ + if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; + if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; + + if( Edge1->NextInSEL == Edge2 ) + { + TEdge* Next = Edge2->NextInSEL; + if( Next ) Next->PrevInSEL = Edge1; + TEdge* Prev = Edge1->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; + } + else if( Edge2->NextInSEL == Edge1 ) + { + TEdge* Next = Edge1->NextInSEL; + if( Next ) Next->PrevInSEL = Edge2; + TEdge* Prev = Edge2->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; + } + else + { + TEdge* Next = Edge1->NextInSEL; + TEdge* Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; + } + + if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; + else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; +} +//------------------------------------------------------------------------------ + +void GetHorzDirection(const TEdge &HorzEdge, Direction& Dir, cInt& Left, cInt& Right) +{ + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +/******************************************************************************* +* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * +* Bottom of a scanbeam) are processed as if layered. The order in which HEs * +* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * +* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * +* and with other non-horizontal edges [*]. Once these intersections are * +* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * +* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * +*******************************************************************************/ + +void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) +{ + Direction dir; + cInt horzLeft, horzRight; + + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge* eLastHorz = horzEdge, *eMaxPair = nullptr; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + for (;;) + { + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge* e = GetNextInAEL(horzEdge, dir); + while(e) + { + //Break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) break; + + TEdge* eNext = GetNextInAEL(e, dir); //saves eNext for later + + if ((dir == dLeftToRight && e->Curr.X <= horzRight) || + (dir == dRightToLeft && e->Curr.X >= horzLeft)) + { + //so far we're still in range of the horizontal Edge but make sure + //we're at the last of consec. horizontals when matching with eMaxPair + if(e == eMaxPair && IsLastHorz) + { + + if (horzEdge->OutIdx >= 0) + { + OutPt* op1 = AddOutPt(horzEdge, horzEdge->Top); + TEdge* eNextHorz = m_SortedEdges; + while (eNextHorz) + { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, + horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + { + OutPt* op2 = AddOutPt(eNextHorz, eNextHorz->Bot); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Bot); + AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); + } + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + return; + } + else if(dir == dLeftToRight) + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt); + } + else + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges( e, horzEdge, Pt); + } + SwapPositionsInAEL( horzEdge, e ); + } + else if( (dir == dLeftToRight && e->Curr.X >= horzRight) || + (dir == dRightToLeft && e->Curr.X <= horzLeft) ) break; + e = eNext; + } //end while + + if (horzEdge->NextInLML && IsHorizontal(*horzEdge->NextInLML)) + { + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + } else + break; + } //end for (;;) + + if(horzEdge->NextInLML) + { + if(horzEdge->OutIdx >= 0) + { + OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top); + if (isTopOfScanbeam) AddGhostJoin(op1, horzEdge->Bot); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge* ePrev = horzEdge->PrevInAEL; + TEdge* eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) + { + OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) + { + OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } + else + UpdateEdgeIntoAEL(horzEdge); + } + else + { + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +void Clipper::UpdateEdgeIntoAEL(TEdge *&e) +{ + if( !e->NextInLML ) throw + clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (AelPrev) AelPrev->NextInAEL = e->NextInLML; + else m_ActiveEdges = e->NextInLML; + if (AelNext) AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const cInt topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(topY); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) return true; + if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); + else return false; + } + catch(...) + { + m_SortedEdges = nullptr; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = nullptr; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i ) + delete m_IntersectList[i]; + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const cInt topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX( *e, topY ); + e = e->NextInAEL; + } + + //bubblesort ... + bool isModified; + do + { + isModified = false; + e = m_SortedEdges; + while( e->NextInSEL ) + { + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if(e->Curr.X > eNext->Curr.X) + { + IntersectPoint(*e, *eNext, Pt); + IntersectNode * newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.emplace_back(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = nullptr; + else break; + } + while ( isModified ); + m_SortedEdges = nullptr; //important +} +//------------------------------------------------------------------------------ + + +void Clipper::ProcessIntersectList() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i) + { + IntersectNode* iNode = m_IntersectList[i]; + { + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); + SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); + } + delete iNode; + } + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) +{ + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted Bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) + { + if (!EdgesAdjacent(*m_IntersectList[i])) + { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; + if (j == cnt) return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); + } + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) +{ + TEdge* eMaxPair = GetMaximaPair(e); + if (!eMaxPair) + { + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; + } + + TEdge* eNext = e->NextInAEL; + while(eNext && eNext != eMaxPair) + { + IntersectEdges(e, eNext, e->Top); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; + } + + if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) + { + if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } +#ifdef use_lines + else if (e->WindDelta == 0) + { + if (e->OutIdx >= 0) + { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) + { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) + { + TEdge* eMaxPair = GetMaximaPair(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if(IsMaximaEdge) + { + TEdge* ePrev = e->PrevInAEL; + DoMaxima(e); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->NextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) + { + UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); + AddEdgeToSEL(e); + } + else + { + e->Curr.X = TopX( *e, topY ); + e->Curr.Y = topY; + } + + if (m_StrictSimple) + { + TEdge* ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && + (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) + { + IntPoint pt = e->Curr; +#ifdef use_xyz + SetZ(pt, *ePrev, *e); +#endif + OutPt* op = AddOutPt(ePrev, pt); + OutPt* op2 = AddOutPt(e, pt); + AddJoin(op, op2, pt); //StrictlySimple (type-3) join + } + } + + e = e->NextInAEL; + } + } + + //3. Process horizontals at the Top of the scanbeam ... + ProcessHorizontals(true); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while(e) + { + if(IsIntermediate(e, topY)) + { + OutPt* op = nullptr; + if( e->OutIdx >= 0 ) + op = AddOutPt(e, e->Top); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->PrevInAEL; + TEdge* eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && + ePrev->Curr.Y == e->Bot.Y && op && + ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*e, *ePrev, m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); + } + else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*e, *eNext, m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); + } + } + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = nullptr; + outrec.BottomPt = nullptr; + OutPt *pp = outrec.Pts; + + for (;;) + { + if (pp->Prev == pp || pp->Prev == pp->Next ) + { + DisposeOutPts(pp); + outrec.Pts = nullptr; + return; + } + + //test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) + { + lastOK = nullptr; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) +{ + if (!Pts) return 0; + int result = 0; + OutPt* p = Pts; + do + { + result++; + p = p->Next; + } + while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) +{ + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (!m_PolyOuts[i]->Pts) continue; + Path pg; + OutPt* p = m_PolyOuts[i]->Pts->Prev; + const int cnt = PointCount(p); + if (cnt < 2) continue; + pg.reserve(cnt); + for (int j = 0; j < cnt; ++j) + { + pg.emplace_back(p->Pt); + p = p->Prev; + } + polys.emplace_back(pg); + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.emplace_back(pn); + outRec->PolyNd = pn; + pn->Parent = nullptr; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) + { + pn->Contour.emplace_back(op->Pt); + op = op->Prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) continue; + if (outRec->IsOpen) + { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } + else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + //just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; //gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + if (e2.Curr.X == e1.Curr.X) + { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ + +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt& Left, cInt& Right) +{ + if (a1 < a2) + { + if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} + else {Left = std::max(a1,b2); Right = std::min(a2,b1);} + } + else + { + if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} + else {Left = std::max(a2,b2); Right = std::min(a1,b1);} + } + return Left < Right; +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(const OutRec &outrec) +{ + OutPt* op = outrec.Pts; + do + { + op->Idx = outrec.Idx; + op = op->Prev; + } + while(op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) +{ + if(!m_ActiveEdges) + { + edge->PrevInAEL = nullptr; + edge->NextInAEL = nullptr; + m_ActiveEdges = edge; + } + else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) + { + edge->PrevInAEL = nullptr; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } + else + { + if(!startEdge) startEdge = m_ActiveEdges; + while(startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) +{ + OutPt* result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) + { + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } + else + { + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; + } + return result; +} +//------------------------------------------------------------------------------ + +bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, + const IntPoint & Pt, bool DiscardLeft) +{ + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) + { + while (op1->Next->Pt.X <= Pt.X && + op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1->Next->Pt.X >= Pt.X && + op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) + { + while (op2->Next->Pt.X <= Pt.X && + op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2->Next->Pt.X >= Pt.X && + op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) + { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } + else + { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) +{ + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictSimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) + { + //Strictly Simple join ... + if (outRec1 != outRec2) return false; + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) + { + Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } + else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) + { + Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } + else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) + { + Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } + else + { + Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + j->OutPt1 = op1; j->OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +static OutRec* ParseFirstLeft(OutRec* FirstLeft) +{ + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const +{ + //tests if NewOutRec contains the polygon before reassigning FirstLeft + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->Pts || !outRec->FirstLeft) continue; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (firstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const +{ + //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + Join* join = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->Pts = join->OutPt1; + outRec1->BottomPt = nullptr; + outRec2 = CreateOutRec(); + outRec2->Pts = join->OutPt2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + //We now need to check every OutRec.FirstLeft pointer. If it points + //to OutRec1 it may need to point to OutRec2 instead ... + if (m_UsingPolyTree) + for (PolyOutList::size_type j = 0; j < m_PolyOuts.size() - 1; j++) + { + OutRec* oRec = m_PolyOuts[j]; + if (!oRec->Pts || ParseFirstLeft(oRec->FirstLeft) != outRec1 || + oRec->IsHole == outRec1->IsHole) continue; + if (Poly2ContainsPoly1(oRec->Pts, join->OutPt2)) + oRec->FirstLeft = outRec2; + } + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) + { + //outRec2 is contained by outRec1 ... + outRec2->IsHole = !outRec1->IsHole; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) + { + //outRec1 is contained by outRec2 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2->Pts = nullptr; + outRec2->BottomPt = nullptr; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +// ClipperOffset class +//------------------------------------------------------------------------------ + +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) + : m_destPolys() + , m_srcPoly() + , m_destPoly() + , m_normals() + , m_delta() + , m_sinA(0.0) + , m_sin(0.0) + , m_cos(1.0) + , m_miterLim() + , m_StepsPerRad() + , m_lowest() + , m_polyNodes() +{ + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +ClipperOffset::~ClipperOffset() +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Clear() +{ + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Childs[i]; + m_polyNodes.Childs.clear(); + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) +{ + int highI = (int)path.size() - 1; + if (highI < 0) return; + PolyNode* newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.emplace_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) + { + j++; + newNode->Contour.emplace_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) k = j; + } + if (endType == etClosedPolygon && j < 2) + { + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && + newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) +{ + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() +{ + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths& solution, double delta) +{ + solution.clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (!solution.empty()) solution.erase(solution.begin()); + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Builds a new path from "path" with offset "delta" + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +bool ClipperOffset::PathOffset( const Path & path, Path & offsetPath, double delta ) +{ + IntPoint pathBegin; + IntPoint pathEnd; + + Clear(); + + if (delta < 0) + { + Path reversePath; + + reversePath.reserve(path.size()); + + for (auto p = path.crbegin(); p != path.crend(); ++p) + { + reversePath.emplace_back(*p); + } + + AddPath( reversePath, JoinType::jtSquare, EndType::etOpenButt ); + + delta = fabs(delta); + pathBegin = path.back(); + pathEnd = path.front(); + } + else + { + pathBegin = path.front(); + pathEnd = path.back(); + AddPath( path, JoinType::jtSquare, EndType::etOpenButt ); + } + + // build offset polygon: + + Paths solution; + + FixOrientations(); + DoOffset(delta); + + Clipper clpr; + + clpr.AddPaths(m_destPolys, ptSubject, true); + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + + // filter edges to get only the moved path: + + bool onOffsetPath = false; + for (auto solutionPath : solution) + { + if (onOffsetPath) break; + + ClipperLib::Path::const_iterator it = solutionPath.begin(); + ClipperLib::Path::const_iterator beginningOnPathPoint = solutionPath.end(); + + if (it != solutionPath.end()) + { + ClipperLib::cInt px = it->X; + ClipperLib::cInt py = it->Y; + ClipperLib::cInt dx; + ClipperLib::cInt dy; + + ++it; + + // iterate over edges + while (it != beginningOnPathPoint) + { + ClipperLib::cInt rx = it->X - px; + ClipperLib::cInt ry = it->Y - py; + + if (onOffsetPath) + { + dx = pathEnd.X - px; + dy = pathEnd.Y - py; + } + else + { + dx = pathBegin.X - px; + dy = pathBegin.Y - py; + } + + // check for begin or end point is on edge + if (((dx*ry) == (dy*rx)) && ((std::abs(dx) <= std::abs(rx)) && ((dx*rx) >= 0))) + { + if (!onOffsetPath) + { + // begin point is on edge start copying geometry to offsetPath + onOffsetPath = true; + // set end condition of loop walk again over all verticies + beginningOnPathPoint = it; + } + else break; // end point is on edge, skip the rest of the path + } + + if (onOffsetPath) offsetPath.emplace_back( *it ); + + px = it->X; + py = it->Y; + + ++it; + + if (it == solutionPath.end()) + { + // end of geometry is reached, but there not all geometry copied now + // set it to the beginning and go on. Or pathBegin is lying in the + // in the last segment (closing segment) of the polygon + it = solutionPath.begin(); + if (!onOffsetPath) + { + // check the closing segment and finish search if not found + beginningOnPathPoint = it+1; + } + } + } // while + } + } + + // Should never be nessecary, but could be useful in worthed case + if ((offsetPath.empty()) && (!solution.empty())) + { + auto solutionPath = solution.front(); + for (auto vertex : solutionPath) + { + offsetPath.emplace_back(vertex); + } + return false; + } + + return (!offsetPath.empty()); +} /* ClipperOffset::PathOffset */ + +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree& solution, double delta) +{ + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) + { + PolyNode* outerNode = solution.Childs[0]; + solution.Childs.reserve(outerNode->ChildCount()); + solution.Childs[0] = outerNode->Childs[0]; + solution.Childs[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Childs[i]); + } + else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) +{ + m_destPolys.clear(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) + { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.emplace_back(node.Contour); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; //ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) + { + if (node.m_jointype == jtRound) + { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) + { + m_destPoly.emplace_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.emplace_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.emplace_back(m_destPoly); + continue; + } + //build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.emplace_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.emplace_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.emplace_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.emplace_back(m_destPoly); + } + else if (node.m_endtype == etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.emplace_back(m_destPoly); + m_destPoly.clear(); + //re-build m_normals ... + DoublePoint n = m_normals[len -1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.emplace_back(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) + { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.emplace_back(pt1); + + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.emplace_back(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + k = len - 1; + + for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) + { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.emplace_back(pt1); + + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.emplace_back(pt1); + } + else + { + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.emplace_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) +{ + //cross product ... + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (std::fabs(m_sinA * m_delta) < 1.0) + { + //dot product ... + double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); + if (cosA > 0) // angle => 0 degrees + { + m_destPoly.emplace_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + return; + } + //else angle => 180 degrees + } + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + + m_destPoly.emplace_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.emplace_back(m_srcPoly[j]); + m_destPoly.emplace_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + + } + else + switch (jointype) + { + case jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case jtSquare: DoSquare(j, k); break; + case jtRound: DoRound(j, k); break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) +{ + double dx = std::tan(std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + + m_destPoly.emplace_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + + m_destPoly.emplace_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); + +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) +{ + double q = m_delta / r; + m_destPoly.emplace_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) +{ + double a = std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); + + double X = m_normals[k].X, Y = m_normals[k].Y; + for (int i = 0; i < steps; ++i) + { + m_destPoly.emplace_back(IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.emplace_back(IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() +{ + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) + { + OutRec* outrec = m_PolyOuts[i++]; + OutPt* op = outrec->Pts; + if (!op || outrec->IsOpen) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt* op2 = op->Next; + while (op2 != outrec->Pts) + { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) + { + //split the polygon into two ... + OutPt* op3 = op->Prev; + OutPt* op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec* outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) + { + //OutRec2 is contained by OutRec1 ... + outrec2->IsHole = !outrec->IsHole; + outrec2->FirstLeft = outrec; + if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); + } + else + if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) + { + //OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); + } + else + { + //the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); + } + op2 = op; //ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } + while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePaths(Paths& p) +{ + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPath(in_poly, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Paths &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) +{ + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx*Dx + dy*dy); +} +//------------------------------------------------------------------------------ + +double DistanceFromLineSqrd( + const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) +{ + //The equation of a line in general form (Ax + By + C = 0) + //given 2 points (x¹,y¹) & (x²,y²) is ... + //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 + //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); +} +//--------------------------------------------------------------------------- + +bool SlopesNearCollinear(const IntPoint& pt1, + const IntPoint& pt2, const IntPoint& pt3, double distSqrd) +{ + //this function is more accurate when the point that's geometrically + //between the other 2 points is the one that's tested for distance. + //ie makes it more likely to pick up 'spikes' ... + if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) + { + if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } + else + { + if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) +{ + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +OutPt* ExcludeOp(OutPt* op) +{ + OutPt* result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) +{ + //distance = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2). + + size_t size = in_poly.size(); + + if (size == 0) + { + out_poly.clear(); + return; + } + + OutPt* outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) + { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt* op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) + { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) + { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } + else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else + { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) + { + out_poly[i] = op->Pt; + op = op->Next; + } + delete [] outPts; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path& poly, double distance) +{ + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) +{ + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths& polys, double distance) +{ + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowski(const Path& poly, const Path& path, + Paths& solution, bool isSum, bool isClosed) +{ + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.emplace_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.emplace_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.emplace_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.emplace_back(p); + } + + solution.clear(); + solution.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i < pathCnt - 1 + delta; ++i) + for (size_t j = 0; j < polyCnt; ++j) + { + Path quad; + quad.reserve(4); + quad.emplace_back(pp[i % pathCnt][j % polyCnt]); + quad.emplace_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.emplace_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.emplace_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) ReversePath(quad); + solution.emplace_back(quad); + } +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) +{ + Minkowski(pattern, path, solution, true, pathIsClosed); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void TranslatePath(const Path& input, Path& output, IntPoint delta) +{ + //precondition: input != output + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) +{ + Clipper c; + for (size_t i = 0; i < paths.size(); ++i) + { + Paths tmp; + Minkowski(pattern, paths[i], tmp, true, pathIsClosed); + c.AddPaths(tmp, ptSubject, true); + if (pathIsClosed) + { + Path tmp2; + TranslatePath(paths[i], tmp2, pattern[0]); + c.AddPath(tmp2, ptClip, true); + } + } + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) +{ + Minkowski(poly1, poly2, solution, false, true); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +enum NodeType {ntAny, ntOpen, ntClosed}; + +void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) +{ + bool match = true; + if (nodetype == ntClosed) match = !polynode.IsOpen(); + else if (nodetype == ntOpen) return; + + if (!polynode.Contour.empty() && match) + paths.emplace_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + //Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Childs[i]->IsOpen()) + paths.emplace_back(polytree.Childs[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const IntPoint &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Path &p) +{ + if (p.empty()) return s; + Path::size_type last = p.size() -1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Paths &p) +{ + for (Paths::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +} //ClipperLib namespace diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.hpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.hpp new file mode 100755 index 000000000..08dc41fd4 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/clipper.hpp @@ -0,0 +1,411 @@ +/** +** +** @RDE_HEADER@ +** Clipping and Offsetting of lines and polygons +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** To use under Boost Software License Vers. 1 +** see also http://www.angusj.com/delphi/clipper/documentation/Docs/_Body.htm +** ----------------------------------------------------------------------------------------------- +*/ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.2.1 * +* Date : 31 October 2014 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2014 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.2.0" + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. +//#define use_xyz + +//use_lines: Enables line clipping. Adds a very minor cost to performance. +//#define use_lines + +//use_deprecated: Enables temporary support for the obsolete functions +//#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 + typedef int cInt; + static cInt const loRange = 0x7FFF; + static cInt const hiRange = 0x7FFF; +#else + typedef signed long long cInt; + static cInt const loRange = 0x3FFFFFFF; + static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; + typedef signed long long long64; //used by Int128 class + typedef unsigned long long ulong64; + +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; +#else + IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; +#endif + + friend inline bool operator== (const IntPoint& a, const IntPoint& b) + { + return a.X == b.X && a.Y == b.Y; + } + friend inline bool operator!= (const IntPoint& a, const IntPoint& b) + { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector< IntPoint > Path; +typedef std::vector< Path > Paths; + +inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} +inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} + +std::ostream& operator <<(std::ostream &s, const IntPoint &p); +std::ostream& operator <<(std::ostream &s, const Path &p); +std::ostream& operator <<(std::ostream &s, const Paths &p); + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + explicit DoublePoint(const IntPoint &ip) : X(ip.X), Y(ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); +#endif + +enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; +enum JoinType {jtSquare, jtRound, jtMiter}; +enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + PolyNode(); + virtual ~PolyNode(){}; + Path Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + bool IsOpen() const; + int ChildCount() const; +private: + unsigned Index; //node index in Parent.Childs + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + PolyNode* GetNextSiblingUp() const; + void AddChild(PolyNode& child); + friend class Clipper; //to access Index + friend class ClipperOffset; +}; + +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +bool Orientation(const Path &poly); +double Area(const Path &poly); +int PointInPolygon(const IntPoint &pt, const Path &path); + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); +void CleanPolygon(Path& poly, double distance = 1.415); +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); +void CleanPolygons(Paths& polys, double distance = 1.415); + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); + +void ReversePath(Path& p); +void ReversePaths(Paths& p); + +struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; + +//enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; + +//forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinimum; +struct Scanbeam; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < Join* > JoinList; +typedef std::vector < IntersectNode* > IntersectList; + +//------------------------------------------------------------------------------ + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + virtual void Clear(); + IntRect GetBounds(); + bool PreserveCollinear() const {return m_PreserveCollinear;}; + void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; +protected: + void DisposeLocalMinimaList(); + TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); + void PopLocalMinima(); + virtual void Reset(); + TEdge* ProcessBound(TEdge* E, bool IsClockwise); + void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed); + TEdge* DescendToMin(TEdge *&E); + void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); + + typedef std::vector MinimaList; + MinimaList::iterator m_CurrentLM; + MinimaList m_MinimaList; + + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase +{ +public: + explicit Clipper(int initOptions = 0); + ~Clipper(); + bool Execute(ClipType clipType, + Paths &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool ReverseSolution() const {return m_ReverseOutput;}; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; + bool StrictlySimple() const {return m_StrictSimple;}; + void StrictlySimple(bool value) {m_StrictSimple = value;}; + //set the callback function for z value filling on intersections (otherwise Z is 0) +#ifdef use_xyz + void ZFillFunction(ZFillCallback zFillFunc); +#endif +protected: + void Reset(); + virtual bool ExecuteInternal(); +private: + PolyOutList m_PolyOuts; + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; + typedef std::priority_queue ScanbeamList; + ScanbeamList m_Scanbeam; + TEdge *m_ActiveEdges; + TEdge *m_SortedEdges; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + ZFillCallback m_ZFill; //custom callback +#endif + void SetWindingCount(TEdge& edge) const; + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertScanbeam(const cInt Y); + cInt PopScanbeam(); + void InsertLocalMinimaIntoAEL(const cInt botY); + void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); + void AddEdgeToSEL(TEdge *edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const cInt XPos); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DoMaxima(TEdge *e); + void ProcessHorizontals(bool IsTopOfScanbeam); + void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* GetOutRec(int idx); + void AppendPolygon(TEdge *e1, TEdge *e2) const; + void IntersectEdges(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* CreateOutRec(); + OutPt* AddOutPt(TEdge *e, const IntPoint &pt); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + bool ProcessIntersections(const cInt topY); + void BuildIntersectList(const cInt topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + void BuildResult(Paths& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *outrec) const; + void DisposeIntersectNodes(); + bool FixupIntersectionOrder(); + void FixupOutPolygon(OutRec &outrec); + bool IsHole(TEdge *e); + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); + static void FixHoleLinkage(OutRec &outrec); + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint & offPt); + void ClearJoins(); + void ClearGhostJoins(); + void AddGhostJoin(OutPt *op, const IntPoint & offPt); + bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const; + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const; +#ifdef use_xyz + void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); +#endif +}; +//------------------------------------------------------------------------------ + +class ClipperOffset +{ +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + ~ClipperOffset(); + void AddPath(const Path& path, JoinType joinType, EndType endType); + void AddPaths(const Paths& paths, JoinType joinType, EndType endType); + void Execute(Paths& solution, double delta); + void Execute(PolyTree& solution, double delta); + + bool PathOffset( const Path& path, Path & offsetPath, double delta ); // calc offset of one path + + void Clear(); + double MiterLimit; + double ArcTolerance; +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + void DoOffset(double delta); + void OffsetPoint(int j, int& k, JoinType jointype); + void DoSquare(int j, int k); + void DoMiter(int j, int k, double r); + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + +class clipperException : public std::exception +{ + public: + explicit clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.cpp new file mode 100755 index 000000000..51e1dbb74 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.cpp @@ -0,0 +1,736 @@ +/** +** +** @RDE_HEADER@ +** Rasterize functional path geometries to polylines +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 12.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "curve.h" +#include "path_rasterer.h" + + +#include + +namespace SituationMap { namespace MathUtils +{ + + static QPointF calcBezierVertex( double t, const QPointF control[4] ) + { + double u, u2, u3, t2, t3; + double xx, yy; + + t2 = t*t; + t3 = t2*t; + u = 1.0 - t; + u2 = u*u; + u3 = u2*u; + xx = control[0].x()*u3 + 3.0*(control[1].x()*t*u2 + control[2].x()*t2*u) + control[3].x()*t3; + yy = control[0].y()*u3 + 3.0*(control[1].y()*t*u2 + control[2].y()*t2*u) + control[3].y()*t3; + + return QPointF(xx,yy); + + } /* calcBezierVertex */ + + + static QPointF calcBezierTangent( double t, const QPointF control[4] ) + { + double t2(t*t); + double u(1.0-t); + double u2(u*u); + double xx(3.0*((control[1].x()-control[0].x())*u2 + (control[2].x()-control[1].x())*t*u*2.0 + (control[3].x()-control[2].x())*t2)); + double yy(3.0*((control[1].y()-control[0].y())*u2 + (control[2].y()-control[1].y())*t*u*2.0 + (control[3].y()-control[2].y())*t2)); + + return QPointF(xx,yy); + + } /* calcBezierTangent */ + + + static double hypotBezierTangent( double t, const QPointF control[4] ) + { + QPointF p(calcBezierTangent(t,control)); + return hypot(p.x(),p.y()); + } /* hypotBezierTangent */ + + + static double simpsonApproximation( const QPointF control[4], double a, double b ) + { + const unsigned int NLenSteps = 800; + + double t( b-a ); + double h( t/NLenSteps ); + + double sum( 0.0 ); + + // Zusammengesetzte Integration nach der Simpson-Regel + + double s1( 0.0 ); + double s2( 0.0 ); + + for (unsigned int i = 0; i < NLenSteps; i++) + { + s1 += hypotBezierTangent( a+h*i, control ); + s2 += hypotBezierTangent( a+h*(double(i)-0.5), control ); + } + s2 += hypotBezierTangent( a+h*(double(NLenSteps)-0.5), control ); + + sum += h/6.0 *( hypotBezierTangent(a,control)+hypotBezierTangent(b,control)+2.0*s1+4.0*s2); + + return sum; + + } /* simpsonApproximation */ + + + static inline void buildControlPoints( const QPointF & p_i, + const QPointF & p_i1, + const QPointF & p_i2, + QPointF control[4], + bool startSegm = false, + bool endSegm = false ) + { + if (startSegm) + { + control[0] = p_i; + control[1] = p_i*0.333 + p_i1*0.667; + } + else + { + control[0] = p_i*0.5 + p_i1*0.5; + control[1] = p_i*0.167 + p_i1*0.833; + } + + if (endSegm) + { + control[2] = p_i1*0.667 + p_i2*0.333; + control[3] = p_i2; + } + else + { + control[2] = p_i1*0.833 + p_i2*0.167; + control[3] = p_i1*0.5 + p_i2*0.5; + } + } /* buildControlPoints */ + + + + SmoothLine::SmoothLine(const QVector & vertexVector, bool closedCurve ) + { + SmoothLine::vertexVector( vertexVector, closedCurve ); + } /* BezierSpline::SmoothLine */ + + void SmoothLine::vertexVector( const QVector & vertexVector, bool closedCurve ) + { + _closedCurve = closedCurve; + _vertexVector = vertexVector; + } /* SmoothLine::vertexVector */ + + + qreal SmoothLine::maximumCurveParameter() const + { + if (_vertexVector.size() > 2) + { + return (_closedCurve) ? (qreal)_vertexVector.size() : (qreal)_vertexVector.size() - 2.0; + } + return 0; + } /* SmoothLine::maximumCurveParameter */ + + + QPointF SmoothLine::positionOnCurve( qreal t ) const + { + if (_vertexVector.size() < 3) + { + if (_vertexVector.size() > 1) + { + // It's just a straight line + if (t < 0.0) t = 1.0-t; + + if ((t >= 0.0) && (t <= 1.0)) + { + const QPointF & v0 = _vertexVector[0]; + const QPointF & v1 = _vertexVector[1]; + return v0 + (v1-v0)*t; + } + } + } + else + { + int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 3); + + if (t < 0.0) t = double(maxSegm+1) - t; + + int segment( floor(t) ); + + if ((segment >= 0) && (segment <= maxSegm)) + { + QPointF control[4]; + QPointF p[3]; + + if ((_closedCurve) && (segment == (maxSegm-1))) + { + p[0] = _vertexVector[_vertexVector.size()-2]; + p[1] = _vertexVector.back(); + p[2] = _vertexVector[0]; + + buildControlPoints( p[0], p[1], p[2], control, false, false ); + } + else if ((_closedCurve) && (segment == maxSegm)) + { + p[0] = _vertexVector.back(); + p[1] = _vertexVector[0]; + p[2] = _vertexVector[1]; + + buildControlPoints( p[0], p[1], p[2], control, false, false ); + } + else + { + p[0] = _vertexVector[segment ]; + p[1] = _vertexVector[segment+1]; + p[2] = _vertexVector[segment+2]; + + buildControlPoints( p[0], p[1], p[2], control, ((segment == 0) && (!_closedCurve)), (segment == maxSegm) ); + } + return calcBezierVertex( t-double(segment), control ); + } + else + { + if (t == (maxSegm+1)) + { + QPointF control[4]; + QPointF p[3]; + + if (_closedCurve) + { + p[0] = _vertexVector[0]; + p[1] = _vertexVector[1]; + p[2] = _vertexVector[2]; + + buildControlPoints( p[0], p[1], p[2], control, true, false ); + return calcBezierVertex( 0.0, control ); + } + else + { + p[0] = _vertexVector[maxSegm ]; + p[1] = _vertexVector[maxSegm+1]; + p[2] = _vertexVector[maxSegm+2]; + + buildControlPoints( p[0], p[1], p[2], control, (maxSegm == 0), true ); + return calcBezierVertex( 1.0, control ); + } + } + } + } + return QPointF(0.0,0.0); + } /* SmoothLine::positionOnCurve */ + + + QPointF SmoothLine::directionOnCurve( qreal t ) const + { + if (_vertexVector.size() < 3) + { + if (_vertexVector.size() >1) + { + // It's just a straight line + if (t < 0.0) t = 1.0-t; + + if ((t >= 0.0) && (t <= 1.0)) + { + return (_vertexVector[1]-_vertexVector[0]); + } + } + } + else + { + int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 3); + + if (t < 0.0) t = double(maxSegm+1) - t; + + int segment( floor(t) ); + + if ((segment >= 0) && (segment <= maxSegm)) + { + QPointF control[4]; + QPointF p[3]; + + if ((_closedCurve) && (segment == (maxSegm-1))) + { + p[0] = _vertexVector[_vertexVector.size()-2]; + p[1] = _vertexVector.back(); + p[2] = _vertexVector[0]; + + buildControlPoints( p[0], p[1], p[2], control, false, false ); + } + else if ((_closedCurve) && (segment == maxSegm)) + { + p[0] = _vertexVector.back(); + p[1] = _vertexVector[0]; + p[2] = _vertexVector[1]; + + buildControlPoints( p[0], p[1], p[2], control, false, false ); + } + else + { + p[0] = _vertexVector[segment ]; + p[1] = _vertexVector[segment+1]; + p[2] = _vertexVector[segment+2]; + + buildControlPoints( p[0], p[1], p[2], control, ((segment == 0) && (!_closedCurve)), (segment == maxSegm) ); + } + return calcBezierTangent( t-double(segment), control ); + } + else + { + if (t == (maxSegm+1)) + { + QPointF control[4]; + QPointF p[3]; + + if (_closedCurve) + { + p[0] = _vertexVector[0]; + p[1] = _vertexVector[1]; + p[2] = _vertexVector[2]; + + buildControlPoints( p[0], p[1], p[2], control, true, false ); + return calcBezierTangent( 0.0, control ); + } + else + { + p[0] = _vertexVector[maxSegm ]; + p[1] = _vertexVector[maxSegm+1]; + p[2] = _vertexVector[maxSegm+2]; + + buildControlPoints( p[0], p[1], p[2], control, (maxSegm == 0), true ); + return calcBezierTangent( 1.0, control ); + } + } + } + } + return QPointF(0.0,0.0); + } /* SmoothLine::directionOnCurve */ + + + qreal SmoothLine::arcLength( qreal t) const + { + if (t < 0.0) return 0.0; + + QPointF control[4]; + QPointF p[3]; + QVector::const_iterator it = _vertexVector.begin(); + + if (_closedCurve) + { + int i = 0; + p[i++] = _vertexVector.back(); + while ((i<3) && (it != _vertexVector.end())) + { + p[i] = *it; + ++it; + ++i; + } + buildControlPoints( p[0], p[1], p[2], control, false, false ); + } + else + { + int i = 0; + while ((i<3) && (it != _vertexVector.end())) + { + p[i] = *it; + ++it; + ++i; + } + buildControlPoints( p[0], p[1], p[2], control, true, (it == _vertexVector.end()) ); + } + + if (t < 1.0) return simpsonApproximation( control, 0.0, t ); + + double sum = simpsonApproximation( control, 0.0, 1.0 ); t -= 1.0; + + while (it != _vertexVector.end()) + { + p[0] = p[1]; + p[1] = p[2]; + p[2] = *it; + ++it; + + if (_closedCurve) buildControlPoints( p[0], p[1], p[2], control, false, false ); + else buildControlPoints( p[0], p[1], p[2], control, false, (it == _vertexVector.end()) ); + + if (t < 1.0) + { + sum += simpsonApproximation( control, 0.0, t ); + break; + } + else sum += simpsonApproximation( control, 0.0, 1.0 ); + } + + if ((t > 0.0) && (_closedCurve)) + { + p[0] = p[1]; + p[1] = p[2]; + p[2] = _vertexVector.front(); + + buildControlPoints( p[0], p[1], p[2], control, false, false ); + + sum += simpsonApproximation( control, 0.0, t ); + } + return sum; + } /* SmoothLine::arcLength */ + + + HermiteSpline::HermiteSpline( const QVector & vertexVector + , const QVector & tangentVector, bool closedCurve ) + { + HermiteSpline::vertexVector(vertexVector, tangentVector, closedCurve ); + } /* HermiteSpline::HermiteSpline */ + + + void HermiteSpline::vertexVector( const QVector & vertexVector + , const QVector & tangentVector, bool closedCurve ) + { + _closedCurve = closedCurve; + _vertexVector = vertexVector; + _tangentVector = tangentVector; + + int countOfTangents = (_closedCurve) ? _vertexVector.size()*2 : (_vertexVector.size()-1) * 2; + + if (_tangentVector.size() >= countOfTangents) + { + // fatal error + throw + SituationMap::Exception( __FILE__, __LINE__, + "No enough tangents defined to calculate spline. Each spline segment must have defined 2 tangents per vertex, excepting the border segments where only 1 tangent must be defined."); + + } + } /* HermiteSpline::vertexVector */ + + + qreal HermiteSpline::maximumCurveParameter() const + { + if (_vertexVector.size() < 2) return 0.0; + return (_closedCurve) ? _vertexVector.size() : (_vertexVector.size() - 1.0); + } /* HermiteSpline::maximumCurveParameter */ + + + QPointF HermiteSpline::positionOnCurve( qreal t ) const + { + if (_vertexVector.size() < 2) return QPointF(0.0,0.0); + + int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 2); + + if (t < 0.0) t = double(maxSegm+1) - t; + + int segment( floor(t) ); + + if ((segment >= 0) && (segment <= maxSegm)) + { + QPointF control[4]; + + if ((_closedCurve) && (segment == maxSegm)) + { + control[0] = _vertexVector.back(); + control[3] = _vertexVector.front(); + control[1] = control[0] + _tangentVector[_tangentVector.size()-2]/3.0; + control[2] = control[3] - _tangentVector[_tangentVector.size()-1]/3.0; + } + else + { + control[0] = _vertexVector[segment]; + control[3] = _vertexVector[segment+1]; + control[1] = control[0] + _tangentVector[segment*2]/3.0; + control[2] = control[3] - _tangentVector[segment*2+1]/3.0; + } + return calcBezierVertex( t-double(segment), control ); + } + + return QPointF(0.0,0.0); + + } /* HermiteSpline::positionOnCurve */ + + + QPointF HermiteSpline::directionOnCurve( qreal t ) const + { + if (_vertexVector.size() < 2) return QPointF(0.0,0.0); + + int maxSegm = _vertexVector.size() - ((_closedCurve) ? 1 : 2); + + if (t < 0.0) t = double(maxSegm+1) - t; + + int segment( floor(t) ); + + if ((segment >= 0) && (segment <= maxSegm)) + { + QPointF control[4]; + + if ((_closedCurve) && (segment == maxSegm)) + { + control[0] = _vertexVector.back(); + control[3] = _vertexVector.front(); + control[1] = control[0] + _tangentVector[_tangentVector.size()-2]/3.0; + control[2] = control[3] - _tangentVector[_tangentVector.size()-1]/3.0; + } + else + { + control[0] = _vertexVector[segment]; + control[3] = _vertexVector[segment+1]; + control[1] = control[0] + _tangentVector[segment*2]/3.0; + control[2] = control[3] - _tangentVector[segment*2+1]/3.0; + } + return calcBezierTangent( t-double(segment), control ); + } + + return QPointF(0.0,0.0); + + } /* HermiteSpline::directionOnCurve */ + + + qreal HermiteSpline::arcLength( qreal t ) const + { + if (t < 0.0) return 0.0; + if (_vertexVector.size() < 2) return 0.0; + + double sum = 0.0; + QPointF control[4]; + + QVector::const_iterator itv = _vertexVector.begin(); + QVector::const_iterator itt = _vertexVector.begin(); + + if (_closedCurve) { if (2*_vertexVector.size() != _tangentVector.size()) return 0.0; } + else { if (2*_vertexVector.size()-2 != _tangentVector.size()) return 0.0; } + + control[3] = *itv; ++itv; + + while (itv != _vertexVector.end()) + { + control[0] = control[3]; + control[1] = control[0] + *itt / 3.0; ++itt; + control[2] = *itv - *itt / 3.0; ++itt; + control[3] = *itv; + + if (t < 1.0) return sum + simpsonApproximation( control, 0.0, t ); + sum = simpsonApproximation( control, 0.0, 1.0 ); t -= 1.0; + + ++itv; + } // while + + if (_closedCurve) + { + control[0] = control[3]; + control[1] = control[0] + *itt / 3.0; ++itt; + control[2] = _vertexVector.front() - *itt / 3.0; ++itt; + control[3] = _vertexVector.front(); + + if (t < 1.0) return sum + simpsonApproximation( control, 0.0, t ); + sum = simpsonApproximation( control, 0.0, 1.0 ); // t -= 1.0; // 'ScanBuild: 't is never read' + } + return sum; + } /* HermiteSpline::arcLength */ + + + + BezierSpline::BezierSpline( const QVector & ctrlPntVector, bool closedCurve ) + : HermiteSpline() + { + controlPointVector(ctrlPntVector,closedCurve); + } /* BezierSpline::BezierSpline */ + + + void BezierSpline::controlPointVector( const QVector & ctrlPntVector, bool closedCurve ) + { + if (ctrlPntVector.size() < 4) return; + + QVector vertexVector; + QVector tangentVector; + + QVector::const_iterator it = ctrlPntVector.begin(); + + vertexVector.append( *it ); + ++it; + while (it != ctrlPntVector.end()) + { + // set first tangent + tangentVector.append( ((*it) - vertexVector.back()) * 3.0 ); + ++it; + if (it == ctrlPntVector.end()) + { + throw SituationMap::Exception(__FILE__, __LINE__, "Missing bezier spline tangent control point." ); + } + ++it; + + if (it == ctrlPntVector.end()) + { + // error + if (closedCurve) + { + tangentVector.append( ((it[-1]) - vertexVector.front()) * 3.0 ); + } + else throw SituationMap::Exception(__FILE__, __LINE__, "Missing bezier spline vertex control point." ); + break; + } + else + { + vertexVector.append( *it ); + tangentVector.append( ((it[-1]) - vertexVector.back()) * 3.0 ); + ++it; + } + } // while + + HermiteSpline::vertexVector(vertexVector,tangentVector,closedCurve); + + } /* BezierSpline::controlPointVector */ + + + void BezierSpline::vertexVector( const QVector&, const QVector&, bool ) + { + throw SituationMap::Exception(__FILE__, __LINE__, "Function is not defined for BezierSpline. Use controlPointVector instead." ); + } /* BezierSpline::vertexVector */ + + + + + Ellipse::Ellipse( QPointF center, qreal rx, qreal ry, qreal phi, qreal sectorStart, qreal sectorEnd ) + : _center(center) + , _radiusX(fabs(rx)) + , _radiusY(fabs(ry)) + , _sinPhi(sin(phi)) + , _cosPhi(cos(phi)) + , _phiSectorStart(sectorStart) + , _phiSectorEnd(sectorEnd) + { + if (_radiusX < _radiusY) + { + // swap _radiusX and _radiusY + _radiusX = fabs(ry); + _radiusY = fabs(rx); + + // and rotate + phi += MathConst::pi_half; + _sinPhi = sin(phi); + _cosPhi = cos(phi); + } + + qreal a2 = _radiusX * _radiusX; + qreal b2 = _radiusY * _radiusY; + + _eccentricity2 = (a2-b2) / a2; + + if (fabs(_phiSectorStart) > MathConst::pi_two) _phiSectorStart /= MathConst::pi_two; + if (fabs(_phiSectorEnd) > MathConst::pi_two) _phiSectorEnd /= MathConst::pi_two; + + if (_phiSectorStart >= 0) + { + if (_phiSectorStart > _phiSectorEnd) _phiSectorStart -= MathConst::pi_two; + } + else + { + if (_phiSectorStart > _phiSectorEnd)_phiSectorEnd += MathConst::pi_two; + } + } /* Ellipse::Ellipse */ + + + qreal Ellipse::maximumCurveParameter() const { return _phiSectorEnd; } + qreal Ellipse::minimumCurveParameter() const { return _phiSectorStart; } + + QPointF Ellipse::positionOnCurve(qreal t) const + { + // Using ellipse equation in polar coordinates relative to center point(0,0) + // because in this case the parameter t is identical with polar angle. + + qreal sin_t = sin(t); + qreal cos_t = cos(t); + + /* + qreal r = _radiusY / (1.0 - _eccentricity2 * cos_t*cos_t); + qreal x = cos_t*r; + qreal y = sin_t*r; + */ + qreal x = cos_t*_radiusX; + qreal y = sin_t*_radiusY; + return _center + QPointF( x*_cosPhi - y*_sinPhi, y*_cosPhi + x*_sinPhi ); + + } /* Ellipse::positionOnCurve */ + + QPointF Ellipse::directionOnCurve(qreal t) const + { + qreal sin_t = sin(t); + qreal cos_t = cos(t); + + if (cos_t == 0.0) return QPointF( -sin_t, 0.0 ); + + qreal tan_t = sin_t / cos_t; + qreal tan_n = tan_t / 1.0 - _eccentricity2; // tan normal + + // normal QPointF( 1.0/sqrt(1+tan_n*tan_n), tan_n/sqrt(1+tan_n*tan_n) ) + QPointF tangente( -tan_n/sqrt(1+tan_n*tan_n), 1.0/sqrt(1+tan_n*tan_n) ); + + return QPointF( tangente.x()*_cosPhi - tangente.y()*_sinPhi, tangente.y()*_cosPhi + tangente.x()*_sinPhi ); + } /* Ellipse::directionOnCurve */ + + + qreal Ellipse::arcLength(qreal t) const + { + return arcLength( 0.0, t ); + } /* Ellipse::arcLength */ + + + qreal Ellipse::arcLength(qreal tmin, qreal tmax ) const + { + Q_UNUSED(tmin); + Q_UNUSED(tmax); + + // ToDo: calculate convergenting formula based on polar center points equation of ellipse if needed + return 0.0; + } /* Ellipse::arcLength */ + + + class CurvePathRasterer : public PathRasterer + { + public: + + CurvePathRasterer() : PathRasterer(), _curveFunction(nullptr) {} + explicit CurvePathRasterer( const Curve * splineFunction ) : PathRasterer(), _curveFunction(splineFunction) {} + ~CurvePathRasterer() = default; + + void function( qreal t, QPointF & result ) override + { + if (_curveFunction) result = _curveFunction->positionOnCurve(t); + else + { + throw SituationMap::Exception(__FILE__, __LINE__, "Curve function is undefined for PathRasterer." ); + } + } /* function */ + + + protected: + + const Curve * _curveFunction; + + }; /* CurvePathRasterer */ + + + PathRasterer * Ellipse::createPathRaster() const { return new CurvePathRasterer(this); } + PathRasterer * SmoothLine::createPathRaster() const { return new CurvePathRasterer(this); } + PathRasterer * HermiteSpline::createPathRaster() const { return new CurvePathRasterer(this); } + PathRasterer * BezierSpline::createPathRaster() const { return new CurvePathRasterer(this); } + + +} } // namespace SituationMap::MathUtils diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.h new file mode 100755 index 000000000..e4c67967f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/curve.h @@ -0,0 +1,288 @@ +/** +** +** @RDE_HEADER@ +** Implementation of Splines +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 12.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include +#include + +#include +#include + + +namespace SituationMap { namespace MathUtils +{ + + class PathRasterer; + + /** + @class Curve curve.h + @brief Base class to calculate a polyline from curve functions like splines, circles, ellpises ... + + Pure virtual base class to calculate a polyline from curve functions. The curve function is defined + in derived classes. The resolution of rasterization can be set in PathRasterer class. + + @see PathRasterer, Ellipse, Spline, SmoothLine, HermiteSpline, BezierSpline + */ + + class SITUATIONMAP_EXPORT Curve + { + public: + + /** + Returns the maximum curve paramter t. The curve is defined in interval tmin ... tmax + */ + virtual qreal maximumCurveParameter() const = 0; + + + /** + Returns the minimum curve paramter t. The curve is defined in interval tmin ... tmax + */ + virtual qreal minimumCurveParameter() const = 0; + + + /** + Returns the position on curve at \c t + @param t curve parameter, should be in range of 0 ... tmax + */ + virtual QPointF positionOnCurve ( qreal t ) const = 0; + + /** + Returns the tangent at position \c t + @param t curve parameter, should be in range of 0 ... tmax + */ + virtual QPointF directionOnCurve( qreal t ) const = 0; + + /** + Returns the length of curve from start to position t + @param t curve parameter, should be in range of 0 ... tmax + */ + virtual qreal arcLength( qreal t ) const = 0; + + /** + Returns the length of curve from position \c tmin to position \c tmax + @param tmin curve parameter, should be in range of 0 ... maximumCurveParameter() + @param tmax curve parameter, should be in range of tmin ... maximumCurveParameter() + */ + virtual qreal arcLength( qreal tmin, qreal tmax ) const { return arcLength(tmax)-arcLength(tmin); } + + /** + Returns the length of complete curve + */ + inline qreal arcLength() const { return arcLength( maximumCurveParameter() ); } + + /** + Create instance of class \c PathRasterer to rasterize the spline curve. After usage the + caller should use delete to free memory and avoid memory leaks. This methode is pure virtual + and has to be override by the derived class. + + @see PathRasterer + */ + virtual PathRasterer * createPathRaster() const = 0; + + }; /* Curve */ + + + /** + @class SmoothLine curve.h + @brief Class to calculate a ellipse. A angle have to be defined in RAD in mathematical manner (counter clockwise and (1,0) is zero angle) + */ + class SITUATIONMAP_EXPORT Ellipse : public Curve + { + public: + + Ellipse() = default; + + Ellipse( QPointF center, qreal rx, qreal ry, qreal phi, qreal sectorStart = 0, qreal sectorEnd = MathConst::pi_two ); + + virtual ~Ellipse() = default; + + qreal maximumCurveParameter() const override; + qreal minimumCurveParameter() const override; + + QPointF positionOnCurve ( qreal t ) const override; + QPointF directionOnCurve( qreal t ) const override; + + qreal arcLength( qreal t ) const override; + qreal arcLength( qreal tmin, qreal tmax ) const override; + + PathRasterer * createPathRaster() const override; + + protected: + + QPointF _center; + qreal _radiusX; + qreal _radiusY; + qreal _eccentricity2; + qreal _sinPhi; + qreal _cosPhi; + qreal _phiSectorStart; + qreal _phiSectorEnd; + + }; /* Ellipse */ + + + /** + @class SmoothLine curve.h + @brief Class to calculate a line with rounded verticies based on spline functions + */ + + class SITUATIONMAP_EXPORT SmoothLine : public Curve + { + public: + + /** + @name Construction & Destruction + @{ + */ + SmoothLine() = default; + + explicit SmoothLine( const QVector & vertexVector, bool closedCurve = false ); + + virtual ~SmoothLine() = default; + /** @} */ + + /** + Defines the predicated polyline + @param vertexVector verticies of the polyline + @param closedCurve if \c closedCurve is true the polyline is closed to a polygon + */ + void vertexVector( const QVector & vertexVector, bool closedCurve = false ); + + + qreal maximumCurveParameter() const override; + qreal minimumCurveParameter() const override { return 0.0; } + + QPointF positionOnCurve ( qreal t ) const override; + QPointF directionOnCurve( qreal t ) const override; + + qreal arcLength( qreal t ) const override; + + PathRasterer * createPathRaster() const override; + + protected: + + bool _closedCurve; + QVector _vertexVector; + + }; /* SmoothLine */ + + + /** + @class HermitSpline curve.h + @brief Class to calculate 2D hermit spline curves + */ + class SITUATIONMAP_EXPORT HermiteSpline : public Curve + { + public: + + /** + @name Construction & Destruction + @{ + */ + HermiteSpline() = default; + + HermiteSpline( const QVector & vertexVector + , const QVector & tangentVector + , bool closedCurve = false ); + + ~HermiteSpline() = default; + /** @} */ + + /** + Defines the gemeotry of the hermite spline + @param vertexVector verticies to define the hermite spline + @param tangentVector tangents to define the hermite spline + @param closedCurve if \c closedCurve is true the spline curve is closed + */ + virtual void vertexVector( const QVector & vertexVector + , const QVector & tangentVector + , bool closedCurve = false ); + + + qreal maximumCurveParameter() const override; + qreal minimumCurveParameter() const override { return 0.0; } + + QPointF positionOnCurve ( qreal t ) const override; + QPointF directionOnCurve( qreal t ) const override; + + qreal arcLength( qreal t ) const override; + + PathRasterer * createPathRaster() const override; + + private: + + bool _closedCurve; + QVector _vertexVector; + QVector _tangentVector; + + }; /* HermiteSpline */ + + + /** + @class BezierSpline curve.h + @brief Class to calculate 2D bezier spline curves + */ + class SITUATIONMAP_EXPORT BezierSpline : public HermiteSpline + { + public: + + /** + @name Construction & Destruction + @{ + */ + BezierSpline() = default; + + explicit BezierSpline( const QVector & ctrlPntVector, bool closedCurve = false ); + + ~BezierSpline() = default; + /** @} */ + + + /** + Defines the control points of the bezier curve + @param ctrlPntVector control points of bezier curve + @param closedCurve if \c closedCurve is true the spline curve is closed + */ + void controlPointVector( const QVector & ctrlPntVector, bool closedCurve = false ); + + + /** + Don't call vertexVector methode from class HermiteSpline. It is not defined for bezier curves. + If you call it anyway, you will get an execption. + */ + void vertexVector( const QVector & vertexVector + , const QVector & tangentVector + , bool closedCurve = false ) override; + + PathRasterer * createPathRaster() const override; + + }; /* BezierSpline */ + + +} } // namespace SituationMap::MathUtils diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.cpp new file mode 100755 index 000000000..6ac626a09 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.cpp @@ -0,0 +1,409 @@ +/** +** +** @RDE_HEADER@ +** Implementation of 2d matrix operations +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 22.06.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + + +#include "geometry_engine.h" + + +#include +#include +#include +#include + +#include + +// ESRI +#include +#include +//#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace SituationMap { namespace MathUtils +{ + // + // Line + // + + GeometryEngine::Geometry *GeometryEngine::createLine(Map *map, const QPointF &p1, const QPointF &p2) + { + QVector vertices; + vertices << p1 << p2; + + return SituationMap::MathUtils::GeometryEngine::createPolyline(map, vertices); + } // GeometryEngine::Geometry *GeometryEngine::createLine( Map * map, QPointF p1, QPointF p2 ) + + + template + inline GeometryEngine::Geometry * createPolyline(Map * map, const T & verticies) + { + MapImpl * mapImpl = dynamic_cast(map); + if (! mapImpl) + return nullptr; + + SituationMap::CoordTranslationFunctor *mapCtFunctor = map->coordTranslationFunctor(); + if (! mapCtFunctor) + return nullptr; + + Esri::ArcGISRuntime::PolylineBuilder builder ( mapImpl->getEsriSpatialReference() ); + + for (auto pit = verticies.cbegin(); pit != verticies.cend(); ++pit) + { + const QPointF p(mapCtFunctor->geographicToMap(*pit)); + builder.addPoint(p.x(),p.y()); + } + + Esri::ArcGISRuntime::Polyline * esriPolyline = new Esri::ArcGISRuntime::Polyline( builder.toGeometry() ); + + return esriPolyline; + } // GeometryEngine::Geometry * createPolyline(Map * map, const T & verticies) + + + + GeometryEngine::Geometry *GeometryEngine::createPolyline(Map *map, const QVector &vertices ) + { + return SituationMap::MathUtils::createPolyline>(map, vertices); + } // GeometryEngine::Geometry * GeometryEngine::createPolyline( Map * map, const QVector & verticies ) + + + // + // Polygon + // + + template + inline GeometryEngine::Geometry *createPolygon(Map *map, const T &verticies) + { + MapImpl * mapImpl = dynamic_cast(map); + + if (! mapImpl) return nullptr; + + SituationMap::CoordTranslationFunctor * mapCtFunctor = map->coordTranslationFunctor(); + + if (! mapCtFunctor) return nullptr; + + Esri::ArcGISRuntime::PolygonBuilder builder( mapImpl->getEsriSpatialReference() ); + + for (auto pit = verticies.cbegin(); pit != verticies.cend(); ++pit ) + { + const QPointF p(mapCtFunctor->geographicToMap(*pit)); + builder.addPoint(p.x(),p.y()); + } + + Esri::ArcGISRuntime::Polygon * esriPolygon = new Esri::ArcGISRuntime::Polygon( builder.toGeometry() ); + + return esriPolygon; + } // GeometryEngine::Geometry * GeometryEngine::createPolygon( Map * map, const QVector & verticies ) + + + GeometryEngine::Geometry * GeometryEngine::createPolygon(Map * map, const QVector & vertices ) + { + return SituationMap::MathUtils::createPolygon>( map, vertices ); + } // GeometryEngine::Geometry * GeometryEngine::createPolygon( Map * map, const QVector & verticies ) + + + // + // cleanup etc. + // + + void GeometryEngine::deleteGeometry(Geometry *geom) + { + delete geom; + } // void GeometryEngine::deleteGeoemtry( Geometry * geom ) + + + bool GeometryEngine::contains( AbstractMapObject *mapObj, const Geometry &hull) + { + if (nullptr == mapObj) return false; + return mapObj->geometryIsInHull( hull ); + + } //bool GeometryEngine::contains( AbstractMapObject * mapObj, const Geometry & hull ) + + + bool GeometryEngine::contains(const Geometry &geom, const Geometry &hull) + { + if (geom.isEmpty() || hull.isEmpty()) + return false; + return Esri::ArcGISRuntime::GeometryEngine::contains(hull, geom); + } // bool GeometryEngine::contains(const Geometry & geom, const Geometry & hull) + + + bool GeometryEngine::intersect( AbstractMapObject *mapObj, const Geometry &intersector) + { + if (! mapObj) + return false; + return mapObj->geometryIntersect(intersector); + } // bool GeometryEngine::intersect( const Geometry & inputGeom, const Geometry & intersector ) + + + bool GeometryEngine::intersect(const Geometry &inputGeom, const Geometry &intersector) + { + if (inputGeom.isEmpty() || intersector.isEmpty()) + return false; + +#ifdef DEBUGGERTEST + if (inputGeom.type() == EsriRuntimeQt::GeometryType::Polyline) + { + const EsriRuntimeQt::Polyline & l = inputGeom; + int numPoints = l.pointCount(); + + for (int i = 0; i < numPoints; ++i) + { + QPointF p = l.qxy(i); + __asm int 3 + } + } + + if (intersector.type() == EsriRuntimeQt::GeometryType::Line) + { + const EsriRuntimeQt::Line & l = intersector; + double startx = l.startX(); + double starty = l.startY(); + double endx = l.endX(); + double endy = l.endY(); + + __asm int 3 + } +#endif + + return Esri::ArcGISRuntime::GeometryEngine::crosses(inputGeom, intersector); + } // bool GeometryEngine::intersect( const Geometry & inputGeom, const Geometry & intersector ) + +QVector GeometryEngine::parallelTranslation(SituationMap::CoordTranslationFunctor *mapCtFunctor, const QVector &line, double distance) +{ + /* ~ first lokal functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + auto length = [](const QPointF &v) -> double { return sqrt(v.x()*v.x() + v.y()*v.y()); }; + + + auto normalize = [](const QPointF &v) -> QPointF { return v / sqrt(v.x()*v.x() + v.y()*v.y()); }; + + + auto intersection = [](const QPointF &p1, const QPointF &r1, const QPointF &p2, const QPointF &r2) -> QPointF + { + double f = r2.y()*r1.x() - r1.y()*r2.x(); + + double a = (p1.x() + r1.x()) * p1.y() - p1.x() * (p1.y() + r1.y()); + double b = (p2.x() + r2.x()) * p2.y() - p2.x() * (p2.y() + r2.y()); + + double sx = (r2.x() * a - r1.x() * b) / f; + double sy = (-r1.y() * b + r2.y() * a) / f; + + return QPointF(sx, sy); + + }; // intersection + + + auto orientation = [](const QPointF &p, const QPointF &q, const QPointF &r) -> int + { + // See https://www.geeksforgeeks.org/orientation-3-ordered-points/ + // for details of below formula. + double f = (q.y() - p.y()) * (r.x() - q.x()) - (q.x() - p.x()) * (r.y() - q.y()); + + if (qFuzzyIsNull(f)) return 0; // colinear + + return (f > 0) ? 1 : 2; // clock or counterclock wise + }; // orientation + + + auto testIntersection = [&orientation, &intersection, &normalize](const QPointF &p1, const QPointF &q1, const QPointF &p2, const QPointF &q2) -> bool + { + // Find the four orientations needed for general and + // special cases + int o1 = orientation(p1, q1, p2); + int o2 = orientation(p1, q1, q2); + int o3 = orientation(p2, q2, p1); + int o4 = orientation(p2, q2, q1); + + // General case + if (o1 != o2 && o3 != o4) + { + const QPointF & n1 = q1 - p1; + const QPointF & n2 = q2 - p2; + const QPointF & s = intersection(p1, normalize(n1), p2, normalize(n2)); + + double f1 = (!qFuzzyIsNull(n1.x())) ? (s.x() - p1.x()) / n1.x() : (s.y() - p1.y()) / n1.y(); + double f2 = (!qFuzzyIsNull(n2.x())) ? (s.x() - p2.x()) / n2.x() : (s.y() - p2.y()) / n2.y(); + + return ((f1 >= 0) && (f1 <= 1.0) && (f2 >= 0) && (f2 <= 1.0)); + } + + return false; // Doesn't fall in any of the above cases + + }; // testIntersection + + + /* ~ now let's do the job ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + if (line.size() < 2) + { + // Nothing to do --> return input line + return line; + } + + QVector transformedLine; + + transformedLine.reserve(line.size()); + + for (auto &point : std::as_const(line)) + { + // Let's transform the coordinates to maps coordinate system ... + transformedLine << mapCtFunctor->geographicToMap(point); + } + + struct LineSegment + { + QPointF m_p1; + QPointF m_p2; + QPointF m_t; + + LineSegment() = default; + LineSegment(const QPointF &p1, const QPointF &p2, const QPointF &t) + : m_p1(p1), m_p2(p2), m_t(t) + {} + }; + + // build single line segments to translate along it's normal + QVector multiSegmLine; + + multiSegmLine.reserve(transformedLine.size() - 1); + + for (int i = 1; i < transformedLine.size(); ++i) + { + const QPointF & p1 = transformedLine[i - 1]; + const QPointF & p2 = transformedLine[i]; + const QPointF & t = normalize(p2 - p1); + QPointF n = QPointF(t.y()*distance, -t.x()*distance); + multiSegmLine << LineSegment(p1 + n, p2 + n, t); + } + + + transformedLine.clear(); + transformedLine << multiSegmLine.first().m_p1; + + for (int i = 1; i < multiSegmLine.size(); ++i) + { + const LineSegment & l1 = multiSegmLine[i - 1]; + const LineSegment & l2 = multiSegmLine[i]; + + const QPointF & dt = l1.m_t - l2.m_t; + + transformedLine << ((qFuzzyIsNull(dt.x()) && qFuzzyIsNull(dt.y())) ? l1.m_p2 : intersection(l1.m_p2, l1.m_t, l2.m_p1, l2.m_t)); + } + + transformedLine << multiSegmLine.last().m_p2; + + // check result + for (int i = 1; i < transformedLine.size(); ++i) + { + const QPointF & p1 = transformedLine[i - 1]; + const QPointF & p2 = transformedLine[i]; + + if (length(p2 - p1) < distance) + { + // failure segment is to short relative to distance + return QVector(); + } + for (int j = 1; j < transformedLine.size(); ++j) + { + if (j == i) continue; + + const QPointF & g1 = transformedLine[j-1]; + const QPointF & g2 = transformedLine[j]; + + if (testIntersection(p1, p2, g1, g2)) + { + // failure resulting line has self intersections + return QVector(); + } + } + } + + QVector pLine; + + // Transform coordinates back to geographic ... + pLine.reserve(transformedLine.size()); + for (auto &p : std::as_const(transformedLine)) + { + pLine << mapCtFunctor->mapToGeographic(p); + } + + return pLine; +} +// QVector GeometryEngine::parallelTranslation(SituationMap::CoordTranslationFunctor *mapCtFunctor, const QVector &line, double distance) + +QVector GeometryEngine::buffer(AbstractMapObject *mapObj, double distance) +{ + if (!mapObj) + return QVector(); + + AbstractMapObjectImpl *mapObjImpl = dynamic_cast(mapObj); + + if (!mapObjImpl) + return QVector(); + + + const Esri::ArcGISRuntime::Geometry &esriGeometry = mapObjImpl->getEsriGeometry(); + + // Esri::ArcGISRuntime::GeometryEngine::buffer -> works for 2D + // Esri::ArcGISRuntime::GeometryEngine::bufferGeodetic -> works for 2D and 3D!? + const Esri::ArcGISRuntime::Polygon polyline = Esri::ArcGISRuntime::GeometryEngine::bufferGeodetic(esriGeometry, + distance, + Esri::ArcGISRuntime::LinearUnitId::Meters, + 1, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + if (polyline.parts().isEmpty()) + return QVector(); + + + const Esri::ArcGISRuntime::ImmutablePart &immutablePart = polyline.parts().part(0); + const int size = immutablePart.pointCount(); + + QVector resultVec; + resultVec.reserve(size); + for (int i = 0; i < size; ++i) + { + const Esri::ArcGISRuntime::Point &esriP = immutablePart.point(i); + resultVec.append(Point3D(esriP.x(), esriP.y(), esriP.z())); + } + + // qCritical() << " resultVec = " << resultVec.size(); + // qCritical() << " res = " << polyline.toJson(); + + return resultVec; +} + + +} } // namespace SituationMap::MathUtils + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.h new file mode 100755 index 000000000..56bef725d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/geometry_engine.h @@ -0,0 +1,86 @@ +/** +** +** @RDE_HEADER@ +** 2D matrix operations +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 22.06.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include +#include + +namespace Esri { +namespace ArcGISRuntime { + class Geometry; +} } + + +namespace SituationMap +{ + class Map; + class AbstractMapObject; + class CoordTranslationFunctor; + + namespace MathUtils + { + + class SITUATIONMAP_EXPORT GeometryEngine + { + + public: + + using Geometry = Esri::ArcGISRuntime::Geometry; + + + static Geometry *createLine (Map *map, const QPointF &p1, const QPointF &p2); + static Geometry *createPolyline(Map *map, const QVector &vertices); + static Geometry *createPolygon (Map *map, const QVector &vertices); + + static void deleteGeometry(Geometry *geom); + + static bool contains(const Geometry &geom, const Geometry &hull); + static bool contains(AbstractMapObject *mapObj, const Geometry &hull); + + static bool intersect(const Geometry &inputGeom, const Geometry &intersector); + static bool intersect(AbstractMapObject *mapObj , const Geometry &intersector); + + /** + Calculating a parallel line. + @param mapCtFunctor CoordTranslationFunctor to translate coordinates from geographic to map coordinate system and vis versa. + @param line Line to use as input, the coordinates are geograpihc coordinates. + @param distance Distance in meter. + */ + static QVector parallelTranslation(SituationMap::CoordTranslationFunctor *mapCtFunctor, const QVector &line, double distance ); + + static QVector buffer(AbstractMapObject *mapObj, double distance); + + + }; // GeometryEngine + + + } // namespace MathUtils +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/math_const.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/math_const.h new file mode 100755 index 000000000..79be0f391 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/math_const.h @@ -0,0 +1,47 @@ +/** +** +** @RDE_HEADER@ +** Math const +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 04.12.2015, JT : First edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +namespace MathConst +{ + + constexpr double pi = M_PI; + constexpr double pi_two = pi * 2; + constexpr double pi_half = M_PI_2; + constexpr double pi_quater = M_PI_4; + + constexpr double EarthRadius = 6371001.0; + + constexpr double WGS84_Major_a = 6378137.0; // WGS84 Ellipsoid, a value (Radius am Äquator, große Halbachse) + constexpr double WGS84_Minor_b = 6378137.0*( 1.0 - 1.0/298.257223563); + constexpr double WGS84_SQR_EXC = ((WGS84_Major_a * WGS84_Major_a) - (WGS84_Minor_b * WGS84_Minor_b))/(WGS84_Major_a * WGS84_Major_a); + +} // namespace MathConst diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.cpp new file mode 100755 index 000000000..2af58b453 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.cpp @@ -0,0 +1,114 @@ +/** +** +** @RDE_HEADER@ +** Implementation of 2d matrix operations +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 22.06.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "matrix2d.h" + +#include + + +namespace SituationMap { namespace MathUtils +{ + + QPointF Matrix2D::apply( const QPointF & v ) const + { + QGenericMatrix<1,3,qreal> vM; + + vM(0,0) = v.x(); + vM(1,0) = v.y(); + vM(2,0) = 1.0; + + QGenericMatrix<1,3,qreal> result = m_matrix * vM; + + qreal x = result(0,0) / result(2,0); + qreal y = result(1,0) / result(2,0); + + return QPointF(x,y); + + } /* Matrix2D::apply */ + + + void Matrix2D::putTranslation( const QPointF & t ) + { + QGenericMatrix<3,3,qreal> tM; + + tM(0,2) = t.x(); + tM(1,2) = t.y(); + + m_matrix = tM * m_matrix; + } /* Matrix2D::putTranslation */ + + + void Matrix2D::putScale( const QPointF & s) + { + QGenericMatrix<3,3,qreal> sM; + + sM(0,0) = s.x(); + sM(1,1) = s.y(); + + m_matrix = sM * m_matrix; + } /* Matrix2D::putScale */ + + + void Matrix2D::putScale( const QPointF & s, qreal sinPhi, qreal cosPhi ) + { + putRotate(QPointF(cosPhi,-sinPhi)); + putScale(s); + putRotate(sinPhi,cosPhi); + } /* Matrix2D::putScale */ + + + void Matrix2D::putRotate( qreal sinPhi, qreal cosPhi ) + { + QGenericMatrix<3,3,qreal> rM; + + rM(0,0) = cosPhi; + rM(0,1) = sinPhi; + rM(1,0) = -sinPhi; + rM(1,1) = cosPhi; + + m_matrix = rM * m_matrix; + } /* Matrix2D::putRotate */ + + + std::ostream & operator<< ( std::ostream & stream, const Matrix2D & mat ) + { + + stream << "Matrix2D:" << std::endl; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + stream << "[" << r << "," << c << "] = " << mat(r,c) << " "; + } + stream << std::endl; + } + return stream; + } // std::ostream & operator<< ( std::ostream & stream, const Matrix2D & mat ) + + +} } // namespace SituationMap::MathUtils diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.h new file mode 100755 index 000000000..f286fd5df --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/matrix2d.h @@ -0,0 +1,413 @@ +/** +** +** @RDE_HEADER@ +** 2D matrix operations +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 22.06.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include + +#include + +#include + + +namespace SituationMap { namespace MathUtils +{ + + /** + @class Matrix2D matrix2d.h + @brief The class implements a 3x3 matrix for calculating 2D coordinate transformation with homogeneouse coordinates. + + One of the most common and important tasks in computer graphics is to transform the coordinates ( position, + orientation, and size ) of either objects within the graphical scene or the camera that is viewing the scene. + It is also frequently necessary to transform coordinates from one coordinate system to another, ( e.g. world + coordinates to viewpoint coordinates to screen coordinates. ) + + All of these transformations can be efficiently and succintly handled using some simple matrix representations, + which we will see can be particularly useful for combining multiple transformations into a single composite + transform matrix. The base transformations for translating, scaling and rotating coordinates can be combined in + an arbitrary manner. + + Translation in 2D + + Point (X,Y) is to be translated by amount Dx and Dy to a new location (X',Y'). + + \code + X' = Dx + X + Y' = Dy + Y + \endcode + + or P' = T + P where + + \code + P' = | X' | + | Y' | + + T = | Dx | + | Dy | + + P = | X | + | Y | + \endcode + + Scaling in 2D + + Point (X,Y) is to be scaled by amount Sx and Sy to location (X',Y'). + + \code + X' = Sx * X + Y' = Sy * Y + \endcode + + or P' = S * P where + + \code + P' = | X' | + | Y' | + + S = | Sx 0 | + | 0 Sy | + + P = | X | + | Y | + \endcode + + Scaling is performed about the origin (0,0) not about the center of the line/polygon/whatever. + + - Scale > 1 enlarge the object and move it away from the origin. + - Scale = 1 leave the object alone + - Scale < 1 shrink the object and move it towards the origin. + - uniform scaling: Sx = Sy + - differential scaling Sx != Sy -> alters proportions + + + Rotation in 2D + + point (X,Y) is to be rotated about the origin by angle theta to location (X',Y') + + \code{.unparsed} + X' = X * cos(theta) - Y * sin(theta) + Y' = X * sin(theta) + Y * cos(theta) + \endcode + + or P' = R * P where + + \code + P' = | X' | + | Y' | + + R = | cos(theta) -sin(theta) | + | sin(theta) cos(theta) | + + P = | X | + | Y | + \endcode + + Note that this does involve sin and cos which are much more costly than addition or multiplication. + + Rotation is performed about the origin \f$(0,0)\f$ not about the center of the line/polygon/whatever. + + + Derivation of the 2D Rotation Equations + + Where does this matrix come from? + + (X,Y) is located r away from (0,0) at a CCW angle of phi from the X axis. + (X',Y') is located r away from (0,0) at a CCW angle of theta+phi from the X axis. + + Since rotation is about the origin, (X',Y') must be the same distance from the origin as (X,Y). + + From trigonometry we have: + + \code + X = r * cos(phi) + Y = r * sin(phi) + \endcode + + and + + \code + X' = r * cos(theta+phi) + Y' = r * sin(theta+phi) + \endcode + + Now making use of the following trigonometric identities: + + \code + cos(a+b) = cos(a) * cos(b) - sin(a) * sin(b) + sin(a+b) = sin(a) * cos(b) + cos(a) * sin(b) + \endcode + + and substituting in for the above equations for X' and Y', we get: + + \code + X' = r * cos(theta) * cos(phi) - r * sin(theta) * sin(phi) + Y' = r * sin(theta) * cos(phi) + r * cos(theta) * sin(phi) + \endcode + + Then we substitute in X and Y from their definitions above, and the final result simplifies to: + + \code + X' = X * cos(theta) - Y * sin(theta) + Y' = X * sin(theta) + Y * cos(theta) + \endcode + + Homogeneous Coordinates in 2 Dimensions + + Scaling and rotations are both handled using matrix multiplication, which can be combined as we + will see shortly. The translations cause a difficulty, however, since they use addition instead + of multiplication. We want to be able to treat all 3 transformations (translation, scaling, + rotation) in the same way - as multiplications. The solution is to give each point a third + coordinate (X,Y,W), which will allow translations to be handled as a multiplication also. (Note + that we are not really moving into the third dimension yet. The third coordinate is being added to + the mathematics solely in order to combine the addition and multiplication of 2-D coordinates.) + + Two triples (X,Y,W) and (X',Y',W') represent the same point if they are multiples of each other e.g. + (1,2,3) and (2,4,6). + + At least one of the three coordinates must be nonzero. If W is 0 then the point is at infinity. This + situation will rarely occur in practice in computer graphics. If W is nonzero we can divide the triple + by W to get the cartesian coordinates of X and Y which will be identical for triples representing the + same point (X/W,Y/W,1). This step can be considered as mapping the point from 3-D space onto the + plane W=1. Conversely, if the 2-D cartesian coordinates of a point are known as (X,Y), then the + homogenous coordinates can be given as (X,Y,1) + + So, how does this apply to translation, scaling, and rotation of 2D coordinates? + + + Translation of 2D Homogenous Coordinates + + point (X,Y) is to be translated by amount Dx and Dy to location (X',Y') + + \code + X' = Dx + X + Y' = Dy + Y + \endcode + + or P' = T * P where + + \code + | X' | + P' = | Y' | + | 1 | + + | 1 0 Dx | + T = | 0 1 Dy | = T(Dx,Dy) + | 0 0 1 | + + | X | + P = | Y | + | 1 | + \endcode + + Hey Look! Translation is now a multiplication instead of an addition! + + Scaling of 2D Homogenous Coordinates + + P' = S * P where + + \code + P' = | X' | + | Y' | + | 1 | + + | Sx 0 0 | + S = | 0 Sy 0 | = S(Sx,Sy) + | 0 0 1 | + + | X | + P = | Y | + | 1 | + \endcode + + Rotation of 2D Homogenous Coordinates + + P' = R * P where + + \code + | X' | + P' = | Y' | + | 1 | + + | cos(theta) -sin(theta) 0 | + R = | sin(theta) cos(theta) 0 | = R(theta) + | 0 0 1 | + + | X | + P = | Y | + | 1 | + \endcode + + Composition of 2D Transformations + + There are many situations in which the final transformation of a point is a combination of several + (often many) individual transformations. For example, the position of the finger of a robot might be + a function of the rotation of the robots hand, arm, and torso, as well as the position of the robot + on the railroad train and the position of the train in the world, and the rotation of the planet + around the sun, and . . . + + Applying each transformation individually to all points in a model would take a lot of time. Instead + of applying several transformations matrices to each point we want to combine the transformations to + produce 1 matrix which can be applied to each point. + + In the simplest case we want to apply the same type of transformation (translation, rotation, scaling) + more than once. + + - translation is additive as expected + - scaling is multiplicative as expected + - rotation is additive as expected + + But what if we want to combine different types of transformations? + + A very common reason for doing this is to rotate a polygon about an arbitrary point (e.g. the center of + the polygon) rather than around the origin. + + - Translate so that P1 is at the origin T(-Dx,-Dy) + - Rotate R(theta) + - Translate so that the point at the origin is at P1 T(Dx,Dy) + + Note the order of operations here is right to left: + + \code + P' = T(Dx,Dy) * R(theta) * T(-Dx,-Dy) * P + \endcode + + is equal to + + \code + P' = T(Dx,Dy) * { R(theta) * [ T(-Dx,-Dy) * P ] } + \endcode + + is equal to + + \code + P' = [ T(Dx,Dy) * R(theta) * T(-Dx,-Dy) ] * P + \endcode + + The matrix that results from these 3 steps can then be applied to all of the points in the polygon. Another + common reason for doing this is to scale a polygon about an arbitrary point (e.g. the center of the polygon) + rather than around the origin. + + - Translate so that P1 is at the origin + - Scale + - Translate so that the point at the origin is at P1 + + A detailed description can be also found in J.D.Foley, A.Van Dam "Fundamentals of Interactive Computer Graphics" + + */ + class SITUATIONMAP_EXPORT Matrix2D + { + public: + + /** + @name Construction, Destruction and Assign + @{ + */ + Matrix2D() = default; + explicit Matrix2D(const QGenericMatrix<3, 3, qreal> & matrix) : m_matrix(matrix) {;} + + Matrix2D & operator = ( const QGenericMatrix<3, 3, qreal> & matrix) { m_matrix = matrix; return *this; } + + /** @} */ + + /** + Set matrix to identity. + */ + void setToIdentity() { m_matrix.setToIdentity(); } + + /** + Returns true if this matrix is the identity; false otherwise. + */ + bool isIdentity() const { return m_matrix.isIdentity(); } + + /** + Returns true if a translation is set, otherwise false + */ + bool isTranslation() const { return (!qFuzzyCompare( m_matrix(0,2),0.0) || !qFuzzyCompare(m_matrix(1,2),0.0)); } + + /** + Returns true if a rotation is set, otherwise false + */ + bool isRotation() const { return (!qFuzzyCompare( m_matrix(0,1),0.0) || !qFuzzyCompare(m_matrix(1,0),0.0)); } + /** + Apply the coordinate transformation to one coordinate and returns the result. + */ + QPointF apply( const QPointF & ) const; + + /** + Add translation to the current matrix + */ + void putTranslation( const QPointF & ); + + /** + Add scaling to the current matrix. + */ + void putScale( const QPointF & ); + + + /** + Add scaling to the current matrix. + */ + void putScale(const QPointF & s, const QPointF & oVec) { putScale(s, oVec.y(), oVec.x() ); } + void putScale( const QPointF & s, qreal sinPhi, qreal cosPhi ); + + /** + Add rotation to the current matrix. + @param oVec vector defining the rotation regarding to (1,0). The length of \a oVec has to be 1 (unit vector) + */ + void putRotate( const QPointF & oVec ) { putRotate(oVec.y(),oVec.x()); } + void putRotate( qreal sinPhi, qreal cosPhi ); + + /** + Returns underlying QGenericMatrix. + */ + QGenericMatrix<3,3,qreal> & operator()() { return m_matrix; } + const QGenericMatrix<3,3,qreal> & operator()() const { return m_matrix; } + + qreal operator()(int row, int col) const { return m_matrix(row,col); } + qreal & operator()(int row, int col) { return m_matrix(row,col); } + + protected: + + QGenericMatrix<3,3,qreal> m_matrix; + + }; // Matrix2D + + /** + Returns the product of N*M + */ + inline Matrix2D operator* ( const Matrix2D & N, const Matrix2D & M ) { return Matrix2D( N()*M() ); } + + + /** + Output operator + */ + SITUATIONMAP_EXPORT std::ostream & operator<< (std::ostream & stream, const Matrix2D & mat ); + +} } // namespace SituationMap::MathUtils diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.cpp new file mode 100755 index 000000000..3a8e6850a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.cpp @@ -0,0 +1,99 @@ +/** +** +** @RDE_HEADER@ +** Rasterize functional path geometries to polylines +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 12.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "path_rasterer.h" +#include + +namespace SituationMap { namespace MathUtils +{ + PathRasterer::PathRasterer() : _eps(1.0), _maxVertexCount(0) + { + } /* PathRasterer::PathRasterer */ + + + void PathRasterer::rasterize(qreal tMin, qreal tMax, QVector & vertexList) { exeRasterize( tMin, tMax, vertexList ); } + void PathRasterer::rasterize(qreal tMin, qreal tMax, QList & vertexList) { exeRasterize( tMin, tMax, vertexList ); } + + + template void PathRasterer::exeRasterize( qreal tMin, qreal tMax, List & vertexList ) + { + vertexList.clear(); + + if (tMin >= tMax) return; + + QPointF vMin, vMax; + + function( tMax, vMax ); + function( tMin, vMin ); + vertexList.append(vMin); + splitSegment( vMin, tMin, vMax, tMax, _maxVertexCount, vertexList ); + vertexList.append(vMax); + } /* PathRasterer::exeRasterize */ + + + template void PathRasterer::splitSegment( const QPointF & v0 + , qreal t0 + , const QPointF & v1 + , qreal t1 + , unsigned int maxVertexCount + , List & vertexList ) + { + if (maxVertexCount < 1) + return; + + QPointF v2; + qreal t2 = (t0+t1) * 0.5; + + function( t2, v2 ); + + auto length = []( const QPointF & v ) -> qreal { return sqrt(v.x()*v.x() + v.y()*v.y()); }; + + qreal l0 = length(v1-v0); + qreal l1 = length(v1-v2); + qreal l2 = length(v2-v0); + + + /* ------------------------------------------------------------------------- + * Berechnen der Höhe des Dreiecks über l0 für den Vergleich mit + * dem Schwellwert (siehe Kosinussatz, Trigonometrische Funktionen, etc.). + * ------------------------------------------------------------------------- */ + + qreal cosPhi = (l2*l2+l0*l0-l1*l1)/(2.0*l2*l0); + qreal h_eps = l2*sqrt(1.0-cosPhi*cosPhi); + + // (maxVertexCount > 0) -> see pre-condition + if (((std::isnan(h_eps)) || (h_eps > _eps))) + { + maxVertexCount /= 2; + splitSegment( v0, t0, v2, t2, maxVertexCount, vertexList ); + vertexList.append( v2 ); + splitSegment( v2, t2, v1, t1, maxVertexCount, vertexList ); + } + } /* PathRasterer::splitSegment */ + +} } // namespace SituationMap::MathUtils diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.h new file mode 100755 index 000000000..9f110fda5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/path_rasterer.h @@ -0,0 +1,94 @@ +/** +** +** @RDE_HEADER@ +** Rasterize functional path geometries to polylines +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 12.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include +#include +#include +#include + +namespace SituationMap { namespace MathUtils +{ + + /** + @class PathRasterer path_rasterer.h + @brief Base class to rasterize parametric functions, like splines + + Base class to rasterize parametric functions, like splines, with an interval halving method + to approximate the function by a discrete polyline. + + @see Ellipse, Spline, SmoothLine, HermiteSpline, BezierSpline + */ + class SITUATIONMAP_EXPORT PathRasterer + { + public: + + PathRasterer(); + virtual ~PathRasterer() = default; + + /** + Defines a threshold values to decide ending convergence + */ + inline void epsilon( qreal eps ) { _eps = eps; } + + /** + Defines the maximum of calculated verticies if \c epsilon doesn't terminate the convergence operation + */ + inline void maxVertexCount(unsigned int max) { _maxVertexCount = max; } + + /** + Method to override to define the parametric function + */ + virtual void function( qreal t, QPointF & result ) = 0; + + /** + Creating a vertex vector with an interval halving method to approximate a parametric function by a polyline + */ + void rasterize(qreal tMin, qreal tMax, QVector & vertexList); + + void rasterize(qreal tMin, qreal tMax, QList & vertexList); + + protected: + + qreal _eps; + unsigned int _maxVertexCount; + + template void exeRasterize( qreal tMin, qreal tMax, List & vertexList ); + + template void splitSegment( const QPointF & v0 + , qreal t0 + , const QPointF & v1 + , qreal t1 + , unsigned int maxVertexCount + , List & vertexList ); + + }; /* PathRasterer */ + +} } // namespace SituationMap::MathUtils diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.cpp new file mode 100755 index 000000000..3b5fe05ee --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.cpp @@ -0,0 +1,207 @@ +/** + * @RDE_HEADER@ + * Helper functions related to CoordinateTranslation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "simple_helper.h" +#include "math_const.h" +#include "clipper.hpp" +#include +// Qt +#include +#include + +namespace SituationMap +{ +namespace MathUtils +{ + + +QPointF rotate(const QPointF &p, const QPointF ¢er, qreal sinA, qreal cosA) +{ + qreal x = p.x() - center.x(); + qreal y = p.y() - center.y(); + return QPointF(x * cosA - y * sinA + center.x(), y * cosA + x * sinA + center.y()); +} + + +QPointF rotate(const QPointF &p, const QPointF ¢er, qreal phi) { return rotate(p, center, sin(phi), cos(phi)); } + + +qreal getMathAngle(qreal sinPhi, qreal cosPhi) +{ + qreal phi = acos(cosPhi); + if (sinPhi < 0) phi = MathConst::pi_two - phi; + return phi; +} + + +qreal getMathAngle(const QPointF &oVec) { return getMathAngle(oVec.y(), oVec.x()); } + + +double getAngleDegreeNorth(const QPointF &p1, const QPointF &p2) +{ + double x = p2.x() - p1.x(); + double y = p2.y() - p1.y(); + + // Winkel der Geraden bestimmen + double alphaRad = atan2(y, x); + +// // Fuer Winkel > PI liefert atan2 math. rechts drehende (also negative) Werte. +// // Um Werte von 0-2PI zu bekommen muss deshalb hier von 2Pi abgezogen werden. + + alphaRad -= M_PI / 2.0; + alphaRad *= -1.0; + + if (alphaRad < 0.0) + alphaRad = 2 * M_PI + alphaRad; + + return (alphaRad * 180.0 / M_PI); +} + +QPointF getPointOnLine(const QPointF &p1, const QPointF &p2, double position_0_to_1) +{ + QPointF pRet; + + pRet.rx() = p1.x() + (p2.x() - p1.x()) * position_0_to_1; + pRet.ry() = p1.y() + (p2.y() - p1.y()) * position_0_to_1; + + return pRet; +} + +Point3D getPointOnLine(const Point3D &p1, const Point3D &p2, double position_0_to_1) +{ + Point3D pRet; + + pRet.rx() = p1.x() + (p2.x() - p1.x()) * position_0_to_1; + pRet.ry() = p1.y() + (p2.y() - p1.y()) * position_0_to_1; + pRet.rz() = p1.z() + (p2.z() - p1.z()) * position_0_to_1; + + return pRet; +} + +void rotatePoint(QPointF &pToBeRotated, const QPointF ¢er, double angleDegree) +{ + // angle to rad + double radAngle = ((M_PI) / -180.0) * angleDegree; + + // Translate + QPointF t = pToBeRotated - center; + + // Rotate + QPointF tmp; + tmp.rx() = t.x() * cos(radAngle) - t.y() * sin(radAngle); + tmp.ry() = t.x() * sin(radAngle) + t.y() * cos(radAngle); + + // Translate back and write result + pToBeRotated = tmp + center; +} + + + +QList mineFieldCenterLineToOutline(const QList ¢erLine, + double width) +{ + QList outline; + + // Clipper is using ints (typedef signed long long cInt;) + // The coordinate of centerline will have values much lower the the maximum. + // Therefore the factor can be 1000. So the resolution is bit enough (->Millimeter): + const int factorInt = 10; + const double factorDouble = 10.0; + + ClipperLib::ClipperOffset pathOffset; + ClipperLib::Path borderPath; // vector< int > + ClipperLib::Paths offsetPathVec; // vector< vector< int > > + + for (auto point : centerLine) + { + borderPath << ClipperLib::IntPoint(static_cast(point.x()*factorInt), + static_cast(point.y()*factorInt)); + } + + pathOffset.AddPath(borderPath, ClipperLib::JoinType::jtSquare, ClipperLib::EndType::etOpenButt); + pathOffset.Execute(offsetPathVec, static_cast(fabs(width)*factorInt)); + + for (auto path : offsetPathVec) + { + ClipperLib::Path::const_iterator it = path.begin(); + + for (; it != path.end(); ++it) + { + outline.append(QPointF(static_cast(it->X) / factorDouble, + static_cast(it->Y) / factorDouble) + ); + + } + } + + return outline; +} + +qreal getAngleDegree2North(qreal angleDeg) +{ + double rotationAngle = angleDeg < 360. ? angleDeg : std::fmod(angleDeg, 360.); + // set rotation to 0° North(12h) counter clock wise + rotationAngle = rotationAngle < 90. ? std::fabs(90 - rotationAngle) : std::fabs(360 - rotationAngle + 90) ; + return rotationAngle; +} + +qreal getAngleDegree2Math(qreal angleDeg) +{ + // set rotation to 0° math(3h) clockwise + const double rotationMath = angleDeg < 90. ? std::fabs(angleDeg - 90) : std::fabs(360 - angleDeg + 90) ; + return rotationMath; +} + +QPolygonF getCircleSectorPolygon(const QPointF ¢erPoint, qreal radius, double rotationAngleDeg, double centralAngleDeg) +{ + // | <-rotationAngle + // | .\ | / + const double angleRad = angle * LtsCoordTrans::deg2rad(); + + // relPosition des StartPoint (point on circle) + const QPointF position(cos(angleRad) * radius, sin(angleRad) * radius); + + // StartPoint einfügen + poly << centerUtm.geographic(position); + + // calculate all steps, inclusive EndPos + for (int step = 1; step<=steps; ++step) + { + double curAngle = angle + (step*singleIncrementDegree); + double curAngleRad = curAngle * LtsCoordTrans::deg2rad(); + poly << centerUtm.geographic( QPointF(cos(curAngleRad) * radius, sin(curAngleRad) * radius) ); + } // for + return poly; +} + +} // namespace SituationMap::MathUtils +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.h new file mode 100755 index 000000000..b70acd3bb --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/MathUtils/simple_helper.h @@ -0,0 +1,130 @@ +/** + * @RDE_HEADER@ + * Helper functions related to CoordinateTranslation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Own +#include "situation_map_export.h" +#include + + +// Qt +#include +#include + + +// ESRI + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ +namespace MathUtils +{ + +/** + @brief Rotate a 2 dimensional point + @param p point to rotate + @param center center point of rotation + @param sinA sinus of rotation angle + @param cosA cosinus of rotation angle + */ +QPointF SITUATIONMAP_EXPORT rotate(const QPointF &p, const QPointF ¢er, qreal sinA, qreal cosA); + +/** + @brief Rotate a 2 dimensional point + @param p point to rotate + @param center center point of rotation + @param phi rotation angle in rad +*/ +QPointF SITUATIONMAP_EXPORT rotate(const QPointF &p, const QPointF ¢er, qreal phi); + +/** + * @brief Get math angle in radiant from orientation vector + * @param oVec orientation vector, must be normalized to length 1 + * @return mathematical angle in rad + */ +qreal SITUATIONMAP_EXPORT getMathAngle(const QPointF &oVec); + + +/** + * @brief Get math angle in radiant from sinus and cosinus + * @param sinPhi sinus of angle, is equal to y component of orientation vector + * @param cosPhi cosinus of angle, is equal to x component of orientation vector + * @return mathematical angle in rad + */ +qreal SITUATIONMAP_EXPORT getMathAngle(qreal sinPhi, qreal cosPhi); + + +double SITUATIONMAP_EXPORT getAngleDegreeNorth(const QPointF &p1, const QPointF &p2); + +/** + * @brief Get North angle from Math angle + * @param mathematical orientated angle in degree + * @return north (0° = 12h) orientated angle in degree + */ +qreal SITUATIONMAP_EXPORT getAngleDegree2North(qreal angleDeg); + +/** + * @brief Get North angle from Math angle + * @param mathematical orientated angle in degree + * @return math (0° = 3h) orientated angle in degree + */ +qreal SITUATIONMAP_EXPORT getAngleDegree2Math(qreal angleDeg); + + +/** + * @brief Get the position on a given line (for positioning a text) + * @param p1 startPoint of line + * @param p2 endPoint of line + * @param position_0_to_1 value from 0.0 up to 1.0. + * 0.0 -> 0% -> startPoint + * 0.5 -> 50% -> middle between startPoint and endPoint + * 1.0 -> 100% -> endPoint + * @return position between start and end. + */ +QPointF SITUATIONMAP_EXPORT getPointOnLine(const QPointF &p1, const QPointF &p2, double position_0_to_1); + +Point3D SITUATIONMAP_EXPORT getPointOnLine(const Point3D &p1, const Point3D &p2, double position_0_to_1); + +/** + * @brief getCircleSectorPolygon + * @param centerPoint [geo coordinates] + * @param radius [m] + * @param rotationAngleDeg (orientation of segment) + * @param centralAngleDeg + * @return polygon describing the circle sector + */ +QPolygonF SITUATIONMAP_EXPORT getCircleSectorPolygon(const QPointF ¢erPoint, qreal radius, double rotationAngleDeg, double centralAngleDeg); + + + +/** + * @brief Rotate a point around with another point as center. + * @param pToBeRotated Point to be rotated + * @param center Center + * @param angleDegree Angle in degree (clockwise). + */ +void SITUATIONMAP_EXPORT rotatePoint(QPointF &pToBeRotated, + const QPointF ¢er, + double angleDegree); + + + + +QList SITUATIONMAP_EXPORT mineFieldCenterLineToOutline(const QList ¢erLine, + double width); + + + + + + +} // namespace MathUtils +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/CMakeLists.txt new file mode 100755 index 000000000..91ff5d6a4 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/CMakeLists.txt @@ -0,0 +1,49 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +SET(SRCS_TEST1 + situation_map_test_main.cpp + sit_map.cpp + + log4cxxSituationMapModuleTests.props +) + +SET(HDRS_TEST1 + situation_map_test_main.h + sit_map.h +) + +IF(BUILD_TESTING AND TARGET situationMap) + ADD_EXECUTABLE(situationMapModuleTest ${SRCS_TEST1} ${HDRS_TEST1}) + TARGET_LINK_LIBRARIES(situationMapModuleTest + NGLLogger + nglapplication + LtsCoordTrans + advancedSymbology_static + situationMapObjects + situationMap_static + situationMapFunctionDialog + ArcGIS::ArcGIS + Qt6::Core + Qt6::Test + ) + + ADD_TEST(NAME situationMapModuleTest_NONE COMMAND $ none) + ADD_TEST(NAME situationMapModuleTest_MIL2525D COMMAND $ mil2525d) + + SET(LOG4CXX_PROPS_FILE log4cxxSituationMapModuleTests.props) + ADD_CUSTOM_COMMAND(TARGET situationMapModuleTest POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${LOG4CXX_PROPS_FILE} $/${LOG4CXX_PROPS_FILE} + ) + + IF(MSVC) + # Configure the template file + SET(VS_USER_FILE situationMapTest.vcxproj.user) + configure_file( ../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) + ENDIF(MSVC) + +ENDIF() diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/log4cxxSituationMapModuleTests.props b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/log4cxxSituationMapModuleTests.props new file mode 100755 index 000000000..fadc434b8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/log4cxxSituationMapModuleTests.props @@ -0,0 +1,159 @@ +log4j.reset=true +log4j.rootLogger=WARN, file + +# SitMap +########################################################### +#log4j.logger.SituationMap.SituationMapTestsMain=DEBUG +#log4j.logger.SituationMap.SitMap=WARN + +# AdvancedSymbology (AdvSymbology) +########################################################### +#log4j.logger.AdvSymbology.SymbolDictionary=INFO +#log4j.logger.AdvSymbology.SymbolDictionaryImpl=INFO +#log4j.logger.AdvSymbology.Sidc2525Delta=INFO + +# SituationMap +########################################################### +#log4j.logger.SituationMap.INFO +#log4j.logger.SituationMap.MapImpl=FATAL +#log4j.logger.SituationMap.MapSettings=FATAL +#log4j.logger.SituationMap.MapInitializer=FATAL +#log4j.logger.SituationMap.LayerFactoryImpl=FATAL +#log4j.logger.SituationMap.LayerControl=FATAL +#log4j.logger.SituationMap.CoordTranslationFunctorImpl=FATAL +#log4j.logger.SituationMap.CoordTranslationThread=FATAL +#log4j.logger.SituationMap.PositionsTranslated=FATAL +#log4j.logger.SituationMap.GridImpl=FATAL +#log4j.logger.SituationMap.ObjectFactory=FATAL +#log4j.logger.SituationMap.IdGenerator=FATAL +#log4j.logger.SituationMap.ObjectContainer=FATAL +#log4j.logger.SituationMap.ObjectContainerCTC=FATAL +#log4j.logger.SituationMap.OverlayHandlerImpl=FATAL +#log4j.logger.SituationMap.ViewshedImpl=FATAL +#log4j.logger.SituationMap.ViewshedBase=FATAL + +#log4j.logger.SituationMap.AbstractMapObjectImpl=FATAL +#log4j.logger.SituationMap.AbstractEffectSimulationImpl=FATAL +#log4j.logger.SituationMap.OriginTranslated=FATAL +#log4j.logger.SituationMap.PositionsTranslated=FATAL +#log4j.logger.SituationMap.AbstractMultiPointMapObjectImpl=FATAL +#log4j.logger.SituationMap.AbstractSinglePointMapObjectImpl=FATAL +#log4j.logger.SituationMap.BasicPathObjectImpl=FATAL +#log4j.logger.SituationMap.IndicationContainer=FATAL + +#log4j.logger.SituationMap.PolygonImpl=FATAL +#log4j.logger.SituationMap.PolylineImpl=FATAL +#log4j.logger.SituationMap.TextImpl=FATAL + +#log4j.logger.SituationMap.AreaFireImpl=FATAL +#log4j.logger.SituationMap.BiologicalAgentImpl=FATAL +#log4j.logger.SituationMap.BuildingOutlineImpl=FATAL +#log4j.logger.SituationMap.ChemicalAgentImpl=FATAL +#log4j.logger.SituationMap.DangerAreaImpl=FATAL +#log4j.logger.SituationMap.FunctionalAreaImpl=FATAL +#log4j.logger.SituationMap.HitLineImpl=FATAL +#log4j.logger.SituationMap.MapObjectIndicatorImpl=FATAL +#log4j.logger.SituationMap.MineImpl=FATAL +#log4j.logger.SituationMap.MineFieldImpl=FATAL +#log4j.logger.SituationMap.NuclearAgentImpl=FATAL +#log4j.logger.SituationMap.ParticipantImpl=FATAL +#log4j.logger.SituationMap.SingleFireImpl=FATAL + +#log4j.logger.SituationMap.AbstractGraphicalObject=FATAL +#log4j.logger.SituationMap.GraphicalObjectEllipse=FATAL +#log4j.logger.SituationMap.GraphicalObjectPath=FATAL +#log4j.logger.SituationMap.GraphicalObjectMultiPath=FATAL +#log4j.logger.SituationMap.GraphicalObjectPictureMarker=FATAL +#log4j.logger.SituationMap.GraphicalObjectRange=FATAL +#log4j.logger.SituationMap.GraphicalObjectText=FATAL +#log4j.logger.SituationMap.TacticalAppearance=FATAL +#log4j.logger.SituationMap.MessageBase=FATAL + +#log4j.logger.SituationMap.EventDispatcher=FATAL +#log4j.logger.SituationMap.Rubberband=FATAL +#log4j.logger.SituationMap.RubberbandRectangle=FATAL +#log4j.logger.SituationMap.RubberbandDrawablesAndHandles=FATAL +#log4j.logger.SituationMap.RubberbandFrameHandle=FATAL +#log4j.logger.SituationMap.RubberbandPointHandle=FATAL +#log4j.logger.SituationMap.RubberbandStateSelect=FATAL + +# SituationMap-SideBar +########################################################### +#log4j.logger.SituationMap.SideBar=FATAL +#log4j.logger.SituationMap.SideBarItem=FATAL +#log4j.logger.SituationMap.SideBarModel=FATAL + + + +# Appnd_File +########################################################### +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_SituationMap.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %-5p %c{1} |#| %m (%-.30F:%L) %n + +#log4j.appender.stdout.layout.ConversionPattern= %d %-5p %-30c |#| %m (%-.30F:%L) %n + + +# Appnd_Syslog +########################################################### +log4j.appender.syslog=org.apache.log4j.SyslogAppender +log4j.appender.syslog.Threshold=ERROR +log4j.appender.syslog.SysLogHost=localhost +log4j.appender.syslog.Facility=LOCAL7 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m +log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true +log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO +log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL + + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.cpp new file mode 100755 index 000000000..76b63e188 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.cpp @@ -0,0 +1,347 @@ +/** + * @RDE_HEADER@ + * Moduletest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "sit_map.h" + +#include + +#include +//#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include + + +// Qt +#include +#include +#include +#include + + + + +class myMapInitializer : public SituationMap::MapInitializer +{ + Q_OBJECT + + using BaseClass = SituationMap::MapInitializer; + +public: + + explicit myMapInitializer(SituationMap::Map &map + , SituationMap::MapSettings &mapSettings + , const QString &debugName + , QObject *parent = nullptr) + : BaseClass(map, mapSettings, debugName, parent) {} + + bool initializeObjectFactory() override + { + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::MapObjectIndicator), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Cube), new SituationMap::GenericObjectFactory()); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Polygon), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Polyline), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Text), new SituationMap::GenericObjectFactory()); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Participant), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::AreaFire), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BiologicalAgent), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::ChemicalAgent), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BuildingOutline), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::DangerArea), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::FunctionalArea), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::GeoImage), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::HitLine), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Mine), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::MineField), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::NineLiner), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::NuclearAgent), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SingleFire), new SituationMap::GenericObjectFactory()); +// SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SingleFireCentral), new SituationMap::GenericObjectFactory()); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SidcSymbol), new SituationMap::GenericObjectFactory()); + + return true; + } // initializeObjectFactory() + + // overload to support tpk-Files/BasemapBuiltIn + bool initializeCoordTranslationFunctor() override + { + if (m_map.layerFactory().isUsingBaseMapBuiltIn()) + { + int wkid = 102100; + NOTICE_LOG(m_logger) << LOGFUNCTION << "\e[31m !!! using BaseMapBuiltIn -> wkId=" << wkid << "\e[0m"; + + m_map.initCoordTranslationFunctor(); + return true; + } + return BaseClass::initializeCoordTranslationFunctor(); + } + + + AdvancedSymbology::SymbologyType symbologyType() const override + { + return m_symbologyType; + } // symbologyType + + void setSymbologyType(AdvancedSymbology::SymbologyType symbologyType) + { + m_symbologyType = symbologyType; + } + + +protected slots: + + void onMapReady() override + { + + // using center and scale: + m_map.setCenterPoint(QPointF(92750, 80525)); // QPointF(92750, 80525) -> Rennplatz - Bremen + m_map.setScale(3000000); // 3000000 -> Lower Saxony + + // instead of "center and scale" it is possible to set an extent + // double xmin, double ymin, double xmax, double ymax + // m_map.setExtent(-200000, -200000, 300000, 300000); + + + // minMaxScale is optional: + // m_map.setMinScale(10000000); + // m_map.setMaxScale(100); + } + +protected: + AdvancedSymbology::SymbologyType m_symbologyType = AdvancedSymbology::SymbologyType::APP6B; + +}; // myMapInitializer + + +using namespace SituationMap; + +class SitMap::Private +{ +public: + Private() = default; + ~Private() = default; + + // map + AdvancedSymbology::SymbologyType m_symbologyType; + SituationMap::Map *m_map = nullptr; + + // admin + QGridLayout *m_mainLayout = nullptr; + QPushButton *m_pbDestroyMap = nullptr; + QPushButton *m_pbQuit = nullptr; + + QTimer *m_getRotationTimer = nullptr; + +}; // class SitMap::Private +//----------------------------------------------------------------------------- + +SitMap::SitMap(AdvancedSymbology::SymbologyType symbologyType, QWidget *parent) + : QDialog(parent) + , m_logger(SituationMap::getLogger("SitMap")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->setObjectName(QLatin1String("SitMap-Widget")); + + d->m_symbologyType = symbologyType; + d->m_mainLayout = new QGridLayout(this); + d->m_mainLayout->setObjectName(QLatin1String("layout")); + + //----------------------------------- + // Buttons + //----------------------------------- + d->m_pbDestroyMap = new QPushButton(QLatin1String("Destroy Map")); + d->m_mainLayout->addWidget(d->m_pbDestroyMap, 0, 0); + connect(d->m_pbDestroyMap, &QPushButton::clicked, this, &SitMap::onCreateDestroyClicked); + + + //----------------------------------- + // Map + //----------------------------------- + createMap(); + + { + QGridLayout *bottomLayout = new QGridLayout(); + bottomLayout->setObjectName(QLatin1String("bottomLayout")); + d->m_mainLayout->addLayout(bottomLayout, 2, 0); + + d->m_pbQuit = new QPushButton(QLatin1String("Quit")); + connect(d->m_pbQuit, &QPushButton::clicked, [ = ]() + { + QCoreApplication::quit(); + }); + bottomLayout->addWidget(d->m_pbQuit, 0, 0); + + // tbc + + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SitMap::~SitMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d->m_map; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool SitMap::isReady() +{ + return d->m_map->isReady(); +} + +SituationMap::Map *SitMap::map() +{ + return d->m_map; +} + +void SitMap::onCreateDestroyClicked() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + static bool destroy = false; + destroy = !destroy; + if (destroy) + { + d->m_pbDestroyMap->setText(QLatin1String("Create Map")); + d->m_getRotationTimer->stop(); // to avoid slot being called + d->m_map->deleteLater(); + } + else + { + d->m_pbDestroyMap->setText(QLatin1String("Destroy Map")); + createMap(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SitMap::createMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // 1. Map + d->m_map = SituationMap::Map::create(this); + + connect(d->m_map, &SituationMap::Map::mapReady, this, [this]() + { + + this->update(); // to fix redraw problem + + DEBUG_LOG(m_logger) << " ===== emitting mapReady"; + emit mapReady(); + + // Timer + d->m_getRotationTimer = new QTimer(this); + connect(d->m_getRotationTimer, &QTimer::timeout, this, [this]() + { + DEBUG_LOG(m_logger) << " get rotationDegree of=" << d->m_map->rotation(); + }); + d->m_getRotationTimer->setSingleShot(true); + d->m_getRotationTimer->start(2000); + }); + + connect(d->m_map, &SituationMap::Map::mapReadyAndSymbolDictionaryLoaded, this, [this]() + { + DEBUG_LOG(m_logger) << " ===== emitting mapReadyAndSymbolDictionaryLoaded"; + emit mapReadyAndSymbolDictionaryLoaded(); + }); + + + // e.g. TDS-build/share/Lts/situation_map/situation_map_defaults.ini + QString initFilePath = NGL::findFile(NGL::FileType::ConfigFile, QStringLiteral("situation_map_defaults.ini")); + + if (initFilePath.isEmpty()) + { + FATAL_LOG(m_logger) << " initFilePath " << initFilePath; + abort(); + } + + SituationMap::MapSettings *mapSettings = new SituationMap::MapSettings(initFilePath, d->m_map); + myMapInitializer *mapInitializer = new myMapInitializer(*d->m_map, *mapSettings, QLatin1String("ModuleTest"), d->m_map); + + + // Take SymbologyType from argv not from ini file: + AdvancedSymbology::SymbologyType symbologyTypeArgv = d->m_symbologyType; + mapInitializer->setSymbologyType(symbologyTypeArgv); + + + if (mapInitializer->hasErrors()) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " MapInitializer-filename='" << mapInitializer->filename() + << "'' has following errors: " + << " exists = " << mapInitializer->exists() + << " accessError = " << mapInitializer->accessError() + << " formatError = " << mapInitializer->formatError(); + return; + } + else + { + if (mapInitializer->initMap()) + { + INFO_LOG(m_logger) << LOGFUNCTION << " MapInitializer::initMap() ok"; + } + else + { + ERROR_LOG(m_logger) << LOGFUNCTION << " MapInitializer::initMap() failed"; + } + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " GridLevelCount=" << d->m_map->grid().levelCount(); + + // BasePoint, extent, rotation + connect(d->m_map, &SituationMap::Map::extentChanged, this, [ = ]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " extent changed: "; + }); + + + // Add widget + d->m_mainLayout->addWidget(d->m_map, 1, 0); + + // 50. turn over to application (if required): + + // Create FunctionDialog: + SituationMap::FunctionDialog::addButtonToMap(d->m_map); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +#include "sit_map.moc" diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.h new file mode 100755 index 000000000..96e7940a8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/sit_map.h @@ -0,0 +1,58 @@ +/** + * @RDE_HEADER@ + * Moduletest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Qwn +#include + + +namespace SituationMap +{ +class Map; +} + +namespace AdvancedSymbology +{ +enum class SymbologyType; +} + + +// Qt +#include + +class SitMap : public QDialog +{ + Q_OBJECT + +public: + SitMap(AdvancedSymbology::SymbologyType symbologyType, QWidget *parent = nullptr); + virtual ~SitMap(); + + virtual bool isReady(); + + SituationMap::Map *map(); + + +signals: + void mapReady(); + void mapReadyAndSymbolDictionaryLoaded(); + +public slots: + void onCreateDestroyClicked(); + void createMap(); + + +private: + SituationMap::LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + Private *d = nullptr; + +}; + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.cpp new file mode 100755 index 000000000..dc929c5a9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.cpp @@ -0,0 +1,3518 @@ +/** + * @RDE_HEADER@ + * Moduletest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "situation_map_test_main.h" + + +#include +#include + +#include "sit_map.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +//#include +#include + +#include + +#include + +#include + +//ESRI +#include +#include +#include +#include +#include +//#include + + +//Qt +#include +#include +#include +#include +#include +#include +#include + + +#include +#include + +static LtsCoordTrans::UtmCoordinate s_basePoint; + +const int timeToDelay = 0; // suggestion: 2000 + +using namespace SituationMap; // 'enable' operator<< (QPointF etc.) + +bool fuzzyCompare(const Point3D &p1, const Point3D &p2) +{ + const Point3D &diff = p1 - p2; + return (diff.x() < 0.00001) && + (diff.y() < 0.00001) && + (diff.z() < 0.00001); +} + +bool fuzzyCompare(const PositionCont &p1, const PositionCont &p2) +{ + if (p1.size() != p2.size()) + return false; + + for(int i=0; im_symbologyType = symbologyType; +} + +SituationMapTestsMain::~SituationMapTestsMain() +{ + delete d; +} + + +//------------------------------------------------------------------------------------------------------- + + +void SituationMapTestsMain::initTestCase() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION; + + if (d->m_sitMap) + d->m_sitMap->close(); + + delete d->m_sitMap; + + LayerFactoryImpl::setForceCreationOfGraphicsOverlay(false); // false => createLayerOnDemand + + + d->m_sitMap = new SitMap(d->m_symbologyType); + QVERIFY2(d->m_sitMap, "Initialisation of SituationMap failed"); + + connect(d->m_sitMap, &SitMap::mapReadyAndSymbolDictionaryLoaded, [&]() + { +// INFO_LOG(m_logger) << LOGFUNCTION << " got mapReady "; + }); + + d->m_sitMap->setMinimumWidth(1100); + d->m_sitMap->setMinimumHeight(1100); + + d->m_sitMap->show(); + + d->m_map = d->m_sitMap->map(); + d->m_geoOff = QPointF( s_basePoint.x,s_basePoint.y ); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + QString msg = QStringLiteral("layerFactory: count failed (%1)").arg(layerFac.count()); + QVERIFY2((layerFac.count() >= 3) && (layerFac.count() <= 16) , msg.toStdString().c_str()); + + QSignalSpy spy(d->m_sitMap, &SitMap::mapReady); + QTRY_VERIFY(d->m_sitMap->isReady()); + QTRY_VERIFY2(spy.count() == 1, "mapReadyEmitted not emitted"); + + QVERIFY2(d->m_sitMap->isReady() , "map is NOT READY"); + + + // Wait also for Dictionary (since it´s done within a WorkerThread) + AdvancedSymbology::SymbolDictionary &dict = d->m_map->symbolDictionary(); + QTRY_VERIFY2(dict.isLoaded(), "dict is NOT Loaded"); + + QTest::qWait(1000); +} + + +void SituationMapTestsMain::checkMapReady() +{ + if (! d->m_sitMap->isReady()) + { + QSignalSpy spy(d->m_sitMap, &SitMap::mapReadyAndSymbolDictionaryLoaded); + QTRY_VERIFY2(spy.count() > 0, "mapReady not received"); + } +} + +void SituationMapTestsMain::checkMapNotifier() +{ + QPointer mapNotifier = d->m_sitMap->map()->createMapNotifier(); + QVERIFY2(mapNotifier.isNull() == false, "no MapNotifier"); + + QSignalSpy spy1(mapNotifier.data(), &MapNotifier::mapReady); + QSignalSpy spy2(mapNotifier.data(), &MapNotifier::mapReadyAndSymbolDictionaryLoaded); + + mapNotifier->run(); + + QTRY_VERIFY2(spy1.count() == 1, "mapReady not received"); + QTRY_VERIFY2(spy2.count() == 1, "mapReadyAndSymbolDictionaryLoaded not received"); +} + +void SituationMapTestsMain::checkCenter() +{ + QTest::qWait(timeToDelay); + + int scaleSAVE = d->m_map->scale(); + QPointF centerSAVE = d->m_map->centerPoint(); + + { + // real check: + QPointF center(LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD9100084500"))); // Bremen motorway junction + + // double xmin, double ymin, double xmax, double ymax + d->m_map->setExtent(center.x() - 0.01, center.y() - 0.01, + center.x() + 0.01, center.y() + 0.01); + + QPointF readCenter = d->m_map->centerPoint(); + DEBUG_LOG(m_logger) << "Center: " << center << " got: " << readCenter; + + QPointF dist = center - readCenter; + + INFO_LOG(m_logger) << LOGFUNCTION + << " center =" << center + << " read center =" << readCenter + << " dist.manhattanLength=" << dist.manhattanLength(); + + QVERIFY2(dist.manhattanLength() < 10, "checkCenter failed"); + // QVERIFY2(center.toPoint()==QPoint(5500, 5500), "checkCenter failed"); + + QTest::qWait(timeToDelay); + } + + // restore old values: + d->m_map->setScale(scaleSAVE); + d->m_map->setCenterPoint(centerSAVE); + QTest::qWait(timeToDelay); +} + +void SituationMapTestsMain::checkExtent() +{ + const QPointF centerPnt(LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD9100084500"))); // Bremen motorway junction + + d->m_map->setCenterPoint(centerPnt, 500000); // what is the useful + QTest::qWait(1000); // Let map react + + // some 'reference' values: + QPointF center = d->m_map->centerPoint(); + QRectF extent = d->m_map->extent(); +// QRectF fullExtent = d->m_map->fullExtent(); + + INFO_LOG(m_logger) << LOGFUNCTION << " center = " << center; + INFO_LOG(m_logger) << LOGFUNCTION << " extent = " << extent; +// INFO_LOG(m_logger) << LOGFUNCTION << " fullExtent = " << fullExtent; + + QVERIFY2(extent. contains(center), "extent.contains failed"); +// QVERIFY2(fullExtent.contains(center), "fullExtent.contains failed"); +// QVERIFY2(fullExtent.contains(extent.topLeft()), "fullExtent.contains failed"); +// QVERIFY2(fullExtent.contains(extent.topRight()), "fullExtent.contains failed"); +// QVERIFY2(fullExtent.contains(extent.bottomLeft()), "fullExtent.contains failed"); +// QVERIFY2(fullExtent.contains(extent.bottomRight()), "fullExtent.contains failed"); + QTest::qWait(1000); // Let map react + + INFO_LOG(m_logger) << LOGFUNCTION << " set identical values now...."; + + // I + // set extent (as QRectF) + QTest::qWait(1000); // Let map react + d->m_map->setExtent(extent); + QTest::qWait(1000); // Let map react + + // reference extent and new extent should be identical: + QRectF newExtent = d->m_map->extent(); + QCOMPARE(newExtent.x(), extent.x()); + QCOMPARE(newExtent.y(), extent.y()); + QCOMPARE(newExtent.width(), extent.width()); + QCOMPARE(newExtent.height(), extent.height()); + + +/* + // III + // set fullExtent (as QRectF) + QTest::qWait(1000); // Let map react + d->m_map->setFullExtent(fullExtent); + QTest::qWait(1000); // Let map react + + // reference fullExtent and new fullExtent should be identical: + QRectF newFullExtent = d->m_map->fullExtent(); + QCOMPARE(newFullExtent.x(), fullExtent.x()); + QCOMPARE(newFullExtent.y(), fullExtent.y()); + QCOMPARE(newFullExtent.width(), fullExtent.width()); + QCOMPARE(newFullExtent.height(), fullExtent.height()); +*/ +} + + + +void SituationMapTestsMain::checkMapIsUTM() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + if (d->m_map->layerFactory().isUsingBaseMapBuiltIn()) + { + #ifdef Q_OS_WIN + NOTICE_LOG(m_logger) << LOGFUNCTION << "***** !!! skipping test - isUsingBaseMapBuiltIn() = true !!! *****"; + #else + // Escape sequenz \e[31m should provide a red colored log output but doesn't work for all compilers and terminal types!!! JT 2016/07/13 + NOTICE_LOG(m_logger) << LOGFUNCTION << "\e[31m !!! skipping test - isUsingBaseMapBuiltIn() = true !!! \e[0m"; + NOTICE_LOG(m_logger) << LOGFUNCTION << "\e[31m !!! skipping test - isUsingBaseMapBuiltIn() = true !!! \e[0m"; + NOTICE_LOG(m_logger) << LOGFUNCTION << "\e[31m !!! skipping test - isUsingBaseMapBuiltIn() = true !!! \e[0m"; + NOTICE_LOG(m_logger) << LOGFUNCTION << "\e[31m !!! skipping test - isUsingBaseMapBuiltIn() = true !!! \e[0m"; + NOTICE_LOG(m_logger) << LOGFUNCTION << "\e[31m !!! skipping test - isUsingBaseMapBuiltIn() = true !!! \e[0m"; + #endif + return; + } + + int currentWkid = d->m_map->wkid(); + + INFO_LOG(m_logger) << LOGFUNCTION + << " currentWkid = " << currentWkid; + + char wkidInfo[50]; + sprintf(&wkidInfo[0], "wkid failed (wkid = %d)", currentWkid); + + QVERIFY2(currentWkid == 32632, &wkidInfo[0]); // wkid=32632 (UTM Zone 32 N WGS 84) / Bremen + + + // SimCoords: + QPointF simPosTopLeft = d->m_map->viewPortWindowPointToGeoCoordinate(QPoint(0, 0)); + QPointF simPosBottomLeft = d->m_map->viewPortWindowPointToGeoCoordinate(QPoint(0, 1000)); + + QPointF simPosTopRight = d->m_map->viewPortWindowPointToGeoCoordinate(QPoint(1000, 0)); + QPointF simPosBottomRight = d->m_map->viewPortWindowPointToGeoCoordinate(QPoint(1000, 1000)); + + INFO_LOG(m_logger) << LOGFUNCTION << " tl = " << simPosTopLeft << " tr = " << simPosTopRight; + INFO_LOG(m_logger) << LOGFUNCTION << " bl = " << simPosBottomLeft << " br = " << simPosBottomRight; + +// 2015-11-17 tl = QPointF(75954.216#96400.032) tr = QPointF(107704.280#96400.032) +// 2015-11-17 bl = QPointF(75954.216#64649.968) br = QPointF(107704.280#64649.968) + + double distXLeft = simPosTopLeft.x() - simPosBottomLeft.x(); + double distXRight = simPosTopRight.x() - simPosBottomRight.x(); + double distYTop = simPosTopLeft.y() - simPosTopRight.y(); + double distYBottom = simPosBottomLeft.y() - simPosBottomRight.y(); + + INFO_LOG(m_logger) << LOGFUNCTION + << " distXLeft = " << distXLeft + << " distXRight = " << distXRight + << " distYTop = " << distYTop + << " distYBottom = " << distYBottom; + + const double allowError = 1; //meter + QVERIFY2(fabs(distXLeft) < allowError, "distXLeft failed"); + QVERIFY2(fabs(distXRight) < allowError, "distXRight failed"); + QVERIFY2(fabs(distYTop) < allowError, "distYTop failed"); + QVERIFY2(fabs(distYBottom) < allowError, "distYBottom failed"); + + { + double distHor = simPosTopLeft.x() - simPosTopRight.x(); // horizontal + double distVer = simPosTopLeft.y() - simPosBottomLeft.y(); // vertical + + INFO_LOG(m_logger) << LOGFUNCTION + << " distHor = " << fabs(distHor) + << " distVer = " << fabs(distVer); + + QVERIFY2((fabs(distHor) - fabs(distVer)) < allowError, "dist12 failed"); + } + + QTest::qWait(timeToDelay); +} // SituationMapTestsMain::checkMapIsUTM + + +void SituationMapTestsMain::convertUTMBasePoint() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + + double simX = 94500; + double simY = 81500; + + // here add offset for 32U MD + + double UTMx = simX + 400000 ; // offset M + double UTMy = simY + 5800000 ; // offset for D + + Esri::ArcGISRuntime::SpatialReference UTMsr(32632); // UTM Zone 32 N WGS 84 + + Esri::ArcGISRuntime::Point UTMbasePoint(UTMx, UTMy, UTMsr); + + Esri::ArcGISRuntime::Point basePointMap = Esri::ArcGISRuntime::GeometryEngine::project(UTMbasePoint, Esri::ArcGISRuntime::SpatialReference(d->m_map->wkid())); + + INFO_LOG(m_logger) << LOGFUNCTION + << " UTMbasePoint, x=" << QString::number(UTMbasePoint.x(), 'f', 9) + << " ; " << QString::number(UTMbasePoint.y(), 'f', 9) + << " basePointMap, x=" << QString::number(basePointMap.x(), 'f', 9) + << " ; " << QString::number(basePointMap.y(), 'f', 9); + + // __asm int 3 +} // SituationMapTestsMain::convertUTMBasePoint + +void SituationMapTestsMain::checkOriginTranslated() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + CoordTranslationFunctor *coordTransFunctor = d->m_map->coordTranslationFunctor(); + QVERIFY2(coordTransFunctor, "OriginTranslated: coordTransFunctor failed"); + + { + const Point3D pRef(70000, 70000, .0); + + SituationMap::OriginTranslated oTrans; + oTrans.setCoordTransFunctor(coordTransFunctor); + + // do + oTrans.setOrigin(pRef); + + // verify + QVERIFY2(oTrans.isEmpty() == false, "OriginTranslated: isEmpty failed"); + + const Point3D originOut = oTrans.origin(); + QVERIFY2(pRef == originOut, "OriginTranslated: origin failed"); + + const Point3D originMapOut = oTrans.originTranslated(); + QVERIFY2(pRef != originMapOut, "OriginTranslated: originTranslated failed"); + + // Copy + SituationMap::OriginTranslated oTransCopy; + QVERIFY2(oTransCopy.isEmpty() == true, "OriginTranslated: isEmpty failed"); + QVERIFY2((oTrans == oTransCopy) == false, "OriginTranslated: operator== failed"); + QVERIFY2((oTrans != oTransCopy) == true, "OriginTranslated: operator!= failed"); + + oTransCopy = oTrans; + QVERIFY2(oTransCopy.isEmpty() == false, "OriginTranslated: isEmpty failed"); + QVERIFY2((oTrans == oTransCopy) == true, "OriginTranslated: operator== failed"); + QVERIFY2((oTrans != oTransCopy) == false, "OriginTranslated: operator!= failed"); + } +} + + +class MyPositionsTranslated : public SituationMap::PositionsTranslated +{ +public: + void setPositionsTranslated(const PositionCont &posCont) + { + SituationMap::PositionsTranslated::setPositionsTranslated(posCont); + } +}; + +void SituationMapTestsMain::checkPositionsTranslated() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + CoordTranslationFunctor *coordTransFunctor = d->m_map->coordTranslationFunctor(); + QVERIFY2(coordTransFunctor, "OriginTranslated: coordTransFunctor failed"); + + const QPointF pRef1_2D(LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD7000070000"))); + const QPointF pRef2_2D(LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD8000080000"))); + + const Point3D pRef1(pRef1_2D.x(), pRef1_2D.y(), .0); + const Point3D pRef2(pRef2_2D.x(), pRef2_2D.y(), .0); + + SituationMap::PositionCont posContRef; + posContRef << pRef1 << pRef2; + + SituationMap::PositionCont positionsMapOutRef; + + { + SituationMap::PositionsTranslated pTrans; + pTrans.setCoordTransFunctor(coordTransFunctor); + + coordTransFunctor->resetProjectionCounter(); + QCOMPARE(coordTransFunctor->projectionCounter(), 0); + + + // setPositions + pTrans.setPositions(posContRef); + + // verify + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + QCOMPARE(pTrans.size(), 2); + + QCOMPARE(pTrans.first(), pRef1); + QCOMPARE(pTrans.last(), pRef2); + + const SituationMap::PositionCont positionsOut = pTrans.positions(); + + QCOMPARE(posContRef, positionsOut); + + positionsMapOutRef = pTrans.positionsTranslated(); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QVERIFY2(posContRef != positionsMapOutRef, "PositionsTranslated: originTranslated failed"); + + QCOMPARE(coordTransFunctor->projectionCounter(), 2); + + // Copy -> not allowed + SituationMap::PositionsTranslated pTransCopy; + // pTransCopy = pTrans; // Compiler error :-) + + + // angle are verified within the UnitTest + + // clear + pTrans.clear(); + + // verify + QVERIFY2(pTrans.isEmpty() == true, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.positions().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + QVERIFY2(pTrans.positionsTranslated().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QVERIFY2(coordTransFunctor->projectionCounter() == 2, "OriginTranslated: projectionCounter failed"); + } + + { + MyPositionsTranslated pTrans; + pTrans.setCoordTransFunctor(coordTransFunctor); + + coordTransFunctor->resetProjectionCounter(); + QVERIFY2(coordTransFunctor->projectionCounter() == 0, "OriginTranslated: projectionCounter failed"); + + + // setPositionsTranslated + pTrans.setPositionsTranslated(positionsMapOutRef); + + // verify + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QCOMPARE(pTrans.size(), 2); + + QVERIFY2( fuzzyCompare(pTrans.first(), pRef1), "PositionsTranslated: verify failed"); + QVERIFY2( fuzzyCompare(pTrans.last(), pRef2), "PositionsTranslated: verify failed"); + + const SituationMap::PositionCont &positionsOut = pTrans.positions(); + QVERIFY2( fuzzyCompare(posContRef, positionsOut), "PositionsTranslated: verify failed"); + + QCOMPARE(coordTransFunctor->projectionCounter(), 2); + + + // try calling setPositionsTranslated with different values: + { + SituationMap::PositionCont positionsMapOutRefCopy = positionsMapOutRef; + positionsMapOutRefCopy.append(positionsMapOutRefCopy.first()); + + pTrans.setPositionsTranslated(positionsMapOutRefCopy); + + qCritical() << " size = " << pTrans.size(); + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QCOMPARE(pTrans.size(), 3); + + const SituationMap::PositionCont &positionsOut = pTrans.positions(); + QCOMPARE(positionsOut.size(), 3); + + QVERIFY2( fuzzyCompare(positionsOut.first(), pRef1), "PositionsTranslated: verify failed"); + QVERIFY2( fuzzyCompare(positionsOut[1], pRef2), "PositionsTranslated: verify failed"); + QVERIFY2( fuzzyCompare(positionsOut.last(), pRef1), "PositionsTranslated: verify failed"); + } + } + + // setPositions and then addSinglePosition + { + SituationMap::PositionsTranslated pTrans; + pTrans.setCoordTransFunctor(coordTransFunctor); + + coordTransFunctor->resetProjectionCounter(); + QCOMPARE(coordTransFunctor->projectionCounter(), 0); + + + // setPositions + pTrans.setPositions(posContRef); + + // verify + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QCOMPARE(pTrans.size(), 2); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + + // add single position: + pTrans.addSinglePosition(pRef1); + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QCOMPARE(pTrans.size(), 3); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + + QCOMPARE(pTrans.first(), pRef1); + QCOMPARE(pTrans.last(), pRef1); + + // verify + const SituationMap::PositionCont positionsOut = pTrans.positions(); + QCOMPARE(positionsOut.size(), 3); + QCOMPARE(pRef1, positionsOut[2]); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + + const SituationMap::PositionCont positionsMapOut333 = pTrans.positionsTranslated(); + QCOMPARE(pTrans.size(), 3); + QCOMPARE(positionsMapOutRef[0], positionsMapOut333[0]); + QCOMPARE(positionsMapOutRef[0], positionsMapOut333[2]); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QCOMPARE(coordTransFunctor->projectionCounter(), 3); + + + // setPositions (to original) + pTrans.setPositions(posContRef); + + // verify + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QCOMPARE(pTrans.size(), 2); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + + QCOMPARE(pTrans.first(), pRef1); + QCOMPARE(pTrans.last(), pRef2); + + const SituationMap::PositionCont positionsMapOut222 = pTrans.positionsTranslated(); + QCOMPARE(pTrans.size(), 2); + QCOMPARE(positionsMapOutRef, positionsMapOut222); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QCOMPARE(coordTransFunctor->projectionCounter(), 5); + + // clear + pTrans.clear(); + + // verify + QVERIFY2(pTrans.isEmpty() == true, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.positions().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + QVERIFY2(pTrans.positionsTranslated().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QVERIFY2(coordTransFunctor->projectionCounter() == 5, "OriginTranslated: projectionCounter failed"); + } + + // addSinglePosition + { + SituationMap::PositionsTranslated pTrans; + pTrans.setCoordTransFunctor(coordTransFunctor); + + coordTransFunctor->resetProjectionCounter(); + QVERIFY2(coordTransFunctor->projectionCounter() == 0, "OriginTranslated: projectionCounter failed"); + + + // verify + QVERIFY2(pTrans.isEmpty() == true, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.positions().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + if (d->m_symbologyType != AdvancedSymbology::SymbologyType::None) // diversify flow + { + QVERIFY2(pTrans.positionsTranslated().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + } + + // add single position: + pTrans.addSinglePosition(pRef1); + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.size() == 1, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + + QVERIFY2(pTrans.first() == pRef1, "PositionsTranslated: first failed"); + + + // verify + const SituationMap::PositionCont positionsOut = pTrans.positions(); + QVERIFY2(positionsOut.size() == 1, "PositionsTranslated: positions failed"); + QVERIFY2(pRef1 == positionsOut[0], "PositionsTranslated: positions failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + + const SituationMap::PositionCont positionsMapOut333 = pTrans.positionsTranslated(); + QVERIFY2(positionsMapOut333.size() == 1, "PositionsTranslated: size failed"); + QVERIFY2(positionsMapOutRef[0] == positionsMapOut333[0], "PositionsTranslated: positionsTranslated failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QVERIFY2(coordTransFunctor->projectionCounter() == 1, "OriginTranslated: projectionCounter failed"); + + + // setPositions (to original) + pTrans.setPositions(posContRef); + + // verify + QVERIFY2(pTrans.isEmpty() == false, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.size() == 2, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + + const SituationMap::PositionCont positionsMapOut222 = pTrans.positionsTranslated(); + QVERIFY2(pTrans.size() == 2, "PositionsTranslated: size failed"); + QVERIFY2(positionsMapOutRef == positionsMapOut222, "PositionsTranslated: positionsTranslated failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QVERIFY2(coordTransFunctor->projectionCounter() == 3, "OriginTranslated: projectionCounter failed"); + + // clear + pTrans.clear(); + + // verify + QVERIFY2(pTrans.isEmpty() == true, "PositionsTranslated: isEmpty failed"); + QVERIFY2(pTrans.size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.positions().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == false, "PositionsTranslated: isTranslated failed"); + QVERIFY2(pTrans.positionsTranslated().size() == 0, "PositionsTranslated: size failed"); + QVERIFY2(pTrans.isTranslated() == true, "PositionsTranslated: isTranslated failed"); + QVERIFY2(coordTransFunctor->projectionCounter() == 3, "OriginTranslated: projectionCounter failed"); + } +} + +void SituationMapTestsMain::checkPositionAngles() +{ + // here we check if the angle calculation works as expected + // -> the underlying coord system used for calculation must be orthogonal - e.g. UTM + + CoordTranslationFunctor *coordTransFunctor = d->m_map->coordTranslationFunctor(); + SituationMap::PositionsTranslated pTrans; + pTrans.setCoordTransFunctor(coordTransFunctor); + + { + // first check: we use an orthogonal triangle --> 0,0, 0,4 3,4 + QList posCont2D({LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD8000080000")), // p0 + LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD8000080004")), // p1 + LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD8000380004"))}); // p2 + SituationMap::PositionCont posCont = SituationMap::convert2D3D(posCont2D); + + pTrans.setPositions(posCont); + const double start = 0; + //const double close = 210; // p2 -> p0 + // calculated value: + const double close = 216.86989765331455; // p2 -> p0 + const double end = 90; // p1 -> p2 + + const double angleStart = pTrans.angleOfStartSegment(); + const double angleClose = pTrans.angleOfClosingSegment(); + const double angleEnd = pTrans.angleOfEndSegment(); + qDebug("Start: %20.15lf, Close: %20.15lf, End: %20.15lf", angleStart, angleClose, angleEnd); + QVERIFY2(qFuzzyCompare(angleStart, start), qPrintable(QStringLiteral("Start angle is not %1 degrees").arg(start))); + QVERIFY2(qFuzzyCompare(angleClose, close), qPrintable(QStringLiteral("Close angle is not %1 degrees").arg(close))); + QVERIFY2(qFuzzyCompare(angleEnd, end), qPrintable(QStringLiteral("End angle is not %1 degrees").arg(end))); + } + + { + // second check: a 'normal' triangle + QList posCont2D({LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD8000080000")), + LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD8000080004")), + LtsCoordTrans::geographicFromMgrs(QStringLiteral("32UMD8000480004"))}); + SituationMap::PositionCont posCont = SituationMap::convert2D3D(posCont2D); + + + pTrans.setPositions(posCont); + const double start = 0; + const double close = 225.00000000667; // should be not correct + const double end = 90; + + const double angleStart = pTrans.angleOfStartSegment(); + const double angleClose = pTrans.angleOfClosingSegment(); + const double angleEnd = pTrans.angleOfEndSegment(); + qDebug("Start: %20.15lf, Close: %20.15lf, End: %20.15lf", angleStart, angleClose, angleEnd); + QVERIFY2(qFuzzyCompare(angleStart, start), qPrintable(QStringLiteral("Start angle is not %1 degrees").arg(start))); + QVERIFY2(qFuzzyCompare(angleClose, close), qPrintable(QStringLiteral("Close angle is not %1 degrees").arg(close))); + QVERIFY2(qFuzzyCompare(angleEnd, end), qPrintable(QStringLiteral("End angle is not %1 degrees").arg(end))); + } +} + + + +void SituationMapTestsMain::measureProject() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + QElapsedTimer timer; + int wkid = d->m_map->wkid(); + if (wkid >= 0) + { + Esri::ArcGISRuntime::SpatialReference sr(wkid); + + int jMax = 10000; + + //-------------------------------------------------------------------------- + // create SpatialReference by wkid + //-------------------------------------------------------------------------- + int wkid = 102100; + timer.start(); + for (int j = 0; j < jMax; ++j) + { + Esri::ArcGISRuntime::SpatialReference temp(wkid); + int help = temp.wkid(); + Q_UNUSED(help); + } + qint64 msec = timer.elapsed(); + qDebug() << " create SpatialReference by wkid (" << wkid << "): " << jMax << " operations took " << msec << "ms"; + + //-------------------------------------------------------------------------- + // create SpatialReference by wkt + //-------------------------------------------------------------------------- + QLatin1String SpatialReferenceWkt( + "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",\ + GEOGCS[\"GCS_WGS_1984\",\ + DATUM[\"D_WGS_1984\",\ + SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],\ + PRIMEM[\"Greenwich\",0.0],\ + UNIT[\"Degree\",0.0174532925199433]],\ + PROJECTION[\"Mercator_Auxiliary_Sphere\"],\ + PARAMETER[\"False_Easting\",0.0],\ + PARAMETER[\"False_Northing\",0.0],\ + PARAMETER[\"Central_Meridian\",0.0],\ + PARAMETER[\"Standard_Parallel_1\",0.0],\ + PARAMETER[\"Auxiliary_Sphere_Type\",0.0],\ + UNIT[\"Meter\",1.0]]\""); + timer.restart(); + for (int j = 0; j < jMax; ++j) + { + Esri::ArcGISRuntime::SpatialReference temp(SpatialReferenceWkt); + int help = temp.wkid(); + Q_UNUSED(help); + } + msec = timer.elapsed(); + qDebug() << " create SpatialReference by wkt (" << wkid << "): " << jMax << " operations took " << msec << "ms"; + + + //-------------------------------------------------------------------------- + // null projection map to map + //-------------------------------------------------------------------------- + timer.restart(); + for (int j = 0; j < jMax; ++j) + { + Esri::ArcGISRuntime::Point source(1000000, 1000000 + j, sr); + Esri::ArcGISRuntime::Point bremenPoint = Esri::ArcGISRuntime::GeometryEngine::project(source, sr); + Q_UNUSED(bremenPoint); + } + msec = timer.elapsed(); + qDebug() << " null projection (" << wkid << "): " << jMax << " operations took " << msec << "ms"; + + + //-------------------------------------------------------------------------- + // project sim to map (BREMEN) + //-------------------------------------------------------------------------- + timer.restart(); + const Esri::ArcGISRuntime::SpatialReference &destSR = sr; + // Bremen: basePoint='32UMD 00000 00000' offset x=400000 (M) ; 5800000 (D) ; wkid=32632 (UTM Zone 32 N WGS 84) + const Esri::ArcGISRuntime::SpatialReference sourceSR(32632); + for (int j = 0; j < jMax; ++j) + { + Esri::ArcGISRuntime::Point simPoint(90000, 78000 + j, sourceSR); + Esri::ArcGISRuntime::Point mapPoint = Esri::ArcGISRuntime::GeometryEngine::project(simPoint, destSR); + Q_UNUSED(mapPoint); + } + msec = timer.elapsed(); + qDebug() << " simple sim2Map: " << jMax << " operations took " << msec << "ms"; + + + + { + //-------------------------------------------------------------------------- + // project lat/long to map (WGS84 to map) 1 + //-------------------------------------------------------------------------- + timer.restart(); + for (int j = 0; j < jMax; ++j) + { + // Esri::ArcGISRuntime::SpatialReference sr(4236); // EPSG:4236 = WGS84 + Esri::ArcGISRuntime::SpatialReference sr = static_cast(d->m_map)->getEsriSpatialReference(); + Esri::ArcGISRuntime::Point myPoint(0.155, 0.926 + (j * 0.001), sr); + Esri::ArcGISRuntime::Point bremenPoint = Esri::ArcGISRuntime::GeometryEngine::project(myPoint, sr); + Q_UNUSED(bremenPoint); + } + msec = timer.elapsed(); + qDebug() << " Lat/Long (WGS84) to map 1 (" << wkid << "): " << jMax << " operations took " << msec << "ms"; + + + //-------------------------------------------------------------------------- + // project lat/long to map (WGS84 to map) geographicToMap 2D + //-------------------------------------------------------------------------- + timer.restart(); + CoordTranslationFunctor *coordFunc = d->m_map->coordTranslationFunctor(); + for (int j = 0; j < jMax; ++j) + { + const QPointF &res = coordFunc->geographicToMap(QPointF(0.155, 0.926 + (j * 0.001))); + Q_UNUSED(res); + } + msec = timer.elapsed(); + qDebug() << " Lat/Long (WGS84) to map (geographicToMap 2D) " << jMax << " operations took " << msec << "ms"; + + + //-------------------------------------------------------------------------- + // project lat/long to map (WGS84 to map) geographicToMap Point3D + //-------------------------------------------------------------------------- + timer.restart(); + for (int j = 0; j < jMax; ++j) + { + const Point3D &res = coordFunc->geographicToMap(Point3D(0.155, 0.926 + (j * 0.001), 1.23456)); + Q_UNUSED(res); + } + msec = timer.elapsed(); + qDebug() << " Lat/Long (WGS84) to map (geographicToMap Point3D) " << jMax << " operations took " << msec << "ms"; + } + + + //-------------------------------------------------------------------------- + // project FalseEasting/Northing to map (to map) + //-------------------------------------------------------------------------- + QLatin1String myWkt( + "PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",\ + GEOGCS[\"GCS_WGS_1984\",\ + DATUM[\"D_WGS_1984\",\ + SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],\ + PRIMEM[\"Greenwich\",0.0],\ + UNIT[\"Degree\",0.0174532925199433]],\ + PROJECTION[\"Mercator_Auxiliary_Sphere\"],\ + PARAMETER[\"False_Easting\",1000000.0],\ + PARAMETER[\"False_Northing\",1000000.0],\ + PARAMETER[\"Central_Meridian\",0.0],\ + PARAMETER[\"Standard_Parallel_1\",0.0],\ + PARAMETER[\"Auxiliary_Sphere_Type\",0.0],\ + UNIT[\"Meter\",1.0]]\""); + + int i = 1000000; + + { + Esri::ArcGISRuntime::SpatialReference mySR(myWkt); + + timer.restart(); + for (int j = 0; j < jMax; ++j) + { + Esri::ArcGISRuntime::Point myPoint(i, i + j, mySR); + Esri::ArcGISRuntime::Point projPoint = Esri::ArcGISRuntime::GeometryEngine::project(myPoint, sr); + Q_UNUSED(projPoint); + } + msec = timer.elapsed(); + } + qDebug() << " FalseEasting/Northing to map (" << wkid << "): " << jMax << " operations took " << msec << "ms"; + } + else + ERROR_LOG(m_logger) << " wkid invalid"; +} + +void SituationMapTestsMain::measurceLocationToElevationBulk() +{ + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + SituationMap::LayerFactoryImpl *layerFacImpl = dynamic_cast(&layerFac); + + MapImpl *mapImpl = static_cast(d->m_map); + + LocationToElevationBulk leBulk; + leBulk.init(d->m_map, layerFacImpl->baseSurface()); + + leBulk.addLocation(Esri::ArcGISRuntime::Point(495000, 5880000, mapImpl->getEsriSpatialReference())); + leBulk.addLocation(Esri::ArcGISRuntime::Point(495100, 5881000, mapImpl->getEsriSpatialReference())); + + QSignalSpy spy(&leBulk, &LocationToElevationBulk::locationsToElevationsCompleted); + + leBulk.start(); + + QTRY_VERIFY2(spy.count() == 1, "LocationToElevationBulk: signalCount failed,"); +} + +void SituationMapTestsMain::checkMapObjectProperties() +{ + SituationMap::RefPointer mapObject = + d->m_map->objectFactory().create(QLatin1String("AES"), QLatin1String("checkMapObjectPropertiesDebugInfo")); + + QVariant variant; + bool success; + + // check property + variant = mapObject->property("propA"); + QVERIFY2(!variant.isValid(), "variant should be INVALID"); + + // check setProperty + // set to '4711' + variant.setValue(4711); + QVERIFY2(variant.isValid(), "variant should be ok"); + + success = mapObject->setProperty("propA", variant); + QVERIFY2(success, "setProperty failed"); + + // get property '4711' + variant.clear(); + variant = mapObject->property("propA"); + QVERIFY2(variant.isValid(), "property should be ok"); + QVERIFY2(variant.toInt() == 4711, "property failed"); + + // change to '4712' + variant.setValue(4712); + QVERIFY2(variant.isValid(), "variant should be ok"); + success = mapObject->setProperty("propA", variant); + QVERIFY2(success, "setProperty failed"); + + // get property '4712' + variant.clear(); + variant = mapObject->property("propA"); + QVERIFY2(variant.isValid(), "property failed"); + QVERIFY2(variant.toInt() == 4712, "property failed"); + + + //******************* + // invalid QVariant: + //******************* + QVariant variantInvalid; + QVERIFY2(!variantInvalid.isValid(), "variantInvalid failed"); + success = mapObject->setProperty("propA", variantInvalid); // to remove property + QVERIFY2(success, "setProperty failed"); + + variant.clear(); + variant = mapObject->property("propA"); // get non-existing property + QVERIFY2(!variant.isValid(), "variant should be INVALID"); + + + success = mapObject->setProperty("doesNotExist", variantInvalid); // use non-existing propertyName + QVERIFY2(!success, "setProperty failed"); + + //******************* + // Two Properties: + //******************* + success = mapObject->setProperty("propA", 4711); + QVERIFY2(success, "setPropertyA failed"); + success = mapObject->setProperty("propB", 4712); + QVERIFY2(success, "setPropertyB failed"); + + variant.clear(); + variant = mapObject->property("propA"); + QVERIFY2(variant.toInt() == 4711, "property failed"); + + variant.clear(); + variant = mapObject->property("propB"); + QVERIFY2(variant.toInt() == 4712, "property failed"); + + + + //************************ + // Removing / Destroying + //************************ + mapObject->removeMySelf(); + +} + +void SituationMapTestsMain::checkObjectFactoryContainer() +{ + QElapsedTimer timer; + timer.start(); + + uint buildingId = 0; + uint bAgentId = 0; + + // mapObject 'BuildingOutline' -> 10 + for (int i = 0; i < 10; ++i) + { + SituationMap::RefPointer building = + d->m_map->objectFactory().create(QLatin1String("AES")); + QVERIFY2(building != nullptr, "create failed"); + building->setDebugInfo(QStringLiteral("building_%1").arg(i)); + + buildingId = building->uniqueMapId(); + } + + // mapObject 'BiologicalAgent' -> 100 + for (int i = 0; i < 100; ++i) + { + SituationMap::RefPointer biologicalAgent = + d->m_map->objectFactory().create(QLatin1String("AES")); + QVERIFY2(biologicalAgent != nullptr, "create failed"); + biologicalAgent->setDebugInfo(QStringLiteral("biologicalAgent_%1").arg(i)); + + biologicalAgent->setProperty("TYPE", QLatin1String("BiologicalAgent")); + bAgentId = biologicalAgent->uniqueMapId(); + } + + // mapObject 'Participant' -> 1000 + for (int i = 0; i < 1000; ++i) + { + SituationMap::RefPointer participant = + d->m_map->objectFactory().create(QLatin1String("Participants")); + QVERIFY2(participant != nullptr, "create failed"); + participant->setDebugInfo(QStringLiteral("participant_%1").arg(i)); + } + + + quint32 countUsedIds = d->m_map->objectFactory().count(); + QVERIFY2(countUsedIds == 1110, "wrong number of objects"); + + bool contains = false; + + // simple contains + contains = d->m_map->objectFactory().contains(11); // random select + QVERIFY2(contains, "contains failed"); + + contains = d->m_map->objectFactory().contains(123456789); // random select + QVERIFY2(!contains, "contains failed"); + + + // assume, that '5' is a building: + + // simple contains + contains = d->m_map->objectFactory().contains(buildingId); + QVERIFY2(contains, "contains failed"); + + // typed contains + contains = d->m_map->objectFactory().contains(buildingId); + QVERIFY2(!contains, "typed contains failed"); + + // get + { + SituationMap::RefPointer obj = d->m_map->objectFactory().get(buildingId); + QVERIFY2(obj != nullptr, "get failed"); + QVERIFY2(obj.get(), "get failed"); + QVERIFY2(obj->uniqueMapId() == buildingId, "get failed"); + int compare = obj->debugInfo().compare(QStringLiteral("building_9"), Qt::CaseInsensitive); + QVERIFY2(compare == 0, "get failed"); + } + + + + // get + { + SituationMap::RefPointer obj = d->m_map->objectFactory().get(123456789); + QVERIFY2(obj == nullptr, "get failed"); + QVERIFY2(!obj.get(), "get failed"); + } + + + // typed get + { + SituationMap::RefPointerbAgent + = d->m_map->objectFactory().get(bAgentId); + QVERIFY2(bAgent != nullptr, "typed get#1 failed"); + QVERIFY2(bAgent.get(), "typed get#2 failed"); + QVERIFY2(bAgent->uniqueMapId() == bAgentId, "get failed"); + } + + + { + QElapsedTimer innerTimer; + innerTimer.start(); + + for (int j = 0; j < 100; ++j) + { + int found = 0; + for (int i = 1000; i > 0; --i) + { + // get + SituationMap::RefPointer mapObj + = d->m_map->objectFactory().get(i); + if (mapObj.valid()) + ++found; + } + QVERIFY2(found == 1000, "typed get#3 failed"); + } + + INFO_LOG(m_logger) << " get (base type) took " << innerTimer.elapsed() << "ms"; + } + { + QElapsedTimer innerTimer; + innerTimer.start(); + + for (int j = 0; j < 100; ++j) + { + int found = 0; + for (int i = 1000; i > 0; --i) + { + // typed get + SituationMap::RefPointerbAgent + = d->m_map->objectFactory().get(i); + if (bAgent.valid()) + ++found; + } + QVERIFY2(found == 100, "typed get#3 failed"); + } + + INFO_LOG(m_logger) << " get (selected by type) took " << innerTimer.elapsed() << "ms"; + } + { + QElapsedTimer innerTimer; + innerTimer.start(); + + for (int j = 0; j < 100; ++j) + { + int found = 0; + for (int i = 1000; i > 0; --i) + { + // get + SituationMap::RefPointer mapObj + = d->m_map->objectFactory().get(i); + if (mapObj.valid() && mapObj->property("TYPE").toString().compare(QLatin1String("BiologicalAgent")) == 0) + ++found; + } + QVERIFY2(found == 100, "typed get#3 failed"); + } + + INFO_LOG(m_logger) << " get (selected by QLatin1String) took " << innerTimer.elapsed() << "ms"; + } + + INFO_LOG(m_logger) << " checkObjectFactoryContainer took " << timer.elapsed() << "ms"; + +} + +void SituationMapTestsMain::checkObjectAndLayerFactory() +{ + // Preperations: + bool success = false; + + initTestCase(); + checkMapReady(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + SituationMap::ObjectFactory &objFac = d->m_map->objectFactory(); + + { + + INFO_LOG(m_logger) << " technicalLayerCount = " << d->m_map->technicalLayerCount(); + + + INFO_LOG(m_logger) << " layernames = " << layerFac.layernames().join(QLatin1String(";")); + int basemapCount = layerFac.layernames(LayerType::Basemap).count(); + int jumpPointLayerCount = layerFac.layernames(LayerType::JumpPoint).count(); + int objectLayerCount = layerFac.layernames(LayerType::Operational).count(); + QVERIFY2((1 <= basemapCount) && (basemapCount <= 3), "layerFactory: count failed"); + QVERIFY2(objectLayerCount == 13, QString(QStringLiteral("layerFactory: count failed (is %1)")).arg(objectLayerCount).toStdString().c_str()); + + QVERIFY2(d->m_map->technicalLayerCount() == basemapCount , "layerFactory: technicalLayerCount failed"); + QVERIFY2(layerFac.count() == basemapCount + jumpPointLayerCount + objectLayerCount , "layerFactory: count failed"); + + INFO_LOG(m_logger) << " objFac.count() =" << objFac.count(); + + + + qDebug() << " technicalLayerCount =" << d->m_map->technicalLayerCount(); + + int initialObjCount = objFac.count(); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(LayerType::Operational); + QString layername = layernames[0]; + QVERIFY2(layername.isEmpty() == false, "objectFactory: count failed"); + + // real test + QVERIFY2(objFac.count() == 0, "objectFactory: count failed"); + QVERIFY2(d->m_map->technicalLayerCount() == basemapCount , "layerFactory: technicalLayerCount failed"); + + SituationMap::RefPointer objPtr1; + objPtr1 = objFac.create(layername, QLatin1String("UnitTestId1")); + QVERIFY2(objPtr1.valid() == true , "objectFactory: create11 failed"); + + SituationMap::RefPointer objPtr2; + objPtr2 = objFac.create(layername, QLatin1String("UnitTestId2")); + QVERIFY2(objPtr2.valid() == true , "objectFactory: create22 failed"); + + SituationMap::RefPointer objPtr3; + objPtr3 = objFac.create(layername, QLatin1String("UnitTestId3")); + QVERIFY2(objPtr3.valid() == true , "objectFactory: create33 failed"); + + SituationMap::RefPointer objPtr4; + objPtr4 = objFac.create(layername, QLatin1String("UnitTestId4")); + QVERIFY2(objPtr4.valid() == true , "objectFactory: create44 failed"); + + QVERIFY2(objFac.count() == (initialObjCount + 4) , "objectFactory: count failed"); + + + // now set required parameters: + QPointF offset = QPointF(50000, 50000); + int extent = 30000; + QList posCont2D; + posCont2D << s_basePoint.geographic( offset - QPointF( 0, 0) ); + posCont2D << s_basePoint.geographic( offset - QPointF(extent, 0) ); + posCont2D << s_basePoint.geographic( offset - QPointF(extent, extent) ); + posCont2D << s_basePoint.geographic( offset - QPointF( 0, extent) ); + + objPtr3->setPositions(convert2D3D(posCont2D)); + + // => technicalLayerCount increases by 1/one, caused by objFac.create<>) + QVERIFY2(d->m_map->technicalLayerCount() == basemapCount + 1 , "layerFactory: technicalLayerCount failed"); + + // now update the objects (to let layerFactory create MessageGroupLayer implicit/onDemand) + success = objPtr3->updateView(); + QVERIFY2(success , "object: updateView failed"); + + + qDebug() << " technicalLayerCount =" << d->m_map->technicalLayerCount(); + + if (d->m_symbologyType != AdvancedSymbology::SymbologyType::None) + { + // in the meantime, the corresponding technicalLayer (MessageGroup) for the objects is created + // => technicalLayerCount increases again by 1/one, caused by objFac.updateView) +// QVERIFY2(d->m_map->technicalLayerCount() == basemapCount + 1 , "layerFactory: technicalLayerCount failed"); + // => technicalLayerCount increases again by 2/two, caused by objFac.updateView) + QVERIFY2(d->m_map->technicalLayerCount() == basemapCount + 2 , "layerFactory: technicalLayerCount failed"); + } + else + QVERIFY2(d->m_map->technicalLayerCount() == basemapCount + 1 , "layerFactory: technicalLayerCount failed"); + + } + + // + // End of scope -> local RefPointer should be destroyed now: + // + + + //************************ + // Removing / Destroying + //************************ + const QList allUniqueIds = objFac.getAllKeys(); + qDebug() << " remove, current size()=" << allUniqueIds.size(); + QVERIFY2(objFac.count() == allUniqueIds.size(), "objectFactory: count/size/... inkonsistent"); + + int numberRemoved = 0; + for (SituationMap::UniqueMapId uniqueId : allUniqueIds) + { + qDebug() << " remove uniqueId=" << uniqueId << "..."; + { + RefPointer mapObj = objFac.get(uniqueId); + if (mapObj) + { + mapObj->removeMySelf(); + ++numberRemoved; + } + else + qWarning() << " uniqueId=" << uniqueId << " not found"; + } + // End of Scope.... + qDebug() << " remove uniqueId=" << uniqueId << "... Done"; + } + qDebug() << " remove finished, current size()=" << objFac.count(); + + QVERIFY2(allUniqueIds.size() == numberRemoved , "objectFactory: at least one failure"); + QVERIFY2(objFac.count() == 0 , "objectFactory: count failed"); +} // checkObjectAndLayerFactory + + +void SituationMapTestsMain::checkOverlayHandler1() +{ + // use nullptr as map: + SituationMap::Map *map = nullptr; + + SituationMap::OverlayHandlerImpl oh(map); + + QVERIFY2(oh.overlayCount() == 0 , "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(123456789) == false , "OverlayHandler: containsOverlay failed"); + + bool success = false; + + success = oh.setOverlay(1234, QStringLiteral("OverlayDisplayname"), QByteArray("myInvalidOverlayData")); + QVERIFY2(success == 0 , "OverlayHandler: setOverlay failed"); + QVERIFY2(oh.overlayCount() == 0 , "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(1234) == false , "OverlayHandler: containsOverlay failed"); +} + +void SituationMapTestsMain::checkOverlayHandler2() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + { + SituationMap::OverlayHandler &oh = d->m_map->overlayHandler(); + + QVERIFY2(oh.overlayCount() == 0 , "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(123456789) == false , "OverlayHandler: containsOverlay failed"); + + bool success = false; + + // add invalid data: + qWarning() << " add invalid data:"; + { + int layerCountBEFORE = d->m_map->layerFactory().count(); + + success = oh.setOverlay(1234, QStringLiteral("OverlayDisplayname"), QByteArray("myInvalidOverlayData")); + QVERIFY2(success == false, "OverlayHandler: setOverlay failed"); + QVERIFY2(oh.overlayCount() == 0, "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(1234) == false, "OverlayHandler: containsOverlay failed"); + + int layerCountAFTER = d->m_map->layerFactory().count(); + QVERIFY2((layerCountAFTER - layerCountBEFORE) == 0, "OverlayHandler: layerCountDiff failed"); + + QSignalSpy spy(d->m_map, &SituationMap::Map::layerOrderChanged); + QVERIFY2(spy.count() == 0, "OverlayHandler: signalCount failed, layerOrderChanged"); + } + } +} + + +void SituationMapTestsMain::checkOverlayHandler3() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::OverlayHandler &oh = d->m_map->overlayHandler(); + QVERIFY2(oh.overlayCount() == 0 , "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(123456789) == false , "OverlayHandler: containsOverlay failed"); + + bool success = false; + + + // + // create PlanData 1: + // + QScopedPointer geoModel(GeometryModelFactory::singleton()->create()); + QVERIFY2((geoModel != nullptr), "OverlayHandler: Creating empty GeometryModel failed."); + + QList vertexList; + vertexList << QPointF(0.15, 0.9) << QPointF(0.151, 0.901); // area Bremen + + QList> attrList; + attrList += QPair(GeometryModel::Attribute::Color, QVariant(QColor("red"))); + + const GeometryModel::Node &node1 = geoModel->addPrimitive(GeometryModel::PrimitiveType::Line, vertexList, attrList); + QVERIFY2((node1.isValid()), "OverlayHandler: node invalid."); + + QByteArray planData1 = geoModel->toByteArray(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " planData1=" << planData1; + + + // + // create PlanData 2: + // + vertexList.clear(); + vertexList << QPointF(0.1511, 0.911) << QPointF(0.15111, 0.9011); // area Bremen + const GeometryModel::Node &node2 = geoModel->addPrimitive(GeometryModel::PrimitiveType::Line, vertexList, attrList); + QVERIFY2((node2.isValid()), "OverlayHandler: node invalid."); + + QByteArray planData2 = geoModel->toByteArray(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " planData2=" << planData2; + + + // + // add valid data / planData 1: + // + qWarning() << " add valid data / planData 1:"; + { + int layerCountBEFORE = d->m_map->layerFactory().count(); + + + QSignalSpy spy(d->m_map, &SituationMap::Map::layerOrderChanged); + + success = oh.setOverlay(1234, QStringLiteral("OverlayDisplayname"), planData1); + QVERIFY2(success == true, "OverlayHandler: setOverlay failed"); + QVERIFY2(oh.overlayCount() == 1, "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(1234) == true, "OverlayHandler: containsOverlay failed"); + QVERIFY2(spy.count() == 1, "signalCount failed, layerOrderChanged"); + + + int layerCountAFTER = d->m_map->layerFactory().count(); + QVERIFY2((layerCountAFTER - layerCountBEFORE) == 1, "OverlayHandler: layerCountDiff failed"); + DEBUG_LOG(m_logger) << LOGFUNCTION << " ln=" << d->m_map->layerFactory().layernames().join(QLatin1String(";")); + + + // verify objCount on EsriLayer + SituationMap::LayerFactoryImpl *layFacImpl = dynamic_cast(&d->m_map->layerFactory()); + QVERIFY2((layFacImpl) , "OverlayHandler: layFacImpl failed"); + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = layFacImpl->getGraphicsLayer(QLatin1String("OverlayLayer_1234")); + QVERIFY2((layFacImpl) , "OverlayHandler: EsriLayer failed"); + int numberOfObjects = graphicsLayer->graphics()->size(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " planData1 -> numberOfObjects=" << numberOfObjects; + QVERIFY2(numberOfObjects == 1 , "OverlayHandler: numberOfObjects on EsriLayer failed"); + } + + + // + // updateOverlay with planData 2: + // + qWarning() << " add valid data / planData 2:"; + { + QSignalSpy spy(d->m_map, &SituationMap::Map::layerOrderChanged); + + success = oh.setOverlay(1234, QStringLiteral("OverlayDisplayname"), planData2); + QVERIFY2(success == true, "OverlayHandler: setOverlay failed"); + + success = oh.setOverlay(1234, QStringLiteral("OverlayDisplayname"), planData2); + QVERIFY2(success == true, "OverlayHandler: setOverlay failed"); + QVERIFY2(oh.overlayCount() == 1, "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(1234) == true, "OverlayHandler: containsOverlay failed"); + QVERIFY2(spy.count() == 0, "signalCount failed, layerOrderChanged"); // == 0 because layer already exists + + // verify objCount on EsriLayer + SituationMap::LayerFactoryImpl *layFacImpl = dynamic_cast(&d->m_map->layerFactory()); + QVERIFY2((layFacImpl) , "OverlayHandler: layFacImpl failed"); + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = layFacImpl->getGraphicsLayer(QLatin1String("OverlayLayer_1234")); + QVERIFY2((layFacImpl) , "OverlayHandler: EsriLayer failed"); + int numberOfObjects = graphicsLayer->graphics()->size(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " planData2 -> numberOfObjects=" << numberOfObjects; + QVERIFY2(numberOfObjects == 2 , "OverlayHandler: numberOfObjects on EsriLayer failed"); + } + + + // + // check order: + // OverlayLayer shall be directly on top of the basemaps + // + if (1) { + const QStringList &layerNames = d->m_map->layerFactory().layernames(); + const int layerCount = layerNames.count(); + const int basemapCount = d->m_map->layerFactory().layernames(LayerType::Basemap).count(); + QVERIFY2((layerCount - basemapCount) > 0 , "Order of Layer: count failed"); // preCondition + + const QString &ovlName = layerNames[basemapCount]; // access to successor of basemaps + const bool match = ovlName.startsWith(QLatin1String("OverlayLayer")); + QVERIFY2(match == true , "Order of Layer: OverlayLayer is NOT successor of basemaps"); // preCondition + } + + + // removeOverlay + { + QSignalSpy spy(d->m_map, &SituationMap::Map::layerOrderChanged); + + int layerCountBEFORE = d->m_map->layerFactory().count(); + + bool success = oh.removeOverlay(1234); + QVERIFY2(success == true, "OverlayHandler: removeOverlay failed"); + QVERIFY2(oh.overlayCount() == 0, "OverlayHandler: overlayCount failed"); + QVERIFY2(oh.containsOverlay(1234) == false, "OverlayHandler: containsOverlay failed"); + + QVERIFY2(spy.count() == 1, "signalCount failed, layerOrderChanged"); + + int layerCountAFTER = d->m_map->layerFactory().count(); + QVERIFY2((layerCountAFTER - layerCountBEFORE) == -1, "OverlayHandler: layerCountDiff failed"); + DEBUG_LOG(m_logger) << LOGFUNCTION << " ln=" << d->m_map->layerFactory().layernames().join(QLatin1String(";")); + } + +} + +/* +void SituationMapTestsMain::checkRubberbandFactory() +{ + initTestCase(); + checkMapReady(); + + SituationMap::RB::RubberbandPointFeature *rbandPt = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rbandPt != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandPointFeature"); + + SituationMap::RB::RubberbandMultiPointFeature *rbandMPt = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rbandMPt == nullptr, "RubberbandFactory::create failed for second rubberband at time"); + + if (rbandPt) delete rbandPt; // here it is ok to use delete directly + if (rbandMPt) delete rbandMPt; + + rbandMPt = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rbandMPt != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandMultiPointFeature"); + if (rbandMPt) delete rbandMPt; + + // check creating other rubberbands with factory + SituationMap::RB::Rubberband *rband = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rband != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandPointCluster"); + if (rband) delete rband; + + rband = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rband != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandArrow"); + if (rband) delete rband; + + rband = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rband != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandCorridor"); + if (rband) delete rband; + + rband = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rband != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandEllipse"); + if (rband) delete rband; + + rband = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rband != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandRectangle"); + if (rband) delete rband; + + rband = d->m_map->rubberbandFactory().create(); + + QVERIFY2(rband != nullptr, "RubberbandFactory::create failed for SituationMap::RB::RubberbandSmoothLine"); + if (rband) delete rband; + +} // SituationMapTestsMain::checkRubberbandFactory +*/ + +void SituationMapTestsMain::measureCoordTranslationFunctor() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + // Bremen: basePoint='32UMD 00000 00000' offset x=400000 (M) ; 5800000 (D) ; wkid=32632 (UTM Zone 32 N WGS 84) + d->m_map->initCoordTranslationFunctor(); + + SituationMap::CoordTranslationFunctor *functor = d->m_map->coordTranslationFunctor(); + QVERIFY2(functor != nullptr, "CoordTranslationFunctor falied"); + + QElapsedTimer timer; + + int jMax = 1000; + { + timer.start(); + for (int j = 0; j < jMax; ++j) + { + QPointF point(0.01, 0.08 + j * 0.0001); + QPointF ret = functor->geographicToMap(point); + Q_UNUSED(ret); + } + INFO_LOG(m_logger) << " simToMap " << jMax << " operations took " << timer.elapsed() << "ms"; + } + + { + timer.restart(); + for (int j = 0; j < jMax; ++j) + { + QPointF point(850000, 6880000 + j); + QPointF ret = functor->mapToGeographic(point); + Q_UNUSED(ret); + } + INFO_LOG(m_logger) << " mapToSim " << jMax << " operations took " << timer.elapsed() << "ms"; + } + +} // SituationMapTestsMain::checkRubberbandFactory + + +void SituationMapTestsMain::checkCoordinateConversion() +{ + // Karte laden fehlt hier noch, nach meinem Urlaub geht's weiter ... JT + initTestCase(); + + checkMapReady(); + checkCenter(); + + // d->m_map->setExtent(100000, 100000, 10000000, 10000000); + + QPoint windowPos(400, 200); + + QPointF mapPos = d->m_map->viewPortWindowPointToMapCoordinate(windowPos); + QPointF geoPos = d->m_map->viewPortWindowPointToGeoCoordinate(windowPos); + + QPointF windowPosMap = d->m_map->mapCoordinateToViewPortWindowPoint(mapPos); + QPointF windowPosGeo = d->m_map->geoCoordinateToViewPortWindowPoint(geoPos); + + qDebug() << LOGFUNCTION << "ConversionCheck with windowPos " << windowPos; + qDebug() << "mapPos" << mapPos << " ---> winPos " << windowPosMap; + qDebug() << "geoPos" << geoPos << " ---> winPos " << windowPosGeo; + + QVERIFY2((windowPos - windowPosMap).manhattanLength() < 5, "Coordinate conversion for map coordinates failed."); + QVERIFY2((windowPos - windowPosGeo).manhattanLength() < 5, "Coordinate conversion for geo coordinates failed."); + + // define a square + QPoint wrp1(100, 100); + QPoint wrp2(300, 100); + QPoint wrp3(300, 300); + QPoint wrp4(100, 300); + + QPointF srp1 = d->m_map->viewPortWindowPointToGeoCoordinate(wrp1); + QPointF srp2 = d->m_map->viewPortWindowPointToGeoCoordinate(wrp2); + QPointF srp3 = d->m_map->viewPortWindowPointToGeoCoordinate(wrp3); + QPointF srp4 = d->m_map->viewPortWindowPointToGeoCoordinate(wrp4); + + qreal w1 = srp2.x() - srp1.x(); + qreal w2 = srp3.x() - srp4.x(); + qreal h1 = srp1.y() - srp4.y(); + qreal h2 = srp2.y() - srp3.y(); + + QString msg; + msg = QStringLiteral("Rectangle seems not to be a rectangle under current coordinate projections. (cur wkid=%1").arg(d->m_map->wkid()); + QVERIFY2(fabs(w1 - w2) < 0.1, msg.toUtf8().data()); + QVERIFY2(fabs(h1 - h2) < 0.1, msg.toUtf8().data()); + + msg = QStringLiteral("Square seems not to be a square under current coordinate projections. (cur wkid=%1").arg(d->m_map->wkid()); + QVERIFY2(fabs(h1 - w1) < 0.1, msg.toUtf8().data()); + + // d->m_map->layerFactory().initializeBasemaps( QStringList(QLatin1String("http://192.168.133.88:6080/arcgis/rest/services/BremenGeoTiffUTM/MapServer")) + // , AdvancedSymbology::SymbologyType::MIL2525C ); + +} /* SituationMapTestsMain::checkCoordinateConversion */ + + +#ifdef CrashUnderWindows +void SituationMapTestsMain::checkCreationAndRemoveMySelf() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::ObjectFactory &objFac = d->m_map->objectFactory(); + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + + int initialObjCount = objFac.count(); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + QVERIFY2(objFac.count() == 0, "objectFactory: count failed"); + + { + QList > objList; + + SituationMap::RefPointer objPtr1; + objPtr1 = objFac.create(layername, QLatin1String("UnitTestId1")); + QVERIFY2(objPtr1.valid() == true , "objectFactory: create11 failed"); + objList.append(SituationMap::RefPointer (objPtr1.get())); + + SituationMap::RefPointer objPtr2; + objPtr2 = objFac.create(layername, QLatin1String("UnitTestId2")); + QVERIFY2(objPtr2.valid() == true , "objectFactory: create22 failed"); + objList.append(SituationMap::RefPointer (objPtr2.get())); + + SituationMap::RefPointer objPtr3; + objPtr3 = objFac.create(layername, QLatin1String("UnitTestId3")); + QVERIFY2(objPtr3.valid() == true , "objectFactory: create33 failed"); + objList.append(SituationMap::RefPointer (objPtr3.get())); + + SituationMap::RefPointer objPtr4; + objPtr4 = objFac.create(layername, QLatin1String("UnitTestId4")); + QVERIFY2(objPtr4.valid() == true , "objectFactory: create44 failed"); + objList.append(SituationMap::RefPointer (objPtr4.get())); + + QVERIFY2(objList.count() == 4 , "objList: count failed"); + QVERIFY2(objFac.count() == (initialObjCount + 4) , "objectFactory: count failed"); + + // remove all: + for (SituationMap::RefPointer item : std::as_const(objList)) + item->removeMySelf(); + } + + QVERIFY2(objFac.count() == 0, "objectFactory: count failed"); +} +#endif + +//------------------------------------------------------------------------------------------------------- + +#define QVERIFY3(statement, description, context) \ + { \ + QString myDesc = QString( QLatin1String("%1:%2") ).arg(context, QLatin1String(description) ); \ + QVERIFY2(statement, myDesc.toUtf8().data()); \ + } + + +template +void checkGraphicalObjectVisibility(const QScopedPointer &go, const QString &objName) +{ + // Check visibility: + + // hide: + go->setVisibility(false); + QVERIFY3(go->updateView() == true, "updateView failed", objName); + + Esri::ArcGISRuntime::Graphic *graphic = go->graphic(); + QVERIFY3(graphic, "graphic failed", objName); + + // 1 + QVERIFY3(graphic->isVisible() == false, "isVisible failed1", objName); + + // show: + go->setVisibility(true); + QVERIFY3(go->updateView() == true, "updateView failed", objName); + QVERIFY3(go->changed() == false, "changed failed", objName); + QVERIFY3(graphic->isVisible() == true, "isVisible failed2", objName); + + // hide again: + go->setVisibility(false); + QVERIFY3(go->updateView() == true, "updateView failed", objName); + QVERIFY3(go->changed() == false, "changed failed", objName); + QVERIFY3(graphic->isVisible() == false, "isVisible failed3", objName); +} + + +class GraphObj : public SituationMap::AbstractGraphicalObject +{ + using BaseClass = AbstractGraphicalObject; +public: + using BaseClass::BaseClass; + using BaseClass::changed; + using BaseClass::resetChanged; + + bool updateViewImpl() override + { + return true; + } + bool create() override + { + return true; + } +}; + +#ifdef CrashUnderWindows +void SituationMapTestsMain::checkGraphicalObject() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + MapImpl *mapImpl = static_cast(d->m_map); + + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + static_cast(d->m_map)->getGraphicsLayer(layername); + + + QScopedPointer go(new GraphObj(mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObj failed"); + + QVERIFY2(go->changed() == true, "GraphObj: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "GraphObj: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "GraphObj: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "GraphObj: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "GraphObj: isArea failed"); +} +#endif + +//------------------------------------------------------------------------------------------------------- +class GraphObjEllipse : public SituationMap::GraphicalObjectEllipse +{ + using BaseClass = GraphicalObjectEllipse; + +public: + using BaseClass::BaseClass; + using BaseClass::changed; + using BaseClass::resetChanged; +}; + +void SituationMapTestsMain::checkGraphicalObjectEllipse() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + MapImpl *mapImpl = static_cast(d->m_map); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + mapImpl->getGraphicsLayer(layername); + + QScopedPointer go(new GraphObjEllipse(mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObjEllipse failed"); + + QVERIFY2(go->changed() == true, "GraphObjEllipse: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "GraphObjEllipse: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "GraphObjEllipse: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "GraphObjEllipse: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "GraphObjEllipse: isArea failed"); + + go->setOrigin(Point3D(94000, 78000, .0)); + QVERIFY2(go->isSinglePoint() == true, "GraphObjEllipse: isSinglePoint failed"); + + SituationMap::EllipseParameters ellipsePara; + ellipsePara.setLengthX(1000); + ellipsePara.setLengthY(1000); + // ellipsePara.setDirectionDegree(0); // circle -> irrelevant + // ellipsePara.setMaxSegmentLength(10); // use default + + go->setEllipseParameters(ellipsePara); + QVERIFY2(go->changed() == true, "GraphObjEllipse: changed failed"); + + bool success = go->updateView(); + QVERIFY2(success == true, "GraphObjEllipse: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjEllipse: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjEllipse: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjEllipse: changed failed"); + + // Check visibility: + checkGraphicalObjectVisibility(go, QStringLiteral("GraphObjEllipse")); +} + +//------------------------------------------------------------------------------------------------------- +class GraphObjPath : public SituationMap::GraphicalObjectPath +{ + using BaseClass = GraphicalObjectPath; + +public: + using BaseClass::BaseClass; + using BaseClass::changed; + using BaseClass::resetChanged; +}; + +template +void runGraphicalObjectPath(SituationMap::Map *map, + const SituationMap::GraphicalObjectPathType type) +{ + SituationMap::LayerFactory &layerFac = map->layerFactory(); + MapImpl *mapImpl = static_cast(map); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + mapImpl->getGraphicsLayer(layername); + + QScopedPointer go(new TYPE(type, + mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObjPath failed"); + + QVERIFY2(go->changed() == true, "GraphObjPath: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "GraphObjPath: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "GraphObjPath: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "GraphObjPath: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "GraphObjPath: isArea failed"); + + // With coordinates + PositionCont posCont; + posCont << Point3D(94000, 78000, .0); + posCont << Point3D(95000, 70000, .0); + posCont << Point3D(94500, 78500, .0); + go->setPositions(posCont); + QVERIFY2(go->isSinglePoint() == false, "GraphObjPath: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == true, "GraphObjPath: isSinglePoint failed"); + QVERIFY2(go->isArea() == true, "GraphObjPath: isSinglePoint failed"); + + QVERIFY2(go->changed() == true, "GraphObjPath: changed failed"); + + bool success = go->updateView(); + QVERIFY2(success == true, "GraphObjPath: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPath: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPath: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPath: changed failed"); + + // check incorrect positions: + + // check doubled start and end position: + { + // set positions: + SituationMap::PositionCont posCont; + QPointF center = map->centerPoint(); + Point3D center3D(center.x(), center.y(), .0); + + posCont << center3D + Point3D( 0, 0, .0); + posCont << center3D + Point3D( 0, 5000, .0); + posCont << center3D + Point3D(5000, 5000, .0); + posCont << center3D + Point3D( 0, 0, .0); + go->setPositions(posCont); + + bool success = go->updateView(); + QVERIFY2(success == true, "GraphObjPath: updateView failed"); + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPath: updateView failed"); + } + + // check doubled positions: + { + // set positions: + SituationMap::PositionCont posCont; + QPointF center = map->centerPoint(); + Point3D center3D(center.x(), center.y(), .0); + + posCont << center3D + Point3D( 0, 0, .0); + posCont << center3D + Point3D( 0, 0, .0); + posCont << center3D + Point3D( 0, 0, .0); + posCont << center3D + Point3D(5000, 0, .0); + posCont << center3D + Point3D(5000, 5000, .0); + posCont << center3D + Point3D( 0, 0, .0); + posCont << center3D + Point3D( 0, 0, .0); + go->setPositions(posCont); + + bool success = go->updateView(); + QVERIFY2(success == true, "GraphObjPath: updateView failed"); + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPath: updateView failed"); + } + + // Check visibility: + checkGraphicalObjectVisibility(go, QStringLiteral("GraphObjPath")); +} + +void SituationMapTestsMain::checkGraphicalObjectPath() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + // real test + runGraphicalObjectPath(d->m_map, SituationMap::GraphicalObjectPathType::Polyline); + runGraphicalObjectPath(d->m_map, SituationMap::GraphicalObjectPathType::Polygon); +} + + + + + +//------------------------------------------------------------------------------------------------------- +class GraphObjPictureMarker : public SituationMap::GraphicalObjectPictureMarker +{ + using BaseClass = GraphicalObjectPictureMarker; + +public: + using BaseClass::BaseClass; + using BaseClass::changed; + using BaseClass::resetChanged; + + int scaleCounter() const + { + return m_scaleCounter4Moduletest; + } + + bool imageDefaultSizeChanged() const + { + return m_imageDefaultSize.changed(); + } + + QSize esriPictureMarkerSize() const + { + if (!m_pictureMarkerSymbol) + return QSize(); + return QSize(m_pictureMarkerSymbol->width(), m_pictureMarkerSymbol->height()); + } + +}; + + +void runGraphicalObjectPictureMarker(SituationMap::Map *map, + const SituationMap::MarkerSymbolType markerType) +{ + SituationMap::LayerFactory &layerFac = map->layerFactory(); + MapImpl *mapImpl = static_cast(map); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + QScopedPointer go(new GraphObjPictureMarker(mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObjPictureMarker failed"); + + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "GraphObjPictureMarker: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "GraphObjPictureMarker: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "GraphObjPictureMarker: isArea failed"); + + go->setOrigin(Point3D(94000, 78000, .0)); + QVERIFY2(go->isSinglePoint() == true, "GraphObjPictureMarker: isSinglePoint failed"); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + + go->setMarkerSymbol(markerType, 100); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + + bool success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + // Change: + QImage image(100, 100, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + go->setImage(image); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); +} + + +void SituationMapTestsMain::checkGraphicalObjectPictureMarker1() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + runGraphicalObjectPictureMarker(d->m_map, SituationMap::MarkerSymbolType::Circle); + runGraphicalObjectPictureMarker(d->m_map, SituationMap::MarkerSymbolType::Square); + + runGraphicalObjectPictureMarker(d->m_map, SituationMap::MarkerSymbolType::PushPin2D); +} + +void SituationMapTestsMain::checkGraphicalObjectPictureMarker2() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + MapImpl *mapImpl = static_cast(d->m_map); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + QScopedPointer go(new GraphObjPictureMarker(mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObjPictureMarker failed"); + + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "GraphObjPictureMarker: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "GraphObjPictureMarker: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "GraphObjPictureMarker: isArea failed"); + + go->setOrigin(Point3D(94000, 78000, .0)); + QVERIFY2(go->isSinglePoint() == true, "GraphObjPictureMarker: isSinglePoint failed"); + + QImage image(100, 100, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + go->setImage(image); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + + bool success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + // Change: + go->setMarkerSymbol(SituationMap::MarkerSymbolType::Cross, 100); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + + // Check visibility: + checkGraphicalObjectVisibility(go, QStringLiteral("GraphObjPictureMarker")); +} + + +//------------------------------------------------------------------------------------------------------- +void SituationMapTestsMain::checkGraphicalObjectPictureMarker3() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + MapImpl *mapImpl = static_cast(d->m_map); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + QScopedPointer go(new GraphObjPictureMarker(mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObjPictureMarker failed"); + + go->setOrigin(Point3D(94000, 78000, .0)); + QVERIFY2(go->isSinglePoint() == true, "GraphObjPictureMarker: isSinglePoint failed"); + + QImage image(100, 100, QImage::Format_ARGB32); + image.fill(Qt::red); + + bool success = false; + + qCritical() << " Goo !!!!"; + + + // Set image first time + { + go->setImage(image); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == true, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter1a=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 0, "GraphObjPictureMarker: scaleCounter failed"); + + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter1b=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 0, "GraphObjPictureMarker: scaleCounter failed"); + + QVERIFY2(go->currentSize() == QSize(100, 100), "GraphObjPictureMarker: esriPictureMarkerSize failed"); + } + + // Set identical image again, nothing changed + { + go->setImage(image); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter2=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 0, "GraphObjPictureMarker: scaleCounter failed"); + + QVERIFY2(go->currentSize() == QSize(100, 100), "GraphObjPictureMarker: esriPictureMarkerSize failed"); + } + + // Set different image + { + QImage image2(200, 200, QImage::Format_ARGB32); + image2.fill(Qt::red); + + go->setImage(image2); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter3=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 0, "GraphObjPictureMarker: scaleCounter failed"); + + QVERIFY2(go->currentSize() == QSize(200, 200), "GraphObjPictureMarker: esriPictureMarkerSize failed"); + } + + // + // use value for defaultSize: + // + { + const QSize myDefaultSize(50, 50); + + // Set image first time + { + go->setImage(image, myDefaultSize); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == true, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter11a=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 1, "GraphObjPictureMarker: scaleCounter failed"); + + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter11b=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 1, "GraphObjPictureMarker: scaleCounter failed"); + + QVERIFY2(go->currentSize() == myDefaultSize, "GraphObjPictureMarker: esriPictureMarkerSize failed"); + } + + // Set different image, but keep value for defaultSize + { + QImage image2(200, 200, QImage::Format_ARGB32); + image2.fill(Qt::red); + + go->setImage(image2, myDefaultSize); + QVERIFY2(go->changed() == true, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter12=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 2, "GraphObjPictureMarker: scaleCounter failed"); + + QVERIFY2(go->currentSize() == myDefaultSize, "GraphObjPictureMarker: esriPictureMarkerSize failed"); + } + } + + // + // take scale into account: + // + { + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + + go->setScale(0.5); + + success = go->updateView(); + QVERIFY2(success == true, "GraphObjPictureMarker: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjPictureMarker: changed failed"); + QVERIFY2(go->imageDefaultSizeChanged() == false, "GraphObjPictureMarker: imageDefaultSizeChanged failed"); + qCritical() << " scaleCounter21=" << go->scaleCounter(); + QVERIFY2(go->scaleCounter() == 3, "GraphObjPictureMarker: scaleCounter failed"); + + QVERIFY2(go->currentSize() == QSize(25, 25), "GraphObjPictureMarker: esriPictureMarkerSize failed"); + } +} + + +//------------------------------------------------------------------------------------------------------- +class GraphObjRange : public SituationMap::GraphicalObjectRange +{ + using BaseClass = GraphicalObjectRange; + +public: + using BaseClass::BaseClass; + using BaseClass::changed; + using BaseClass::resetChanged; +}; + +void SituationMapTestsMain::checkGraphicalObjectRange() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + MapImpl *mapImpl = static_cast(d->m_map); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + QScopedPointer go(new GraphObjRange(mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObjRange failed"); + + QVERIFY2(go->changed() == true, "GraphObjRange: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "GraphObjRange: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "GraphObjRange: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "GraphObjRange: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "GraphObjRange: isArea failed"); + + go->setOrigin(Point3D(94000, 78000, .0)); + QVERIFY2(go->isSinglePoint() == true, "GraphObjRange: isSinglePoint failed"); + + // set value: + go->setRange(1000, 15); + QVERIFY2(go->changed() == true, "GraphObjRange: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjRange: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjRange: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjRange: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjRange: changed failed"); + + // Change: + go->setRange(2000, 15); + QVERIFY2(go->changed() == true, "GraphObjRange: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjRange: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjRange: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjRange: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjRange: changed failed"); + + // Change: + go->setRange(2000, -15); + QVERIFY2(go->changed() == true, "GraphObjRange: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjRange: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjRange: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjRange: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjRange: changed failed"); + + // Check visibility: + checkGraphicalObjectVisibility(go, QStringLiteral("GraphObjRange")); +} + + +//------------------------------------------------------------------------------------------------------- +class GraphObjText : public SituationMap::GraphicalObjectText +{ + using BaseClass = GraphicalObjectText; + +public: + using BaseClass::BaseClass; + using BaseClass::changed; + using BaseClass::resetChanged; +}; + +void SituationMapTestsMain::checkGraphicalObjectText() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + MapImpl *mapImpl = static_cast(d->m_map); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QVERIFY(layernames.size() > 1); + QString layername = layernames[1]; + + // real test + QScopedPointer go(new GraphObjText(mapImpl->getEsriGeoView(), + mapImpl->getGraphicsLayer(layername), + QLatin1String("TacticalAppearanceModuleTest"), + 4712)); + QVERIFY2(go, "objectFactory: GraphObjText failed"); + + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "GraphObjText: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "GraphObjText: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "GraphObjText: isArea failed"); + + go->setOrigin(Point3D(94000, 78000, .0)); + QVERIFY2(go->isSinglePoint() == true, "GraphObjText: isSinglePoint failed"); + + // set value: + + // + // Text1 + // + go->setText(QLatin1String("myDummyText")); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // Text2 + go->setText(QLatin1String("myDummyTextMODIFIED")); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // + // textStyle1 + // + go->rTextStyle().setSize(11.23456f); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // set identical value again -> not changed + const TextStyle ts = go->textStyle(); + go->setTextStyle(ts); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + + // textStyle2 + go->rTextStyle().setSize(111.9876f); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // + // setXOffset1 + // + go->setXOffset(10.876f); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // set identical value again -> not changed + go->setXOffset(10.876f); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // setXOffset2 + go->setXOffset(10.123f); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // + // setYOffset1 + // + go->setYOffset(222.876f); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // set identical value again -> not changed + go->setYOffset(222.876f); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // setYOffset2 + go->setYOffset(222.123f); + QVERIFY2(go->changed() == true, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + QVERIFY2(go->updateView() == true, "GraphObjText: updateView failed"); + QVERIFY2(go->changed() == false, "GraphObjText: changed failed"); + + // Check visibility: + checkGraphicalObjectVisibility(go, QStringLiteral("GraphObjText")); +} + +//------------------------------------------------------------------------------------------------------- +class GraphObjTactApp : public SituationMap::TacticalAppearance +{ + using BaseClass = TacticalAppearance; + +public: + using BaseClass::BaseClass; + using BaseClass::changed; + using BaseClass::resetChanged; +}; + +void SituationMapTestsMain::checkGraphicalObjectTacticalAppearance() +{ + if (d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + return; + + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + GraphObjTactApp *go = new GraphObjTactApp(static_cast(d->m_map), + layername, + QLatin1String("TacticalAppearanceModuleTest"), + 4711); + QVERIFY2(go->changed() == true, "TacticalAppearance: changed failed"); + go->resetChanged(); + QVERIFY2(go->changed() == false, "TacticalAppearance: changed failed"); + + QVERIFY2(go->isSinglePoint() == false, "TacticalAppearance: isSinglePoint failed"); + QVERIFY2(go->isMultiPoint() == false, "TacticalAppearance: isMultiPoint failed"); + QVERIFY2(go->isArea() == false, "TacticalAppearance: isArea failed"); + + // set value: + + go->setUniqueDesignation(QLatin1String("uniquDesignation")); + go->setVisibility(true); + go->setSelected(false); + go->setPickable(false); + go->setScale(2.34f); + QVERIFY2(go->changed() == true, "TacticalAppearance: changed failed"); + + SituationMap::SymbolData symbolData; + + // + // Check SINGLE-POINTSYMBOL + // + { + switch (d->m_symbologyType) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + symbolData.sidc = AdvancedSymbology::SidcCodec::encodeComplete( d->m_symbologyType, "10051000001213030000" ); + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + case AdvancedSymbology::SymbologyType::APP6B: + symbolData.sidc = AdvancedSymbology::SidcCodec::encodeComplete( d->m_symbologyType, "SFGPUCD---"); // PointSymbol + break; + case AdvancedSymbology::SymbologyType::None: + break; + } + go->setSymbol(symbolData); + + QVERIFY2(go->changed() == true, "TacticalAppearance: changed failed"); + + // no origin/position + QVERIFY2(go->updateView() == false, "TacticalAppearance: updateView failed"); // -> 3 * ERROR-msg + QVERIFY2(go->changed() == false, "TacticalAppearance: changed failed"); + + // with origin/position + QPointF origin = s_basePoint.geographic( QPointF(94000, 78000) ); + go->setOrigin(Point3D(origin.x(), origin.y(), .0)); + QVERIFY2(go->changed() == true, "TacticalAppearance: changed failed"); + QVERIFY2(go->updateView() == true, "TacticalAppearance: updateView failed"); + QVERIFY2(go->updateView() == true, "TacticalAppearance: updateView failed"); + QVERIFY2(go->changed() == false, "TacticalAppearance: changed failed"); + } + + // + // Check MULTI-POINTSYMBOL + // + { + switch (d->m_symbologyType) + { + case AdvancedSymbology::SymbologyType::APP6D: + case AdvancedSymbology::SymbologyType::MIL2525D: + symbolData.sidc = AdvancedSymbology::SidcCodec::encodeComplete(d->m_symbologyType, "10042500002701000000"); // 01042500002701000000 -> SymbolSet = 25 270100 (obstacle) + break; + case AdvancedSymbology::SymbologyType::MIL2525C: + symbolData.sidc = AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::MIL2525C, "GUMPNB----"); // MIL2525: 1100, GHSPAR----, Forward Arming and Refueling Area H -> Polygon + break; + case AdvancedSymbology::SymbologyType::APP6B: + symbolData.sidc = AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::APP6B, "GHTPGC----"); // App6B: 3370, GHTPGC----, Task - Canalize H, -> Polyline + break; + case AdvancedSymbology::SymbologyType::None: + break; + } + + if (! symbolData.sidc.isEmpty()) + { + go->setSymbol(symbolData); + + // With ONE coordinate + QVERIFY2(go->updateView() == true, "TacticalAppearance: updateView failed"); + QVERIFY2(go->changed() == false, "TacticalAppearance: changed failed"); + + // With coordinates + QList posCont2D; + posCont2D << s_basePoint.geographic( QPointF(94000, 78000) ); + posCont2D << s_basePoint.geographic( QPointF(95000, 70000) ); + posCont2D << s_basePoint.geographic( QPointF(94500, 78500) ); + go->setPositions(convert2D3D(posCont2D)); + + QVERIFY2(go->changed() == true, "TacticalAppearance: changed failed"); + QVERIFY2(go->updateView() == true, "TacticalAppearance: updateView failed"); + QVERIFY2(go->updateView() == true, "TacticalAppearance: updateView failed"); + QVERIFY2(go->changed() == false, "TacticalAppearance: changed failed"); + } + } + + // cleanup: + delete go; +} + +//------------------------------------------------------------------------------------------------------- +class BasePathObjImp : public SituationMap::BasicPathObjectImpl +{ + using BaseClass = BasicPathObjectImpl; + +public: + using BaseClass::BaseClass; + using BaseClass::init; +}; + + + +void SituationMapTestsMain::checkBasicPathObjectImpl() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + SituationMap::ObjectFactory &objFac = d->m_map->objectFactory(); + + // use an LOGICAL layer (not basemap) + const QStringList layernames = layerFac.layernames(); + QVERIFY(layernames.size() > 1); + QString layername = layernames[1]; + + // real test + BasePathObjImp *pathObj = new BasePathObjImp(QLatin1String("BasicPathObjectModuleTest")); + bool initOk = pathObj->init(static_cast(d->m_map), + layername, + SituationMap::GraphicalObjectPathType::Polygon); + QVERIFY2(initOk == true, "BasicPathObjectImpl: init failed"); + + // set positions: + SituationMap::PositionCont posCont; + // check empty container: + pathObj->setPositions(posCont); + bool success = pathObj->updateView(); + QVERIFY2(success == false, "BasicPathObjectImpl: updateView failed"); + + + // check empty container with ArrowHeads + pathObj->setArrowHeadStart(true); + pathObj->setArrowHeadEnd(true); + success = pathObj->updateView(); + QVERIFY2(success == false, "BasicPathObjectImpl: updateView failed"); + + + // check container without ArrowHeads + pathObj->setArrowHeadStart(false); + pathObj->setArrowHeadEnd(false); + QPointF center = d->m_map->centerPoint(); + + QList posCont2D; + posCont2D << s_basePoint.geographic( center + QPointF( 0, 0) ); + posCont2D << s_basePoint.geographic( center + QPointF( 0, 5000) ); + posCont2D << s_basePoint.geographic( center + QPointF(5000, 5000) ); + posCont2D << s_basePoint.geographic( center + QPointF(5000, 0) ); + pathObj->setPositions(convert2D3D(posCont2D)); + + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + int numberOfValuesChanged = pathObj->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "BasicPathObject: numberOfValuesChanged failed"); + + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + numberOfValuesChanged = pathObj->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "BasicPathObject: numberOfValuesChanged failed"); + + QTest::qWait(timeToDelay); + + + pathObj->setArrowHeadStart(true); + pathObj->setArrowHeadEnd(true); + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + numberOfValuesChanged = pathObj->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "BasicPathObject: numberOfValuesChanged failed"); + + for (int i = 0; i < 2; ++i) + pathObj->setTextOnSegment(i, QStringLiteral("segText_%1").arg(i)); + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + numberOfValuesChanged = pathObj->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "BasicPathObject: numberOfValuesChanged failed"); + + QTest::qWait(timeToDelay); + + +// QSignalSpy spy(d->m_sitMap, SIGNAL(mapReady())); +// spy.wait(5000); + + // set origin/radius: + // !!!arrowHeads will disappear!!! + QPointF center2D = s_basePoint.geographic(center); + pathObj->setOrigin(Point3D(center2D.x(), center2D.y(), .0)); + pathObj->setRadius(2000); + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + numberOfValuesChanged = pathObj->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "BasicPathObject: numberOfValuesChanged failed"); + + + // setIdentifierVisible + pathObj->setIdentifierVisible(true); + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + numberOfValuesChanged = pathObj->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "BasicPathObject: numberOfValuesChanged failed"); + + QTest::qWait(timeToDelay); + +#ifdef SUPPORT_SUBSIDIARY_LINES + // + // Subsidiary: + // + QList subsidiaryIdxInUse; + + // check pathIndexInUse: + subsidiaryIdxInUse = pathObj->subsidiaryPathIndexInUse(); + QVERIFY2(subsidiaryIdxInUse.size() == 0 , "BasicPathObjectImpl: subsidiaryPathIndexInUse failed"); + + // check non-existing: + SituationMap::PositionCont retrieved; + retrieved = pathObj->subsidiaryPositions(12345); + QVERIFY2(retrieved.size() == 0 , "BasicPathObjectImpl: subsidiaryPositions failed"); + + // set path: + SituationMap::PositionCont posCont2; + posCont2 << center + QPointF(0, 0); + posCont2 << center + QPointF(0, 3000); + posCont2 << center + QPointF(3000, 3000); + + pathObj->setSubsidiaryPositions(12, posCont2); + const SituationMap::PositionCont &posContTemp = pathObj->subsidiaryPositions(12); + QVERIFY2(posContTemp == posContTemp, "BasicPathObjectImpl: subsidiaryPositions failed"); + + + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView (setSubsidiaryPositions) failed"); + numberOfValuesChanged = pathObj->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "BasicPathObject: numberOfValuesChanged failed"); + + // Overlapping polygons (with fillpattern solid) will NOT show an fillpattern within the onverlapping area!!! + // Reason: ??? + // 2015-11-19, Version 10.2.6) + + QTest::qWait(timeToDelay); + + + subsidiaryIdxInUse = pathObj->subsidiaryPathIndexInUse(); + QVERIFY2(subsidiaryIdxInUse.size() == 1 , "BasicPathObjectImpl: subsidiaryPathIndexInUse failed"); + + // retrieve: + retrieved = pathObj->subsidiaryPositions(12); + QVERIFY2(posCont2 == retrieved , "BasicPathObjectImpl: subsidiaryPositions failed"); + + // clear: + pathObj->clearSubsidiaryPositions(); + subsidiaryIdxInUse = pathObj->subsidiaryPathIndexInUse(); + QVERIFY2(subsidiaryIdxInUse.size() == 0 , "BasicPathObjectImpl: subsidiaryPathIndexInUse failed"); +#endif // SUPPORT_SUBSIDIARY_LINES + + + // cleanup: + delete pathObj; + QVERIFY2(objFac.count() == 0 , "objectFactory: count failed"); + + + // + // check identical positions: + // + + { + // real test + QScopedPointer pathObj(new BasePathObjImp(QLatin1String("BasicPathObjectModuleTest"))); + bool initOk = pathObj->init(static_cast(d->m_map), + layername, + SituationMap::GraphicalObjectPathType::Polygon); + QVERIFY2(initOk == true, "BasicPathObjectImpl: init failed"); + + // check doubled start and end position: + { + // set positions: + QList posCont; + QPointF center = d->m_map->centerPoint(); + qWarning() << " center=" << center; + posCont << center + QPointF(0, 0); + posCont << center + QPointF(0, 0.001); + posCont << center + QPointF(0.001, 0.001); + posCont << center + QPointF(0, 0); + pathObj->setPositions(convert2D3D(posCont2D)); + + bool success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + + QTest::qWait(timeToDelay); + } + + // check doubled positions: + { + // set positions: + SituationMap::PositionCont posCont; + QPointF center2D = d->m_map->centerPoint(); + Point3D center(center2D.x(), center2D.y(), .0); + + posCont << center + Point3D(0, 0, .0); + posCont << center + Point3D(0, 0, .0); + posCont << center + Point3D(0, 0, .0); + posCont << center + Point3D(0.001, 0, .0); + posCont << center + Point3D(0.001, 0.001, .0); + posCont << center + Point3D(0, 0, .0); + posCont << center + Point3D(0, 0, .0); + pathObj->setPositions(posCont); + + bool success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + success = pathObj->updateView(); + QVERIFY2(success == true, "BasicPathObjectImpl: updateView failed"); + QTest::qWait(timeToDelay); + } + + } + +} + +void SituationMapTestsMain::checkChemicalAgent() +{ + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::ObjectFactory &objFac = d->m_map->objectFactory(); + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + + SituationMap::RefPointer cAgent; + cAgent = objFac.create(layername, QLatin1String("ChemAgentId1")); + QVERIFY2(cAgent.valid() == true , "objectFactory: create failed"); + + QVERIFY2(objFac.count() == 1 , "objectFactory: count failed"); + + QList posCont2D; + posCont2D << s_basePoint.geographic( QPointF( 1000, 1000) ); + posCont2D << s_basePoint.geographic( QPointF(85000, 70000) ); + posCont2D << s_basePoint.geographic( QPointF(84500, 78500) ); + cAgent->setPositions(convert2D3D(posCont2D)); + + bool success = cAgent->updateView(); + QVERIFY2(success == true, "ChemicalAgent: updateView failed"); + success = cAgent->updateView(); + QVERIFY2(success == true, "ChemicalAgent: updateView failed"); + + QTest::qWait(timeToDelay); + + QPointF origin = s_basePoint.geographic( QPointF(0, 1000) ); + cAgent->setOrigin(Point3D::fromQPointF(origin)); + + cAgent->setRadius(20000); + success = cAgent->updateView(); + QVERIFY2(success == true, "ChemicalAgent: updateView failed"); + +} + +void SituationMapTestsMain::checkParticipant() +{ + if (d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + + return; + + + + // Preperations: + reInitTestCaseUntilSetCenter(); + + SituationMap::ObjectFactory &objFac = d->m_map->objectFactory(); + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + + SituationMap::RefPointer objPtr1; + objPtr1 = objFac.create(layername, QLatin1String("UnitTestId1")); + QVERIFY2(objPtr1.valid() == true , "objectFactory: create failed"); + + QVERIFY2(objFac.count() == 1 , "objectFactory: count failed"); + + objPtr1->setTextModifier(SituationMap::Participant::TextModifier::EF_ReinforcedReduced, QLatin1String("EF_ReinforcedReduced")); + objPtr1->setTextModifier(SituationMap::Participant::TextModifier::M_HigherFormation, QLatin1String("M_HigherFormation")); + objPtr1->setTextModifier(SituationMap::Participant::TextModifier::T_UniqueDesignation, QLatin1String("T_UniqueDesignation")); + objPtr1->setTextModifier(SituationMap::Participant::TextModifier::W_DateTimeGroup, QLatin1String("W_DateTimeGroup")); + + objPtr1->setVisibility(true); +// objPtr1->setVisibility(SituationMap::ObjectAppearance::Technical, false); // no sense + + + SituationMap::SymbolData symData; + symData.sidc = QLatin1String("SUGPUSAJ--****X"); + // 123456789012345 + + symData.color = Qt::cyan; // Will be overwritten via partyId. + symData.size = 0.5; + + objPtr1->setSymbol(symData); + objPtr1->setScale(2.0); + objPtr1->setPartyId(4); + + QPointF origin = s_basePoint.geographic( QPointF(94000, 78003) ); + objPtr1->setOrigin(Point3D::fromQPointF(origin)); + + objPtr1->setInjury(SituationMap::Injury::TotalKill, 3); + + objPtr1->setAdditionalSymbol(SituationMap::AdditionalSymbolType::NbcProtection, true); + objPtr1->setAdditionalSymbol(SituationMap::AdditionalSymbolType::Manipulation, true); + objPtr1->setAdditionalSymbol(SituationMap::AdditionalSymbolType::NoPosition, true); + objPtr1->setAdditionalSymbol(SituationMap::AdditionalSymbolType::BiteError, true); + + objPtr1->setMarked(false); + + objPtr1->setRanges(QVector() << 1000 << 10000); + + bool success = objPtr1->updateView(); + QVERIFY2(success == true, "participant: updateView failed"); + + SituationMap::ParticipantImpl *partiImpl = dynamic_cast(objPtr1.get()); + + if (partiImpl) + { + int numberOfValuesChanged = partiImpl->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged = " << numberOfValuesChanged; + QVERIFY2(numberOfValuesChanged == 0, "participant: numberOfValuesChanged failed"); + + bool success2 = objPtr1->updateView(); + QVERIFY2(success2 == true, "participant: updateView failed"); + + int numberOfValuesChanged2 = partiImpl->numberOfValuesChanged(); + INFO_LOG(m_logger) << " numberOfValuesChanged2 = " << numberOfValuesChanged2; + QVERIFY2(numberOfValuesChanged2 == 0, "participant: numberOfValuesChanged2 failed"); + + } + + // remove: + objPtr1->removeMySelf(); + QVERIFY2(objFac.count() == 0, "objectFactory: count failed"); + +} + + +void SituationMapTestsMain::checkEventDispatcher() +{ + reInitTestCaseUntilSetCenter(); + + QGraphicsView * mapWidget = d->m_map->getQGraphicsView(); + + SituationMap::RB::EventDispatcher * dispatcher1 = new SituationMap::RB::EventDispatcher( static_cast(mapWidget->viewport()) ); + unsigned int noOfDisp1 = dispatcher1->numberOfEventDispatcher(); + + SituationMap::RB::EventDispatcher * dispatcher2 = new SituationMap::RB::EventDispatcher( static_cast(mapWidget->viewport()) ); + unsigned int noOfDisp2 = dispatcher1->numberOfEventDispatcher(); + + QVERIFY2( noOfDisp1 == 2 , "wrong number of event dispatcher (first test)"); + QVERIFY2( noOfDisp2 == noOfDisp1+1, "wrong number of event dispatcher (second test)"); + + delete dispatcher2; + + noOfDisp2 = dispatcher1->numberOfEventDispatcher(); + QVERIFY2(noOfDisp2 == noOfDisp1, "wrong number of event dispatcher (third test)"); + +} // SituationMapTestsMain::checkEventDispatcher + + + +static bool rubberbandcheckIsComplete = false; +static QVector rubberbandVertexList; + +template void SituationMapTestsMain::checkRubberband(const char *className) +{ + reInitTestCaseUntilSetCenter(); + + static bool testok = true; + + auto slotFunc = [ = ](SituationMap::RB::RubberbandBase * rubberband) + { + if (rubberband->coordinatesAreValid()) + { + QVector coordinateVector = rubberband->coordinateVector(); + if (coordinateVector.size() == rubberbandVertexList.size()) + { + for (int i = 0; i < coordinateVector.size(); ++i) + { + QPointF simPoint = coordinateVector[i]; + QPointF p = rubberbandVertexList[i]; + if ((fabs(simPoint.x() - p.x()) > d->m_map->pixelSizeMeter()) || (fabs(simPoint.y() - p.y()) > d->m_map->pixelSizeMeter())) + testok = false; + } + } + else testok = false; + } + rubberbandcheckIsComplete = true; + }; // slotFunc + + + rubberbandcheckIsComplete = false; + testok = true; + + RubberbandClass *rband = d->m_map->rubberbandFactory().create(); + + static char msg[200]; + + if (rband == nullptr) + { + sprintf(msg, "%s rubberband factory failure", className); + QVERIFY2(false, msg); + } + else + { + rband->setCoordinateVector(rubberbandVertexList); + + connect(rband, &SituationMap::RB::Rubberband::apply, slotFunc); + connect(rband, &SituationMap::RB::Rubberband::abort, slotFunc); + + QTimer::singleShot(200, this, [ = ]() + { + rband->applyAndEndAction(nullptr, 0, 0); + }); + + // Wait for finish Rubberband + while (!rubberbandcheckIsComplete) + QCoreApplication::processEvents(); + + delete rband; rband = nullptr; // here it is ok to use delete directly + + sprintf(msg, "%s coordinate test failed", className); + QVERIFY2(testok, msg); + } +} // SituationMapTestsMain::checkRubberband + + + +void SituationMapTestsMain::checkRubberbandMultiPointFeature() +{ + SituationMap::CoordTranslationFunctor * ct = d->m_map->coordTranslationFunctor(); + rubberbandVertexList.clear(); + + if (ct) + { + rubberbandVertexList << ct->mapToGeographic(QPointF(-338163 , 168551 )+d->m_geoOff) + << ct->mapToGeographic(QPointF( 154770 , 168551 )+d->m_geoOff) + << ct->mapToGeographic(QPointF( 154770 , - 71025 )+d->m_geoOff) + << ct->mapToGeographic(QPointF(-338163 , - 71025 )+d->m_geoOff) + << ct->mapToGeographic(QPointF( 36042.4 , 38162.5)+d->m_geoOff) + << ct->mapToGeographic(QPointF( 302120 , - 46643.1)+d->m_geoOff) + << ct->mapToGeographic(QPointF( 244876 , -224735 )+d->m_geoOff) + << ct->mapToGeographic(QPointF(- 21201.4, -139929 )+d->m_geoOff) + ; + } + else + { + rubberbandVertexList << QPointF(-338163 , 168551 )+d->m_geoOff + << QPointF( 154770 , 168551 )+d->m_geoOff + << QPointF( 154770 , - 71025 )+d->m_geoOff + << QPointF(-338163 , - 71025 )+d->m_geoOff + << QPointF( 36042.4 , 38162.5)+d->m_geoOff + << QPointF( 302120 , - 46643.1)+d->m_geoOff + << QPointF( 244876 , -224735 )+d->m_geoOff + << QPointF(- 21201.4, -139929 )+d->m_geoOff + ; + } + + checkRubberband("RubberbandMultiPointFeature"); + +} // SituationMapTestsMain::checkRubberbandMultiPointFeature + + + +void SituationMapTestsMain::checkRubberbandRectangle() +{ + SituationMap::CoordTranslationFunctor * ct = d->m_map->coordTranslationFunctor(); + rubberbandVertexList.clear(); + + if (ct) + { + rubberbandVertexList << ct->mapToGeographic( QPointF (154770 , 168551)+d->m_geoOff) + << ct->mapToGeographic(QPointF( 154770 , - 71025)+d->m_geoOff) + << ct->mapToGeographic(QPointF(-338163 , - 71025)+d->m_geoOff) + << ct->mapToGeographic(QPointF(-338163 , 168551)+d->m_geoOff) + ; + } + else + { + rubberbandVertexList << QPointF (154770 , 168551)+d->m_geoOff + << QPointF( 154770 , - 71025)+d->m_geoOff + << QPointF(-338163 , - 71025)+d->m_geoOff + << QPointF(-338163 , 168551)+d->m_geoOff + ; + } + + reInitTestCaseUntilSetCenter(); + + static bool testok = true; + + auto slotFunc = [ = ](SituationMap::RB::RubberbandBase * rubberband) + { + // check if coordinates of rubberband and input vector are remaining identical + if (rubberband->coordinatesAreValid()) + { + const QVector & coordinateVector = rubberband->coordinateVector(); + if (coordinateVector.size() == rubberbandVertexList.size()) + { + double mapPixelSize = d->m_map->pixelSizeMeter(); + + for (int i = 0; i < coordinateVector.size(); ++i) + { + const QPointF &simPoint = coordinateVector[i]; + int matchCount = 0; + for (int j = 0; j < rubberbandVertexList.size(); ++j) + { + QPointF p = rubberbandVertexList[j]; + double dx = fabs(simPoint.x() - p.x()); + double dy = fabs(simPoint.y() - p.y()); + if ((dx < mapPixelSize) && (dy < mapPixelSize)) + { + ++matchCount; + } + } + if (matchCount != coordinateVector.size()) testok = false; + } + } + else testok = false; + } + rubberbandcheckIsComplete = true; + }; // slotFunc + + + rubberbandcheckIsComplete = false; + testok = true; + + SituationMap::RB::RubberbandRectangle *rband = d->m_map->rubberbandFactory().create(); + + if (rband == nullptr) + QVERIFY2(false, "RubberbandRectangle rubberband factory failure"); + else + { + rband->setCoordinateVector(rubberbandVertexList); + + connect(rband, &SituationMap::RB::Rubberband::apply, slotFunc); + connect(rband, &SituationMap::RB::Rubberband::abort, slotFunc); + + QTimer::singleShot(200, this, [ = ]() + { + rband->applyAndEndAction(nullptr, 0, 0); + }); + + // Wait for finish Rubberband + while (!rubberbandcheckIsComplete) + QCoreApplication::processEvents(); + + delete rband; rband = nullptr; // here it is ok to use delete directly + + QVERIFY2(testok, "RubberbandRectangle coordinate test failed"); + + } +} // SituationMapTestsMain::checkRubberbandRectangle + + +void SituationMapTestsMain::checkRubberbandRectangleIndex() +{ + SituationMap::CoordTranslationFunctor * ct = d->m_map->coordTranslationFunctor(); + rubberbandVertexList.clear(); + + if (ct) + { + rubberbandVertexList << ct->mapToGeographic( QPointF (154770 , 168551)+d->m_geoOff) + << ct->mapToGeographic(QPointF( 154770 , - 71025)+d->m_geoOff) + << ct->mapToGeographic(QPointF(-338163 , - 71025)+d->m_geoOff) + <mapToGeographic( QPointF(-338163 , 168551)+d->m_geoOff) + ; + } + else + { + rubberbandVertexList << QPointF (154770 , 168551)+d->m_geoOff + << QPointF( 154770 , - 71025)+d->m_geoOff + << QPointF(-338163 , - 71025)+d->m_geoOff + << QPointF(-338163 , 168551)+d->m_geoOff + ; + } + + reInitTestCaseUntilSetCenter(); + + static bool testok = true; + + auto slotFunc = [ = ](SituationMap::RB::RubberbandBase *rubberband) + { + if (rubberband->coordinatesAreValid()) + { + QVector coordinateVector = rubberband->coordinateVector(); + if (coordinateVector.size() == rubberbandVertexList.size()) + { + for (int i = 0; i < coordinateVector.size(); ++i) + { + const QPointF & simPoint = coordinateVector[i]; + const QPointF & p = rubberbandVertexList[i]; + if ((fabs(simPoint.x() - p.x()) > d->m_map->pixelSizeMeter()) && (fabs(simPoint.y() - p.y()) > d->m_map->pixelSizeMeter())) + { + testok = false; + } + } + } + else testok = false; + } + rubberbandcheckIsComplete = true; + }; // slotFunc + + + rubberbandcheckIsComplete = false; + testok = true; + + SituationMap::RB::RubberbandRectangle *rband = d->m_map->rubberbandFactory().create(); + + if (rband == nullptr) + QVERIFY2(false, "RubberbandRectangle rubberband factory failure"); + else + { + rband->setCoordinateVector(rubberbandVertexList); + + connect(rband, &SituationMap::RB::Rubberband::apply, slotFunc); + connect(rband, &SituationMap::RB::Rubberband::abort, slotFunc); + + QTimer::singleShot(200, this, [ = ]() + { + rband->applyAndEndAction(nullptr, 0, 0); + }); + + // Wait for finish Rubberband + while (!rubberbandcheckIsComplete) + QCoreApplication::processEvents(); + + delete rband; rband = nullptr; // here it is ok to use delete directly + + QVERIFY2(testok, "RubberbandRectangle coordinate index test failed"); + } +} // SituationMapTestsMain::checkRubberbandRectangleIndex + + +void SituationMapTestsMain::checkRubberbandCorridor() +{ + rubberbandVertexList.clear(); + + checkRubberband( "RubberbandCorridor" ); +} // SituationMapTestsMain::checkRubberbandCorridor + + +template void SituationMapTestsMain::checkRubberbandProjection(const char *className) +{ + reInitTestCaseUntilSetCenter(); + + SituationMap::CoordTranslationFunctor * ct = d->m_map->coordTranslationFunctor(); + rubberbandVertexList.clear(); + + if (ct) + { + rubberbandVertexList << ct->mapToGeographic(QPointF( 154770 , 168551)+d->m_geoOff) + << ct->mapToGeographic(QPointF(-338163 , 168551)+d->m_geoOff) + << ct->mapToGeographic(QPointF( 154770 , - 71025)+d->m_geoOff) + ; + } + else + { + rubberbandVertexList << QPointF (154770 , 168551)+d->m_geoOff + << QPointF(-338163 , 168551)+d->m_geoOff + << QPointF( 154770 , - 71025)+d->m_geoOff + ; + } + + SituationMap::RB::Rubberband *rband = d->m_map->rubberbandFactory().create(); + + if (rband) + { + rband->setCoordinateVector(rubberbandVertexList); + bool mismatch = false; + QVector vertexList = rband->coordinateVector(); + + for (int i = 0; i < vertexList.size(); ++i) + { + qreal dx = fabs(vertexList[i].x() - rubberbandVertexList[i].x()); + qreal dy = fabs(vertexList[i].y() - rubberbandVertexList[i].y()); + + if ((dx > 0.001) && (dy > 0.001)) mismatch = true; + } + + delete rband; + + static char msg[200]; + + sprintf(msg, "%s oordinate projection failed.", className); + QVERIFY2((!mismatch), msg); + } +} // SituationMapTestsMain::checkRubberbandProjection + + +void SituationMapTestsMain::checkRubberCoordinateProjection() +{ + checkRubberbandProjection("RubberbandPointCluster"); + checkRubberbandProjection("RubberbandMultiPointFeature"); +} // SituationMapTestsMain::checkRubberCoordinateProjection + + +//------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------- + +void SituationMapTestsMain::reInitTestCaseUntilSetCenter() +{ + // Preperations: + initTestCase(); + checkMapReady(); +// // checkCenter(); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + INFO_LOG(m_logger) << " layernames = " << layerFac.layernames().join(QLatin1String(";")); + + + if (layerFac.isUsingBaseMapBuiltIn()) + { + d->m_basemapCountRef = 1; + } else { + d->m_basemapCountRef = 1; // maybe '2', depending on ini + } + + +#ifdef CHECK_LAYER + int basemapCount = layerFac.layernames(LayerType::Basemap).count(); + int jumpPointLayerCount = 0; + jumpPointLayerCount = layerFac.layernames(LayerType::JumpPoint).count(); + QVERIFY2(jumpPointLayerCount == 1 , "layerFactory: count failed"); + + int operationalLayerCount = layerFac.layernames(LayerType::Operational).count(); + QVERIFY2(basemapCount == d->m_basemapCountRef , "layerFactory: count failed"); + QVERIFY2(operationalLayerCount == 9 , "layerFactory: count failed"); // AES, Participants + + int technLayerCount = d->m_map->technicalLayerCount(); + INFO_LOG(m_logger) << " technicalLayerCount = " << technLayerCount; + QVERIFY2(technLayerCount == basemapCount, "objectFactory: count failed"); + + INFO_LOG(m_logger) << " basemapCount = " << basemapCount; + INFO_LOG(m_logger) << " jumpPointLayerCount = " << jumpPointLayerCount; + INFO_LOG(m_logger) << " operationalLayerCount = " << operationalLayerCount; + QVERIFY2(layerFac.count() == basemapCount + jumpPointLayerCount + operationalLayerCount , "layerFactory: count failed"); + + SituationMap::ObjectFactory &objFac = d->m_map->objectFactory(); + INFO_LOG(m_logger) << " objFac.count() =" << objFac.count(); + + QVERIFY2(objFac.count() == 0, "objectFactory: count failed"); +#endif +} + + +//------------------------------------------------------------------------------------------------------- +void SituationMapTestsMain::cleanupTestCase() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION; + delete d->m_sitMap; + DEBUG_LOG(m_logger) << LOGFUNCTION; +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.h new file mode 100755 index 000000000..4bf4cf953 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/ModuleTests/situation_map_test_main.h @@ -0,0 +1,118 @@ +/** + * @RDE_HEADER@ + * Moduletest + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include +#include +#include + +#include + +namespace AdvancedSymbology +{ +enum class SymbologyType; +} + + +class SituationMapTestsMain : public QObject +{ + Q_OBJECT + +public: + SituationMapTestsMain(AdvancedSymbology::SymbologyType symbologyType); + virtual ~SituationMapTestsMain(); + +private Q_SLOTS: + + void initTestCase(); + + void checkMapReady(); + void checkMapNotifier(); + void checkCenter(); + void checkExtent(); + + void checkMapIsUTM(); + + +// void convertMgrsBasePoint(); // not supported by QuartztBeta1 + void convertUTMBasePoint(); + + void checkOriginTranslated(); + void checkPositionsTranslated(); + void checkPositionAngles(); + + + void measureProject(); + void measurceLocationToElevationBulk(); + + void checkMapObjectProperties(); + + void checkObjectFactoryContainer(); // contains, get etc. + void checkObjectAndLayerFactory(); // checks layerOnDemand + + void checkOverlayHandler1(); + void checkOverlayHandler2(); + void checkOverlayHandler3(); + +// void checkRubberbandFactory(); + + + void measureCoordTranslationFunctor(); + void checkCoordinateConversion(); + +#ifndef Q_OS_WIN +# define CrashUnderWindows +#endif +#ifdef CrashUnderWindows + void checkCreationAndRemoveMySelf(); + + void checkGraphicalObject(); +#endif + void checkGraphicalObjectEllipse(); + void checkGraphicalObjectPath(); + void checkGraphicalObjectPictureMarker1(); + void checkGraphicalObjectPictureMarker2(); + void checkGraphicalObjectPictureMarker3(); + void checkGraphicalObjectRange(); + void checkGraphicalObjectText(); + void checkGraphicalObjectTacticalAppearance(); + + void checkBasicPathObjectImpl(); + + void checkChemicalAgent(); + void checkParticipant(); + + void checkEventDispatcher(); + + void checkRubberbandMultiPointFeature(); + void checkRubberbandRectangle(); + void checkRubberbandRectangleIndex(); + void checkRubberbandCorridor(); + + void checkRubberCoordinateProjection(); + + void cleanupTestCase(); + +protected: + + void reInitTestCaseUntilSetCenter(); + + template void checkRubberband(const char *className); + template void checkRubberbandProjection(const char *className); + + +private: + + SituationMap::LOGGER_T m_logger; + + class Private; + Private *d = nullptr; + + +}; + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/CMakeLists.txt new file mode 100755 index 000000000..b4b505b8a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# LIBRARY +SET(Portal_SRCS + portal.cpp + portal.h + portal_export.h +) + +SOURCE_GROUP(Portal FILES ${Portal_SRCS}) +NGL_ADD_LIBRARY(WS situationMapPortal SHARED ${Portal_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(situationMapPortal + PUBLIC +) + + +NGL_TARGET_LINK_LIBRARIES(situationMapPortal + PRIVATE + ArcGIS::ArcGIS + NGLLogger + Qt6::Network + Qt6::Gui + Qt6::Core + situationMapCommon + PUBLIC +) + +NGL_INSTALL_TARGET(situationMapPortal) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.cpp new file mode 100755 index 000000000..edbf7eaf7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.cpp @@ -0,0 +1,351 @@ +/** + * @RDE_HEADER@ + * Portal + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, NLSE, 2018-10-08 + */ + + +// Own +#include "portal.h" +//#include + + +// Esri +#include +#include + +#include + + +// Qt + + +namespace Esri +{ +namespace ArcGISRuntime +{ + +QString toString(const Error &value) +{ + std::ostringstream stream; + stream << "Esri::ArcGISRuntime::Error: " + << " code=" << value.code() + << ", domain=" << (int)value.domain() + << ", extendedErrorType=" << (int)value.extendedErrorType() + << ", message='" << value.message() << "'" + << ", additionalMessage='" << value.additionalMessage() << "'"; + return QString::fromStdString(stream.str()); +} + +std::ostream &operator<< (std::ostream &stream, + const Esri::ArcGISRuntime::Error &value) +{ + stream << toString(value); + return stream; +} + +} +} + +namespace SituationMap +{ + + + + +class Portal::Private +{ +public: + Private() = default; + ~Private() = default; + + + Esri::ArcGISRuntime::Portal *m_portal = nullptr; +// QTimer *m_suppressTimer = nullptr; + +}; // class Portal::Private +//----------------------------------------------------------------------------- + + + + +//----------------------------------------------------------------------------- + +Portal::Portal(QObject *parent) + : BaseClass(parent) + , d(new Private) + , m_logger(getLogger("Portal")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +Portal::~Portal() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool Portal::init() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d->m_portal; + + QUrl portalUrl(QStringLiteral("http://excgeo20.sl.defence-elec.de/arcgis")); // http://excgeo20.sl.defence-elec.de/arcgis/ + Esri::ArcGISRuntime::Credential credential(QStringLiteral("portaladmin"), QStringLiteral("mout11de")); + d->m_portal = new Esri::ArcGISRuntime::Portal(portalUrl, false); +// d->m_portal = new Esri::ArcGISRuntime::Portal(portalUrl, &credential); + + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::errorOccurred, this, &Portal::onPortalErrorOccurred); + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::doneLoading, this, &Portal::onPortalDoneLoading); + + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::basemapsChanged, this, &Portal::onBasemapsChanged); + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::featuredGroupsChanged, this, &Portal::onFeaturedGroupsChanged); + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::featuredItemsChanged, this, &Portal::onFeaturedItemsChanged); + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::homepageFeaturedContentChanged, this, &Portal::onHomepageFeatureContentChanged); + + d->m_portal->load(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool Portal::fetchData() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_portal || d->m_portal->loadStatus() != Esri::ArcGISRuntime::LoadStatus::Loaded) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " not loaded yet"; + return false; + } + + d->m_portal->fetchBasemaps(); + d->m_portal->fetchFeaturedGroups(); + d->m_portal->fetchFeaturedItems(); + d->m_portal->fetchHomepageFeaturedContent(); + + + // findItems : + Esri::ArcGISRuntime::PortalQueryParametersForItems query; + query.setSearchString(QLatin1String("1==1")); + // query.setTypes(QList() << PortalItemType::WebMap); + + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::findItemsCompleted, this, &Portal::onFindItemsCompleted); + d->m_portal->findItems(query); + + + // findGroups + QString groupsQuery(QLatin1String("1==1")); + Esri::ArcGISRuntime::PortalQueryParametersForGroups findGroupsQueryParameters(groupsQuery); + + connect(d->m_portal, &Esri::ArcGISRuntime::Portal::findGroupsCompleted, this, &Portal::onFindGroupsCompleted); + d->m_portal->findGroups(findGroupsQueryParameters); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + + +void Portal::onPortalErrorOccurred(const Esri::ArcGISRuntime::Error &error) +{ + ERROR_LOG(m_logger) << LOGFUNCTION << " Portal -> EsriError occured: " << error; +} + +void Portal::onPortalDoneLoading(const Esri::ArcGISRuntime::Error &loadError) +{ + if (loadError.isEmpty()) + { + NOTICE_LOG(m_logger) << LOGFUNCTION << " Portal loaded"; + fetchData(); + } else { + ERROR_LOG(m_logger) << LOGFUNCTION << " Portal -> EsriError occured: " << loadError; + } +} + +void Portal::onBasemapsChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + NOTICE_LOG(m_logger) << LOGFUNCTION << " ***** fired"; + + Esri::ArcGISRuntime::BasemapListModel *model = d->m_portal->basemaps(); + if (!model) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " model does not exits"; + return; + } + + for (int i=0; isize(); ++i) + { + Esri::ArcGISRuntime::Basemap *item = model->at(i); + if (!item) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " item is empty"; + continue; + } + NOTICE_LOG(m_logger) << LOGFUNCTION + << " i=" << i + << " name = '" << item->name() << "'" + << " url = '" << item->url().toString() << "'"; + + Esri::ArcGISRuntime::LayerListModel *llm = item->baseLayers(); + if (!llm) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " llm is empty"; + continue; + } + for (int j=0; isize(); ++j) + { + Esri::ArcGISRuntime::Layer *layer = llm->at(j); + if (!layer) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " layer is empty"; + continue; + } + NOTICE_LOG(m_logger) << LOGFUNCTION + << " j=" << j + << " layer = '" << layer->name() << "'" + << " layerType = '" << (int)layer->layerType(); + } // for + + } // for + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} +void Portal::onFeaturedGroupsChanged() +{ + NOTICE_LOG(m_logger) << LOGFUNCTION << " ***** fired"; + + Esri::ArcGISRuntime::PortalGroupListModel *model = d->m_portal->featuredGroups(); + if (!model) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " model does not exits"; + return; + } + + for (int i=0; isize(); ++i) + { + Esri::ArcGISRuntime::PortalGroup *item = model->at(i); + if (!item) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " item is empty"; + continue; + } + NOTICE_LOG(m_logger) << LOGFUNCTION + << " i=" << i + << " title = '" << item->title() << "'" +// << " url = '" << item->url().toString() << "'" + ; + } // for + +} +void Portal::onFeaturedItemsChanged() +{ + NOTICE_LOG(m_logger) << LOGFUNCTION << " ***** fired"; + + Esri::ArcGISRuntime::PortalItemListModel *model = d->m_portal->featuredItems(); + if (!model) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " model does not exits"; + return; + } + + for (int i=0; isize(); ++i) + { + Esri::ArcGISRuntime::PortalItem *item = model->at(i); + if (!item) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " item is empty"; + continue; + } + NOTICE_LOG(m_logger) << LOGFUNCTION + << " i=" << i + << " name = '" << item->name() << "'" + << " serviceUrl = '" << item->serviceUrl().toString() << "'"; + } // for +} +void Portal::onHomepageFeatureContentChanged() +{ + NOTICE_LOG(m_logger) << LOGFUNCTION << " ***** fired"; + + Esri::ArcGISRuntime::PortalItemListModel *model = d->m_portal->homepageFeaturedContent(); + if (!model) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " model does not exits"; + return; + } + + for (int i=0; isize(); ++i) + { + Esri::ArcGISRuntime::PortalItem *item = model->at(i); + if (!item) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " item is empty"; + continue; + } + NOTICE_LOG(m_logger) << LOGFUNCTION + << " i=" << i + << " name = '" << item->name() << "'" + << " serviceUrl = '" << item->serviceUrl().toString() << "'"; + } // for + +} + +void Portal::onFindItemsCompleted(Esri::ArcGISRuntime::PortalQueryResultSetForItems *result) +{ + NOTICE_LOG(m_logger) << LOGFUNCTION << " ***** fired"; + Esri::ArcGISRuntime::PortalItemListModel *model = result->itemResults(); + if (!model) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " model does not exits"; + return; + } + + for (int i=0; isize(); ++i) + { + Esri::ArcGISRuntime::PortalItem *item = model->at(i); + if (!item) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " item is empty"; + continue; + } + NOTICE_LOG(m_logger) << LOGFUNCTION + << " i=" << i + << " name = '" << item->name() << "'" + << " serviceUrl = '" << item->serviceUrl().toString() << "'"; + } // for +} + +void Portal::onFindGroupsCompleted(Esri::ArcGISRuntime::PortalQueryResultSetForGroups *result) +{ + NOTICE_LOG(m_logger) << LOGFUNCTION << " ***** fired"; + Esri::ArcGISRuntime::PortalGroupListModel *model = result->groupResults(); + if (!model) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " model does not exits"; + return; + } + + for (int i=0; isize(); ++i) + { + Esri::ArcGISRuntime::PortalGroup *item = model->at(i); + if (!item) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " item is empty"; + continue; + } + NOTICE_LOG(m_logger) << LOGFUNCTION + << " i=" << i + << " title = '" << item->title() << "'" +// << " toJson() = '" << item->toJson() << "'" + ; + } // for +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.h new file mode 100755 index 000000000..25a2501b9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal.h @@ -0,0 +1,80 @@ +/** + * @RDE_HEADER@ + * SideBar + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, NLSE, 2018-10-08 + */ + +#pragma once + +// Own +#include "portal_export.h" +#include + + +// Qt +#include + + +namespace Esri { +namespace ArcGISRuntime { +class Error; +class PortalQueryResultSetForItems; +class PortalQueryResultSetForGroups; +} +} +class QModelIndex; +class QStandardItem; + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SITUATIONMAPPORTAL_EXPORT Portal : public QObject +{ + using BaseClass = QObject; + + Q_OBJECT + +public: + explicit Portal(QObject *parent = nullptr); + virtual ~Portal(); + + bool init(); + + bool fetchData(); + +signals: + +protected Q_SLOTS: + void onPortalErrorOccurred(const Esri::ArcGISRuntime::Error &error); + void onPortalDoneLoading(const Esri::ArcGISRuntime::Error &loadError); + + void onBasemapsChanged(); + + void onFeaturedGroupsChanged(); + void onFeaturedItemsChanged(); + void onHomepageFeatureContentChanged(); + + void onFindItemsCompleted(Esri::ArcGISRuntime::PortalQueryResultSetForItems* result); + void onFindGroupsCompleted(Esri::ArcGISRuntime::PortalQueryResultSetForGroups* result); + + +private: + +private: + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal_export.h new file mode 100755 index 000000000..d3a43cda5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Portal/portal_export.h @@ -0,0 +1,21 @@ +/** + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef SITUATIONMAPPORTAL_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define SITUATIONMAPPORTAL_EXPORT +# else +# if defined(MAKE_SITUATIONMAPPORTAL_LIB) +# define SITUATIONMAPPORTAL_EXPORT Q_DECL_EXPORT +# else +# define SITUATIONMAPPORTAL_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/compass.png b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/compass.png new file mode 100755 index 0000000000000000000000000000000000000000..0943a7a284550f5849f79aa1abf4120a8239d2fe GIT binary patch literal 2413 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 literal 0 HcmV?d00001 diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/compass.svg b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/compass.svg new file mode 100755 index 000000000..69d0f3b7e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/compass.svg @@ -0,0 +1,81 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/preview_aerial.png b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/preview_aerial.png new file mode 100755 index 0000000000000000000000000000000000000000..602bf28bcad553f344485cb2ad7e9358c959a7e4 GIT binary patch literal 260269 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ÄRz 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ӎ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

    DE-$BZ5{8Oc2+=E2KcEe= zwt41unhv}%xySdz!7;_op_)Lrr|U8fzp?z&hHdiLWe@E7)r~EZJLfTv&hOeL^8`uH zt{&^2vZi5l=YTX}Tr9YksK`Fjb7rbaV`9ojnmB5OxOf@zDy6d0k&hwf$j3XUe|lU@ zsU~6LG5amh!(+(=owG=CZ_lmetzG%b;V(YDlb-jvT(q|3|C*JXvo3;-DCmS75AN13 z2mTx%IXey#F77YhcGYuNDNL8AK?i`Bs10vv<_df@(w{l~rVvT|S5O9Jwj98fnm+J| zw8K#Qj2E3GZ^f0z4Zy<@f4TGYV@^y%66Nz)Y4W~~jd*A@a7C4w(qr`q$Ar@XiS`|S z#;6rArvlp_;QVScqz!+!C6(nHLm)`6 z*$=rcE>O+Xf8`InAg4?O!vF`S&kRyYh5Y}xQ#Ld%21s=|c7Yo7`Cx^;ubca8k=a)Q#bKF4s(&<< z9j-SQsr-`BG8r+l6`Vh~{^1BAvSIRts8A9&JBvku1D!NUf&R%!x`;O+^<3SNk<<~< zq+e{gDdTLK^wYa(`iqN!|G-?2D2N)o$#I%}cK}Q?cISX$Y+w7lM3xm+G;0esIk-@~ zd7Oo1LpCC7x&GuRSr7k`>wZUKGlN!;w2CpPaEg5QuWp&I{o-(4Sa zzS{o$<*m%g^RQ9uR)e?76q(C^@%`d26&MIW_9wkuOLZLJ-q!zJ6IZX2B4Lu6)xR$|=j}v+;YTaBv(nD_c_C3IZ zTEpQXuU@f}_ca)8P729FBz{cqm^_pR4#JInGM+ZmK1Ck+-@V<&U^rX}`2E(YLrTB^ zfIO<(>V|{c0v->J?hW!LnMax=o(1lPi~9$Y34SzR!alKyabOG<^(fJ{ zH*9tfHQj{`Z%6bKXpN0W_R?aCJgEKlq#RWx8?HSL2>eoU19i3e${1I--eHj13-F?~ zY+>Cr+@i9@({vU43uBlj>07syWkoa!_+(`J3v&Q%W~NP(XFpCByjW=5qcP-3ZDL=TTmLSqEn^wt)OG@bA@yLv8| z)r|b`2Q%~0Xq1;|C{+@otcASYf65z$cQ5c}e*QeAm{?uL!HT?yS(w68>R{D*UsDXF zCF#qjQ4_tOe?Pxj?6i2RXuhop+H+_(xPquQ7mITkDdKCAhilpzIvbdNOi{R(^0Q`9=AI3KGtr!Z z6U~2bmzKa&gc~i(twJ#FrmL@SK5Xjh+2u7gQwQ zT~hQ-qv=b70d*cTM$Q7T8>*}VLBHAi1gRbF`pHv~=OgYc0QR=@-u`7xq6U&SDhotJ z%xY@?6p->^lipo}2;p{6$u=`+mE_(+0TDOd6#~*w#pH`;&&)Wu$gxkJ=gywugSk@5 z&MUF2Y4Ki=t)ar7qXh>F(oyWUZ?4ju5mB{PsOeP8-W5GlRj=N*Cg zd5$7w)hNZ9vA#Dc#c^n?PyAKpne{HULiFqHy=&qi;kX^(!nzp@7vEU%TH7iu&grSK z#tbJg=MMhurpNwcU6``FGu`JuFgG$Ujo4Z!*hD#zMq(lCs%1Nm7@ON=JkiaPtyAdY zB4T4*vJ%UE&yn_eJl7FG*!OT`e}4Ksay5~!0YIp}5Jjg2g9$B%@B}uRXz<ax+k!B3)}qPkr8vnqYgpfjg~IKS1MX2BC3y83kdzc+u=(W)6_Z%YKbyYsX3&^N)q z1PZ+g=xNbETd zBCN~`(01kmZWHymC(iez8%9iFMf}!79uYYi5NX*_|gFV zx^tn;wnPK?9^(*tr?GlL$wf$H@$6*t*mz+ zj-g+y@*{=L5Fbdl@m-Mm8Hge`Fpzxze4DG_xuz-Iw)WG19gl9^~X*BP2^h z;4gvnc@L~MQNtgqPJ6c0aD@c?;<1U-8H={KvSJn&S82c7R#dWSR0;}vDpIK)lac{B$ci7CA)KYz*SqVj#kE`NY^Y=#cJ4#$d` zJ?W_`jX7jAQrNZlYHx!9^AJblv} zM`t`I&Qa}j^9A=@PEKY1j*3{P@P0jDjN4BbwYJ4-sc@`HRJBxZkg;~*5dZ}I=qpLQ z^jH-|1Sar+W39S|ripa5O_zc`Ac1TNNg+)m^XrXN9rs zALXm=$k6Gy&n`k2VJgV49|olEdN-(Hf&3#}x4;*8z)tDE>$L-)f={NWShaY8wl@ z(%NQE=cuAY5?F)u7Tl6w6d`%sSB9qDcz zy1syYb+|%jGB9PsH*wBYW0Q=Sk=B0I*WD{R;4UPasI8-;W9v!OI)xaa$>GC=ezENc z7S6z(r2ThjyncA__J3~xJfrR_BT$33UtT`*=9M&&;;GILxgPncJx}4lpK@%8+iwFe zt$&>km1kw;(1uGJdPPxA0@7xxEgo8EA}*0$yRc!qFY@?>^D^3CNM~m!&B`inzst>9 z;QkYJs{HXG;HSn(+t%y*Ki!G1AMbrWIG{LOX&Z(@`f!kVB8i)h#J^41xSkAI4A6vT z@G{V&qPpeg`nv}MRQrAlkZW=RZf)&mS((D+pqeZwZ5+Ud1oiL;Rcr{eRH?BRLnsqz zU}_BYMaXOlHHIfD^AKaE$Ml7Gq`-3nQT(8jhn^&v1hh5GjdcqOBxu3YTmN&+16)D0 zU;hAYEM%(?9S!Eu;C__i9zf%$^G)#<`H6cuZ;IF20_M0=YY@ z!G4%*Fxk4n@Og1bEKYyaB3nQg8iPW1L1L1gZJ_>*HYnT^5)-xhr!0Z%eb?qy(qI5i z-0z$wtUVfHerI)-sIB{j08c%C*nW$R?+c@OJj)DSNT)Nz%HBFW$qZMC9z5Ex(%uR3 z%}BR`<**aq<%q3eTuv*Yik!)RUO*}!4g2Wq;}IRa-}LimZ5S_iBml@rCRJj~wuVS$kw70qrvHM5SdF3_=W{VJd3pHF!l|)E1rze&(8Dp^z zZnBM^*<>aOvy}zL7aD3T6I)a0!lW?R@y#F^)isTh<$4<#OjY>rrFx|BdwsGv+s;{Q z#jV{OvtwspDc(S2*(BG643`#Z3Ws9G$B4cD6rAc&>Wp6tc3k!QKOFgIyS0uQ;tPw5 zZ2a9N!U6rJY-PNcD*9syXCKq3%Qj1pm@3f;?0ZIPG_;V}DdCZY{JyaQzVcF%5^3%; z&58p=3yMFem!xRFcI6K}>s`@SHPF^kWDwwyX}?8`PgPKAEa=O<;XGSa`*^b5$Dew^ zmn3K%_pYB0fgLuG6hov*)QzuQG*>#EagJJU_USlZP(7ct@*~XTDb@rYd?o&E<*%Eo zCPM-=tQ*hdrt7uO-Dnf_=bar(wk0!g+#(aK;&rixJe0ySd^qwaBXaK+JBG6}{RrLc zA^*gv4TdI2`Fyg{R>}DT05Soyw8u5?c>E`tL(^^JBIaH!bK0=Ha1c6*p5dC z)V8)@CM-2x_o%{owO-X~^ksFL##Vc1+r#DeCrurru0El#Eh*!t*tK=0qjs4Nz5Ih0 zT7M|$jbsvJbQ*O|LWOI~)XN1Y^IV|(G0c&CWLW4Ke2*i2IWZGjk<_^Ak~HM~ojfW4 zSv4vDV%+0@@|INkU43SyjPD9ob(lqx#&Z^2REKB>#_?}XE?2A1B&g5M#LwJ*J!W={ zA-ergYr9g0(>gB@Gj>n{7h0N(kFBaAl-;>OPIu|SXl627gZ;UE5e$*CoA+Jg540`x z)phN0yfq8DMhwSrp|KbMOx7~mvhFykTMnpJ^MlZ&4pp~#ME?A0Dwgf`)b~8AW+3#! zC`mtkR5LPxDna9YeY`K>x#izB)-UkH;R0?si_J+?>tZIhSEnNn{~4WXyo!tzr8gN` zf63c(LvH&pG_@aQW~$1#P!t*i2w6Li`2Z%Sfe%`2GPm-{sHPDfuanj227Br!JOa!L zZ$k2og@52q@(Ik%Aw1s#&9#I`LR(vTI#0jS3_A7VO!&*bS!_Rm);L4LE;wANU41?K z=H_IQCNTr=@>_9Z(UE)#Q&(;h-V)vv?TXa{QgVX zZ-OCZly(^3j!(92C2!6yPsaT;6e(vPEKGZztrfee#L9j08(eWm`6Mn@*9+J3UxW61 zbJdZ;N`O%2%1mlo-y9;?h}utRsId94PgPg1wL7_TC$xn|*1FV_frY9xN0-h)1>cf?4B12eLZulK`Xf!q$f+cbdFT0 zDw=H^an<5bQMoZ@#at^5A=>Hdr~1xH1Cst~VlJ^JcT|slz&<&2d&?T<_?;C*v%@|X z2Peh=!sX)f^8fL4mQhiz;Tql`QX(zg3`0swhjfFKQi8w$BA}!+C^>WtHKeq3cS?5( z2na|^ch`A6Yn}6RujN)3&V1kdKF@t$SC=BRf*OMn3DldZ?>O!4?cp>LgmkZj%PJe2 zrG;`B`sqIm2mUm($PE`JuXt^|yjSNd%VYA67x|?TPS@x6#X15xi<>>pFw7asP-Ea< zo<|=DfhlEG>%fF4%ton{2bn-)YvH4-sn>M1+?*)k4{jWQUspt(ERYkUk?R~hU2A86GmrWXfpNw3skTBBEfFC2kx72-R z#4*_6vbm4xqK3d^x-}g;;qo~aLF(aWIp&apxuDvEf)=vpn zf2VtK#3`8wdrX5UIhw6Ou+H4Y{aGjc=t!ZC1y)sG8wkNv9U_oG`C#j?C2p&0Z%J}N zySRBjE7PE+co&em;hEFY@<|x)$kqn3y|psmXyhm{v1i!BpLcN z3nz=GOUcXE5@sv`)R%wd!!6r7t@l#*VPDp+>6CDwL_wWmfv&ii!>N-Pyax@w2fJCT}(^R8zSU~>3re{#?D z-%sgqALD?xvWF@o4L_n=NJIjHiF#Tev^Cd!ws>P}^lM{_G=24?thMLhHf3qq_uA@# zk@oEaTq`p`nAf^aU+~yhXwwJI;-`&%=cesa(RZIXz|z$Yr<5rF-F~)vj?vm+QlInVYj)`_`54 zr?wLs--ot)jGx=o8v!g_>sU5V3}i16(OKNV10WZ;98KGHLo*In=QYKjdU1Y16Fq2& zG=SO^u@7xH9nW$!*d`!X0cm9G;^up0I1MgBHUF1yJ)LBL3mE1ph#;6uhE6(ZLAMsB z3`I!IxPa|;)oi$Y_EBA71yYhR22Oy>etJVaVf{!diLZuv?TP-}uz*7+sE&_qonh`! zG1f6{!h-{f_;=-GU{ZMAjgxx2fsRiH}%!*bnTZlIZV4i!$fH!iu| ztN59kR9`yW(}TWxgr+u$8A$xkwdO@L$$vGM*wz7Ntp4Wn&Z2Jt`_eGQ5a23|Z%>_! zez*1wrGK-*HdV`QZkBEtrz`G-_pByO|2LoP=;f>oO_L|imFt~y(${}$!T)!B+eImF z5@#4oyZ6VLcTVj$G&+Uds2VA4krE7uXK z%#*_SEF>lpIPH6j?e^&|B@oMB)}_A=CUR9cIiyr=3zryEhVj^dA<(n0iVQsy$AqC- zD@lBupG?*oB3uAF${nDXX%`on0>bnTV9K?zu&OvjoogojlK8f2DZeUY~)$r z(2;~q9-Jf9?Tlu+Kj($Z%iu6dapVw0bd%l|AXW6@Li*U|jCy#UhsS-5Q3V?u=KNwX z6yj#iVZ($AZ4fh(Sb=hiD%77AEooX>A(Hmh4nIp`A^{Wfc$@bo>O6$f!3*{E%o`yO zx0fP`%hN5CLO;UqVTz4OGULtybpltC4|Tpjd@q*niS7@U?%UjM=YVMlc%hRV8-|XL zc{lnX&osWVQ`{Lx0W4O~Mpn||zAL6gCIO7sT3}`xADJBhgLcx-swPiP5)Jjkt~#sZnJEQ{L9$5Z-QC@9Hf;MtP=W~F-HX||-)pC*ae8b|zt|x!@59|7 z$|Bb7Ug~v|nSy^2>b|_~nr< zJ0oFdx4?~ziiX7$nDbqbetjkNrJTf)1}!c@xqcsq(Sm#TGo~1=CVq@;jAcvzbPp6v zKBXl_`}+wzWP24U0EBWiyqD3jfn(M-C>tbZVY`4_z*=pncI}T+#e7TPc&7I2b@pOk{W<0zNV#Dge|3y!6Pe|U^v^`w6J@`w$JNWU) ztb&Z532;hzmgA(vtbs^yQiC(yMQQo(y+mfiOMZlO4D1$c^k}Lo8auQ#B$VhASFeL< zj|)bdyMhmVUkVj3U(<+^a+Wu=D%BX|>TvK<;m(RmGPKo5FHuP=f>~hd%b0@dlz>fa z&;Yre;JSYsbSJU7Y4y9&HTb-Iuqxx4*g*+Of;(ASOUB=XWB4}oh`-f3HjL8EiJ+Gk zaCURTg^G&U!H@Ll@Q}cPQC$!=ksc1GK(D0J6c(f34Kva+=(URycDE{=sk>p;SX3iGvBKai&3u!jAs@A;@1KX^1CWx(burj8By4 z+-eCHYiRXf5)>8@RXBQ#eMGF%&#~w>u$+d0lT01jk5g43qxbr~&Nd_)E8r@%KF>5# z9>SmYokS)>zf=E>l|NRMy*W{un!cTk-Sg(PO`qX%(HZP zJ%c5d(b>ru8L0uXfB{q7vpVRyuQ4GRpo}4w{^$B^2R32dVUQ%vs!`)1FOtyn)aWzk zp1qbhP}Z9KDK^l5=PCNO^?dG4$X|+`ouYp`xZkZ#VO+jfFYd=B?}M99k))c$ftE#b zWi#{$_2p6Vxdjq1^h!k@#Hf)0ejAOrM*tzVDH7UPiWszF8ZPr#(xJ-+M7>VP7i3+PC?=X!&IpMS93*Os%K0Qt195pqi zYdhajYbQ+~Vpj07Y6|A*nW8uJZlfN^aN+UCY~Y<}StP}4O^0dk_&G90)l5w_D+V(P zoQ>zMMJ~<{v`c3Cx)7?*iCz?ImFf1c@m{76-eFvX$TK8m6A*}ZU>x|iE4v3OLPA;K z#K-O&lN~9u;Fkc4DR?}2)Px>1g8D?;@RLz-K+t8NiK?imk*d(Ue(*1)f2>c^P+is8 z;)$n7@vm7kFy7A-=oJGl){7oH&$S95SsarGcz;hl6l0q&r{(j@m}AjLC2lxcmcPK* z0t*ANC>^3@4O(#dmKY)ZM!)Awf9}5Go&dlrG%f(duV|_G;YD-!_s@Sygbla9bQiCe zfo8OH1p@_?Vx)ZATW;{+a#_v>cB;K=xib>IqIHsVG3pHeN&Nu=V9jdo(!Ikj^EX$C zZFlp{r@x)h(xV`QwkGvMKaN<|QvevU+@u z8+$+$)eDz(Ime}82u8~bg`3Z=O}azVP}Wsrl8Ez?`m}_FHh_6{>nZxKQFB)s2420_ zIve1;kIZ5)$MuNk)0PRN;mnHUpa$0*k`5n3;%r6r+@^7PM-RPZ>M{RJ;C6r=^_Ai^pE!eEb9E ztJNQ#8cd1Yeaxt;B1%Y935UvrS4)*>uE%HiJXRvGJ~QAbBM}xBr7LFrxgGyFh{>Ac z`;!Iwew}2XVb(U*@6rapDH!SlVTO6wXJ5TZ!1FS>D!Zpyx}L84KC@$M_sPlGBh-Af z&yug&@OfTBU<@3g%!CxiUAoTp9i3@Aclll5&k#(q{mv%N3f(RHw8Z5N^#=rL$Rpi3EnA>h|%WLi6ug& zfx$uIV_|fE6iUhz#aLMm5`_dAAP9AStB`=UaUu*#LF{izxsj^hb=bm0D`fo|{@eIz z0GpT~ziy$?wh2`PhXKX1+1%!8Re?7z6D)SO!VOx~eYuU^Z=*lTn~qbj3nmbJMI7J% z-mluL=CmDjcYCK#ha+5Gp{w1M98aW#w>1#n22wxHrLW^_MjUVJW zG(WvaYT#@c|8imwA7+!X1}>DQX~5|XioQ)L^Oun-*#@iZChieY2TSdJ5MfbCQxUR? zbQ1;|T%s9sxB(nY%ucqf(k(0vh@Z&aUwyhKsgNbp4U3KZ0BV`%c#+5kry!!R5W8_2 zXLmQ}KE9cBn72E*F1|w=>-stqdZ&aZ-o>w_C963>*6u18|07p3gvtilDLPIN@H5Jx z8I5A-=S}U|dwD3gM)#gs!FX9mb`*9G#8-OuJlEdL2wDFW78BpdH}MYCEF0{)Ab9ju zRZJ>>+^pc$pW@kfGUUPb&U|kr8nkb#R5SC8u=Bq}hr(GFbre&ekPtr;d; zh-$J9+fzR{Nd#d;4>ut?$PblEw!0b$4D1Zuec9^jpi=v__zUD+@L%hU&{9|F7_Urb z-0^QluxVQj*nT+aTJO~}WaA@63srf%_;BdGfd=02jBJE0ni(ENBXtkg8vP_%r$N*Z za=-?dH6oGx(vJ7{qNmu-_E^yO#2Y3jr`z#+X?`=`XZr-Fm#FcM2Hbek`W<)9sR{cb z>^3&ejp9M8JW7oHk~jGeaTkm3q)K0yX?^{sj(JVx%6{43vFxL>tX*Kwn{n8)!{oLL#gDAd07F{#dda#Lz@7?-VqvFS_IRwwj3dz zuOMZM#@w~_og<0I1(LwG(TbUQ4e0d z+ZN&eZ^q(4q#6fIUph{%qfnYNBm4E+dKAzgT7Ct$g+4+7gF3I#&D-5nBY`mUdARgT z{7WDPh>0ro?TetK1th?FuPefQ=H9}%QOaTpFI0h6cM~mA&s;P>N%IN+Kln_VICn!(s zRNt^Tsw(~i@&EPF^SE6ZHbfCuo%&KU16*-;a@>&o-7vaD`zqhOJ8zeA<3z2u%>)h@id4mm<+O*3W}j zm3ux}bz+M#vyJR0J{CIpl4%};xusX@-%B-_P8i9dvrpvIHNMPfhLwig;d3!UpJa`Y zqMgae43_y0SB#Y=BWRL@LNN8J$g9fx#GcVNG|B%WB2OkV@Tc*%QLUL=Tl^uDoGlTf z8k=236v_T9;1&EOF>6`r*xm)ai-8I#NqqF=bmO}<<#fw!pD<;O^C_TJ_N^vny!*Mu z{i4^_=y#7UWNv|m%g8F4{jR0CCp#EV ziJWyrLmkxQF)nDzW4U7cbFVH(o?Bl2jlrCZ>7UJ((W*ex?%gPM45iSl5KobB`q7yk ztYfnYe!l6x>+T~(`F$&?!YE%4CW{5(-9&E2?d6~R=p45Oo`Femg&wLD?8B^4vm2Jp z2g0ykFp}fsYCdEIwBh0LBc5(8EVQp>zGpVKO-nc8&T82+d)#Ts)rLeXiYD!3|EeR!%A z3$m4v6)b~c^vMDx)u_W?Q$=gqtV1E)+jDGOKHGAU8tS0=%t$rOvUaj|!a6cCQf_yH zKt2Djlba=88SLlsHgHBy;PPhme86zi>Bev7X_Io~1}}XNR@l1*om>5^U^1wSmASM0 zI9dPcNruD+MJPoCT>&<8KJzJtx3R~$c5z8bMO&Rl2Y0>aCN9w=B(0x2Wf;5&0pe%O zwi@1T#-H*$A#w26dBOU^uNPM;m;fugcj5ELLE2hyG*&^G0(p_Y|`5QHJ%y-E7h zsE?k9IL|$*t*ael5Xpi#dH4k7?ex*d@0Czrmz<-Lkw=+p*cl9yT-oAoO;p*GS;eA* zu7A`=Bj!{%xS^=zV+O*^4B~upzltRvH82~${A&=mVjZ-~sjjnO`4CQ5I#I;$wpXB0 zkwebGG8-%WD1lKB}P$>=#|KgOR{u=B2w2=-PwVI!56`gkipWA&SUzMZ~v z9!ZQ*dc_Xnc?l1>FpdaOBZ59qD@4m0Yp@!#AA9d}Vv6hj#MW7Z_jU9LOd1lCGqqi% z!@&sWr9tWUqA=9KQXtZyc%B@A-mm9p<`6=8agGdpAw2}|X82BzrH^0lvCa#oW#E-g zp>^r(lXmSMF>91lT`}xtrmb!{8)C&WWTB3Rq5qU9grY0U0Uv@pG9t7ipvXx&-t?&Q z3dczTd&Xq>BixNG zh|BF%p`{y@&voosMAZIoV09_~?4CH{2LHwpy23ZZh#34cBo6ZU>2)vTWIG5P1I7Fx zQjnntj5^^$8opFycp4TxQ#eheW?zEP^(9H-YXOH~k|>kg2RDzZe5L&p<%53jf@1Ho zPbQv^DPBH$Uks6Rh+*l^u_8`}hafc-SIOsiG8@v40-4|43_JvOU>i~>$=Fo92L;a9 z|1kL*DO-GQjCD=+boJvs^SBDTo0`jREn3;asT9D(D!QTD%+R?R$_Ce$PKSRy`Qjyk)|D)B|Mj z@8y*>=-DZfKWde6MNeo!)D>q3vY%~Ura23Q@i_^D*k?gS*Ig4HzBGa0p`P*5JAS95 zuU@$wBli-TRKu(KwKDXHy~??6mzVS^gJD!OyGIda4>UoAD;P9NghwmGJN0}iUW?Mi zGL>>qW~1NpQ*dyW{JA3Fo6poK9&2gtaoG8nc!C2Br~9F`j>*wvty^uWaIrOjQJBiV?zs9 z*F>+(_Mf#e5G?C0gY}lNGZxqeb}&R)CzRx){yLq#8Bk;*< zpPJ_2;GZ^r^)ql4;*X*$O40>*)8w-ia z?(_yWaaQ&~(m)tL;#ZWt6)x*SyOR-BZ+Dt-4j(yNJg*;1){Y2-+sRUu8Za)$B82I+ zRnb6loBhckxPxy;h}@Y*A^Fk{&U7B@BJ}n2zUi3C zP_LlL=?Y0m$P+M8AWXaw!Os{<(c=-s;DoS>F#mkvy7^)tna+wE%4b#Ju^DrxL_!R?5hU7 zXw-`-n-^MnUoG_87RB#~CI>1xjArEUnT&eVJhu}Pz%VCRg9eykSO=PKT;g+`x6H~f1yxss@{Efn#3W@1 zADf|p4;XNsgN8aT9$tCzPpT5ec6(6#kGHalhS0$|N=kT7yh3vsGHI6^P)b)bzf*+f zeA{BnGacLe%suHIfEEjpPk>lXvS!f{p<$J-N|=}wuej4h$UoB8toMD8Sl0?Usp8Yo z-x$wU;NajGI?2}T>gYh#$dCXf1A+jaO2}x2{})IkmfwF{*EWU+79}`EPX?0;M`o9? ztcM{;Ar2k3VlIvtmHZ!gWJaGWIEXw&EY_kLQgOaNe~}a(^fQzx5)b!KCZk~U@P5H$ z#RE4qZ*0H1+)BV|llgY~lW#oj-QQp%&m)nx%e6O($wBc-2-#>gi_55oRo{1+2H8tH zhX~q-q>uSBC9l^9+wPvJi>l3aa;mSLG?`pJ-(S27uz6D(()@+pFh$JgHfcEJ4J+3R z7G{y4VreoNVxW9gio=KU_zC5!;9xvLdr6Gu$1VH%8SmAVUTGok!so;|iQcN$Y!>?q zZNZk`Q&VY)LaYSFLcVnE+SYnDIFG$Sr6R){``h4Gpob0=onMa*-+mRDAbcf-!@1zZCIu{R2qNisV63? z`~d9d7J)p1lZ4#}>bZ9R#o1b`5qEjW{%tD0Cfr%Qk)SU6)q&kNk`Miq;0D z{P|oxHYYc)p(x*5b2QBy@7d)ku(c0fdO*Rr@w}}n^Y!oa_bzr$7K(i3@>nxw3+LMD zg(G*4u}!pkK5*G}zjAxH%cv|bS8Om^F7Q%f! z07?Fc|3}EG8gCU*y#gr^SvjfIUpD`*Km4sBa;>#KJJv+Nd ztr$*A{KSY(P;~aV_RUO4zCcixS(Lofy9ZnO?AN^xR=ZTY+WMuVc#=bD2iZIhR58Qr zWVq-;LchJ1(X+P#hWFGWN7dcV&h74xFqW)r?4Gs6byjaIhPf{KxN^3AKo^ZlKiVA! z+MSi@CtQ_vzU}s6YqYI5)UDT|ZQb?)Qd%$x?*~Tkk+4!w_#WFAOzYhjVaRoOYM&S# zAkRCxt(4XEp1b43n-|Fu%fUw#rDv>fmOiY0DE3L>qcTJ3P8KEltaEbrX3o#}Zi~hR zmEGAmRSAE~?`!PtIFrC$T-jO?DPDuM`rq40xZfA{nbLR3tOKJ{2}uF{w#ub?bfFBY zopc$anTouuGM&86*PRlbvt&}*^$iVbgx#0MiZxnt_>Tb?P56fkH8eAr8KOf%y7k!; z@Vm5vez@>_HYLYH}e1)=H}SA9tPY7q{FHMY9!O zM=C>-hw4=w3vk0Fkp0Sy+rM#~WSMN>^0hnA!db2NLL7P?aK0rR%7z%wLmU5%W<$ZM zBgdH1&v4D7Nw(a83tlhJHr!AmNu!mmwv1}^4D_Mu0DtzXWD2B1Afx(QnsMb#Rh{1? z|18ukt=NCcV8I4|&TFT1pCotASV&cUwJf1O%ZRHSVi`V?GrE0E;ksBallq@uU&ft@ zuX?V5rKv+qyOZ1T8rH+1tM4edsDK5mr4UsXD_fu!id0e&tUw1_DK>ijil1(fx3F+R z&uDGg3XgSd(+bGW25Q(q>xSbwm-#%xT}VVp@9o<_*y3dl8(Tqj_tll~xm&BDC@>VX z?hvxBo#afCfNEo}M*8umhhX2R_z`;thp#M{0jUKk!w$dp*DHB`EYv?vWhu# zBqS!ba=8|Fb3w~Z6 z73Ny<`r3OTnaxKHzT?(EN!;8H_>^bGh9{x9wvo!20ki=CzMqlbS}$7xv=HP~O-;Q& zYp=F9eGrpXlUED3f1?E#n>#UpLK!k#WwBF$n#Dq-K#+YzrWwoq{E3|Qkr&=s!`!Mc z?&>()h%-2=5GR;XM-CJS%(tMd3tjx$Zmq#|A#0(+d6C0E)9p_-)Ydi`n|C}wcqnE< zTlM;pgy^r#g`Kgzv%WXVVjkB4ndXyq%yB#5@FCYWW5G`Y`U;K0T-RbEqD&^Gubz`& zBH)(!e8C3<X5vE;Wnt8nM6^# zgXm(wlkjeL6Oy0eC!}wLn?CzLEZnuB(g6-10jALr=Lw>2*4;a2qCh`Ojl+Q)DwF zk7!8E_;N6sFUM{d1IKDT$R1#O5`peyCv|&9KURg_EscEG^SzS<%GzzG57I+0-WAWK z2VCFF;D=Vx9fNlH3I*Jp%=xLzkm!XBM7>~O0}iUJjNKP)7vi;obz7esbp~~vJ6D`C9;gPN z4BnpJWgJnq9mxUlw6K^&&u635S>n~JNVNnQ^gJ%R^N~D)Ai0!YnXnMbh)q~n_=S#v z{e-&A@;g~x{~}max1Ny4kv0-0%|)w%&|rFgep%gHD=!4z<@~{1-?~HZPui$=x^3$h@!g}1 zG{Nt$3uUF?ndCr4Off}4`>_3nY!+wJ$|~;Uu{s_uQZ4^|p|S`fbK&Fo_XGl>!?$>@ zA~M9Xjmev%M2b#h7p0l|}jG||+w)XANT*~MmQ*D+?2$M|v`Eb`j0 zJncjUxuNJS7;!r|8le~r-H9gd%AjzadYMIqD7Ox zpbJntGuc?+6MZ#F7jF-F1bLe~zXLL)l_I@|pKrtqBPawDW*S<&seJ{qU+j+W8uL;| z=$jasiPbcmP1vQ2&zt_D&17X$W~k6B_M;?ZWoWTX@o7@nS@VkkJHk7Hk(2NhJfG7i z0s^(a=EeVjgWJ&&25n_yIgW8(#4sq$K)(}OrICca5fBy;&rRiWyoM!)CjS`HQx(S} z4q=HL*B(VKSEBiSi64BY0R?pP$}Rrvrx0#I{bj?;OgeLR_V9+~$&!;d@9wWk4+#N8q1i z2J$i8tTnml#%p{O5i!w!P0q180Im%zA+U2%nGu{wqlA7~hod>l<`oN9A7Ly<%!ISp z4E^~dsvCEkPlU?~DBvWsUamsyC;4Rj1>)}R5rCKT`6{!ZRi`|+ES0I{RyZRoR4$vM zKSC8qm~Pm`5t0E17G_ccoLHrgOpK7SVAe!Uk} ze_Nj{dGEPi_T$~h?99*S=%fL{0yv{Wd#EE`L9pIV!#jmZ9? zz!y3>DwD8uGEZ4Z6K}naFFS@=MKxXQn?*rY(^w1Qy8CCz<=Ny)^zfYf>{7!4Q+U^#^xLU!6 zOAx%4H}kE)Ki}gGISrrJ*4BYd(DLqWY9%r+Ct21Mo=~Ak>iBPd z^ZR$BA6-8n_&f6j#;dQ2O2pfSbZbQbFkCDfDJ$%_4<2{SG@X#BZ>XO=e=vT4N2Vd| zN0;L{YYN<->?!kTRpb?vV-hyY3figM-(HYcIyrXjxGvHaB@c~=vZxQu=7iA(d@meu z`;fgOl2!c8n&P}m{)Nl==Zo+0>^kY}q>wRCn|kU$3-emb6F3bDyid|P|ZdL)uEURyQ`x@$qQ*{*3n;|Ysz?2%IM%as~7%oQ1 zWAhDm=Du*dNI*HoWW3n;l|F1&9Ndx}Le%qBvR`Mrf9@%tN197QadQpgXNPiKXU%gf zUjFgyGL!Zk>()Sp$g<^P?`<%Jojr!>=FM8u4-jrvp)?ppRwK=R?jVL??@f#=s~e@O zrWj7e@@heiBV2(IRO^j}K79>QdZm`;)fyn4TG8SVADkf$vvwhnTAz6nxL#v{=1{6= z!4c+I8t|zpM~7NYt*Wt6dEh4x4q|eWuXL$CMBD`{J0QyUo`@}%SKBkxBCpTV$+onl zs-faXFRhQPwR?=G>p`WKNjrH&gKbR=ETlm2lc}=G2_7mISmD5wGG{-oUODj=P%^1P z1-d($t^Qa^-mrFSMI!m6ry#a()?9rKF2-3(VN0Gt&_Yhh|-2K zACS|57m0elj0mFpLJZ_=XPtwZSm`YC8a&P@9LO9Tyn^sY zm#zKi==ynSutR9g1_;N0LT$F!j;-a-lVpiCrQ^d+_|%gX;wZ^0*NM9gzJ`T4y-pA$ zi-P7%x=$wv9*XPfFRhIKkdUE&NB5YRT{zN^G!<7wugHZ;#gwWZGjepw%-^dfRPw}{ z7{HD7ukWzT@K5Hp1<09YRb(9-sEAw#sye9$B6!KwdOy0jpLEY-#(aT}H2tl#_Me>e zo=(`7Mew-1wHkF%w5T*PGNKEiD`FrW<~zqbpPgImu!|aF2x)idCkgx2D$^jrbxs$; zqa_k#k+sR;g~Ayu!=NNPuEsV+5lS>B-t^}>{<+_n%#1V5Z_lBWx+OWR$@g>N&GE~N z!L9{s18b)^nR^HU?Hp$!SYmJ#&8w2<_yJg1{8q*m6W9sqhlfYFJYetR{~Z(J(0-OO zv&p8t=H%lDGCkSj-w~{^BlC<8CD^UOl4&ahE9epqso>d!g$l4Sl;|N0^q-S|x4K7E zO=^R$2H@APi!NO~T+Y2|RaKsL+v!n5-^}lmjSF-C*mg~Lks+2-+1#FSm*G3y0#s)w zQ4;H`H&Mbu4Qu`yG)K-4;U){7p?g@xwp?S)wvKxP&u-hTtTxH{z=PfGck>zcYU%LA z3ig5&-iutmL{az{)8XHG7VF*miv9$O2EZ+b|c>V$F-MTO-#tc?Q7Q#|m1|@6;!XlNT zYV{PwFhRZIR-^pA-|O-ar4W-tdgjjpot zf{{o~v?Z|z@fp>EPgKpYi@LhTFUenwCTxd@jQfCT3rfind~06ifp(X-14ybF(iuH= zoq{dvgt|Il8Nl+t|MH9NIP^0+R#$4g#q(4K4;aaA@pwogxEA2fRhyDqiJa3*HW*Z`FGEnINP zjYuv|6BZ&3=o#)tZ4MDP8Q#hgDNH!xJ%}I898<{g4<`6>SUWpAf9HNzy(yYG%8vf= z9<6;~8#}}27%NENVYq356TI>*ZPJgZPOnER2* zpj#H^Y~5KEa`JMN^`ld={b=Vn-uN<3E_wI~gJjoF)#Obzj>#-Nq(*F@8H(oz#sLOT zjGNQzw}61NdK7o`zU$k(xrIXvaF%Wlu(kLkN@uoPSIM;)tdFG42>)7oaG8HCkjR8> zCW|!LBoQAq=O&9Dwv2Hs+PVm>Ve_92FcEJY)sVc8zPTAK@BYGWlhR!4?QiYc?q1d@ z>2r)CV-?EhlvF@ig&ZuGTv?wl<2~~p()#MzG-@A9<~p|7_K;R~bo_*ikcj{f5AW|B zk!>$?+VG@9keKjXersi)y;#=F0U;JD__B4EJoVVLqZoW5=5|V+oC!4b_K%_>_SF1@ zWZxF7$`fR$Gc;2n5}%&m*nz1_oM|i~$O44Nj)!8+vRge2(WQQ7@^+wzkGpRbGdiWRRQk$?XD0d01B z@x*L|cX4(17_E5{)P{10r7_5oLdZPDpjNTwRSqA8gSQQlj23>DuvppTL1le&*RM4C zcwVu8edVoYue-;MB?7hL-K1vcBO35q%wc|(XUAxf7)sJ7$AnCXm0nu!_9su?{CLX4 z6mM1v|IXrUMP#sw;yCbUaa0l{#3fK(KV5Pwj4E*?KId795qc zRC}K>-ex@7x#N=>-o$IJ>>Drg& zSy=P&v5IOBPkTmczkVh=#VR2CT;>O|31+Jq0X) zK}^GrB#|^E8y;s0rgLzHSO$XFce&5g_xHaQVn@iQ%?{-m6dqsxFuy2}S4!?gwsFh42TanQxn z_r&D1b$MqwK))n=)f|l;=m`Z<7?E6DT!4_*)aKG2)_Q})fiOh^rY`|H~3vezU zX{nf^{``( zt@C3P$O^~tUS73myvIccyr6v_k0>uD4F+r+g-4+cVpB#0JOpDnh*gAkG#a>W=f2Jy z|IVn);6vr;{JG=3K&I7v4)IgnIQw9t-Tg0h#_sX`?-c)HP@Qo$RC7+TQw9KfO7M$q0J zGCK#08w?AC7Jp-NGPBBkEv+RRYTMf6)J#iG3+*bpL=laKtS$#S?h zZtlf!JjI*e0{t2n{fs|$JiO7Y!tz;xjdg;GF7ys%M}?g~_z_rG8C1yT&q@=^+K#T< zj&&-Taj-^|Qm9(tt~)*(eF6+*EiI2ow!?n=96cIN+~MF#fX_q_WLe%&)9)tM+p;{K@`L5oB_jU?aKZoFI!YEG8y3qy390xH{f~H?5+wTD3Kr zF@yn4>YEm)WYnsdxw{1tQAMPq+Z`gy{}#9se?FBZP?bqpHLw)GU?xR#=*ni+7=QXy zPWKYjy*n;iAKp$+Pgkf?cfTT6lh>Q}+Kht`oOuL7aCw;Q^Kpanz>=xkE4}@Mb*=Iz z+cWI=$H}Cmy9czdI1_oEBS1g*sam4@-zW?Ss$%TW+#FlmW%rHOR+vdwn#fw!<5c9J ze^UmC5ddz1D{K7V>9zPSa`6nUa&CwF3E}9; z_t%47=Z6w1Dq+r^;+0oav03)c_I=yiY*toQ99&#ZKtU~$p>XZb)pC=LFPj62@79Vp zDD4}$3#N+XVpTzZ@jL?FzBk&^jp!7&%*Oh(;FA_f%S{uZw8PqZ*}Vi4aQXg%;(z6b zz#_}WWzr=5_sZ+GDC%mb?Z%K$#=_L%@d9^$K3|`sYTf*DPka%ky6o!-_q*;lLZZSl zgcgN1DwwhGf~{Yx4Wag*9U?y<$zF%f)S7hAj(*<=&4E!5Dvmccul4V9wsJ6hV1sb~ zJ=;igLH#vJes>go-2ssQ_zLu~n6s+r`r24#hyDS}zw#vOQ#+@`GLFWe8C+ULKdoyG zy*v(Rf)x~+kFHbnER)#{4XJ2pPl|wt*4#YM@&@%ycsH)o+n-qr4AYm%-jEF zU+J5?HTAVh9QhU&D_|Yjq(k9yjX;^q9KIf4BMoK-iK!J;E0#RskKoK4>DzMH zU+m|v9lP`En%kMD6^Ld_QbeP1li>nV%c#2swk9!8+6Wziu#mW=w>hF@*e+dcR%S*O zXag6lC$~Pk?M=-C^3%UZCZacL_SbK^jKPz`qs~pPmXMcz=JPO)Fd^I+iY`-OAKD#;id0kB3q19DtV2 zZ39c?ZwvwQD5R<#7<>@-a+GmQ?l@y1l6>l-EOH5C2}Vya5%}{?8=W0POgLU2jhaG5 zQ&vQ1S_Wx4@A2o;E30n8`(e`NnP2Hb&@|hJT)4HNTy3|!Q5n1T7M>~7vyTs&ub`9t z0sU^Osa=4D6|J)()Zt(SN+V%Hi{az2zCN0hkv0n!{LI)>1t-z>p^+qDz znPyxC-wXs5n3d3cYA)b|mMLK52ZT7z!*q#U_$ZAQnhAWn5+2GWub`)>wRL6XVDG5g zr)_9#CPr2c(!}`aMs1(dj$qQ^Up8+*t>bnJ$La$ga;~erFMZ6`*pCHZ5odqf{t@BK zqI;LOIkcs*zp!4VfEN-Qt;(r^-y`OA7~OgIh~f;p#v?G!f)Je(W0Sd9FhOr9k;ger zJ>UNWMS!Uym7HQ;qRo!PNfarA)%U+L zg{jTx|Ir7_ecb>l?Y^Vb`^WM$3mCf? zgxN71h7Nr1D5+@DkGr|9|YU)YP-a1>C~ItQ~Yz z!q}X{{ZGxq8ot-svYP92Q#({N)byBGTgCnH?3AaT-uu5BIy3HOGme-ajx^ltA845K z^MAXq;%<_XI<0f!Ak{Y>W0daD6*>8pBGnlcMero<;y_xd&zTo$@i(b4!~%4S^lA1$ zPHuf(mM6fpucrrE`LB_{TR?obPxe+2*ie4F>v+$PBFn)ZmPfAoTcThS`|9D&(y9eQ|_y-f;dGQ3`jsV*YdI zx4n4c(X!#;D4~iyL$S45sZ=y8>iN|pDSr#UvJBKqwIAH?&qPB)K9&vk&$9abU9IOl z{|Qwh^k|`9P`4V!iiBi-FyA|Ndw-Q8WnhzN*uOAejie!k;)f6E_X=Dzn{>$=V}%f!(m;08}Pcm7*0 zxxiC}sQ(__SDLXqF7k=c^i3i5t9hGB`u4m2g+uc_bBln%c9|UG$)bO>G)Gry2HlDo zf86hQMJ`wHtPiN@aEf5s;I8YLQd}(4=5mMOF`75~f!nIVo;98X0reU^eMAH3H%vOQ$1Bsx~G>4USKSeZHt2@*;$q4t$7R}Ez` zemIKXr<~20pjxFX>Sz+>GF-+-?cD!g7GMrhjA^XMn*u&yPIYdgxter145;?FzBY{u z>nG@hYIIE9$vhz9nFvXce{Us?EvEjv!zre5=^nNFU!u!^iO`p^JF!$LfwaPEowcb6 z(+Yd=uFX77dFhi8g>()L5*AxNS>KVUY33oo@)(yq5tnX9$;B)+C+s6w^u>GqB~xeN zjw6=?VyU55_t{MIaifSRTtn|mRxL;-Sx^+f$$^yW$XkmvEj`OHuS6hg%3n2sOz|Uj zGemo~;W&)9cPHFr1Y)nN!#s4=ImOdwu@kXVijOtahYHlg<-`Z%5wtj4XrsqyFO_ABosyqQ z74QvA6Duk)M9@Wpef9lu8m&{$$H^y!~Yw;XouO0c9RkF}Id z^r!VqE|v1^6+U+v>uV5`4bMtPBAvmqYH$z<|7F)0Rq$f_aB;yyympCEV+1*~bVvO` z;i;b7^GihMwez}3=!fcHq4etPGP*}wcesT!QqqewwD{4{y^u|iO%SYLmL4Q}g zWeclxMDDusye-x%uW9X6`RJG+n@ToE^eJ1|)W-vzF*Nx*SK)-kZ!ZT)i zm(YrSzMW>y>~k>T0QDrx7Gfw{rBJ8uLNA;8ko&n_vMs8_`SIiMV4hFeZwmEToIZU5 zViSz^sdy`hvJIRrDMx|u^BI#EepPd`T!(6(Bh}xB{jh)g2hKY^OdWd~BEk$bG1Yn{ z({>Hv1w^19M;L)7a(j%YtE&r1h|ZM~r=q6KBj~O92(qOh=ss`(NZjB^c0b<1|ybbf&)cE$4R-!7VX3FgVEkJBZ!ymOYb#@ zjxv!%7+;y9VTl`O^cSMr&Pu*!I>q>?sRYT!0YpUtMs(1odiik8`*0w*ls9WVZR4g| zcZ3XXlPpH`SHH2~&a{Gdo0%-b@jiTRkSkR0ai!Ljg5-=I&-$Gmdek|Kw$4hued~L& zMoXju4G-?M{rGVlE1QT~SIxmYPOGsuWc-<=SQpXJzX?JqG6!V$+Ke}q zqQh(vI?<9ac^HLMV!ub4oTejHloc${cvrTj%f%voWFkR0U6n0G_3arR(J!z!Q}<@S zN@c-X|HTzgbs|47*X`(#+Z2M2A#k;vM*m>`ue)R^kQUH(CqvLo17{s9wuL zk=sQI%L?0fSmgYD`C&Qq*4Ux^8}9aD?J{_BjQM=o!+oUqu%sC?45imY;l@=y!Vmo- zD^uO%{qCV^ZsGp^1w+xNtb>-%w6tbRl2Zvn2oO8OrWW*3^iVOeWGIJ$(4;NzfSWh* zs~$x0O;bbIBb-+KvEg8ENXVKzh-ns-kj2XR!J!l}^ym-*esO4GACFH_Y}^i-*(y53 zmv}}!y}_lYB`z}jB@l{Wg_e_SB}Kv5p7PMTVriFt;q|h$)nBV+Zf!g7e@(6|ZTr14 zX_D0aq4wiR2qfFMT1)rq?XCOO)C%s!r^e=5&k$Q7sm7^pSAEpUUZg(NP@#-&2dG1Cn0SiB1mE*KzX6bK^Fwr}scdy!hFcRgJ9fQtVGfTlmR4 z3z@D)eZi3?Mi_(VNip-Q)nlI6H3%zPiOXldn;9=nk}^L`%X#jo>tXVKJsK|y`bfy9 zWq7TU;^2Ujew_ocsR`j*6SBuack|r5F9G<#pG-=9jpsdRz@Z@$CRQASoIJ3^;(yOA zZrp|-6+?YUNTblQV~R@pAZ(Ekhqd|Vr^De>Rg=3W&rSJy&R{@Vmq+_Gl3qQ5$I~xV znrt`$iynlPE|JtrRCCl~^X^x-?{h`hcjpCWc{z+#EN@S<+{iG55nYn%=8}^PKb{er z5oW-S6+`E2r7P5XdIvKyf~6uT-)nEyII#*$h+z5Z{C^tsfimA7K=Yw%_hn0w{8O*1JDQUhr(CcN1;!BTMY8;m1f zkqLC3)|ZX&UKLDza2>MPET*)v*{b?Z4zLFG_0JMZh`A9)ZfRV1>V&jQch^QZk9 zHz*tE{~3gcHLvr9y>7gc+0B7uGMFVCypo+aZkYzgy6Qe^&1zvueygVdtF63hz9y7r z7Yr{8__mAnml?9N>-6j7e)WuSLUVIRbRV9L_e;09hBv#j?$<71omUHo$0GnNI(p&T zzp_GKVjDU>u0%1;gxL9l`dJq0-qNbX^PDk5i90)%za$HE;brq=p@b53Qj&5#$3j2j z1^n)?QBSAz<_zAwGxGC$B9(Cbh7?oP?D^~&U~HtEK!BEMX%Fyb`1ys45O&CBZQ;R{ zZWNocQ84t^n>BZ{$73u=1npqqH-!e38v5T&1aT9g1Tm68NYMYI7|2R$XG}OL*7!Lp zJI^V?!ou2+6M`_UvgA>7;vwUnbSv;gG=o;mDF|pBDLVm?Nk= zzC<`z*XAVX;rt~~K;&6-*)4hp%(>9C@(J-RPbj!u{(61?1;hveeh@ImbW?Q=!MStU zf6A(FG{-OG=HifH6kh$?d%7m4@sXt3`uup=Hc`@)(b!m(`_ay0@cnXf@>a}$E0M@( zQOjBbtEp)TP-(Bw6ARyIq}=5MKzy^_ugJPL|M>B>cy@bEPdwNAbv`SYxSutXK}AE*J6d?S57BKl1G^5h7o$RH@SC1N~; zObLqMbB;L(*j+esDg1oD8B*k`+vn%#rQp@teS>j+1d(v1)nS;xy4Z`D20 zr9VPNwO1B!u+6;x_EcSzuH`HCb}jv5FIh-RL2~(z%4d!)ac1ydYhf5 zJ|#poYnfZ9y?al#z;}%djjhgnFqgu}ZZBVA_Ia|L}-V|BQcTTcc&=w8)URu}k zLW@M3OnCq&duvv`gm@zaU2nE+fieo?8IoxbeAw>$OoQC zQ^D;dEbd-^Ixy_YIB1HY!-m0F%@-=# zB;~?w*H*NSIR4CB6(&H9P`BX$(W!MO4~lYLfL$RQh<1mCZg~Xv8rM|~Sn;jLb>5o) zy_=i-Z#?j4mp}WXi1A$yx?QJ3rWt&AWtb~RsBLmpJ>`s@G=iBM@DJcwMKfPSs51AN zn)OH)YU0J=sWx2BN|0mDX7A5d&|v>TuTDSN#$v>NY(y}TA~VULO!2=1>Z2WIyzNHm zx(_c%*a2k-X5V(t7GagHEpmaqscC7UoDuzOx1QvQS6F&#?|id!+v!P^kmqT*LF<2r ze#8!E7SMo~fpir9LPN9b9trm-G)Z~QgQnxqd1sn&x1J*45bQ7l3szg{BH0pI0N`+M zYZenNOq@PwPX{eP&@TnS?9!soq4%4PB7z9FUllDt6A}WIZAz68HthQGO#B_$!l!f( zrgmRiHNtcX4K%Snq@J0s2M4Jd7Ftf0rOJ-0KzbM`c; z`cSe>;#p1|4z2KS#*it#mK}UdY$DU4v1-m})*8H7Ufzt;F!zAL4a!-oxB+k`#gC9V zmI|;ajv$Fz4TFACj^h*lyESsgPOKnhJGgmy8IM@g_%%EGL!2IvF&t3wq|~fuGbL4r znQGd+x14Wx+n1`eCaN2e(dQ1~R8hwcUYRaT3S3#m`emj~Br8^AV!;kN5;@~yrYo|< zx|&0lgqOael_IOioCnl1>7c?5w$CJaBHMw`s`fiHv

    IT%o4)+vj=JS1cVmx_MI{ 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 KMNUMnLSTYV*y literal 0 HcmV?d00001 diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/preview_topographic.png b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/preview_topographic.png new file mode 100755 index 0000000000000000000000000000000000000000..2596282ecc6ff3f2f797c1b67b9b3d70e6d6f12f GIT binary patch literal 179683 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

    S;@7@}K6RW>Ye*!- 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 literal 0 HcmV?d00001 diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/pushPinRedClipArt.png b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/pushPinRedClipArt.png new file mode 100755 index 0000000000000000000000000000000000000000..d88e7c97a6ccbb563aa013671223faa2392cb832 GIT binary patch literal 17793 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 literal 0 HcmV?d00001 diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/situation_map.qrc b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/situation_map.qrc new file mode 100755 index 000000000..16f269476 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Resources/situation_map.qrc @@ -0,0 +1,7 @@ + + + preview_aerial.png + preview_topographic.png + pushPinRedClipArt.png + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/README.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/README.txt new file mode 100755 index 000000000..2d9c60c4b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/README.txt @@ -0,0 +1,34 @@ +// @RDE_HEADER@ +// Notizen zu den Rubberbands +// $Id$ +// $HeadURL$ +// $Author$ +// Created by Norbert Thoden, SLSE, 2023-08-26 + +Notizen zu den Rubberbands + +- Sie wurden von Jörg Turla erstellt + +- 2023: Erweiterung durch Steve Kemayou and Norbert Thoden + +Norbert Thoden, 08/2023 : +- Habe ein neues RB erstellt: RubberbandMultiEllipseFeature +- Vorlage war RubberbandMultiPointFeature (Polyline) + Weiterer Stützpunte habe nicht mehr die Linie verlängtert, sondern neue Ellipse/Kreise eingefügt. + +- API & Name wurden überwiegend für Ellipse konzipiert, getestet wurde aber im Wesentlichen mit Kreis (constrainQuadratic=true9 + + Probleme/ToDo sind u.a: + - Screenresolution führt dazu, das bei eng gefassten Constraints (z.B. radius =5m, min/Max ebenfalls bei 5m) + die checkMinMaxRadiusConstraint nicht bestehen und als Fehler betrachtet werden... :-( + + - Setzen der Predefines Values (setCenterAndRadii) unterstützt noch keine Constraints + + + Empfehlung für neues RB + - Bedeutung erlernen von: selectAction, moveAction, movePickedAction und ihrer returnValues. + - Hilfsfile für rotations methoden etc. um ständiges kopieren (von Lmabdas!?) zu vermeiden. + - ... + +: Norbert Thoden, 08/2023: + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abort.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abort.xpm new file mode 100755 index 000000000..62a95633d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abort.xpm @@ -0,0 +1,149 @@ +/* XPM */ +static const char * abort_xpm[] = { +"24 24 122 2", +" c None", +". c #E98774", +"+ c #F6866F", +"@ c #FB6C50", +"# c #F28773", +"$ c #F8856E", +"% c #EF8672", +"& c #F5846E", +"* c #F96346", +"= c #F96042", +"- c #F96649", +"; c #F28670", +"> c #8E5246", +", c #F7836C", +"' c #F96245", +") c #F9674B", +"! c #F18570", +"~ c #F3836D", +"{ c #F76245", +"] c #F75F41", +"^ c #F76447", +"/ c #F2846E", +"( c #945549", +"_ c #F7806A", +": c #F76143", +"< c #F76548", +"[ c #F0856F", +"} c #E47B66", +"| c #F56043", +"1 c #F55D3F", +"2 c #F56144", +"3 c #9A574A", +"4 c #F57E67", +"5 c #F55E41", +"6 c #F56245", +"7 c #D1725F", +"8 c #E55539", +"9 c #F35A3C", +"0 c #F35D40", +"a c #F27F68", +"b c #9F5A4C", +"c c #F47B63", +"d c #F35B3E", +"e c #D44E34", +"f c #E35337", +"g c #F1583A", +"h c #F15B3D", +"i c #F17D66", +"j c #F37860", +"k c #F1593B", +"l c #D14C32", +"m c #DF5034", +"n c #EE5638", +"o c #EE583A", +"p c #EE5739", +"q c #CC4A30", +"r c #DC4D31", +"s c #EC5335", +"t c #CA472D", +"u c #D94B2F", +"v c #E95133", +"w c #C7452C", +"x c #DF4B2E", +"y c #E74E30", +"z c #CE462B", +"A c #A45949", +"B c #E55235", +"C c #E44C2E", +"D c #E35A3F", +"E c #975244", +"F c #E76950", +"G c #E2492B", +"H c #E24B2D", +"I c #E47059", +"J c #83473C", +"K c #E36A52", +"L c #DF4628", +"M c #E04A2C", +"N c #E06F59", +"O c #E16C55", +"P c #DC4528", +"Q c #DC4426", +"R c #DD482B", +"S c #DC6F59", +"T c #DD6C56", +"U c #DA4326", +"V c #DA4123", +"W c #D74022", +"X c #D84123", +"Y c #DB472A", +"Z c #D96E59", +"` c #DA6A55", +" . c #D94325", +".. c #D83F21", +"+. c #D13D20", +"@. c #D63F21", +"#. c #DA472B", +"$. c #D46C58", +"%. c #D26A55", +"&. c #D64326", +"*. c #D53D1F", +"=. c #CF3B1E", +"-. c #D33C1F", +";. c #D7482B", +">. c #C36552", +",. c #D03A1D", +"'. c #D33B1D", +"). c #CB391C", +"!. c #D13A1D", +"~. c #CA381C", +"{. c #CD381A", +"]. c #D2391B", +"^. c #CB371A", +"/. c #D0381B", +"(. c #C7361A", +"_. c #CA3518", +":. c #D03719", +"<. c #C93518", +"[. c #CD3619", +"}. c #C43417", +"|. c #BF3216", +"1. c #BD3116", +" ", +" . . ", +" + @ # $ @ % ", +" & * = - ; > , ' = ) ! ", +" ~ { ] ] ] ^ / ( _ : ] ] ] < [ ", +" } | 1 1 1 1 1 2 ~ 3 4 5 1 1 1 1 1 6 7 ", +" 8 9 9 9 9 9 9 0 a b c d 9 9 9 9 9 9 e ", +" f g g g g g g h i j k g g g g g g l ", +" m n n n n n n o p n n n n n n q ", +" r s s s s s s s s s s s s t ", +" u v v v v v v v v v v w ", +" x y y y y y y y y z ", +" A B C C C C C C C C D ", +" E F G G G G G G G G G H I ", +" J K L L L L L L L L L L L M N ", +" O P Q Q Q Q Q Q Q Q Q Q Q Q R S ", +" T U V V V V V V W X V V V V V V Y Z ", +" ` .............+. @.............#.$. ", +" %.&.*.*.*.*.*.*.=. -.*.*.*.*.*.*.;.>. ", +" ,.'.'.'.'.'.). !.'.'.'.'.'.~. ", +" {.].].].^. /.].].].(. ", +" _.:.<. [.:.}. ", +" |. 1. ", +" "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.cpp new file mode 100755 index 000000000..595957822 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.cpp @@ -0,0 +1,291 @@ +/** +** +** @RDE_HEADER@ +** Abstract Event Dispatcher for Qt5 +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 28.04.2003, JT : First edition for use with Qt4 in START Feature Editor fee +** 25.03.2015, JT : Refactoring for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "abstract_event_dispatcher.h" +#include "gestures.h" + +#include + +#include +#include + + +namespace SituationMap { namespace RB +{ + + static QHash> &runningDispatcher() + { + static QHash> hashTable; + return hashTable; + } // runningDispatcher() + + + QGestureRecognizer * AbstractEventDispatcher::_applyGestureDetection = nullptr; + unsigned int AbstractEventDispatcher::_applyGestureID = 0; + + + AbstractEventDispatcher::AbstractEventDispatcher( QWidget * getEventsFrom ) + : QObject(getEventsFrom) + , _getEventsFrom(getEventsFrom) + , _logger(getLogger("EventDispatcher")) + , _cursorIsInWidget(false) + , _synthesisTouchFromMouseEvents(false) + , _deleteRequested(false) + , _hasGrab(false) + , _finishFlag(false) + , _activeEvent(false) + , _dispatcherCursor() + , _whatsThisBackup() + , _eventActions(nullptr) + { + DEBUG_LOG(_logger) << LOGFUNCTION << " " << this; + initDispatcher(); + } /* AbstractEventDispatcher::AbstractEventDispatcher */ + + + AbstractEventDispatcher::AbstractEventDispatcher( QWidget * getEventsFrom, EventActionInterface * eventActions ) + : QObject(getEventsFrom) + , _getEventsFrom(getEventsFrom) + , _logger(getLogger("EventDispatcher")) + , _cursorIsInWidget(false) + , _synthesisTouchFromMouseEvents(false) + , _deleteRequested(false) + , _hasGrab(false) + , _finishFlag(false) + , _activeEvent(false) + , _dispatcherCursor() + , _whatsThisBackup() + , _eventActions(eventActions) + + { + DEBUG_LOG(_logger) << LOGFUNCTION << " " << this; + initDispatcher(); + } /* AbstractEventDispatcher::AbstractEventDispatcher */ + + + void AbstractEventDispatcher::initDispatcher() + { + _restoreCursor = _getEventsFrom->testAttribute(Qt::WA_SetCursor); + + if (_restoreCursor) + { + _cursorToRestore = getCursorFromWidget(); + + DEBUG_LOG(_logger) << LOGFUNCTION << " this=" << this << " get cursorToRestore"; + } + _restoreMouseTracking = _getEventsFrom->hasMouseTracking(); + +// { +// bool activeEvent = _activeEvent; +// _activeEvent = true; // don't process events while init has not been finished +// QCoreApplication::process Events(); // remove events from event queue; in which circumstances this is necessary? +// _activeEvent = activeEvent; +// } + + _getEventsFrom->setMouseTracking(true); + _getEventsFrom->raise(); + _getEventsFrom->installEventFilter(this); + + _synthesisTouchFromMouseEvents = QCoreApplication::testAttribute( Qt::AA_SynthesizeTouchForUnhandledMouseEvents ); + QCoreApplication::setAttribute( Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false ); + QCoreApplication::setAttribute( Qt::AA_SynthesizeMouseForUnhandledTouchEvents, true ); + + static const Qt::GestureType gestures[] = { Qt::GestureType::TapGesture + , Qt::GestureType::TapAndHoldGesture + , Qt::GestureType::PanGesture + , Qt::GestureType::PinchGesture + , Qt::GestureType::SwipeGesture }; + + for (auto gestureType : gestures) _getEventsFrom->grabGesture(gestureType); + + if (_applyGestureDetection == nullptr) + { + _applyGestureDetection = new ApplyGestureRecognizer(); + _applyGestureID = QGestureRecognizer::registerRecognizer( _applyGestureDetection ); + } + + _getEventsFrom->grabGesture( (Qt::GestureType)_applyGestureID ); + _getEventsFrom->setAttribute( Qt::WA_AcceptTouchEvents, true ); + + runningDispatcher()[_getEventsFrom].append(this); + } /* AbstractEventDispatcher::initDispatcher */ + + + AbstractEventDispatcher::~AbstractEventDispatcher() + { + DEBUG_LOG(_logger) << LOGFUNCTION << " " << this; + + _finishFlag = true; + + if (_getEventsFrom) + { + _getEventsFrom->removeEventFilter(this); + _getEventsFrom->setMouseTracking( _restoreMouseTracking ); + + ungrab(); + + if (!_whatsThisBackup.isNull()) + { + _getEventsFrom->setWhatsThis(_whatsThisBackup); + } + runningDispatcher()[_getEventsFrom].removeAll(this); + } + + QCoreApplication::setAttribute( Qt::AA_SynthesizeTouchForUnhandledMouseEvents, _synthesisTouchFromMouseEvents ); + + } /* AbstractEventDispatcher::~AbstractEventDispatcher */ + + + unsigned int AbstractEventDispatcher::numberOfEventDispatcher() const + { + return static_cast( runningDispatcher()[_getEventsFrom].size() ); + } /* AbstractEventDispatcher::numberOfEventDispatcher */ + + + void AbstractEventDispatcher::onDo_delete() + { + if (_deleteRequested) return; + _deleteRequested = true; + QObject::deleteLater(); + } /* AbstractEventDispatcher::onDo_delete */ + + + void AbstractEventDispatcher::KickTheBucket() + { + if (_getEventsFrom == nullptr) return; + + _finishFlag = true; + + unlinkEventDispatcher(); + + _getEventsFrom->removeEventFilter(this); + _getEventsFrom->setMouseTracking( _restoreMouseTracking ); + + ungrab(); + + if (!_whatsThisBackup.isNull()) + { + _getEventsFrom->setWhatsThis(_whatsThisBackup); + } + + runningDispatcher()[_getEventsFrom].removeAll(this); + _getEventsFrom = nullptr; + + emit signalKickTheBucket(); + +// QCoreApplication::sendPostedEvents(); +// QCoreApplication::process Events(); // dispatch window system events + + onDo_delete(); + + } /* AbstractEventDispatcher::KickTheBucket */ + + + void AbstractEventDispatcher::onDo_kickTheBucket() { KickTheBucket(); } + + + void AbstractEventDispatcher::setWhatsThis(const QString & whatsThis) + { + if (_whatsThisBackup.isNull()) + { + _whatsThisBackup = _getEventsFrom->whatsThis(); + } + _getEventsFrom->setWhatsThis(whatsThis); + } /* AbstractEventDispatcher::setWhatsThis */ + + + + void AbstractEventDispatcher::grab() + { + if (_hasGrab) return; + _hasGrab = true; + DEBUG_LOG(_logger) << LOGFUNCTION << " this=" << this << " set dispatcherCursor = " << (int)_dispatcherCursor.shape(); + setCursorToWidget( _dispatcherCursor ); + _getEventsFrom -> setFocus(); + } /* AbstractEventDispatcher::Grab */ + + + void AbstractEventDispatcher::ungrab() + { + if (!_hasGrab) return; + + _hasGrab = false; + if (_restoreCursor) + { + DEBUG_LOG(_logger) << LOGFUNCTION << " this=" << this << " set cursorToRestore = " << (int)_cursorToRestore.shape(); + setCursorToWidget( _cursorToRestore ); + } + else _getEventsFrom -> unsetCursor(); + + _getEventsFrom -> clearFocus(); + + } /* AbstractEventDispatcher::Ungrab */ + + + bool AbstractEventDispatcher::eventFilter( QObject * widget, QEvent * e ) + { + if (widget != ((QObject*)_getEventsFrom)) return false; + + if (_logger.isDebugEnabled()) + { + switch (e->type()) + { + case QEvent::CursorChange: + { + QCursor cursor = _getEventsFrom->cursor(); + DEBUG_LOG(_logger) << LOGFUNCTION << " Cursor has changed, this = " << this << " cursor = " << cursor.shape(); + } break; + + case QEvent::Enter : + { + _cursorIsInWidget = true; + QCursor cursor = _getEventsFrom->cursor(); + DEBUG_LOG(_logger) << LOGFUNCTION << " Enter event, this = " << this << " cursor = " << cursor.shape(); + } break; + + case QEvent::Leave : + { + _cursorIsInWidget = false; + QCursor cursor = _getEventsFrom->cursor(); + DEBUG_LOG(_logger) << LOGFUNCTION << " Leave event, this = " << " cursor = " << cursor.shape(); + } break; + + default: break; + } + } + return false; + } /* AbstractEventDispatcher::eventFilter */ + + + void AbstractEventDispatcher::setCursorToWidget( const QCursor & cursor ) { _getEventsFrom -> setCursor( cursor ); } + + QCursor AbstractEventDispatcher::getCursorFromWidget() const { return _getEventsFrom->cursor(); } + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.h new file mode 100755 index 000000000..687fcd433 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/abstract_event_dispatcher.h @@ -0,0 +1,234 @@ +/** +** +** @RDE_HEADER@ +** Abstract Event Dispatcher for Qt5 +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 28.04.2003, JT : First edition for use with Qt4 in START Feature Editor fee +** 25.03.2015, JT : Refactoring for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include +#include +#include + +#include + +class QEvent; +class QWidget; +class QGestureRecognizer; + + +namespace SituationMap { namespace RB +{ + + /** + @class AbstractEventDispatcher abstract_event_dispatcher.h + @brief Base class of Qt event filter implementations to handle user interactions of map window + + An EventDispatcher installs an event filter on a QWidget to receive all events that are send to + the widget. Depending on the type of event and the state of the event dispatcher the event will + be processed by the dispatcher and stopped or forwarded to the QWidget. In case of processing + the event methodes of the EventActionInterface are called to controll a rubberband. + + @see EventDelegater, EventDispatcher, EventMouseNavigator + */ + class AbstractEventDispatcher : public QObject + { + friend class EventDelegater; + + Q_OBJECT + + public: + + /** + @brief Logical interface between EventDispatcher and Rubberband + Defines interface functions the EventDispatcher will call to control a rubberband. The rubberband class + is derived from EventActionInterface and overrides the interface functions to implement them. The mapping + user interaction to interface action is defined the derived classes of AbstractEventDispatcher. + */ + class EventActionInterface + { + public: + + EventActionInterface() = default; + virtual ~EventActionInterface() = default; + + virtual void unlinkEventDispatcher ( AbstractEventDispatcher * causer ) = 0; + virtual void resetAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); } + virtual bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool multiSelectActionPress ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool multiSelectActionRelease( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool applyAndEndAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool abortAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + virtual bool buttonPositionAction ( AbstractEventDispatcher * causer, int x, int y ) { Q_UNUSED(causer); Q_UNUSED(x); Q_UNUSED(y); return false; } + + virtual void userActionFinished() = 0; + + }; /* EventActionInterface */ + + /** + @name Construction & Destruction + @{ + */ + explicit AbstractEventDispatcher( QWidget * getEventsFrom ); + explicit AbstractEventDispatcher( QWidget * getEventsFrom, EventActionInterface* ); + + virtual ~AbstractEventDispatcher(); + + AbstractEventDispatcher(const AbstractEventDispatcher &) = delete; + AbstractEventDispatcher & operator = (const AbstractEventDispatcher &) = delete; + /** @} */ + + /** + Conceal deleteLater slot. Use onDo_delete() instead to avoid "QCoreApplication::postEvent: Unexpected null receiver" warning. + */ + void deleteLater() = delete; + + /** + Provides the number of EventDispatcher installed to the QWidget of this EventDispatcher + */ + unsigned int numberOfEventDispatcher() const; + + inline void setEventActions(EventActionInterface * eventActions) { _eventActions = eventActions; } + + inline bool hasGrab() const { return _hasGrab; } + + inline void setCursor(const QCursor & cursor) { _dispatcherCursor = cursor; } + + void setWhatsThis(const QString & whatsThis); + + void grab(); + void ungrab(); + + + public slots: + + /** + @brief Slot calling internal \c KickTheBucket methode + + Calls internal methode \c KickTheBucket. Be careful using this function. Should only be used + from implementations of \c EventActionInterface methodes or \c EventDelegater class. + */ + void onDo_kickTheBucket(); + + /** + @brief Slot calling \c deleteLater function of QObject + + Never use \c delete directly for EventDispatcher class. Instead of use \c onDo_delete() function. + */ + void onDo_delete(); + + protected: + + QWidget * _getEventsFrom; + LOGGER_T _logger; + bool _cursorIsInWidget; + + inline bool hasFinished() { return _finishFlag; } + inline bool hasActiveEvent() { return _activeEvent; } + + inline void setActiveEvent(bool val) { _activeEvent = val; } + + /** + @brief Destroying EventDispatcher as result of an incoming event. + + As result of an incomming event (e.g. Keypress ESC) the EventDispatcher will stop, emiting signal \c signalKickTheBucket() + and calls methode \c EventActionInterface::unlinkEventDispatcher() before setup \c deleteLater() call to destroy it self. + */ + virtual void KickTheBucket(); + + + inline void unlinkEventDispatcher() + { + if (_eventActions) + { + _eventActions -> unlinkEventDispatcher( this ); + _eventActions = nullptr; + } + } /* unlinkEventDispatcher */ + + + inline bool callEventAction( bool( EventActionInterface::*actionMethodePtr)( AbstractEventDispatcher*,int,int), int x, int y ) + { + if (_eventActions == nullptr) return false; + return (_eventActions->*actionMethodePtr)( this, x, y ); + } /* callEventAction */ + + + /** + Event filter function to override to implement event handling. To workaround a cursor handling bug in QGraphicsView it is + necessary to call the eventFilter function of this base class, which will monitor enter and leave events. + */ + virtual bool eventFilter( QObject * widget, QEvent * e ); + + inline EventActionInterface * getEventActions() { return _eventActions; } + + void setCursorToWidget( const QCursor & cursor ); + QCursor getCursorFromWidget() const; + + bool _synthesisTouchFromMouseEvents; + + static unsigned int _applyGestureID; + + private: + + void initDispatcher(); + + bool _deleteRequested; + + bool _hasGrab; + bool _finishFlag; + + bool _restoreCursor; + bool _restoreMouseTracking; + QCursor _cursorToRestore; + + bool _activeEvent; + + QCursor _dispatcherCursor; + QString _whatsThisBackup; + + EventActionInterface * _eventActions; + + + + static QGestureRecognizer * _applyGestureDetection; + + signals: + + void signalKickTheBucket(); + + }; /* EventDispatcher */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/apply.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/apply.xpm new file mode 100755 index 000000000..cd1a26d20 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/apply.xpm @@ -0,0 +1,110 @@ +/* XPM */ +static const char * apply_xpm[] = { +"24 24 83 1", +" c None", +". c #7FBD7C", +"+ c #8BCF88", +"@ c #84C582", +"# c #588456", +"$ c #63BE5F", +"% c #57B953", +"& c #73BF70", +"* c #56B752", +"= c #499B45", +"- c #699F66", +"; c #59B755", +"> c #54B550", +", c #52B14E", +"' c #6ABC67", +") c #52B34E", +"! c #377334", +"~ c #6FAF6C", +"{ c #50B24C", +"] c #4FB14B", +"^ c #48A144", +"/ c #4C734A", +"( c #5DB559", +"_ c #4DAE49", +": c #4CAD48", +"< c #6BB569", +"[ c #4AAC46", +"} c #377F35", +"| c #5E915C", +"1 c #50AD4C", +"2 c #48A944", +"3 c #44A040", +"4 c #61B05E", +"5 c #45A641", +"6 c #527F50", +"7 c #7BBF78", +"8 c #78BA75", +"9 c #639C60", +"0 c #46A542", +"a c #42A33E", +"b c #368633", +"c c #389035", +"d c #3FA13B", +"e c #43A33F", +"f c #619B5E", +"g c #56AC53", +"h c #3D9D39", +"i c #3B9C37", +"j c #3C9E38", +"k c #54A951", +"l c #64A761", +"m c #3D9E39", +"n c #2A6B26", +"o c #3A9B35", +"p c #3B9B36", +"q c #62A55F", +"r c #4B7749", +"s c #48A243", +"t c #33892F", +"u c #328B2F", +"v c #379833", +"w c #459F41", +"x c #4F7E4D", +"y c #5CA659", +"z c #369632", +"A c #349530", +"B c #349630", +"C c #59A955", +"D c #3A9936", +"E c #276C25", +"F c #256C22", +"G c #32932E", +"H c #2F892B", +"I c #2C8728", +"J c #2F912B", +"K c #2F902B", +"L c #2D8E29", +"M c #2D8F29", +"N c #257322", +"O c #226E1F", +"P c #2B8D27", +"Q c #298826", +"R c #288424", +" ", +" ", +" .+++@ ", +" #$%%% ", +" &***= ", +" -;>>, ", +" ')))! ", +" ~{]]^ ", +" /(__: ", +" <[[[} ", +" |1223 ", +" 4555 ", +" 67778 90aab ", +" cddef gddh ", +" ijjk lmjj ", +" noopq rsoot ", +" uvvwxyvvz ", +" ABBCDBBE ", +" FGGGGGH ", +" IJJJK ", +" LMMN ", +" OPQ ", +" R ", +" "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/constrainsMarker.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/constrainsMarker.xpm new file mode 100755 index 000000000..516636927 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/constrainsMarker.xpm @@ -0,0 +1,200 @@ +/* XPM */ +static const char * constrainsMarker_xpm[] = { +"24 24 173 2", +" c None", +". c #AE6557", +"+ c #DF826F", +"@ c #F18C78", +"# c #FE947F", +"$ c #FD937E", +"% c #EC8976", +"& c #E08270", +"* c #A35F51", +"= c #C17060", +"- c #F88C77", +"; c #FD7F67", +"> c #FC7257", +", c #FC6A4E", +"' c #FC6345", +") c #FC6446", +"! c #FC6C50", +"~ c #FD8269", +"{ c #B86A5B", +"] c #EA8672", +"^ c #FC7A61", +"/ c #FB6548", +"( c #FB6244", +"_ c #F45F42", +": c #E85B3F", +"< c #EF5D41", +"[ c #FC7C64", +"} c #EA8673", +"| c #EF846F", +"1 c #F9694D", +"2 c #F96042", +"3 c #F55E41", +"4 c #BB4832", +"5 c #C04A33", +"6 c #F65F41", +"7 c #EE8571", +"8 c #E9816C", +"9 c #F76548", +"0 c #F75E40", +"a c #F66143", +"b c #E6573C", +"c c #F76649", +"d c #E67F6B", +"e c #B86959", +"f c #F56549", +"g c #F45C3E", +"h c #E8583B", +"i c #F45D3F", +"j c #F27D66", +"k c #E25539", +"l c #F5674B", +"m c #F0725A", +"n c #F2593B", +"o c #EE583A", +"p c #B8442D", +"q c #F25C3F", +"r c #EA7C67", +"s c #EE7259", +"t c #A95E4F", +"u c #EF593B", +"v c #EF5638", +"w c #B4432D", +"x c #CF4B30", +"y c #F05D41", +"z c #DF7A66", +"A c #B9452E", +"B c #EF593C", +"C c #A75C4E", +"D c #D36650", +"E c #EC5436", +"F c #DA4E32", +"G c #ED6044", +"H c #CE7361", +"I c #D16550", +"J c #E35E44", +"K c #EA5133", +"L c #DF4D31", +"M c #E24E31", +"N c #EC6449", +"O c #B36354", +"P c #DF5D43", +"Q c #E75436", +"R c #E74E30", +"S c #D04D34", +"T c #E44D2F", +"U c #EB6A51", +"V c #8D4E42", +"W c #D54D32", +"X c #E45539", +"Y c #E34C2E", +"Z c #E44B2D", +"` c #CE5138", +" . c #E76F57", +".. c #D45036", +"+. c #E14E30", +"@. c #D94729", +"#. c #E1492A", +"$. c #D85339", +"%. c #A0341E", +"&. c #E14B2D", +"*. c #E0725B", +"=. c #DB5236", +"-. c #D8492C", +";. c #C24329", +">. c #DE4628", +",. c #E04F32", +"'. c #BB3B22", +"). c #DF4C2F", +"!. c #D56F5B", +"~. c #C2432A", +"{. c #963B28", +"]. c #DC4325", +"^. c #B25C4B", +"/. c #C93D22", +"(. c #DE4F33", +"_. c #C86A57", +":. c #B95D4A", +"<. c #913927", +"[. c #D53F21", +"}. c #D94022", +"|. c #DC5B42", +"1. c #CF3D21", +"2. c #DD5338", +"3. c #B25F4F", +"4. c #DB583F", +"5. c #9D311B", +"6. c #D73E20", +"7. c #D84123", +"8. c #D16A55", +"9. c #D33D1F", +"0. c #DD5A40", +"a. c #D86B55", +"b. c #D74023", +"c. c #9A2F1B", +"d. c #C3371C", +"e. c #D43C1E", +"f. c #D7472B", +"g. c #D06C58", +"h. c #D33C1E", +"i. c #D64326", +"j. c #C4381C", +"k. c #C7361A", +"l. c #D2391B", +"m. c #D54529", +"n. c #DB6B56", +"o. c #B35E4D", +"p. c #B85441", +"q. c #D0391C", +"r. c #CA371A", +"s. c #C13418", +"t. c #D0381A", +"u. c #D13A1D", +"v. c #D65339", +"w. c #DC6A53", +"x. c #D7715D", +"y. c #CF6C59", +"z. c #DC735E", +"A. c #DC6953", +"B. c #D64F35", +"C. c #BD3318", +"D. c #972812", +"E. c #CC3518", +"F. c #CF3618", +"G. c #D03C1E", +"H. c #D24226", +"I. c #D0381B", +"J. c #CA3517", +"K. c #912611", +"L. c #B12E14", +"M. c #BE3115", +"N. c #CA3417", +"O. c #CC3517", +"P. c #AF2D14", +" ", +" . + @ # $ % & * ", +" = - ; > , ' ) ! > ~ - { ", +" ] ^ / ( ( _ : : < ( ( / [ } ", +" | 1 2 3 4 5 6 2 1 7 ", +" 8 9 0 0 a b 0 c d ", +" e f g h g i j k g l e ", +" m n o p n q r o n s ", +" t u v w x v y z A v B C ", +" D E E F E G H E E I ", +" J K L M K N O M K P ", +" Q R S T R U V W R X ", +" Y Z ` Z Z . ..Z +. ", +" @.#.$. %.#.&.*. =.#.-. ", +" ;.>.,. '.>.).!. ).>.~. ", +" {.].].^. /.].(._. :.].].<. ", +" [.}.|. 1.}.2.3. 4.}.[. ", +" 5.6.7.8. 9.6.0.a.b.6.c. ", +" d.e.f.g. h.e.i.e.j. ", +" k.l.m.n.o. p.q.l.l.r. ", +" s.t.u.v.w.x.y.y.z.A.B.u.t.C. ", +" D.E.F.F.G.H.H.I.F.F.J.K. ", +" L.M.N.O.M.P. ", +" "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.cpp new file mode 100755 index 000000000..d108b2937 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.cpp @@ -0,0 +1,127 @@ + +/** +** +** @RDE_HEADER@ +** Definition of ellipse drawable +** $Id$ +** $HeadURL$ +** $Author$ +*/ + +#include "drawable_ellipse.h" + +#include +#include + + +namespace SituationMap { namespace RB +{ + + DrawableEllipse::DrawableEllipse(Esri::ArcGISRuntime::MapGraphicsView * map) : DrawableEllipse(map,map->scene()) {;} + + + DrawableEllipse::DrawableEllipse(Esri::ArcGISRuntime::MapGraphicsView * map, QGraphicsScene * scene) + : RubberbandDrawable(map, scene) + , _elliItem(nullptr) + , _center() + , _radiusX(0) + , _radiusY(0) + , _elliStyle() + , _sinRect(0) + , _cosRect(1) + , _sectorStart() + , _sectorEnd() + { + _elliItem = new QGraphicsEllipseItem(); + _scene -> addItem( _elliItem ); + makeLineStyle( false, _elliStyle ); + _elliItem -> setPen( _elliStyle ); + + _sectItem = new QGraphicsEllipseItem(); + _scene -> addItem( _sectItem ); + makeLineStyle( false, _elliStyle ); + _sectItem -> setPen( _elliStyle ); + } /* DrawableLine::DrawableLine */ + + + DrawableEllipse::~DrawableEllipse() + { + if (_scene) + { + if (_elliItem) _scene->removeItem(_elliItem); + if (_sectItem) _scene->removeItem(_sectItem); + delete _elliItem; + delete _sectItem; + } + _elliItem = nullptr; + _sectItem = nullptr; + } /* DrawableEllipse::~DrawableEllipse */ + + + void DrawableEllipse::updateGraphic() + { + auto length = []( const QPointF & p ) -> qreal { return sqrt(p.x()*p.x()+p.y()*p.y()); }; + + qreal mapRotatePhi = _mapView->mapRotation() * MathConst::pi / 180.0; + + qreal sinMap = sin(mapRotatePhi); + qreal cosMap = cos(mapRotatePhi); + + qreal sinPhi = _sinRect * cosMap + _cosRect * sinMap; + qreal cosPhi = _cosRect * cosMap - _sinRect * sinMap; + + _center.mapToScene(_mapView); + + QPointF mapCenter( _center._mapCoordinate.x(), _center._mapCoordinate.y() ); + + RubberbandCoordinate x0y0(_mapView, convert( mapCenter+QPointF(-_radiusX,-_radiusY), _mapView->map()->spatialReference()) ); + RubberbandCoordinate x1y0(_mapView, convert( mapCenter+QPointF( _radiusX,-_radiusY), _mapView->map()->spatialReference()) ); + RubberbandCoordinate x0y1(_mapView, convert( mapCenter+QPointF(-_radiusX, _radiusY), _mapView->map()->spatialReference()) ); + + qreal width = length(x1y0._sceneCoordinate-x0y0._sceneCoordinate); + qreal height = length(x0y1._sceneCoordinate-x0y0._sceneCoordinate); + + makeLineStyle( false, _elliStyle ); + + QColor fillColor = transparentFillColor(); + //fillColor.setAlpha(0); + + QPen pen(_drawColor); + pen.setWidth(_lineWidth); + + if (_sectorStart != _sectorEnd) + { + auto getAngle = [](qreal sinPhi, qreal cosPhi) -> int + { + qreal phi = acos( cosPhi ); + if (sinPhi < 0) phi = MathConst::pi_two - phi; + return int( (phi * 180.0 / MathConst::pi) * 16); + }; // getAngle + + int startAngle = getAngle(_sectorStart.y(), _sectorStart.x()); + int endAngle = getAngle( _sectorEnd.y(), _sectorEnd.x()); + int spanAngle = (endAngle > startAngle) ? endAngle-startAngle : 5760-startAngle+endAngle; + + _sectItem -> setRect( -width*0.5, -height*0.5, width, height ); + _sectItem -> setPen( pen ); + _sectItem -> setBrush( (fillColor.alpha() > 0) ? QBrush(fillColor) : QBrush() ); + _sectItem -> setTransform( QTransform( cosPhi, -sinPhi, sinPhi, cosPhi, _center._sceneCoordinate.x(), _center._sceneCoordinate.y() )); + _sectItem -> setStartAngle( startAngle ); + _sectItem -> setSpanAngle( spanAngle ); + _sectItem -> setVisible( true ); + + fillColor.setAlphaF(fillColor.alphaF() * 0.5 ); + } + else _sectItem -> setVisible( false ); + + _elliItem -> setRect( -width*0.5, -height*0.5, width, height ); + _elliItem -> setPen(pen); + _elliItem -> setBrush( (fillColor.alpha() > 0) ? QBrush(fillColor) : QBrush() ); + _elliItem -> setTransform( QTransform( cosPhi, -sinPhi, sinPhi, cosPhi, _center._sceneCoordinate.x(), _center._sceneCoordinate.y() )); + + } /* DrawableEllipse::updateGraphic */ + + void DrawableEllipse::adjustCoordinates() { updateGraphic(); } + void DrawableEllipse::adjustWindow( const QRectF& ) { updateGraphic(); } + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.h new file mode 100755 index 000000000..d53f4a347 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_ellipse.h @@ -0,0 +1,72 @@ +/** +** +** @RDE_HEADER@ +** Definition of ellipse drawable +** $Id$ +** $HeadURL$ +** $Author$ +*/ + +#pragma once + + +#include "rubberband_drawable.h" + +#include + + +namespace SituationMap { namespace RB +{ + class DrawableEllipse : public RubberbandDrawable + { + Q_OBJECT + public: + + explicit DrawableEllipse( Esri::ArcGISRuntime::MapGraphicsView * ); + explicit DrawableEllipse( Esri::ArcGISRuntime::MapGraphicsView *, QGraphicsScene * ); + ~DrawableEllipse(); + + DrawableEllipse(const RubberbandDrawable &) = delete; + DrawableEllipse & operator = (const RubberbandDrawable &) = delete; + + + inline void setRect(const QPointF & center, qreal radiusX, qreal radiusY, qreal sin, qreal cos, const QPointF & sectorStart = QPointF(), const QPointF & sectorEnd = QPointF() ) + { + // p1,p2 = corner points of unrotated ellipse in scene coordinates + _center = RubberbandCoordinate( _mapView, convert(center, _mapView->map()->spatialReference()) ); + _radiusX = radiusX; + _radiusY = radiusY; + + _sinRect = sin; + _cosRect = cos; + + _sectorStart = sectorStart; + _sectorEnd = sectorEnd; + updateGraphic(); + } + + qreal radiusX() { return _radiusX; } + qreal radiusY() { return _radiusY; } + + protected: + + QGraphicsEllipseItem * _elliItem; + QGraphicsEllipseItem * _sectItem; + RubberbandDrawable::RubberbandCoordinate _center; + qreal _radiusX; + qreal _radiusY; + QPen _elliStyle; + qreal _sinRect; + qreal _cosRect; + QPointF _sectorStart; + QPointF _sectorEnd; + + void updateGraphic() override; + + + void adjustCoordinates() override; + void adjustWindow( const QRectF& ) override; + + }; /* DrawableEllipse */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.cpp new file mode 100755 index 000000000..cab0e5861 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.cpp @@ -0,0 +1,109 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of line drawables +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 06.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "drawable_line.h" + +#include + + +namespace SituationMap { namespace RB +{ + + DrawableLine::DrawableLine(Esri::ArcGISRuntime::MapGraphicsView * map) + : RubberbandDrawable(map) + , _lineItem(nullptr) + , _lineStyle() + , _dottedLine(false) + { + _lineItem = new QGraphicsLineItem(); + _scene -> addItem( _lineItem ); + + makeLineStyle( _dottedLine, _lineStyle ); + _lineItem -> setPen( _lineStyle ); + + } /* DrawableLine::DrawableLine */ + + + DrawableLine::DrawableLine(Esri::ArcGISRuntime::MapGraphicsView * map, QGraphicsScene * scene) + : RubberbandDrawable(map, scene) + , _lineItem(nullptr) + , _lineStyle() + , _dottedLine(false) + { + _lineItem = new QGraphicsLineItem(); + _scene -> addItem( _lineItem ); + makeLineStyle( _dottedLine, _lineStyle ); + _lineItem -> setPen( _lineStyle ); + + } /* DrawableLine::DrawableLine */ + + DrawableLine::~DrawableLine() + { + if (_scene) + { + if (_lineItem) + { + _scene->removeItem(_lineItem); + delete _lineItem; + } + } + _lineItem = nullptr; + } /* DrawableLine::~DrawableLine */ + + + void DrawableLine::setLine( const RubberbandDrawable::RubberbandCoordinate & p1, const RubberbandDrawable::RubberbandCoordinate & p2 ) + { + _lineCoordinates[0] = p1; + _lineCoordinates[1] = p2; + updateGraphic(); + } /* DrawableLine::setPath */ + + + void DrawableLine::updateGraphic() + { + _lineCoordinates[0].mapToScene(_mapView); + _lineCoordinates[1].mapToScene(_mapView); + + qreal x1 = _lineCoordinates[0]._sceneCoordinate.x(); + qreal y1 = _lineCoordinates[0]._sceneCoordinate.y(); + qreal x2 = _lineCoordinates[1]._sceneCoordinate.x(); + qreal y2 = _lineCoordinates[1]._sceneCoordinate.y(); + + makeLineStyle( _dottedLine, _lineStyle ); + + _lineItem -> setLine( x1,y1,x2,y2 ); + _lineItem -> setPen( _lineStyle ); + _lineItem -> setVisible( _isVisible ); + + } /* DrawableLine::updateGraphic */ + + void DrawableLine::adjustCoordinates() { updateGraphic(); } + void DrawableLine::adjustWindow( const QRectF& ) { updateGraphic(); } + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.h new file mode 100755 index 000000000..00ade66ae --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_line.h @@ -0,0 +1,68 @@ +/** +** +** @RDE_HEADER@ +** Definition of line drawable +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include "rubberband_drawable.h" + + +namespace SituationMap { namespace RB +{ + + class DrawableLine : public RubberbandDrawable + { + Q_OBJECT + public: + + explicit DrawableLine( Esri::ArcGISRuntime::MapGraphicsView * ); + explicit DrawableLine( Esri::ArcGISRuntime::MapGraphicsView *, QGraphicsScene * ); + ~DrawableLine(); + + DrawableLine(const RubberbandDrawable &) = delete; + DrawableLine & operator = (const RubberbandDrawable &) = delete; + + void setLine( const RubberbandDrawable::RubberbandCoordinate & p1, const RubberbandDrawable::RubberbandCoordinate & p2 ); + + inline void dottedLine(bool on = true) { _dottedLine = on; } + + protected: + + QGraphicsLineItem * _lineItem; + RubberbandDrawable::RubberbandCoordinate _lineCoordinates[2]; + QPen _lineStyle; + bool _dottedLine; + + void updateGraphic() override; + + void adjustCoordinates() override; + void adjustWindow( const QRectF& ) override; + + }; /* DrawableLine */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.cpp new file mode 100755 index 000000000..c272673b4 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.cpp @@ -0,0 +1,152 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of path drawables +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.07.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "drawable_painter_path.h" + +#include +#include + + +namespace SituationMap { namespace RB +{ + + DrawablePainterPath::DrawablePainterPath(Esri::ArcGISRuntime::MapGraphicsView * map) + : RubberbandDrawable(map) + , _painterPathItem(nullptr) + , _pathCoordinates() + , _lineStyle() + , _dottedPath(false) + , _closedLine(false) + , _fillSolid(false) + { + _painterPathItem = _scene->addPath( QPainterPath() ); + _painterPathItem -> setFlags( QGraphicsItem::ItemIgnoresTransformations ); + makeLineStyle( _dottedPath, _lineStyle ); + _painterPathItem -> setPen ( (_drawColor.alpha()) ? QPen(_drawColor) : QPen() ); + _fillColor.setAlpha(0); + _painterPathItem -> setBrush( (_fillColor.alpha()) ? QBrush(_fillColor) : QBrush() ); + } /* DrawablePainterPath::DrawablePainterPath */ + + + DrawablePainterPath::DrawablePainterPath(Esri::ArcGISRuntime::MapGraphicsView * map, QGraphicsScene * scene) + : RubberbandDrawable(map, scene) + , _painterPathItem(nullptr) + , _pathCoordinates() + , _lineStyle() + , _dottedPath(false) + , _closedLine(false) + , _fillSolid(false) + { + _painterPathItem = _scene->addPath( QPainterPath() ); + _painterPathItem -> setFlags( QGraphicsItem::ItemIgnoresTransformations ); + makeLineStyle( _dottedPath, _lineStyle ); + _painterPathItem -> setPen ( (_drawColor.alpha()) ? _lineStyle : QPen() ); + _fillColor.setAlpha(0); + _painterPathItem -> setBrush( (_fillColor.alpha()) ? QBrush(_fillColor) : QBrush() ); + } /* DrawablePainterPath::DrawablePainterPath */ + + + DrawablePainterPath::~DrawablePainterPath() + { + if (_scene) + { + if (_painterPathItem) + { + _scene->removeItem(_painterPathItem); + delete _painterPathItem; + } + _painterPathItem = nullptr; + } + } /* DrawablePainterPath::~DrawablePainterPath */ + + + void DrawablePainterPath::setPath( const QVector & path ) + { + _pathCoordinates = path; + updateGraphic(); + } /* DrawablePainterPath::setPath */ + + + void DrawablePainterPath::setPath( const QVector & scenePath ) + { + _pathCoordinates.clear(); + _pathCoordinates.reserve( scenePath.size() ); + + for ( auto p : scenePath ) + { + _pathCoordinates.append( RubberbandDrawable::RubberbandCoordinate(_mapView,p) ); + } + updateGraphic(); + + } /* DrawablePainterPath::setPath */ + + + void DrawablePainterPath::updateGraphic() + { + DEBUG_LOG(_logger) << LOGFUNCTION; + + if ((_closedLine) && (_pathCoordinates.size() < 3)) + { + _painterPathItem -> setVisible( false ); + return; + } + + QPainterPath painterPath; + + QVector::iterator it = _pathCoordinates.begin(); + + if (it != _pathCoordinates.end()) + { + painterPath.moveTo( it->mapToScene(_mapView) ); + DEBUG_LOG(_logger) << LOGFUNCTION << " map-x = " << it->_mapCoordinate.x() << " map-y = " << it->_mapCoordinate.y(); + while (true) + { + ++it; + if (it == _pathCoordinates.end()) break; + painterPath.lineTo( it->mapToScene(_mapView) ); + DEBUG_LOG(_logger) << LOGFUNCTION << " map-x = " << it->_mapCoordinate.x() << " map-y = " << it->_mapCoordinate.y(); + } + if (_closedLine) painterPath.closeSubpath(); + } + + const QColor &fillColor = transparentFillColor(); + makeLineStyle( _dottedPath, _lineStyle ); + + _painterPathItem -> setVisible( _isVisible ); + _painterPathItem -> setPen ( (_drawColor.alpha() > 0) ? _lineStyle : QPen() ); + _painterPathItem -> setBrush( ( fillColor.alpha() > 0) ? QBrush(fillColor) : QBrush() ); + _painterPathItem -> setZValue( _zBufferValue ); + _painterPathItem -> setPath( painterPath ); + + } /* DrawablePainterPath::updateGraphic */ + + void DrawablePainterPath::adjustCoordinates() { updateGraphic(); } + void DrawablePainterPath::adjustWindow( const QRectF& ) { updateGraphic(); } + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.h new file mode 100755 index 000000000..cdf043647 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_painter_path.h @@ -0,0 +1,75 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberband drawing objects +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include "rubberband_drawable.h" + + +namespace SituationMap { namespace RB +{ + + class DrawablePainterPath : public RubberbandDrawable + { + Q_OBJECT + public: + + explicit DrawablePainterPath( Esri::ArcGISRuntime::MapGraphicsView * ); + explicit DrawablePainterPath( Esri::ArcGISRuntime::MapGraphicsView *, QGraphicsScene * ); + ~DrawablePainterPath(); + + DrawablePainterPath(const RubberbandDrawable &) = delete; + DrawablePainterPath & operator = (const RubberbandDrawable &) = delete; + + void setPath( const QVector & scenePath ); + + void setPath( const QVector & path ); + + inline void enableClosedLine(bool on = true) { _closedLine = on; } + + inline void dottedPath(bool on = true) { _dottedPath = on; } + inline void fillSolid(bool on = true) { _fillSolid = on; } + + protected: + + QGraphicsPathItem * _painterPathItem; + QVector _pathCoordinates; + QPen _lineStyle; + bool _dottedPath; + bool _closedLine; + bool _fillSolid; + + void updateGraphic() override; + + void adjustCoordinates() override; + void adjustWindow( const QRectF& ) override; + + }; /* DrawablePainterPath */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.cpp new file mode 100755 index 000000000..b36a06606 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.cpp @@ -0,0 +1,127 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of line drawables +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 06.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "drawable_smooth_path.h" + +#include +#include + +#include +#include + + +namespace SituationMap { namespace RB +{ + + DrawableSmoothPath::DrawableSmoothPath(Esri::ArcGISRuntime::MapGraphicsView * map) + : DrawablePainterPath(map) + , _smoothLine( new SituationMap::MathUtils::SmoothLine() ) + { + _pathRasterer = _smoothLine->createPathRaster(); + } /* DrawableSmoothPath::DrawableLine */ + + + DrawableSmoothPath::DrawableSmoothPath(Esri::ArcGISRuntime::MapGraphicsView * map, QGraphicsScene * scene) + : DrawablePainterPath(map, scene) + , _smoothLine( new SituationMap::MathUtils::SmoothLine() ) + { + _pathRasterer = _smoothLine->createPathRaster(); + } /* DrawableSmoothLine::DrawableLine */ + + + DrawableSmoothPath::~DrawableSmoothPath() + { + delete _pathRasterer; + delete _smoothLine; + _pathRasterer = nullptr; + _smoothLine = nullptr; + } /* DrawableSmoothPath::~DrawableSmoothPath */ + + + void DrawableSmoothPath::setPath( const QVector & vertexVector ) + { + _pathCoordinates = vertexVector; + updateGraphic(); + } /* DrawableSmoothPath::setPath */ + + + void DrawableSmoothPath::updateGraphic() + { + if (_pathCoordinates.size() > 2) + { + makeLineStyle( _dottedPath, _lineStyle ); + + _painterPathItem -> setPen( _lineStyle ); + _painterPathItem -> setVisible( _isVisible ); + + if (_closedLine) + { + const QColor &fillColor = transparentFillColor(); + _painterPathItem -> setBrush( (fillColor.alpha() > 0) ? QBrush(fillColor) : QBrush() ); + } + else _painterPathItem -> setBrush( QBrush() ); + + QVector vertexVector; + vertexVector.reserve( _pathCoordinates.size() ); + + for (auto vertex : std::as_const(_pathCoordinates)) + { + vertex.mapToScene(_mapView); + vertexVector.append( vertex._sceneCoordinate ); + } + + _smoothLine -> vertexVector( vertexVector, _closedLine ); + _pathRasterer -> epsilon( 2 ); + _pathRasterer -> maxVertexCount( 1000 ); + _pathRasterer -> rasterize( _smoothLine->minimumCurveParameter(), _smoothLine->maximumCurveParameter(), vertexVector ); + + QPainterPath path; + + if (vertexVector.size() > 1) + { + QVector::const_iterator vit = vertexVector.constBegin(); + + path.moveTo( *vit ); + for (++vit; vit != vertexVector.end(); ++vit) + { + path.lineTo( *vit ); + } + if (_closedLine) path.lineTo( QPointF(path.elementAt(0)) ); + } + _painterPathItem->setPath(path); + } + else _painterPathItem ->setVisible(false); + + } /* DrawableSmoothPath::updateGraphic */ + + + void DrawableSmoothPath::adjustCoordinates() { updateGraphic(); } + void DrawableSmoothPath::adjustWindow( const QRectF& ) { updateGraphic(); } + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.h new file mode 100755 index 000000000..24c9c64a6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/drawable_smooth_path.h @@ -0,0 +1,77 @@ +/** +** +** @RDE_HEADER@ +** Definition of line drawable +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include "drawable_painter_path.h" + + +namespace SituationMap { + + + namespace MathUtils + { + class SmoothLine; + class PathRasterer; + } + + + namespace RB + { + + + class DrawableSmoothPath : public DrawablePainterPath + { + Q_OBJECT + public: + + explicit DrawableSmoothPath( Esri::ArcGISRuntime::MapGraphicsView * ); + explicit DrawableSmoothPath( Esri::ArcGISRuntime::MapGraphicsView *, QGraphicsScene * ); + ~DrawableSmoothPath(); + + DrawableSmoothPath(const DrawableSmoothPath &) = delete; + DrawableSmoothPath & operator = (const DrawableSmoothPath &) = delete; + + void setPath( const QVector & vertexVector ); + + protected: + + + SituationMap::MathUtils::SmoothLine * _smoothLine; + SituationMap::MathUtils::PathRasterer * _pathRasterer; + + void updateGraphic() override; + + void adjustCoordinates() override; + void adjustWindow( const QRectF& ) override; + + }; /* DrawableSmoothPath */ + + } /* namespace RB */ +} /* namespace SituationMap */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.cpp new file mode 100755 index 000000000..974e722a3 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.cpp @@ -0,0 +1,72 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to delegate mouse and keyboard event to another event dispatcher +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 15.04.2015, JT : First edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "event_delegater.h" + +namespace SituationMap { namespace RB +{ + + EventDelegater::EventDelegater(QWidget * getEventsFrom, AbstractEventDispatcher * delegateEventTo) + : AbstractEventDispatcher(getEventsFrom), _delegateEventTo(delegateEventTo) + { + connect( delegateEventTo, &AbstractEventDispatcher::signalKickTheBucket, + this , &AbstractEventDispatcher::onDo_kickTheBucket ); + + connect( this , &AbstractEventDispatcher::signalKickTheBucket, + delegateEventTo, &AbstractEventDispatcher::onDo_kickTheBucket ); + + _restoreMouseTracking = delegateEventTo->_restoreMouseTracking; + _whatsThisBackup = delegateEventTo->_whatsThisBackup; + + } /* EventDispatcher::EventDispatcher */ + + + bool EventDelegater::eventFilter(QObject * watched, QEvent * e) + { + if (hasFinished()) return true; // don't accept new events if dispatcher is going to finish + if (hasActiveEvent()) return true; // don't accept new events before last event has been handled completly + + if (_delegateEventTo != nullptr) + { + return _delegateEventTo->eventFilter( watched, e ); + } + return false; + } /* EventDispatcher::eventFilter */ + + + void EventDelegater::KickTheBucket() + { + if (_delegateEventTo != nullptr) + { + _delegateEventTo->unlinkEventDispatcher(); + } + AbstractEventDispatcher::KickTheBucket(); + } /* EventDelegater::KickTheBucket */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.h new file mode 100755 index 000000000..595372fd7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_delegater.h @@ -0,0 +1,72 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to delegate mouse and keyboard event to another event dispatcher +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 15.04.2015, JT : first edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include "abstract_event_dispatcher.h" + + +namespace SituationMap { namespace RB +{ + + /** + @class EventDelegater event_delegater.h + @brief Event filter that delegates events to another event filter + + Some Qt-Widgets have a special viewport widget. In this case keyboard and mouse events + are sended to different widgets. To concentrate the event handling in one event filter + for both types of events, it is necessary to forward events from event filter to another + one. + + @see AbstractEventDispatcher, EventDispatcher, EventMouseNavigator + */ + class EventDelegater final : public AbstractEventDispatcher + { + Q_OBJECT + public: + + EventDelegater( QWidget * getEventsFrom, AbstractEventDispatcher * delegateEventTo ); + + ~EventDelegater() = default; + + EventDelegater(const EventDelegater &) = delete; + EventDelegater & operator = (const EventDelegater &) = delete; + + protected: + + QPointer _delegateEventTo; + + bool eventFilter(QObject*, QEvent*) override; + + void KickTheBucket() override; + }; /* EventDelegater */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.cpp new file mode 100755 index 000000000..093749071 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.cpp @@ -0,0 +1,626 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to handle mouse and keyboard event +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 28.04.2003, JT : First edition for use with Qt4 in START Feature Editor fee +** 25.03.2015, JT : Refactoring for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "event_dispatcher.h" + +#include "gestures.h" + +#include + +#include +#include + +#include +#include +#include + +namespace SituationMap { namespace RB +{ + +EventDispatcher::EventDispatcher(QWidget * getEventsFrom) + : AbstractEventDispatcher(getEventsFrom) + , _snapEnabled(false) + , _snapRatio(5) + , _drawRatio(false) + , _doubleClickHandled(false) + , _leftPressHandled(false) +{ +} /* EventDispatcher::EventDispatcher */ + + +EventDispatcher::EventDispatcher(QWidget * getEventsFrom, EventActionInterface * eventActions ) + : AbstractEventDispatcher(getEventsFrom, eventActions) + , _snapEnabled(false) + , _snapRatio(5) + , _drawRatio(false) + , _doubleClickHandled(false) + , _leftPressHandled(false) +{ +} /* EventDispatcher::EventDispatcher */ + + +bool EventDispatcher::eventFilterKey( int key, Qt::KeyboardModifiers mod ) +{ + QPoint moveDelta; + int multiply ( (mod.testFlag(Qt::KeyboardModifier::ShiftModifier)) ? 10 : 1); + bool controlKey( (mod.testFlag(Qt::KeyboardModifier::ControlModifier)) ); + + switch (key) + { + case Qt::Key_Delete : return onDoDelete(); break; + case Qt::Key_Z : if (controlKey) return onDoUndo(); break; + case Qt::Key_Escape : return onDoEscape(); break; + case Qt::Key_Return : return onDoEnd(); break; + case Qt::Key_Left : moveDelta = QPoint(-1, 0)*multiply; break; + case Qt::Key_Right : moveDelta = QPoint( 1, 0)*multiply; break; + case Qt::Key_Up : moveDelta = QPoint( 0,-1)*multiply; break; + case Qt::Key_Down : moveDelta = QPoint( 0, 1)*multiply; break; + default : return false; + } /* switch */ + + if (_getEventsFrom != nullptr) + { + QPoint cursorPos = _getEventsFrom->mapFromGlobal( QCursor::pos() ); + onPress( cursorPos.x(), cursorPos.y(), mod ); + cursorPos += moveDelta; + onMove ( cursorPos.x(), cursorPos.y(), Qt::MouseButton::LeftButton, mod ); + onClick( cursorPos.x(), cursorPos.y(), mod ); + + QCursor::setPos( _getEventsFrom->mapToGlobal(cursorPos) ); + } + return true; +} /* EventDispatcher::eventFilterKey */ + + +bool EventDispatcher::eventFilterGesture( QGestureEvent * gestureEvent ) +{ + bool handled = false; + + if (gestureEvent) + { + const QList & gestureList = gestureEvent->gestures(); + for (auto gesture : gestureList) + { + Qt::GestureType gestureType = gesture->gestureType(); + Qt::GestureState gestureState = gesture->state(); + + QTapGesture * tapGesture = nullptr; + QTapAndHoldGesture * tapAndHoldGesture = nullptr; + QPanGesture * panGesture = nullptr; + QPinchGesture * pinchGesture = nullptr; + QSwipeGesture * swipeGesture = nullptr; + ApplyGesture * applyGesture = nullptr; + QString typeName; + QString stateName; + + switch (gestureType) + { + case Qt::TapGesture : tapGesture = static_cast(gesture); typeName = QLatin1String("TapGesture"); break; + case Qt::TapAndHoldGesture : tapAndHoldGesture = static_cast(gesture); typeName = QLatin1String("TapAndHoldGesture"); break; + case Qt::PanGesture : panGesture = static_cast(gesture); typeName = QLatin1String("PanGesture"); break; + case Qt::PinchGesture : pinchGesture = static_cast(gesture); typeName = QLatin1String("PinchGesture"); break; + case Qt::SwipeGesture : swipeGesture = static_cast(gesture); typeName = QLatin1String("SwipeGesture"); break; + default: + { + if (gestureType == _applyGestureID) + { + applyGesture = static_cast(gesture); + typeName = QLatin1String("ApplyGesture"); + } + } break; + } + switch (gestureState) + { + case Qt::NoGesture : stateName = QLatin1String("NoGesture"); break; + case Qt::GestureStarted : stateName = QLatin1String("GestureStarted"); break; + case Qt::GestureUpdated : stateName = QLatin1String("GestureUpdated"); break; + case Qt::GestureFinished : stateName = QLatin1String("GestureFinished"); break; + case Qt::GestureCanceled : stateName = QLatin1String("GestureCanceled"); break; + } + + DEBUG_LOG(_logger) << LOGFUNCTION << " Gesture type=" << typeName << " Gesture state=" << stateName; + + if (panGesture) + { + const QPoint & offset = panGesture->offset().toPoint(); + const QPoint & hotSpot = _getEventsFrom->mapFromGlobal( panGesture->hotSpot().toPoint() ); + + switch (gestureState) + { + case Qt::GestureStarted: + { + handled = onPress( hotSpot.x(), hotSpot.y(), nullptr ); + if (handled) gestureEvent -> accept( panGesture ); + } break; + + case Qt::GestureUpdated: + handled = onMove ( hotSpot.x() + offset.x(), hotSpot.y() + offset.y(), Qt::MouseButton::LeftButton, nullptr ); + break; + case Qt::GestureFinished: + // handled = onMove ( hotSpot.x() + offset.x(), hotSpot.y() + offset.y(), Qt::MouseButton::LeftButton, 0 ); + handled = onClick( hotSpot.x() + offset.x(), hotSpot.y() + offset.y(), nullptr ); + break; + case Qt::GestureCanceled: + getEventActions()->resetAction( this, 0, 0 ); + break; + default: + break; + } + + } + + if (applyGesture) + { + switch (gestureState) + { + case Qt::GestureStarted : gestureEvent -> accept( applyGesture ); break; + case Qt::GestureFinished : + { + getEventActions()->resetAction( this, qRound(applyGesture->position().x()), qRound(applyGesture->position().y()) ); + handled = onDoubleClick( qRound(applyGesture->position().x()), qRound(applyGesture->position().y()), nullptr ); + if (handled) + { + gestureEvent -> accept( applyGesture ); + return true; + } + } break; + default : break; + } + } + + // currently not used + Q_UNUSED(handled); + Q_UNUSED(tapGesture); + Q_UNUSED(tapAndHoldGesture); + Q_UNUSED(pinchGesture); + Q_UNUSED(swipeGesture); + } + } + return handled; +} /* EventDispatcher::eventFilerGesture */ + + +bool EventDispatcher::eventFilterWheel( QWheelEvent * e ) +{ + const int WHEEL_DELTA = 120; // natural mouse driver constant + + if (e->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier) && + e->buttons().testFlag(Qt::MidButton)) + { + int newValue = (int)_snapRatio + (e->angleDelta() / WHEEL_DELTA).manhattanLength(); + _snapRatio = (newValue < 5) ? 5 : newValue; + return onMove( e->x(), e->y(), e->buttons(), e->modifiers() ); + } + return false; +} /* EventDispatcher::eventFilterWheel */ + + +bool EventDispatcher::eventFilter( QObject * watched, QEvent * e ) +{ + // Maybe ??? + // qCritical() << " _getEventsFrom = " << _getEventsFrom + // << " watched = " << watched + // << " e->type() = " << e->type(); + // if (watched != _getEventsFrom) + // return AbstractEventDispatcher::eventFilter(watched,e); + + + Q_UNUSED(watched); + + QString debugString(QLatin1String("unhandled event")); + + if (hasFinished()) return true; // don't accept new events if dispatcher is going to finish + if (hasActiveEvent()) return true; // don't accept new events before last event has been handled completly + + if (e) + { + bool handled( false ); + + setActiveEvent( true ); + + switch( e->type() ) + { + case QEvent::Enter : + { + debugString = QLatin1String("enter event"); + _getEventsFrom -> setFocus(); + _doubleClickHandled = false; + _leftPressHandled = false; + } break; + + case QEvent::Leave : _doubleClickHandled = _leftPressHandled = false; break; + + case QEvent::GraphicsSceneMousePress : break; + case QEvent::MouseButtonPress : + { + debugString = QLatin1String("mouse button press event"); + + _doubleClickHandled = false; + + QMouseEvent *mouseEvent = static_cast(e); + if (Qt::RightButton == mouseEvent->button()) + { + handled = onMenuPress( mouseEvent->x() + , mouseEvent->y() + , mouseEvent->modifiers() ); + } + if (Qt::LeftButton == mouseEvent->button()) + { + if (!(mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier))) + { + handled = onPress( mouseEvent->x() + , mouseEvent->y() + , mouseEvent->modifiers() ); + if (handled) _leftPressHandled = true; + } + } + if (Qt::MidButton == mouseEvent->button()) + { + handled = mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier); + } + } break; + + case QEvent::TouchBegin: + { + debugString = QLatin1String("mouse button press event TOUCH"); + + _doubleClickHandled = false; + + QTouchEvent *mouseEvent = static_cast(e); + + if (mouseEvent->touchPoints().size() > 0) + { + const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); + + handled = onPress( tp.pos().x() + , tp.pos().y() + , mouseEvent->modifiers() ); + if (handled) _leftPressHandled = true; + } + break; + } + + + case QEvent::GraphicsSceneMouseDoubleClick : break; + case QEvent::MouseButtonDblClick : + { + debugString = QLatin1String("mouse double click event"); + QMouseEvent *mouseEvent = static_cast(e); + if (Qt::LeftButton == mouseEvent->button()) + { + if (!(mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier))) + { + onDoubleClick( mouseEvent->x() + , mouseEvent->y() + , mouseEvent->modifiers() ); + + _doubleClickHandled = handled = true; + } + } + } break; + + case QEvent::GraphicsSceneMouseRelease : break; + case QEvent::MouseButtonRelease : + { + if (!_doubleClickHandled) + { + debugString = QLatin1String("mouse button release event"); + QMouseEvent *mouseEvent = static_cast(e); + if (Qt::RightButton == mouseEvent->button()) + { + handled = onMenuClick( mouseEvent->x() + , mouseEvent->y() + , mouseEvent->modifiers() ); + } + if (Qt::LeftButton == mouseEvent->button()) + { + if (!(mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier))) + { + // The mouse button press event must had be handled before. If not skip button release ... + if (_leftPressHandled) + { + _leftPressHandled = false; + handled = onClick( mouseEvent->x() + , mouseEvent->y() + , mouseEvent->modifiers() ); + } + else callEventAction( &EventActionInterface::buttonPositionAction, mouseEvent->x(), mouseEvent->y() ); + } + } + if (Qt::MidButton == mouseEvent->button()) + { + handled = mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier); + } + } + else _doubleClickHandled = false; + + } break; + + case QEvent::TouchEnd: + { + + debugString = QLatin1String("mouse button release event TOUCH"); + QTouchEvent *mouseEvent = static_cast(e); + + // The mouse button press event must had be handled before. If not skip button release ... + if (mouseEvent->touchPoints().size() > 0) + { + const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); + if (_leftPressHandled) + { + _leftPressHandled = false; + handled = onClick( tp.pos().x() + , tp.pos().y() + , mouseEvent->modifiers() ); + + } + else + { + callEventAction( &EventActionInterface::buttonPositionAction, tp.pos().x(), tp.pos().y() ); + } + } + + break; + } + + case QEvent::GraphicsSceneMouseMove : break; + case QEvent::MouseMove : + { + debugString = QLatin1String("mouse move event"); + + _doubleClickHandled = false; + + QMouseEvent *mouseEvent = static_cast(e); + + if (mouseEvent->buttons() != Qt::MidButton) + { + if (!(mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier))) + { + if (_drawRatio) + { + if (!(mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::AltModifier))) + { + _drawRatio = false; + } + } + else + { + if (mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::AltModifier)) + { + _drawRatio = true; + } + } + + handled = onMove( mouseEvent->x() + , mouseEvent->y() + , mouseEvent->buttons() + , mouseEvent->modifiers()); + + // REGG-5230 - PlanEditor: Position beim Erstellen und Modifizieren + // von Elementen wird nicht aktualisiert + // Next handler is EventMouseNavigator. ::eventFilter() will handle + // QEvent::MouseMove and emit mouseMovedEvent to update PositionStr. + // If handled == true, the next handler is NOT called. + // So emit here alternatively. + if (handled) + emit mouseMovedEvent(mouseEvent->pos().x(), mouseEvent->pos().y()); + } + } + } break; + + case QEvent::TouchUpdate: + { + debugString = QLatin1String("mouse move event Touch"); + + _doubleClickHandled = false; + + QTouchEvent* mouseEvent = static_cast(e); + if (mouseEvent->touchPoints().size() > 0) + { + const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); + + handled = onMove( tp.pos().x() + , tp.pos().y() + , Qt::LeftButton + , mouseEvent->modifiers()); + } + break; + } + + + case QEvent::KeyPress : + { + QKeyEvent *keyEvent = static_cast(e); + + debugString = QStringLiteral("key press event (%1)").arg(keyEvent->key()); + + handled = eventFilterKey( keyEvent->key(),keyEvent->modifiers() ); + if ((keyEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier)) || (keyEvent->key() == Qt::Key_Control)) + { + setSnapEnabled( true ); + _drawRatio = true; + handled = true; + } + } break; + + case QEvent::KeyRelease : + { + debugString = QLatin1String("key release event"); + if (_drawRatio) + { + _drawRatio = false; + setSnapEnabled( true ); + } + // handled = true; + } break; + + + case QEvent::Wheel : + { + debugString = QLatin1String("mouse wheel event"); + QWheelEvent *wheelEvent = static_cast(e); + if (wheelEvent->modifiers() & (Qt::KeyboardModifier::ShiftModifier | Qt::KeyboardModifier::ControlModifier)) + { + handled = eventFilterWheel( wheelEvent ); + } + } break; + + case QEvent::Gesture: + { + handled = eventFilterGesture( static_cast(e) ); + } break; + + default: break; + + } /* switch */ + + if (!hasFinished()) setActiveEvent( false ); + + if (handled) e->accept(); + + TRACE_LOG(_logger) << LOGFUNCTION << " Event " << debugString << " handled = " << handled; + + if (handled) return true; + } + return AbstractEventDispatcher::eventFilter(watched,e); +} /* EventDispatcher::eventFilter */ + + +bool EventDispatcher::onMove(int x, int y, Qt::MouseButtons buttons, Qt::KeyboardModifiers mod) +{ + Q_UNUSED(mod); + if (buttons.testFlag(Qt::MouseButton::LeftButton)) return callEventAction( &EventActionInterface::movePickedAction, x, y ); + return callEventAction( &EventActionInterface::moveAction, x, y ); +} /* EventDispatcher::onMove */ + + +bool EventDispatcher::onPress(int x, int y, Qt::KeyboardModifiers mod) +{ + if (mod == Qt::NoModifier) + return callEventAction( &EventActionInterface::selectAction, x, y ); + if (mod.testFlag(Qt::KeyboardModifier::ShiftModifier)) + return callEventAction( &EventActionInterface::multiSelectActionPress, x, y ); + return false; +} /* EventDispatcher::onPress */ + + +bool EventDispatcher::onClick(int x, int y, Qt::KeyboardModifiers mod) +{ + Q_UNUSED(x); + Q_UNUSED(y); + + if (mod == Qt::NoModifier) + { + bool ret = callEventAction( &EventActionInterface::setCoordinateAction, x, y ); + if (ret) + { + EventActionInterface * eaIf = getEventActions(); + if (eaIf) eaIf->userActionFinished(); + } + return ret; + } + if (mod.testFlag( Qt::KeyboardModifier::ShiftModifier )) + return callEventAction( &EventActionInterface::multiSelectActionRelease, x, y ); + + return false; +} /* EventDispatcher::onClick */ + + +bool EventDispatcher::onMenuPress(int x, int y, Qt::KeyboardModifiers mod) +{ + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(mod); + + return false; +} /* EventDispatcher::onMenuPress */ + + +bool EventDispatcher::onMenuClick(int x, int y, Qt::KeyboardModifiers mod) +{ + Q_UNUSED(mod); + return callEventAction( &EventActionInterface::contextMenuAction, x, y ); +} /* EventDispatcher::onMenuClick */ + + +bool EventDispatcher::onDoubleClick(int x, int y, Qt::KeyboardModifiers ) +{ + callEventAction( &EventActionInterface::completeStateAction, x, y ); + return true; +} /* EventDispatcher::onDoubleClick */ + + +bool EventDispatcher::onDoDelete() +{ + if (_getEventsFrom != nullptr) + { + QPoint cursorPos = _getEventsFrom->mapFromGlobal( QCursor::pos() ); + callEventAction( &EventActionInterface::selectAction, cursorPos.x(), cursorPos.y() ); + bool ret = callEventAction( &EventActionInterface::deleteAction, cursorPos.x(), cursorPos.y() ); + if (ret) + { + EventActionInterface * eaIf = getEventActions(); + if (eaIf) eaIf->userActionFinished(); + } + return ret; + } + return false; +} /* EventDispatcher::onDoDelete */ + + +bool EventDispatcher::onDoUndo() +{ + bool ret = callEventAction( &EventActionInterface::undoAction, 0, 0 ); + if (ret) + { + EventActionInterface * eaIf = getEventActions(); + if (eaIf) eaIf->userActionFinished(); + } + return ret; +} /* EventDispatcher::onDoUndo */ + + +bool EventDispatcher::onDoEscape() +{ + EventActionInterface * eventActions = getEventActions(); + KickTheBucket(); + if (eventActions) eventActions->abortAction( this, 0, 0 ); + return true; +} /* EventDispatcher::onDoEscape */ + +bool EventDispatcher::onDoEnd() +{ + EventActionInterface * eventActions = getEventActions(); + KickTheBucket(); + if (eventActions) eventActions->applyAndEndAction( this, 0, 0 ); + return true; +} /* EventDispatcher::onDoEnd */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.h new file mode 100755 index 000000000..004b9e840 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_dispatcher.h @@ -0,0 +1,140 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to handle mouse and keyboard event +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 28.04.2003, JT : First edition for use with Qt4 in START Feature Editor fee +** 25.03.2015, JT : Refactoring for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include "abstract_event_dispatcher.h" + +class QGestureEvent; +class QWheelEvent; + +namespace SituationMap { namespace RB +{ + + /** + @class EventDispatcher event_dispatcher.h + @brief Event filter that handles events when a rubberband it active. + + Implements class \c AbstractEventDispatcher to manage event handling for rubberbands. Following events are handled: + + | Input-Event | Interface Function | Description | + | :--------------------------------------------- | : ----------------- | : ------------------------------------------------------------------------------------------------------ | + | Mouse move without any modifier | moveAction | Move Cursor or preview | + | Mouse move with pressed left button | movePickedAction | Move selected items. If movePickedAction == false redirect to \c EventMouseNavigator for panning of map. | + | Mouse move with pressed middle button | | Redirect to \c EventMouseNavigator for panning of map | + | Mouse wheel | | Redirect to \c EventMouseNavigator for zooming of map | + | Left mouse button press | selectAction | Do selection of single item or panning of map. | + | Left mouse button press with pressed SHIFT key | multiSelectAction | Do selection of multiple items. | + | Left mouse button release | setCoordinateAction | Set final coordinate to selected items and deselects item. | + | Double click left mouse button | completeStateAction | Finish input. | + | Right mouse button release | contextMenuAction | Show context menu. | + | Key release CTRL-Z | undoMenuAction | Undo last left mouse button click if possible. | + | Key release Delete | deleteMenuAction | Delete selected item. | + | Key release ESC | abortAction | Abort rubberband without saving data. The rubberband emits the abort signal. | + | Key release RETURN | applyAndEndAction | Finish rubberband. The rubberband emits the apply signal. | + | Key release UP | moveAction | Moves 1 pixel up. | + | Key release DOWN | moveAction | Moves 1 pixel down. | + | Key release LEFT | moveAction | Moves 1 pixel left. | + | Key release RIGHT | moveAction | Moves 1 pixel right. | + | Key release UP with SHIFT key pressed | moveAction | Moves 10 pixel up. | + | Key release DOWN with SHIFT key pressed | moveAction | Moves 10 pixel down. | + | Key release LEFT with SHIFT key pressed | moveAction | Moves 10 pixel left. | + | Key release RIGHT with SHIFT key pressed | moveAction | Moves 10 pixel right. | + + @see AbstractEventDispatcher, EventDelegater, EventMouseNavigator, + Rubberband, RubberbandPointFeature, RubberbandPointCluster, + RubberbandMultiPointFeature, RubberbandCorridor, RubberbandRectangle, RubberbandEllipse, + EventMouseNavigator + */ + + class EventDispatcher final : public AbstractEventDispatcher + { + friend class RubberbandBase; + + Q_OBJECT + public: + + /** + @name Construction & Destruction + @{ + */ + explicit EventDispatcher( QWidget * getEventsFrom ); + explicit EventDispatcher( QWidget * getEventsFrom, EventActionInterface* ); + + ~EventDispatcher() = default; + + EventDispatcher(const EventDispatcher &) = delete; + EventDispatcher & operator = (const EventDispatcher &) = delete; + /** @} */ + + inline void setSnapEnabled(bool) {} + inline bool SnapEnabled() { return _snapEnabled; } + + signals: + /** + Signal will be emitted if a mouse move is done (and if event is handled) + */ + void mouseMovedEvent(int windowPosX, int windowPosY); + + + protected: + + bool eventFilter(QObject*, QEvent*) override; + + bool eventFilterKey( int key, Qt::KeyboardModifiers mod ); + bool eventFilterGesture( QGestureEvent * gestureEvent ); + bool eventFilterWheel( QWheelEvent * ); + + private: + + bool onMove (int x, int y, Qt::MouseButtons buttons, Qt::KeyboardModifiers mod); //!< Handle a mouse move event. + + bool onPress ( int x, int y, Qt::KeyboardModifiers mod); //!< Handle left mouse button press event. + bool onClick ( int x, int y, Qt::KeyboardModifiers mod); //!< Handle left mouse button release event. + bool onMenuPress ( int x, int y, Qt::KeyboardModifiers mod); //!< Handle right mouse button press event. + bool onMenuClick ( int x, int y, Qt::KeyboardModifiers mod); //!< Handle right mouse button release event. + bool onDoubleClick( int x, int y, Qt::KeyboardModifiers mod); //!< Handle left mouse button double click event. + + bool onDoDelete(); //!< Handle del key press event. + + bool onDoUndo(); //!< Handle ctrl-c key press event. + bool onDoEscape(); //!< Handle esc key press event. + bool onDoEnd(); //!< Handle enter key press event. + + bool _snapEnabled; + uint _snapRatio; + bool _drawRatio; + bool _doubleClickHandled; + bool _leftPressHandled; + + }; /* EventDispatcher */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.cpp new file mode 100755 index 000000000..dcd2817d6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.cpp @@ -0,0 +1,643 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to delegate mouse and keyboard event to another event dispatcher +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 15.04.2015, JT : First edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "event_mouse_navigator.h" +#include "rubberband.h" + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace SituationMap { namespace RB +{ + +int EventMouseNavigator::_pickTolerance = 5; +qreal EventMouseNavigator::_zoomFactorBase = 1.002; + +EventMouseNavigator::EventMouseNavigator(QWidget * getEventsFrom, SituationMap::MapImpl *sitMap ) + : AbstractEventDispatcher(getEventsFrom) + , _sitMap(sitMap) + , _cursorChanged(false) + , _viewPortPos(QPointF(-1, -1)) + , _selectionMode(SelectionMode::NoSelection) + , _pendingRequest(RequestType::None) + , _mousePanningActive(false) + , _mousePanningActiveForCenterMapMidButton(false) + #ifdef SITUATIONMAP_OWN_WHEEL_HANDLING + , _mouseZoomTask() + #endif + , _pressedMouseButton(Qt::NoButton) +{ + // prepare identify: + switch (_sitMap->mode()) + { + case Mode::Mode_2D: + connect( _sitMap->getEsriMapGraphicsView() + , &Esri::ArcGISRuntime::MapGraphicsView::identifyGraphicsOverlaysCompleted + , this + , &EventMouseNavigator::onGetPickingResult ); + break; + case Mode::Mode_3D: + connect( _sitMap->getEsriSceneGraphicsView() + , &Esri::ArcGISRuntime::SceneGraphicsView::identifyGraphicsOverlaysCompleted + , this + , &EventMouseNavigator::onGetPickingResult ); + break; + default: + // empty + break; + } +} // EventMouseNavigator::EventMouseNavigator + + +bool EventMouseNavigator::eventFilter(QObject * watched, QEvent * e) +{ + Q_UNUSED(watched); + + QString debugString(QLatin1String("unhandled event")); + + if (hasFinished()) return true; // don't accept new events if dispatcher is going to finish + if (hasActiveEvent()) return true; // don't accept new events before last event has been handled completly + + if (e) + { + bool handled( false ); + + setActiveEvent( true ); + + switch( e->type() ) + { + case QEvent::Enter : + { + _pressedMouseButton = Qt::NoButton; + emit enterEvent(); + } break; + + case QEvent::Leave : + { + emit leaveEvent(); + _pressedMouseButton = Qt::NoButton; + } break; + + case QEvent::MouseButtonPress : + { + handled = false; + debugString = QLatin1String("mouse button press event"); + + QMouseEvent * mouseEvent = static_cast(e); + + _pressedMouseButton = mouseEvent->button(); + + _mousePanningActive = false; + _mouseMoveRefPos = mouseEvent->pos(); // for panning + + _restoreMouseCursor = getCursorFromWidget(); + + if (Qt::MidButton == mouseEvent->button()) + { + handled = true; + + _mousePanningActive = true; + _mousePanningActiveForCenterMapMidButton = false; + // setCursorToWidget( QCursor(Qt::CursorShape::ClosedHandCursor) ); + } + } break; + case QEvent::TouchBegin : + { + handled = false; + debugString = QLatin1String("Touch button press event"); + + QTouchEvent * mouseEvent = static_cast(e); + + _pressedMouseButton = Qt::MouseButton::LeftButton; + + _mousePanningActive = false; + if (mouseEvent->touchPoints().size() > 0) + { + const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); + + _mouseMoveRefPos = tp.pos().toPoint(); // for panning + _restoreMouseCursor = getCursorFromWidget(); + } + } break; + + case QEvent::MouseButtonRelease : + { + debugString = QLatin1String("mouse button release event"); + + QMouseEvent * mouseEvent = static_cast(e); + + + if (Qt::LeftButton == mouseEvent->button()) + { + if (_cursorChanged) + { + setCursorToWidget(_restoreMouseCursor); + _cursorChanged = false; + } + + /* + * The ESRI::MapView is doing panning on mouse move when left mouse button is pressed. + * To prevent selection in this case check mouse move distance. + */ + if (Qt::LeftButton == _pressedMouseButton && ((mouseEvent->pos() - _mouseMoveRefPos).manhattanLength() < 3) ) + { + if (Rubberband::activeRubberbands(_sitMap).isEmpty()) + { + handled = false; // pass event to esri map to click buttons + selectionRequested(mouseEvent->localPos()); + // do not handled = true; because some buttons in GraphicsView needs to get the event later. + } + } + else + handled = true; // may be a double click catched at higher event filter + } + + if (Qt::RightButton == mouseEvent->button()) + { + handled = true; + pickRequested( mouseEvent->localPos() ); + } + + if ((Qt::MidButton == mouseEvent->button())) + { + if (!_mousePanningActiveForCenterMapMidButton) + { + // press MidButton and Release MidButton without panning in the meantime: + // center map to current mouse position: + + // set viewpoint : (perform this only in 2D - MapGraphicsView <-> MapSceneView) + // (3D had different screenToLocation...) + Esri::ArcGISRuntime::MapGraphicsView *esriMapGraphicsView = _sitMap->getEsriMapGraphicsView(); + if (esriMapGraphicsView) + { + const QPoint &mousePos = mouseEvent->pos(); + const Esri::ArcGISRuntime::Point & mapCoordEsri = esriMapGraphicsView->screenToLocation(mousePos.x(), mousePos.y()); + const Esri::ArcGISRuntime::Viewpoint & curVp = esriMapGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Viewpoint newVp(mapCoordEsri, curVp.targetScale()); + + esriMapGraphicsView->setViewpointAndWait(newVp); // sync / andWAIT + + _getEventsFrom -> update(); + } + } + } + + if ((_mousePanningActive) || (Qt::MidButton == mouseEvent->button())) + { + handled = (Qt::MidButton == mouseEvent->button()); + setCursorToWidget( _restoreMouseCursor ); + } + _mousePanningActive = false; + + } break; + + case QEvent::TouchEnd : + { + debugString = QLatin1String("Touch button release event"); + + QTouchEvent * mouseEvent = static_cast(e); + if (mouseEvent->touchPoints().size() > 0) + { + const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); + + if (_cursorChanged) + { + setCursorToWidget(_restoreMouseCursor); + _cursorChanged = false; + } + + /* + * The ESRI::MapView is doing panning on mouse move when left mouse button is pressed. + * To prevent selection in this case check mouse move distance. + */ + if (Qt::LeftButton == _pressedMouseButton && ((tp.pos() - _mouseMoveRefPos).manhattanLength() < 3) ) + { + if (Rubberband::activeRubberbands(_sitMap).isEmpty()) + { + handled = false; // pass event to esri map to click buttons + selectionRequested(tp.pos()); + // do not handled = true; because some buttons in GraphicsView needs to get the event later. + } + } + else + handled = true; // may be a double click catched at higher event filter + + } + _mousePanningActive = false; + + } break; + + case QEvent::MouseMove : + { + handled = false; + debugString = QLatin1String("mouse button move event"); + QMouseEvent *mouseEvent = static_cast(e); + + if (isSignalConnected(QMetaMethod::fromSignal(&EventMouseNavigator::mouseMovedEvent))) + { + emit mouseMovedEvent(mouseEvent->pos().x(), mouseEvent->pos().y()); + } + + if (_mousePanningActive) + { + handled = true; + + if (mouseEvent->buttons().testFlag(Qt::MidButton)) + { + _mousePanningActiveForCenterMapMidButton = true; + + // move viewpoint : (perform this only in 2D - MapGraphicsView <-> MapSceneView) + // (3D had different screenToLocation...) + Esri::ArcGISRuntime::MapGraphicsView *esriMapGraphicsView = _sitMap->getEsriMapGraphicsView(); + if (esriMapGraphicsView) + { + QPointF deltaMove = mouseEvent->pos()-_mouseMoveRefPos; + QPointF winCenter = QPointF( qreal(_getEventsFrom->width())*0.5, qreal(_getEventsFrom->height())*0.5 ); + QPointF newCenter = winCenter-deltaMove; + + _mouseMoveRefPos = mouseEvent->pos(); + + const Esri::ArcGISRuntime::Point & mapCoordEsri = esriMapGraphicsView->screenToLocation(newCenter.x(), newCenter.y()); + const Esri::ArcGISRuntime::Viewpoint & curVp = esriMapGraphicsView->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + const Esri::ArcGISRuntime::Viewpoint newVp(mapCoordEsri, curVp.targetScale()); + + esriMapGraphicsView->setViewpointAndWait(newVp); // sync / andWAIT + + _getEventsFrom -> update(); + + DEBUG_LOG(_logger) << LOGFUNCTION << " Panning to " << mapCoordEsri.x() << " - " << mapCoordEsri.y() + << " newCenter pos=" << newCenter + << " mouse pos=" << mouseEvent->pos() + << " delta pos=" << deltaMove; + } + } + else + { + setCursorToWidget( _restoreMouseCursor ); + _mousePanningActive = false; + } + // : move viewpoint + } + } break; + case QEvent::TouchUpdate : + { + handled = false; + debugString = QLatin1String("touch button move event"); + QTouchEvent *mouseEvent = static_cast(e); + + if (mouseEvent->touchPoints().size() > 0) + { + const QTouchEvent::TouchPoint tp = mouseEvent->touchPoints().at(0); + + if (isSignalConnected(QMetaMethod::fromSignal(&EventMouseNavigator::mouseMovedEvent))) + { + emit mouseMovedEvent(tp.pos().x(), tp.pos().y()); + } + + if (_mousePanningActive) + { + handled = true; + } + else + { + setCursorToWidget( _restoreMouseCursor ); + _mousePanningActive = false; + } + + } + } break; + +#ifdef SITUATIONMAP_OWN_WHEEL_HANDLING + case QEvent::Wheel : + { + debugString = QLatin1String("mouse wheel event"); + handled = true; + if (!_mouseZoomTask.isValid() || _mouseZoomTask.isFinished()) + { + + QWheelEvent * wheelEvent = static_cast(e); + qreal delta = wheelEvent->angleDelta().manhattanLength(); + bool zoomIn = (wheelEvent->angleDelta().x() < 0) || (wheelEvent->angleDelta().y() < 0); + qreal zoomFactor = qreal( qRound( qPow( _zoomFactorBase, (zoomIn) ? delta : -delta ) * 10 )) / 10.0; + QPointF winCenter = QPointF( qreal(_getEventsFrom->width())*0.5, qreal(_getEventsFrom->height())*0.5 ); + + const QPoint & cursorPosWidget = _getEventsFrom->mapFromGlobal( QCursor::pos() ); // e doesn't have a position get position from cursor here + + Esri::ArcGISRuntime::MapView * esriMapView = _sitMap->getEsriMapGraphicsView(); + const Esri::ArcGISRuntime::Point & mapCenter = esriMapView -> screenToLocation(winCenter.x(), winCenter.y()); + const Esri::ArcGISRuntime::Point & cursorPosMap = esriMapView -> screenToLocation(cursorPosWidget.x(), cursorPosWidget.y()); + double mapScale = esriMapView -> mapScale() * zoomFactor; + + double mapX = (-cursorPosMap.x()+mapCenter.x())*zoomFactor + cursorPosMap.x(); + double mapY = (-cursorPosMap.y()+mapCenter.y())*zoomFactor + cursorPosMap.y(); + + _mouseZoomTask = esriMapView->setViewpointCenterAsync( Esri::ArcGISRuntime::Point(mapX,mapY,esriMapView->map()->spatialReference()), mapScale ); + if (isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::mouseWheelEvent) )) + { + emit mouseWheelEvent(zoomFactor, QPointF(cursorPosMap.x(), cursorPosMap.y()) ); + } + + } + } break; +#endif + + case QEvent::Gesture: + { + QGestureEvent * gestureEvent = static_cast(e); + + if (gestureEvent) + { + const QList & gestureList = gestureEvent->gestures(); + for (auto gesture : gestureList) + { + Qt::GestureType gestureType = gesture->gestureType(); + Qt::GestureState gestureState = gesture->state(); + + // QTapGesture * tapGesture = nullptr; + // QTapAndHoldGesture * tapAndHoldGesture = nullptr; + QPanGesture * panGesture = nullptr; + QPinchGesture * pinchGesture = nullptr; + // QSwipeGesture * swipeGesture = nullptr; + QString typeName; + QString stateName; + + switch (gestureType) + { + // case Qt::TapGesture : tapGesture = (QTapGesture *)gesture; typeName = QLatin1String("TapGesture"); break; + // case Qt::TapAndHoldGesture : tapAndHoldGesture = (QTapAndHoldGesture*)gesture; typeName = QLatin1String("TapAndHoldGesture"); break; + case Qt::PanGesture : panGesture = (QPanGesture *)gesture; typeName = QLatin1String("PanGesture"); break; + case Qt::PinchGesture : pinchGesture = (QPinchGesture *)gesture; typeName = QLatin1String("PinchGesture"); break; + // case Qt::SwipeGesture : swipeGesture = (QSwipeGesture *)gesture; typeName = QLatin1String("SwipeGesture"); break; + + default: break; + } + switch (gestureState) + { + case Qt::NoGesture : stateName = QLatin1String("NoGesture"); break; + case Qt::GestureStarted : stateName = QLatin1String("GestureStarted"); break; + case Qt::GestureUpdated : stateName = QLatin1String("GestureUpdated"); break; + case Qt::GestureFinished : stateName = QLatin1String("GestureFinished"); break; + case Qt::GestureCanceled : stateName = QLatin1String("GestureCanceled"); break; + } + DEBUG_LOG(_logger) << LOGFUNCTION << " Gesture type=" << typeName << " Gesture state=" << stateName; + + // tapGesture = nullptr; + panGesture = nullptr; // ToDo: check: Why this? + + if (panGesture) + { + QPointF deltaMove = panGesture->delta(); + QPoint winCenter = QPoint( _getEventsFrom->width() / 2, _getEventsFrom->height() / 2 ); + QPoint newCenter = winCenter-QPoint((int)deltaMove.x(),(int)deltaMove.y()); + + const Esri::ArcGISRuntime::Point mapCoordEsri = + _sitMap->getEsriMapGraphicsView()->screenToLocation(newCenter.x(), newCenter.y()); + Esri::ArcGISRuntime::Viewpoint curVp = _sitMap->getEsriMapGraphicsView()->currentViewpoint(Esri::ArcGISRuntime::ViewpointType::CenterAndScale); + Esri::ArcGISRuntime::Viewpoint newVp(mapCoordEsri, curVp.targetScale()); + _sitMap->getEsriMapGraphicsView()->setViewpointAndWait(newVp); + + _getEventsFrom -> update(); + + if (gestureState == Qt::GestureStarted) gestureEvent -> setAccepted(panGesture, true); + + } + + if (pinchGesture) + { + ///// QPinchGesture::ChangeFlags changeFlags = pinchGesture->changeFlags(); + + + ///// if (changeFlags & QPinchGesture::RotationAngleChanged) + ///// { + ///// qreal deltaPhi = pinchGesture->rotationAngle() - pinchGesture->lastRotationAngle(); + + ///// _esriMap -> setRotation( _esriMap->rotation()-deltaPhi ); + ///// } + ///// if (changeFlags & QPinchGesture::ScaleFactorChanged) _esriMap -> zoom( 1.0/pinchGesture->scaleFactor() ); + + _getEventsFrom -> update(); + + if (gestureState == Qt::GestureStarted) gestureEvent -> setAccepted(pinchGesture, true); + } + } + } + } break; + + default: break; + + } // switch + + if (!hasFinished()) setActiveEvent( false ); + + if (handled) e->accept(); + DEBUG_LOG(_logger) << LOGFUNCTION + << " Event '" << debugString << "'" + << " event->type() '" << e->type() << "'" + << " handled = " << handled + << " mouse panning is active = " << _mousePanningActive; + + // + // 'if (handled) return true;' lead to bad behaviour at 3D: globe is often connected to mouse, without any button pressed... + // + // if (handled) return true; + + } + return AbstractEventDispatcher::eventFilter(watched,e); + +} // EventMouseNavigator::eventFilter + + +bool EventMouseNavigator::checkRunningPickingTask() +{ + if ((!_pickingTask.isCanceled()) && (_pendingRequest != RequestType::None)) + { + _pickingTask.cancel(); + _pendingRequest = RequestType::None; + return true; + } + return false; +} // void EventMouseNavigator::checkRunningPickingTask() + + +void EventMouseNavigator::selectionRequested( const QPointF & pos ) +{ + // do the work only in case of slot connections are installed + if (! isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::selectionRequest) )) + { + return; + } + + if (checkRunningPickingTask()) + { + qWarning() << LOGFUNCTION << " request is still pending!"; + } + + _pendingRequest = RequestType::Selection; + _viewPortPos = pos; + + requestPicking(pos); + +} // EventMouseNavigator::selectionRequested + + +void EventMouseNavigator::pickRequested( const QPointF &pos ) +{ + // do the work only in case of slot connections are installed + if (! isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::contextMenuRequest) )) + { + return; + } + + if (checkRunningPickingTask()) + { + qWarning() << LOGFUNCTION << " request is still pending!"; + } + + _pendingRequest = RequestType::Picking; + _viewPortPos = pos; + + requestPicking(pos); + +} // EventMouseNavigator::pickRequested + + +void EventMouseNavigator::requestPicking(const QPointF &pos) +{ + _pickingTask.cancel(); // cancel old task + + // store new task + bool returnPopUpsOnly = false; + int maximumResultsPerOverlay = std::numeric_limits::max(); // huge number; + _pickingTask = _sitMap->getEsriGeoView()->identifyGraphicsOverlays(pos.x(), pos.y(), _pickTolerance, + returnPopUpsOnly, maximumResultsPerOverlay); + //qWarning() << " XXXXXXX" << LOGFUNCTION; + + // check new task + if (!_pickingTask.isValid()) + { + _pendingRequest = RequestType::None; + qWarning() << LOGFUNCTION << " _identfyGraphicsOverlayTask invalid"; + } + +} // EventMouseNavigator::requestPicking + + +void EventMouseNavigator::onGetPickingResult(const QUuid &taskId, const QList &identifyResults) +{ + // qWarning() << " YYYYYYY" << LOGFUNCTION; + + if (_pickingTask.taskId() != taskId) + { + qDebug() << " taskId does not match"; + return; + } + + const QList & selectedIdList = mapToUniqueMapId(identifyResults); + + // qWarning() << " _pendingRequest="<< (int)_pendingRequest << " count= " << _selectedIdList.count(); + + switch (_pendingRequest) + { + case RequestType::Picking: + emit contextMenuRequest(selectedIdList, _viewPortPos); + break; + case RequestType::Selection: + emit selectionRequest(selectedIdList, _viewPortPos); + break; + case RequestType::None: + //emtpy + break; + } // switch + + // reset some 'flags' to enable a new request: + _pendingRequest = RequestType::None; + _viewPortPos = QPointF(-1, -1); +} // EventMouseNavigator::onGetPickingResult + + +QList EventMouseNavigator::mapToUniqueMapId(const QList &identifyResults) +{ + QList idList; + + for (auto iResult : identifyResults) + { + // qWarning() << " process result of layer: " << iResult->graphicsOverlay(); + + const QList &gResults = iResult->graphics(); + for (auto pickedGraphic : gResults) + { + // qWarning() << " process pickedGraphic: " << pickedGraphic; + if (pickedGraphic->attributes()) + { + // qWarning() << " process pickedGraphic cont: " << pickedGraphic->attributes()->containsAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute)); + // qWarning() << " process pickedGraphic cont: " << pickedGraphic->attributes()->containsAttribute(QLatin1String(SituationMap::IsPickableAttribute)); + + bool ok; + SituationMap::UniqueMapId uniqueMapId = pickedGraphic->attributes()->attributeValue(QLatin1String(SituationMap::UniqueMapIdAttribute)).toUInt(&ok); + + if (ok) + { + // qWarning() << " process pickedGraphic uid: " << uniqueMapId; + if (!idList.contains(uniqueMapId)) + { + idList += uniqueMapId; + } + } + else + { + ERROR_LOG(_logger) << " process pickedGraphic " << pickedGraphic + << " uid: '" << pickedGraphic->attributes()->attributeValue(QLatin1String(SituationMap::UniqueMapIdAttribute)).toString() + << "' (has uid attribute is " << pickedGraphic->attributes()->containsAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute)) << ")"; + } + } + } // foreach inner + } // foreach outter + + return idList; +} // EventMouseNavigator::mapToUniqueMapId + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.h new file mode 100755 index 000000000..974a9b8d3 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/event_mouse_navigator.h @@ -0,0 +1,224 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to handle mouse events navigate on map +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 24.04.2015, JT : first edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +// Own +#include +#include "abstract_event_dispatcher.h" + + +//Esri + + +// Qt +#include + + +// #define SITUATIONMAP_OWN_WHEEL_HANDLING + + + +// Forward: +namespace Esri { +namespace ArcGISRuntime { + class Map; + class IdentifyGraphicsOverlayResult; +} } + +namespace SituationMap { +class MapImpl; +} + + +namespace SituationMap { namespace RB +{ + + /** + @class EventMouseNavigator event_mouse_navigator.h + @brief Event filter that handles mouse events for general map operation like selecting object, zooming and paning the map window + + Implements class \c AbstractEventDispatcher to manage event handling for general map operation like selecting object, zooming and + panning the map window. Monitoring the mouse position can be realized by connecting the \c mouseMovedEvent signal. If the user has + clicked to map objects the signal \c selectionRequest is emitted. + + Following events are handled: + + | Input-Event | Interface Function | Description | + | :--------------------------------------------- | : ----------------- | : --------------------------------------------------------------------------- | + | Mouse enter | | To show/hide some widgets | + | Mouse leave | | To show/hide some widgets | + | Mouse move without any modifier | | Move Cursor. | + | Mouse move with pressed left button | | Panning of map if more then 3 pixels moved. | + | Mouse move with pressed middle button | | Panning of map. | + | Mouse wheel | | Redirect to \c Esri map for zooming. | + | Left mouse button release | | Emits signal \c selectionRequest with list of map objects at cursor position. | + + @see AbstractEventDispatcher, EventDelegater, EventDispatcher + */ + + class EventMouseNavigator : public AbstractEventDispatcher + { + + Q_OBJECT + + public: + + /** + @name Construction & Destruction + @{ + */ + EventMouseNavigator(const EventMouseNavigator &) = delete; + EventMouseNavigator & operator = (const EventMouseNavigator &) = delete; + + EventMouseNavigator( QWidget * getEventsFrom, SituationMap::MapImpl *sitMap ); + + virtual ~EventMouseNavigator() = default; + /** @} */ + + inline static void setPickTolerance( int value = 5 ) { _pickTolerance = value; } + + inline static int pickTolerance() { return _pickTolerance; } + + inline static void setZoomFactorBase(qreal value = 1.002) { if ((value > 1) && (value <= 1.1))_zoomFactorBase = value; } + + inline static qreal zoomFactorBase() { return _zoomFactorBase; } + + signals: + /** + Will be emitted if the mouse enters the widget. + */ + void enterEvent(); + + /** + Will be emitted if the mouse leaves the widget. + */ + void leaveEvent(); + + + /** + Will be emitted if the user clicks to map objects with the left mouse button. + */ + void selectionRequest( const QList< SituationMap::UniqueMapId > & idList, const QPointF &viewPortPos ); + + /** + Will be emitted if the user clicks to the map with the rigth mouse button. + */ + void contextMenuRequest( const QList< SituationMap::UniqueMapId > & idList, const QPointF &viewPortPos ); + + /** + Signal will be emitted if a mouse move is done. + */ + void mouseMovedEvent( int x, int y ); + +#ifdef SITUATIONMAP_OWN_WHEEL_HANDLING + /** + Signal will be emitted if the mouse wheel has been rotated + */ + void mouseWheelEvent(qreal factor, const QPointF &cursorPositionMap); +#endif + + protected slots: + + void onGetPickingResult(const QUuid &taskId, const QList<::Esri::ArcGISRuntime::IdentifyGraphicsOverlayResult *> &identifyResults ); + + protected: + + + QList mapToUniqueMapId( const QList<::Esri::ArcGISRuntime::IdentifyGraphicsOverlayResult*> & identifyResults); + + bool eventFilter(QObject*, QEvent*) override; + + + enum class RequestType + { + None, + Selection, + Picking + }; // RequestType + + + enum SelectionMode + { + NoSelection = 0x00, + SingleSelection = 0x01, + MultiSelection = 0x02, + FrameSelection = 0x04 + }; // SelectionMode + + Q_DECLARE_FLAGS(SelectionModes,SelectionMode) + + SituationMap::MapImpl *_sitMap; + + QPoint _mouseMoveRefPos; + QCursor _restoreMouseCursor; + bool _cursorChanged; + + static int _pickTolerance; + + QPointF _viewPortPos; + SelectionModes _selectionMode; + RequestType _pendingRequest; + bool _mousePanningActive; + bool _mousePanningActiveForCenterMapMidButton; + +#ifdef SITUATIONMAP_OWN_WHEEL_HANDLING + QFuture _mouseZoomTask; +#endif + + Qt::MouseButton _pressedMouseButton; + + static qreal _zoomFactorBase; // Base value to calculate zoom factor values from QWheelEvent + + QFuture _pickingTask; + + /** + test for already running picking task. + */ + bool checkRunningPickingTask(); + + /** + Find out which map objects are at given mouse position and emits the signal selectionRequest + */ + void selectionRequested(const QPointF & ); + + /** + Find out which map objects are at given mouse position and emits the signal contextMenuRequest + */ + void pickRequested( const QPointF & ); + + /** + Helper function to get all map objects at a given position of all GraphicsOverlays + @param pos position in window coordinates + */ + void requestPicking(const QPointF &pos); + + }; /* EventMouseNavigator */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.cpp new file mode 100755 index 000000000..99e8e2e74 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.cpp @@ -0,0 +1,106 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +#include "gestures.h" + +#include +#include + +#include + +namespace SituationMap { namespace RB { + +// TabGestureRecognizer + +ApplyGestureRecognizer::ApplyGestureRecognizer() : QGestureRecognizer() +{ +} + +ApplyGestureRecognizer::~ApplyGestureRecognizer() +{} + + +QGesture * ApplyGestureRecognizer::create( QObject * target ) +{ + // qDebug() << "ApplyGesture create"; + + if (target && target->isWidgetType()) + { + ((QWidget*)target)->setAttribute(Qt::WA_AcceptTouchEvents); + } + return new ApplyGesture(); +} // TabGestureRecognizer::create + + +QGestureRecognizer::Result ApplyGestureRecognizer::recognize( QGesture * stateGesture, QObject* , QEvent * event) +{ + ApplyGesture * q = static_cast(stateGesture); + + QGestureRecognizer::Result result = QGestureRecognizer::CancelGesture; + + switch (event->type()) + { + case QEvent::TouchBegin: + { + const QTouchEvent * ev = static_cast(event); + // qDebug() << "ApplyGesture begin"; + q->_position = ev->touchPoints().at(0).pos(); + q->setHotSpot(ev->touchPoints().at(0).screenPos()); + q->_tripleTouch = (ev->touchPoints().size() == 3); + if (ev->touchPoints().size() == 1 ) + result = QGestureRecognizer::Ignore; + else + result = QGestureRecognizer::TriggerGesture; + } break; + + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + const QTouchEvent * ev = static_cast(event); + if (q->state() != Qt::NoGesture && ev->touchPoints().size() >= 1 && ev->touchPoints().size() <= 3) + { + if (ev->touchPoints().size() == 3) q->_tripleTouch = true; + // qDebug() << "ApplyGesture update _tripleTouch=" << q->_tripleTouch; + QTouchEvent::TouchPoint p = ev->touchPoints().at(0); + QPoint delta = p.pos().toPoint() - p.startPos().toPoint(); + enum { TapRadius = 40 }; + if (delta.manhattanLength() <= TapRadius) + { + if (event->type() == QEvent::TouchEnd) + { + result = (q->_tripleTouch) ? QGestureRecognizer::FinishGesture : QGestureRecognizer::CancelGesture; + } + else result = QGestureRecognizer::TriggerGesture; + } + } + } break; + + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + result = QGestureRecognizer::Ignore; + break; + default: + result = QGestureRecognizer::Ignore; + break; + } + + return result; +} // TabGestureRecognizer::recognize( QGesture * stateGesture, QObject * watched, QEvent * event) + + +void ApplyGestureRecognizer::reset( QGesture * stateGesture ) +{ + ApplyGesture * q = static_cast(stateGesture); + q->_tripleTouch = false; +} // TabGestureRecognizer::reset( QGesture * stateGesture ) + + + +}} // namspaces diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.h new file mode 100755 index 000000000..9847c0bb5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/gestures.h @@ -0,0 +1,72 @@ +/** +** +** @RDE_HEADER@ +** Gesture recognition +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Joerg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 09.12.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include +#include +#include + + +namespace SituationMap { namespace RB +{ + class ApplyGestureRecognizer : public QGestureRecognizer + { + public: + ApplyGestureRecognizer(); + virtual ~ApplyGestureRecognizer(); + + QGesture * create( QObject * target ) override; + QGestureRecognizer::Result recognize( QGesture * stateGesture, QObject * watched, QEvent * event) override; + void reset( QGesture * stateGesture ) override; + + }; // ApplyGestureRecognizer + + + class ApplyGesture : public QGesture + { + Q_OBJECT + friend class ApplyGestureRecognizer; + + public: + + ApplyGesture() = default; + ~ApplyGesture() = default; + + ApplyGesture( const ApplyGesture & ) = delete; + ApplyGesture & operator = ( const ApplyGesture & ) = delete; + + QPointF position() const { return _position; } + + private: + + QPointF _position; + bool _tripleTouch; + }; // ApplyGesture + +}} // namespaces diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/polygon.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/polygon.xpm new file mode 100755 index 000000000..d7b513ff4 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/polygon.xpm @@ -0,0 +1,52 @@ +/* XPM */ +static const char * polygon_xpm[] = { +"24 24 25 1", +" c None", +". c #59BA55", +"+ c #57B953", +"@ c #56B852", +"# c #55B651", +"$ c #53B44F", +"% c #51B24D", +"& c #4FB04B", +"* c #4DAE49", +"= c #4AAC46", +"- c #48A944", +"; c #46A742", +"> c #43A53F", +", c #41A23D", +"' c #3E9F3A", +") c #3C9D38", +"! c #399B35", +"~ c #379833", +"{ c #349630", +"] c #32942E", +"^ c #30922C", +"/ c #2E902A", +"( c #2D8E28", +"_ c #2B8C27", +": c #2A8B26", +" . ", +" ++ ", +" @ @ @ ", +" # # ### ", +" $$ $ $$$ $$ ", +" % %% %%% % ", +" && & &&& & ", +" * *** * ", +" == = ", +" - - ", +" ; ;", +" > >", +" , ,,", +"'' '' ", +") )) ", +"! !! ", +"~~ ~~ ", +" {{{ {{ ", +" ]]] ]] ", +" ^^ ^^ ", +" // // ", +" (( (( ", +" __ __ ", +" :::: "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.cpp new file mode 100755 index 000000000..f58d84ac2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.cpp @@ -0,0 +1,309 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of base class for 2D rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband.h" +#include "rubberband_drawable.h" +#include "rubberband_handle.h" +#include "rubberband_handle_proxy.h" +#include "rubberband_point_handle.h" +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include "rubberband_factory.h" + +#include + +// Qt +#include +#include + + +namespace SituationMap { namespace RB +{ + + + Rubberband::Rubberband( SituationMap::Map * map, QObject * parent, bool isActive ) + : RubberbandBase(map,parent,isActive) + , _internal( new Rubberband::InternalData(map) ) + , _state( RubberbandState::ConstructionState ) + , _previewSidc() + , _previewCursorOffset(32,32) + , _previewSize(32) + { + _fillColor.setAlpha(100); + + connect( &_previewSymbolResultWatcher + , &AdvancedSymbology::SymbolResultWatcher::resultReady + , this + , &Rubberband::onPreviewSymbolResultReady ); + + } /* Rubberband::Rubberband */ + + + Rubberband::~Rubberband() + { + delete _internal; + _internal = nullptr; + } /* Rubberband::~Rubberband */ + + + void Rubberband::connectPreviewPixmap( const QPixmap & symbolPixmap ) + { + if (nullptr == _internal->_connectedPreviewPixmap) + { + _internal->_connectedPreviewPixmap = new QGraphicsPixmapItem( symbolPixmap ); + _internal->_connectedPreviewPixmap -> setOffset(-qreal(symbolPixmap.width())/2,-qreal(symbolPixmap.height())/2); + + _internal->_mapScene -> addItem( _internal->_connectedPreviewPixmap ); + } + } /* RubberbandPointFeature::connectPreviewPixmap */ + + unsigned int Rubberband::coordinateCount() const { return static_cast(_internal->_handleList.size()); } + + + void Rubberband::resetAction(AbstractEventDispatcher*, int, int) + { + disenableSelectedState(); + disenableMultiSelectedState(); + update(); + } /* Rubberband::resetAction */ + + + + bool Rubberband::selectAction( AbstractEventDispatcher*, int x, int y ) + { + if (rubberbandIsAlive()) + { + QPointF scenePos = pixel2ScenePosition(x,y); + + if (_baseInternal->buttonProxyActive(scenePos)) return false; + _baseInternal->_lastClickPosition = scenePos; + return true; + } + return false; + } /* Rubberband::selectAction */ + + + bool Rubberband::multiSelectActionPress( AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive()) + { + if (isEnabledModificationState()) + { + _baseInternal->_lastClickPosition = pixel2ScenePosition(x,y); + enableMultiSelectedState(); + } + return selectAction( causer, x, y); + } + return false; + } /* RubberbandPointFeature::multiSelectActionPress */ + + + bool Rubberband::multiSelectActionRelease( AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive()) + { + if (isEnabledModificationState()) + { + updateButtonPosition(); + activateButtons(); + } + else return setCoordinateAction(causer,x,y); + } + return false; + } /* RubberbandPointFeature::multiSelectActionRelease */ + + + void Rubberband::adjustConnectedPreviewPixmap( int x, int y ) + { + if (_internal->_connectedPreviewPixmap) + { + _internal->_connectedPreviewPixmap -> setPos(pixel2ScenePosition( x+_previewCursorOffset.x(), y+_previewCursorOffset.y() )); + } + } /* Rubberband::adjustConnectedPreviewPixmap */ + + + bool Rubberband::moveAction( AbstractEventDispatcher*, int x, int y ) + { + adjustConnectedPreviewPixmap(x,y); + return rubberbandIsAlive(); + + } /* moveAction */ + + + bool Rubberband::setCoordinateAction( AbstractEventDispatcher*, int x, int y ) + { + if (rubberbandIsAlive()) + { + QPointF scenePos = pixel2ScenePosition(x,y); + if (_baseInternal->buttonProxyActive(scenePos)) return false; + _internal->_lastClickPosition = scenePos; + return true; + } + return false; + } /* Rubberband::setCoordinateAction */ + + + bool Rubberband::movePickedAction ( AbstractEventDispatcher*, int x, int y ) { adjustConnectedPreviewPixmap(x,y); return false; } + bool Rubberband::contextMenuAction ( AbstractEventDispatcher*, int x, int y ) { Q_UNUSED(x); Q_UNUSED(y); return rubberbandIsAlive(); } + bool Rubberband::completeStateAction ( AbstractEventDispatcher*, int x, int y ) { Q_UNUSED(x); Q_UNUSED(y); return rubberbandIsAlive(); } + bool Rubberband::deleteAction ( AbstractEventDispatcher*, int x, int y ) { Q_UNUSED(x); Q_UNUSED(y); return rubberbandIsAlive(); } + bool Rubberband::undoAction ( AbstractEventDispatcher*, int x, int y ) { Q_UNUSED(x); Q_UNUSED(y); return rubberbandIsAlive(); } + + + RubberbandHandle * Rubberband::pickHandle( int x, int y ) const + { + if (_internal->_mapView != nullptr) + { + QPointF scenePosition = pixel2ScenePosition(x,y); + for (auto handle : std::as_const(_internal->_handleList)) + { + if (handle->isPositionInside(scenePosition)) + return handle.get(); + } + } + return nullptr; + } /* Rubberband::pickHandle */ + + + void Rubberband::deSelectHandles() + { + for (auto handle : std::as_const(_internal->_handleList)) + { + RubberbandHandle * handlePtr = handle.get(); + if (handlePtr) + { + handlePtr->setSelection(false); + } + } + } /* Rubberband::deSelectHandles */ + + + QRectF Rubberband::sceneBbox() const + { + DEBUG_LOG(_internal->_logger) << LOGFUNCTION; + + QPointF topLeft( std::numeric_limits::max(), std::numeric_limits::max() ); + QPointF bottomRight( std::numeric_limits::min(), std::numeric_limits::min() ); + + for (auto handle : std::as_const(_internal->_handleList)) + { + RubberbandPointHandle * pointHandlePtr = dynamic_cast(handle.get()); + + if (pointHandlePtr) + { + const QPointF & p = pointHandlePtr->getCoordinate()._sceneCoordinate; + if (topLeft.x() > p.x()) topLeft.setX(p.x()); + if (topLeft.y() > p.y()) topLeft.setY(p.y()); + if (bottomRight.x() < p.x()) bottomRight.setX(p.x()); + if (bottomRight.y() < p.y()) bottomRight.setY(p.y()); + } + } + + return QRectF( topLeft, bottomRight ); + + } /* Rubberband::sceneBbox */ + + + void Rubberband::update() + { + // Check Quartz + // if (isSignalConnected(QMetaMethod::fromSignal(&Rubberband::updatePreview))) + // { + // emit updatePreview( this ); + // } + // if (_internal->_map->symbolDictionary().contains(_previewSidc)) + // { + // const QImage& symbolImage = _internal->_map->symbolDictionary().symbolImageBySidc( _previewSidc, _previewSize ); + // connectPreviewPixmap( QPixmap::fromImage(symbolImage) ); + // } + + if ((!_previewSidc.isEmpty()) && (nullptr == _internal->_connectedPreviewPixmap) && (!_previewSymbolResultWatcher.resultIsValid())) + { + _previewSymbolResultWatcher.setSymbolResult(_baseInternal->_map->symbolDictionary().findBaseSymbol(_previewSidc)); + } + + for (auto handle : std::as_const(_internal->_handleList)) + { + handle -> setDrawColor(_handleDrawColor); + handle -> setFillColor(_handleFillColor); + handle -> setSelectionColor(_handleSelectionColor); + handle -> setPickingRatio( _pickingRatio ); + } + + updateButtonPosition(); + + if (_internal->_constructionObjectDraw != nullptr) + { + // _constructionObjectDraw -> setColors + } + + externalUpdate(); + + } /* Rubberband::update */ + + + void Rubberband::onPreviewSymbolResultReady() + { + QSharedPointer symResult = _previewSymbolResultWatcher.symbolResult(); + + if (!symResult) return; // should NEVER happen! (Which object should have triggered the signal???) + + const QPixmap & symbolPixmap = QPixmap::fromImage(symResult->symbolImage().image()).scaled(_previewSize,_previewSize,Qt::KeepAspectRatio); + + if (symbolPixmap.isNull()) + { + DEBUG_LOG(_internal->_logger) << LOGFUNCTION << "Preview symbol image is empty."; + } + connectPreviewPixmap( symbolPixmap ); + } /* Rubberband::onPreviewSymbolResultReady */ + + + #include "./constrainsMarker.xpm" + + void Rubberband::markFailedConstrains( const QPointF & scenePos, bool on ) + { + if (on) + { + QPixmap markerPixmap(constrainsMarker_xpm); + + if (nullptr == _internal->_constrainFailedMarker) + { + _internal->_constrainFailedMarker = new QGraphicsPixmapItem( markerPixmap ); + _internal->_mapScene -> addItem( _internal->_constrainFailedMarker ); + } + _internal->_constrainFailedMarker -> setOffset(-qreal(markerPixmap.width())/2,-qreal(markerPixmap.height())/2); + _internal->_constrainFailedMarker -> setPos(scenePos-QPointF(_previewCursorOffset)); + _internal->_constrainFailedMarker->setVisible(true); + } + else if (_internal->_constrainFailedMarker) _internal->_constrainFailedMarker->setVisible(false); + } /* Rubberband::markFailedConstrains */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.h new file mode 100755 index 000000000..ed09383e3 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband.h @@ -0,0 +1,319 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for 2D rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include "rubberband_base.h" + +#include +#include + +#include +#include + +#include + + +namespace Esri { +namespace ArcGISRuntime { + class MapGraphicsView; + class Point; +} } + + + +namespace SituationMap +{ + +/** + Namespace for all rubberband stuff (is part of namespace SituationMap) +*/ +namespace RB +{ + class RubberbandDrawable; + class RubberbandHandle; + + /** + @class Rubberband rubberband.h + @brief Base class of all 2D rubberbands + + Rubberbands are used to create or modifiy 2D geometries like lines, circles, polygons etc. by processing + Qt input events like keyboard and mouse events. To create a rubberband the RubberbandFactory has to be used. + At end of rubberband processing the signal \c abort or\c apply are emitted. To delete the rubberband the + function deleteMySelf must be called. To use the delete operator is dangerous because at end of signal + processing some program code may be performed. + + Example to how to use rubberband: + + @code + + SituationMap::Map & map = ... + + const RubberbandFactory & rubberbandFactory = map.rubberbandFactory(); + + RubberbandPointFeature * rubberband = rubberbandFactory.create(); + if (rubberband != nullptr) + { + QObject::connect( rubberband, &SiMapRubBnd::Rubberband::apply, [=](SiMapRubBnd::Rubberband * rubberband ) + { + // rubberband has finished input + if (rubberband) + { + const QVector & vertexList = rubberband->coordinateVector(); + + // do something with vertexList + + rubberband->deleteMySelf(); // notice don't call here delete rubberband directly !!! + } + } ); + + QObject::connect( rubberband, &SiMapRubBnd::Rubberband::abort, this, [=](SiMapRubBnd::Rubberband * rubberband ) + { + // Rubberband has been aborted by user + if (rubberband) + { + rubberband->deleteMySelf(); // notice don't call here delete rubberband directly !!! + } + } ); + } + + @endcode + + Following keyboard commands are available in active rubberbands: + + | Key | Modifier | Funktion | + | :----: |:-------: | :--------------------------------------------------------------------------- | + | Esc | | Rubberband abbrechen | + | Return | | Rubberband abschließen und Eingabe übernehmen wenn möglich, sonst Abbruch | + | Left | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach links schieben | + | Right | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach rechts schieben | + | Up | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach oben schieben | + | Down | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach unten schieben | + | Left | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach links schieben | + | Right | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach rechts schieben | + | Up | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach oben schieben | + | Down | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach unten schieben | + + @see EventDispatcher + @see RubberbandBase, Rubberband, RubberbandPointFeature, RubberbandPointCluster, RubberbandMultiPointFeature, RubberbandRectangle, RubberbandEllipse, RubberbandCorridor, RubberbandArrow + */ + + class SITUATIONMAP_EXPORT Rubberband : public RubberbandBase + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Because class EventDispatcher::EventActionInterface is a pure virtual interface function the classic way of declaration would be + * + * SITUATIONMAP_EXPORT Rubberband : public QObject, public virtual EventDispatcher::EventActionInterface + * + * But with Visual Studio 2013 virtual inheritance combined with constructor delegation doesn't work and may crash the program. In + * this case the virtual inheritance of EventActionInterface isn't need because in the derived classes multiple inheritance will + * not be used. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + Q_OBJECT + + public: + + /** + @defgroup States Internal state of the rubberband + @{ + */ + + enum RubberbandState : unsigned int + { + ConstructionState = 1 << 0, ///< Construction state is active when a geomtrie is been build new + ModificationState = 1 << 1, ///< Modification state is active when a existing geometrie is been modified + SelectedState = 1 << 2, ///< Selection state is active, one ore more handles are selected + MultiSelectedState = 1 << 3, ///< multiple handles can be selected + ConstructionWidthState = 1 << 4 ///< Initial define of a width e.b. for corridors + }; /* RubberbandSate */ + + Q_DECLARE_FLAGS(RubberbandStates,RubberbandState) + + /** @} */ + + /** + @name Construction & Destruction + @{ + */ + + private: + + friend class RubberbandFreeHand; + friend class RubberbandPointFeature; + friend class RubberbandMultiPointFeature; + friend class RubberbandMultiEllipseFeature; + friend class RubberbandRectangle; + friend class RubberbandStateSelect; + friend class RubberbandTransformMove; + friend class RubberbandTransformRotate; + friend class RubberbandTransformScale; + + /** + Constructs a Rubberband for a situation map. Use \c RubberbandFactor to create a rubberband. + In case of the user actions the autoDelete mechanism can be used to delete the rubberband. Sometimes + the application wants to finish a running rubberband. This can be done by calling \c deleteMySelf. The + rubberband will be destroyed also automatically if the parent QObject or the map (see + SituationMap::Map::beginDestroy signal) will be destroyed. + + @param map Pointer to SituationMap::Map + @param parent Pointer to QObject to use as parent. + @isActive If set to false the Rubberband is not active and have to be actived to start the input. + @see RubberbandFactory + */ + explicit Rubberband( SituationMap::Map * map, QObject * parent, bool isActive = true ); + + public: + Q_DISABLE_COPY(Rubberband) + + virtual ~Rubberband(); + + /** @} */ + + /** + @addtogroup States Methodes to query and modify rubberband states + @{ + */ + inline bool isEnabledConstructionState() const { return _state.testFlag(RubberbandState::ConstructionState); } + inline bool isEnabledModificationState() const { return _state.testFlag(RubberbandState::ModificationState); } + inline bool isEnabledSelectedState() const { return _state.testFlag(RubberbandState::SelectedState); } + inline bool isEnabledMultiSelectedState() const { return _state.testFlag(RubberbandState::MultiSelectedState); } + + inline void enableConstructionState() { disenableModificationState(); } + inline void enableModificationState() { disenableConstructionState(); } + inline void enableSelectedState() { _state |= RubberbandState::SelectedState; } + inline void enableMultiSelectedState() { _state |= RubberbandState::MultiSelectedState; } + + inline void disenableConstructionState() { _state &= ~QFlags(RubberbandState::ConstructionState); _state |= RubberbandState::ModificationState; } + inline void disenableModificationState() { _state &= ~QFlags(RubberbandState::ModificationState); _state |= RubberbandState::ConstructionState; } + inline void disenableSelectedState() { _state &= ~QFlags(RubberbandState::SelectedState); } + inline void disenableMultiSelectedState() { _state &= ~QFlags(RubberbandState::MultiSelectedState); } + //@} + + /** + @name EventHandlerInterface Methodes of AbstractEventDispatcher::EventActionInterface + The interface between EventDispatcher and Rubberband are defined in class AbstractEventDispatcher. + To Implement the interface these methodes have to be override again in the derived classes of the Rubberband class. */ + /** + @{ + */ + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionPress ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionRelease( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + void resetAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + /** @} */ + + /** + @name Preview Setting Methodes to define visual behaviour of rubberband preview + @{ + */ + inline void setPreviewSidc (const QString & sidc ) { _previewSidc = sidc; update(); } ///< Defines tactical symbol for preview + inline void setPreviewOffset (const QPoint & off ) { _previewCursorOffset = off; update(); } ///< set offset to cursor position for preview pixmap + inline void setPreviewSize (unsigned int size ) { _previewSize = size; update(); } ///< set size of preview pixmap + + inline QString getPreviewSidc() const { return _previewSidc; } ///< get code of tactical preview symbol + inline const QPoint getPreviewOffset() const { return _previewCursorOffset; } ///< get offset to cursor position for preview pixmap + inline unsigned int getPreviewSize() const { return _previewSize; } ///< get size of preview pixmap + /** @} */ + + + /** + Returns Sidc code of preview symbol (if defined) + */ + inline QString previewSidc() const { return _previewSidc; } + + /** + Sets a QPixmap to use for drawing symbol preview image + @param symbolImage a nice QPixmap + */ + void connectPreviewPixmap( const QPixmap & symbolPixmap ); + + /** + Returns count of coordinates + */ + virtual unsigned int coordinateCount() const override; + + protected: + + void deSelectHandles(); + + + void update() override; + + /** + Returns the rubberband handle at screen position (x,y) or nullptr if there isn't any handle at this + position. + */ + RubberbandHandle * pickHandle( int x, int y ) const; + + /** + Moves preview image to QGraphicView position (x,y) in window coordinates. + */ + void adjustConnectedPreviewPixmap( int x, int y ); + + + /** + Shows a icon near the cursor to mark failing constrains. + */ + void markFailedConstrains( const QPointF & scenePos, bool on ); + + /* + Forward declaration of internal container class for member variables. The derived classes of Rubberband + often have also a declaration of InternalData for there own internal members. + */ + struct InternalData; + + InternalData * _internal; + + RubberbandStates _state; + + QString _previewSidc; + QPoint _previewCursorOffset; + unsigned int _previewSize; + + AdvancedSymbology::SymbolResultWatcher _previewSymbolResultWatcher; + + QRectF sceneBbox() const override; + + protected slots: + + void onPreviewSymbolResultReady(); + + }; /* Rubberband */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.cpp new file mode 100755 index 000000000..d5c806669 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.cpp @@ -0,0 +1,116 @@ +/** +** +** @RDE_HEADER@ +** Implementation of base class for 3d rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + + +#include "rubberband_3d.h" + +#include + +#include + +namespace SituationMap +{ + + +namespace RB +{ + + Rubberband3D::Rubberband3D( SituationMap::Map * map, QObject * parent, bool isActive ) + : RubberbandBase(map,parent,isActive), _markerPixmap(nullptr), _markerSelectionPixmap(nullptr) + { + } // Rubberband3D::Rubberband3D( SituationMap::Map * map, QObject * parent, bool isActive ) + + + Rubberband3D::~Rubberband3D() + { + delete _markerPixmap; + delete _markerSelectionPixmap; + } // Rubberband3D::~Rubberband3D() + + + void Rubberband3D::resetAction( AbstractEventDispatcher*,int,int ) {;} + bool Rubberband3D::selectAction( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::multiSelectActionPress ( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::multiSelectActionRelease( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::setCoordinateAction( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::moveAction( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::movePickedAction( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::contextMenuAction( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::completeStateAction( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::deleteAction( AbstractEventDispatcher*,int,int ) { return false; } + bool Rubberband3D::undoAction( AbstractEventDispatcher*,int,int ) { return false; } + + + QRectF Rubberband3D::sceneBbox() const { return QRectF(); } + + + #include "sphere_pattern.xpm" + + QImage Rubberband3D::markerSelectionImage() + { + if (nullptr == _markerSelectionPixmap) + { + _markerSelectionPixmap = new QPixmap( sphere_pattern_xpm ); + + QPainter painter(_markerSelectionPixmap); + QBrush brush; + + brush.setStyle(Qt::BrushStyle::SolidPattern); + brush.setColor(_handleSelectionColor); + + painter.setCompositionMode(QPainter::CompositionMode::CompositionMode_Multiply); + painter.setPen(_handleDrawColor); + painter.setBrush(brush); + painter.drawEllipse( 0, 0, _markerSelectionPixmap->width(), _markerSelectionPixmap->height() ); + } + return _markerSelectionPixmap->toImage(); + } // QImage Rubberband3D::markerSelectionImage() + + + QImage Rubberband3D::markerImage() + { + if (nullptr == _markerPixmap) + { + _markerPixmap = new QPixmap( sphere_pattern_xpm ); + + QPainter painter(_markerPixmap); + QBrush brush; + + brush.setStyle(Qt::BrushStyle::SolidPattern); + brush.setColor(_handleFillColor); + + painter.setCompositionMode(QPainter::CompositionMode::CompositionMode_Multiply); + painter.setPen(_handleDrawColor); + painter.setBrush(brush); + painter.drawEllipse( 0, 0, _markerPixmap->width(), _markerPixmap->height() ); + } + return _markerPixmap->toImage(); + } // QImage Rubberband3D::markerImage() + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.h new file mode 100755 index 000000000..0fac8bfd2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_3d.h @@ -0,0 +1,214 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for 3d rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include "rubberband_base.h" + +#include +#include + + +class QObject; +namespace Esri { +namespace ArcGISRuntime { + class MapGraphicsView; + class Point; +} } + + + +namespace SituationMap +{ + +/** + Namespace for all rubberband stuff (is part of namespace SituationMap) +*/ +namespace RB +{ + class RubberbandDrawable; + class RubberbandHandle; + + /** + @class Rubberband3D rubberband_3d.h + @brief Base class of all 3D rubberbands + + Rubberbands are used to create or modifiy geometries like single points or lines in 3D views by processing + Qt input events like keyboard and mouse events. To create a rubberband the RubberbandFactory has to be used. + At end of rubberband processing the signal \c abort or\c apply are emitted. To delete the rubberband the + function deleteMySelf must be called. To use the delete operator is dangerous because at end of signal + processing some program code may be performed. + + Example to how to use rubberband: + + @code + + SituationMap::Map & map = ... + + const RubberbandFactory & rubberbandFactory = map.rubberbandFactory(); + + RubberbandPointFeature * rubberband = rubberbandFactory.create(); // the map has to be in 3D mode + if (rubberband != nullptr) + { + QObject::connect( rubberband, &SiMapRubBnd::Rubberband::apply, [=](SiMapRubBnd::Rubberband * rubberband ) + { + // rubberband has finished input + if (rubberband) + { + const QVector & vertexList = rubberband->coordinateVector(); + + // do something with vertexList + + rubberband->deleteMySelf(); // notice don't call here delete rubberband directly !!! + } + } ); + + QObject::connect( rubberband, &SiMapRubBnd::Rubberband::abort, this, [=](SiMapRubBnd::Rubberband * rubberband ) + { + // Rubberband has been aborted by user + if (rubberband) + { + rubberband->deleteMySelf(); // notice don't call here delete rubberband directly !!! + } + } ); + } + + @endcode + + Following keyboard commands are available in active rubberbands: + + | Key | Modifier | Funktion | + | :----: |:-------: | :--------------------------------------------------------------------------- | + | Esc | | Rubberband abbrechen | + | Return | | Rubberband abschließen und Eingabe übernehmen wenn möglich, sonst Abbruch | + | Left | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach links schieben | + | Right | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach rechts schieben | + | Up | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach oben schieben | + | Down | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach unten schieben | + | Left | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach links schieben | + | Right | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach rechts schieben | + | Up | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach oben schieben | + | Down | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach unten schieben | + + @see EventDispatcher + @see RubberbandBase, Rubberband, RubberbandPointFeature3D, RubberbandMultiPointFeature3D + */ + + class SITUATIONMAP_EXPORT Rubberband3D : public RubberbandBase + { + Q_OBJECT + + public: + + /** + @name Construction & Destruction + @{ + */ + + private: + + friend class RubberbandPointFeature3D; + friend class RubberbandMultiPointFeature3D; + + + /** + Constructs a Rubberband for a situation map. Use \c RubberbandFactor to create a rubberband. + In case of the user actions the autoDelete mechanism can be used to delete the rubberband. Sometimes + the application wants to finish a running rubberband. This can be done by calling \c deleteMySelf. The + rubberband will be destroyed also automatically if the parent QObject or the map (see + SituationMap::Map::beginDestroy signal) will be destroyed. + + @param map Pointer to SituationMap::Map + @param parent Pointer to QObject to use as parent. + @isActive If set to false the Rubberband is not active and have to be actived to start the input. + @see RubberbandFactory + */ + explicit Rubberband3D( SituationMap::Map * map, QObject * parent, bool isActive = true ); + + public: + + ~Rubberband3D(); + + /** @} */ + + /** + @name EventHandlerInterface Methodes of AbstractEventDispatcher::EventActionInterface + The interface between EventDispatcher and Rubberband are defined in class AbstractEventDispatcher. + To Implement the interface these methodes have to be override again in the derived classes of the Rubberband class. */ + /** + @{ + */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionPress ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionRelease( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + void resetAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + /** @} */ + + + /** + Retrieve and set 3D coordinates. + @{ + */ + virtual Point3D singleCoordinate3D( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const = 0; + + virtual QVector coordinateVector3D( CoordinateSystem cs = CoordinateSystem::Geo ) const = 0; + + virtual void setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs = CoordinateSystem::Geo ) = 0; + /** @} */ + + + + + protected: + + QRectF sceneBbox() const override; + + QImage markerImage(); + QImage markerSelectionImage(); + + + + private: + + QPixmap * _markerPixmap; + QPixmap * _markerSelectionPixmap; + + }; /* Rubberband3D */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.cpp new file mode 100755 index 000000000..2e789d6ec --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.cpp @@ -0,0 +1,359 @@ + +/** +** @RDE_HEADER@ +** Implementation of rubberband class to define multi point feature geoemtry with arrow +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 21.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_arrow.h" +#include "rubberband_frame_handle.h" +#include "rubberband_line_handle.h" +#include "rubberband_point_handle.h" +#include "rubberband_drawable.h" + +#include "drawable_painter_path.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +namespace SituationMap { namespace RB +{ + + struct RubberbandArrow::InternalData + { + SituationMap::RefPointer _arrowHandle; + SituationMap::RefPointer _arrowItem; + + }; /* struct RubberbandArrow::InternalData */ + + + RubberbandArrow::RubberbandArrow( SituationMap::Map * map, QObject * parent ) + : RubberbandMultiPointFeature(map,parent) + , _internal(new InternalData) + , _arrowSceneHalfWidth(0) + , _arrowSceneLength(0) + + { + _isClosed = false; + } /* RubberbandArrow::RubberbandArrow */ + + + RubberbandArrow::~RubberbandArrow() + { + delete _internal; + _internal = nullptr; + } /* RubberbandArrow::~RubberbandArrow */ + + + unsigned int RubberbandArrow::arrowHalfWidth() const { return _arrowSceneHalfWidth; } + unsigned int RubberbandArrow::arrowLength() const { return _arrowSceneLength; } + + + void RubberbandArrow::setCoordinateVector(const QVector & coordinateVec, CoordinateSystem cs ) + { + setCoordinateVectorArrow( coordinateVec, 0.0, 0.0, cs ); + } /* RubberbandArrow::coordinateVector */ + + + void RubberbandArrow::setCoordinateVectorArrow(const QVector & coordinateVec, unsigned int arrowHalfWidth, unsigned int arrowLength, CoordinateSystem cs ) + { + if (coordinateVec.size() < 2) return; + + RubberbandMultiPointFeature::setCoordinateVector( coordinateVec, cs ); + + const QPointF & aux = pixel2ScenePosition(arrowHalfWidth,arrowLength); + + _arrowSceneHalfWidth = (unsigned int) aux.x(); + _arrowSceneLength = (unsigned int) aux.y(); + + enableModificationState(); + constructArrowHandle(); + + _baseInternal->_applyIsPossible = true; + + activateButtons(); + } /* RubberbandArrow::setCoordinateVectorArrow */ + + + QPointF RubberbandArrow::getArrowHandleScenePosition() + { + if (!_arrowSceneLength) _arrowSceneLength = 40; + if (!_arrowSceneHalfWidth) _arrowSceneHalfWidth = 20; + + + QPointF p0 = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._sceneCoordinate; + QPointF p1 = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate; + + QPointF rv( p0.x()-p1.x(), p0.y()-p1.y() ); + rv /= sqrt(rv.x()*rv.x()+rv.y()*rv.y()); + + return QPointF( p1.x() + rv.x()*_arrowSceneLength - rv.y()*_arrowSceneHalfWidth , + p1.y() + rv.y()*_arrowSceneLength + rv.x()*_arrowSceneHalfWidth ); + + } /* RubberbandArrow::getArrowHandleScenePosition */ + + + QPointF RubberbandArrow::getArrowHandlePosition() + { + const QPointF & handlePos = getArrowHandleScenePosition(); + RubberbandDrawable::RubberbandCoordinate mapHandlePos(Rubberband::_internal->_mapView, handlePos); + + return QPointF( mapHandlePos._mapCoordinate.x(), mapHandlePos._mapCoordinate.y() ); + + } /* RubberbandArrow::getArrowhHandlePosition */ + + + void RubberbandArrow::constructArrowHandle() + { + if (Rubberband::_internal->_handleList.size() < 2) return; + + const QPointF & p = getArrowHandlePosition(); + + _internal->_arrowHandle = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, p ); + + // ensure thate the arrow the arrow size in pixel remain the same size + connect( _internal->_arrowHandle.get(), &RubberbandHandle::adjusted, this, [this]() { + this->_internal->_arrowHandle->updateCoordinate( this->getArrowHandleScenePosition() ); + this->updateArrowPath(); + } ); + update(); + + } /* RubberbandArrow::constructArrowhHandle */ + + + void RubberbandArrow::constructArrow( const QPointF & p ) + { + qreal p0X = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._sceneCoordinate.x(); + qreal p0Y = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._sceneCoordinate.y(); + qreal p1X = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate.x(); + qreal p1Y = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate.y(); + + QPointF rv10( p0X-p1X , p0Y-p1Y ); + QPointF rv1p( p.x()-p1X, p.y()-p1Y ); + + rv10 /= sqrt(rv10.x()*rv10.x()+rv10.y()*rv10.y()); + + qreal scalarProduct = QPointF::dotProduct(rv1p, rv10); + if (scalarProduct > 0.0) + { + _internal->_arrowHandle->getCoordinate().updateSceneCoordinate( Rubberband::_internal->_mapView, p ); + + _arrowSceneLength = (unsigned int) scalarProduct; + _arrowSceneHalfWidth = (unsigned int) sqrt( rv1p.x()*rv1p.x()+rv1p.y()*rv1p.y() - scalarProduct*scalarProduct ); + } + } /* RubberbandArrow::constructArrow */ + + + void RubberbandArrow::updateHandleScenePosition() + { + if (!_internal->_arrowHandle) return; + + QPointF pos = getArrowHandleScenePosition(); + + _internal->_arrowHandle->updateCoordinate( pos ); + + } /* RubberbandArrow::updateHandleScenePosition */ + + + void RubberbandArrow::updateArrowPath() + { + if (!_internal->_arrowHandle) return; + + if (Rubberband::_internal->_handleList.size() < 2) return; + + const QPointF & p0 = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._sceneCoordinate; + const QPointF & p1 = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate; + const QPointF & p = _internal->_arrowHandle->getCoordinate()._sceneCoordinate; + + QPointF rv( p0.x()-p1.x(), p0.y()-p1.y() ); + rv /= sqrt(rv.x()*rv.x()+rv.y()*rv.y()); + + if (!_internal->_arrowItem) + { + _internal->_arrowItem = new DrawablePainterPath( Rubberband::_internal->_mapView ); + _internal->_arrowItem -> enableClosedLine( true ); + _internal->_arrowItem -> fillSolid( true ); + _internal->_arrowItem -> zBufferPlane( RubberbandDrawable::ZBufferPlane::ArrowSymbol ); + } + + QVector arrowPath; + + arrowPath.append( p1 ); + arrowPath.append( p ); + arrowPath.append( QPointF( p1.x() + rv.x()*_arrowSceneLength + rv.y()*_arrowSceneHalfWidth + , p1.y() + rv.y()*_arrowSceneLength - rv.x()*_arrowSceneHalfWidth ) ); + + _internal->_arrowItem -> setPath( arrowPath ); + + RubberbandFrameHandle * frameHandle = getFrameHandle(); + + if (frameHandle) + { + QVector vertexSet; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * pointHandlePtr = dynamic_cast(handle.get()); + + vertexSet.append(pointHandlePtr->getCoordinate() ); + } + vertexSet.append( _internal->_arrowHandle->getCoordinate() ); + vertexSet.append( RubberbandDrawable::RubberbandCoordinate(Rubberband::_internal->_mapView,p) ); + + frameHandle->updateFrameShape( vertexSet ); + } + } /* RubberbandArrow::updateArrowPath */ + + + bool RubberbandArrow::selectAction( AbstractEventDispatcher * causer, int x, int y ) + { + Q_UNUSED(causer); + + if (!rubberbandIsAlive()) return false; + + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (_baseInternal->buttonProxyActive(scenePos)) return false; + + _baseInternal->_lastClickPosition = scenePos; + + if (isEnabledConstructionState()) return RubberbandMultiPointFeature::selectAction(causer,x,y); + if (_internal->_arrowHandle->isPositionInside(scenePos)) + { + _internal->_arrowHandle->setSelection(); + eventHandled = true; + } + else eventHandled = selectHandles( x, y, scenePos ); + + if (eventHandled) + { + update(); + deactivateButtons(); + } + return eventHandled; + } /* RubberbandArrow::selectAction */ + + + bool RubberbandArrow::setCoordinateAction(AbstractEventDispatcher * causer, int x, int y) + { + Q_UNUSED(causer); + + if (!rubberbandIsAlive()) return false; + + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + Rubberband::_internal->_lastClickPosition = scenePos; + + if (isEnabledConstructionState()) return RubberbandMultiPointFeature::setCoordinateAction(causer, x, y); + + if (_internal->_arrowHandle->isSelected()) + { + _internal->_arrowHandle->setSelection(false); + constructArrow(scenePos); + eventHandled = true; + } + else return RubberbandMultiPointFeature::setCoordinateAction(causer, x, y); + + if (eventHandled) update(); + + updateButtonPosition(); + + activateButtons(); + + return eventHandled; + } /* RubberbandArrow::setCoordinateAction */ + + + bool RubberbandArrow::movePickedAction(AbstractEventDispatcher * causer, int x, int y ) + { + if (!rubberbandIsAlive()) return false; + + if ((_internal->_arrowHandle) && (_internal->_arrowHandle->isSelected())) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + constructArrow( scenePos ); + update(); + return false; + } + + return RubberbandMultiPointFeature::movePickedAction(causer,x,y); + + } /* RubberbandArrow::movePickedAction */ + + + bool RubberbandArrow::moveAction(AbstractEventDispatcher * causer, int x, int y) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (_internal->_arrowHandle) + { + if (_internal->_arrowHandle->isPositionInside(scenePos)) + { + _internal->_arrowHandle->setSelection(true); + return false; + } + _internal->_arrowHandle->setSelection(false); + } + return RubberbandMultiPointFeature::moveAction(causer,x,y); + } /* RubberbandArrow::moveAction */ + + + bool RubberbandArrow::completeStateAction(AbstractEventDispatcher * causer, int x, int y) + { + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (isEnabledConstructionState()) + { + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + { + constructArrowHandle(); + } + update(); + } + } + return RubberbandMultiPointFeature::completeStateAction(causer,x,y);; + } /* RubberbandArrow::completeStateAction */ + + + void RubberbandArrow::update() + { + _isClosed = false; + RubberbandMultiPointFeature::update(); + + if (_internal->_arrowHandle) + { + _internal->_arrowHandle -> setDrawColor(_handleDrawColor ); + _internal->_arrowHandle -> setFillColor(_handleFillColor ); + _internal->_arrowHandle -> setSelectionColor(_handleSelectionColor ); + _internal->_arrowHandle -> setPickingRatio( _pickingRatio ); + + updateHandleScenePosition(); + updateArrowPath(); + } + } /* RubberbandArrow::update */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.h new file mode 100755 index 000000000..ca63b054f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_arrow.h @@ -0,0 +1,130 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multi point feature geoemtry with Arrow +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 21.08.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_multi_point_feature.h" + + +namespace SituationMap { namespace RB +{ + + class RubberbandLineHandle; + class RubberbandFrameHandle; + + /** + @class RubberbandArrow rubberband_arrow.h + @brief Implementation of a rubberband for lines with arrow. + @see RubberbandFactory, Rubberband, RubberbandMultiPointFeature + + Implementation of rubberbands used to input corridors. There are two different modes \c InputCenterLine and \c InputBoundary. In the + InputCenterLine mode the center line of the corridor has to be defined. The rubberband displays then the border geometry. + In the InputBoundary mode one side of the border has to be defined. The \c coordinateVector methodes always working on the input geometry. + */ + class SITUATIONMAP_EXPORT RubberbandArrow : public RubberbandMultiPointFeature + { + Q_OBJECT + public: + /** + @name Construction & Destruction + @{ + */ + private: + + friend class RubberbandFactory; + explicit RubberbandArrow( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + ~RubberbandArrow(); + /** @} */ + + + /** + Sets coordinates to modify to internal coordinate list. + + @param coordinateVec List of coordinates to modify. + @param cs Defines the coordinate system of returned coordinates. + */ + void setCoordinateVector( const QVector & coordinateVec, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + /** + Sets coordinates to modify to internal coordinate list. In \c InputCenterLine mode the coordinates will be interpreted as + center line of the corridor. In \c InputBoundary the coordinates are defining one border. Id \c corridorWidth is negative + the coordinates are the right border otherwise the left one. + + @param coordinateVec List of coordinates to modify. + @param arrowHalfWidth Half width of arrow in pixel. + @param arrowLength Length of arrow in pixel. + @param cs Defines the coordinate system of returned coordinates. + */ + void setCoordinateVectorArrow(const QVector & coordinateVec, unsigned int arrowHalfWidth, unsigned int arrowLength, CoordinateSystem cs = CoordinateSystem::Geo ); + + + /** + Return the width of the corridor. + */ + unsigned int arrowHalfWidth() const; + unsigned int arrowLength() const; + + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + protected: + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + + unsigned int _arrowSceneHalfWidth; + unsigned int _arrowSceneLength; + + void update() override; + + + void updateArrowPath(); + void updateHandleScenePosition(); + + void constructArrow( const QPointF & p ); + void constructArrowHandle(); + + QPointF getArrowHandleScenePosition(); + QPointF getArrowHandlePosition(); + + + + }; /* RubberbandCorridor */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.cpp new file mode 100755 index 000000000..3ef42621c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.cpp @@ -0,0 +1,654 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_base.h" +#include "rubberband_base_internal.h" +#include "rubberband_handle_proxy.h" + +#include "rubberband_factory.h" + +#include + +#include +#include + +// Qt +#include +#include + +#include +#include +#include +#include +#include + +#include "apply.xpm" +#include "abort.xpm" +#include "undo.xpm" + + static SituationMap::Map * currentFactoryMap = nullptr; + static bool allowMultipleRubberbands = false; + + +namespace SituationMap { namespace RB +{ + QPixmap * RubberbandBase::BaseInternalData::_applyIcon = nullptr; + QPixmap * RubberbandBase::BaseInternalData::_abortIcon = nullptr; + QPixmap * RubberbandBase::BaseInternalData::_undoIcon = nullptr; + + RubberbandBase::BaseInternalData::BaseInternalData( SituationMap::Map * map, RubberbandBase * me ) + : _map(map) + , _mapCtFunctor(map->coordTranslationFunctor()) + , _isActive(false) + , _applyIsPossible(false) + , _undoIsPossible(false) + , _hasUndoButton(false) + , _lastClickPosition(-1,-1) + , _buttonBoxSceneAnchor() + , _buttonBoxFixPosition(-1,-1) + , _buttonProxy() + , _updateFunctorList() + , _logger(getLogger("RubberbandBase")) + { + QGraphicsView * view = map->getQGraphicsView(); + + if (view) + { + _restoreCursor = view->viewport()->cursor(); // workaround Qt bug. remove when fixed + } + + _buttonBox = new QWidget(); + _buttonBox->setObjectName(QLatin1String("RBButtonBoxWidget")); + _buttonBox->hide(); + _buttonBox->setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); + + _applyButton = new QPushButton( (_applyIcon) ? *_applyIcon : QPixmap(apply_xpm), QString(), _buttonBox ); + _applyButton -> setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); + _applyButton -> setObjectName(QLatin1String("RBApply")); + _applyButton -> setAttribute(Qt::WA_AcceptTouchEvents); + _applyButton -> grabGesture( Qt::GestureType::TapGesture ); + me->connect( _applyButton, &QPushButton::clicked, me, &RubberbandBase::onDo_applyButtonClicked ); + + _cancelButton = new QPushButton( (_abortIcon) ? *_abortIcon : QPixmap(abort_xpm), QString(), _buttonBox ); + _cancelButton -> setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); + _cancelButton -> setObjectName(QLatin1String("RBCancel")); + _cancelButton -> setAttribute(Qt::WA_AcceptTouchEvents); + _cancelButton -> grabGesture( Qt::GestureType::TapGesture ); + me->connect( _cancelButton, &QPushButton::clicked, me, &RubberbandBase::onDo_cancelButtonClicked ); + + _undoButton = new QPushButton( (_undoIcon) ? *_undoIcon : QPixmap(undo_xpm), QString(), _buttonBox ); + _undoButton -> setAttribute( Qt::WidgetAttribute::WA_NoSystemBackground, true ); + _undoButton -> setObjectName(QLatin1String("RBUndo")); + _undoButton -> setAttribute(Qt::WA_AcceptTouchEvents); + _undoButton -> grabGesture( Qt::GestureType::TapGesture ); + _undoButton -> hide(); + me->connect( _undoButton, &QPushButton::clicked, me, &RubberbandBase::onDo_undoButtonClicked ); + + _buttonLayout = new QHBoxLayout( _buttonBox ); + _buttonLayout->setObjectName(QLatin1String("RBButtonBoxLayoutHBox")); + + _buttonLayout -> addWidget( _undoButton ); + _buttonLayout -> addWidget( _cancelButton ); + _buttonLayout -> addWidget( _applyButton ); + + } /* InternalData */ + + + Rubberband::BaseInternalData::~BaseInternalData() + { + _map = nullptr; + _mapCtFunctor = nullptr; + + _buttonProxy = nullptr; // delete RefPointer + if (_buttonBox) _buttonBox -> deleteLater(); // delete all widgets of button box + + _buttonBox = nullptr; + _buttonLayout = nullptr; + _applyButton = nullptr; + _cancelButton = nullptr; + _undoButton = nullptr; + + } /* ~InternalData */ + + + bool RubberbandBase::BaseInternalData::buttonProxyActive( const QPointF & pos ) const + { + if (_buttonProxy) + { + return ((_buttonProxy->isVisible()) && (_buttonProxy->isPositionInside(pos))); + } + return false; + } /* bool RubberbandBase::BaseInternalData::buttonProxyActive( const QPointF & pos ) const*/ + + + void RubberbandBase::BaseInternalData::enableAndShowOnlyPossibleButtons() + { + _applyButton->setEnabled(_applyIsPossible); + + if (_undoButton) + { + if (_hasUndoButton) + { + _undoButton -> show(); + } + _undoButton -> setEnabled(_undoIsPossible); + } + } /* void RubberbandBase::InternalData::showOnlyPossibleButtons() */ + + + static QHash> &activeRubberbands() + { + static QHash> hashTable; + return hashTable; + } // activeRubberbands() + + + void RubberbandBase::currentFactoryMap(SituationMap::Map * map) { ::currentFactoryMap = map; } + void RubberbandBase::allowMultipleRubberbands( bool ok ) { ::allowMultipleRubberbands = ok; } + + + void * RubberbandBase::operator new(size_t bytes) + { + QString exceptionMsg; + + // be careful it is not thread safe here + if (::currentFactoryMap) + { + bool doNew( ::allowMultipleRubberbands ); + + if (!doNew) + { + doNew = true; + const auto rubberbands = SituationMap::RB::activeRubberbands().value(::currentFactoryMap); + for (const auto rbnd : rubberbands) + { + if (rbnd->rubberbandIsAlive()) + { + doNew = false; + break; + } + } + } + + if (doNew) + { + RubberbandBase::currentFactoryMap( nullptr ); + RubberbandBase::allowMultipleRubberbands( false ); + return ::operator new(bytes); + } + else exceptionMsg = QLatin1String("Another rubberband is already alive at moment"); + RubberbandBase::currentFactoryMap( nullptr ); + } + else exceptionMsg = QLatin1String("Use rubberband factory to create rubberbands. Calling new directly is forbidden!"); + + RubberbandBase::currentFactoryMap( nullptr ); + RubberbandBase::allowMultipleRubberbands( false ); + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * because "return nullptr;" is not compliante with the c++ standard + * throw exception instead + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + throw Exception( __FILE__, __LINE__, exceptionMsg ); + + } /* RubberbandBase::operator new */ + + + QList RubberbandBase::activeRubberbands( SituationMap::Map * map ) + { + QList ret; + const auto rubberbands = SituationMap::RB::activeRubberbands().value(map); + for (const auto rbnd : rubberbands) + { + if (rbnd && rbnd->rubberbandIsAlive()) + ret += rbnd; + } + return ret; + } /* RubberbandBase::activeRubberbands */ + + void RubberbandBase::deleteAllActiveRubberbands(Map *map) + { + const QList &rbCont = RubberbandBase::activeRubberbands(map); + qDeleteAll(rbCont); + } /* RubberbandBase::deleteAllActiveRubberbands */ + + + bool RubberbandBase::isRubberbandActive(SituationMap::Map * map) + { + return (activeRubberbands(map).size() > 0); + } /* RubberbandBase::isRubberbandActive */ + + + void RubberbandBase::abortActiveRubberbands( SituationMap::Map * map ) + { + const QList & rbList = activeRubberbands(map); + for (auto rb : rbList) + { + emit rb->abort(rb); + rb->doAutoDelete(); + } + } /* RubberbandBase::abortActiveRubberbands */ + + + void RubberbandBase::deleteMySelf() + { + if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_kickTheBucket(); + if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_delete(); + if (_windowEventDispatcher) _windowEventDispatcher -> onDo_delete(); + + _viewPortEventDispatcher = nullptr; + _windowEventDispatcher = nullptr; + _baseInternal->_isActive = false; + + deleteLater(); + } /* RubberbandBase::deleteMySelf */ + + + RubberbandBase::RubberbandBase( SituationMap::Map * map, QObject * parent, bool isActive ) + : QObject( parent ) + , EventActionInterface() + , _baseInternal( new RubberbandBase::BaseInternalData(map,this) ) + , _drawColor(QColor("black")) + , _fillColor(QColor("black")) + , _handleDrawColor(QColor("black")) + , _handleFillColor(QColor("yellow")) + , _handleSelectionColor(QColor("red")) + , _frameColor(QColor("black")) + , _frameFillColor(QColor(150,150,0,50)) + , _frameSelectionColor(QColor(255,0,0,50)) + , _frameHandleMargin(10) + , _pickingRatio(0) + , _finishAfterConstruction(true) + , _autoDelete(false) + , _viewPortEventDispatcher(nullptr) + , _windowEventDispatcher (nullptr) + , _userData(nullptr) + { + _fillColor.setAlpha(100); + + if (isActive) activate(); + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * In case of multiple rubberbands can be active, it is forbidden to + * create a rubberband of the same type twice anyway. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + bool rubberbandOfSameTypeIsActive = false; + const auto rubberbands = SituationMap::RB::activeRubberbands().value(map); + for (auto rbnd : rubberbands) + { + // Just to check the class names with debugger + // const char * rbndName = typeid(*rbnd).name(); + // const char * thisName = typeid(*this).name(); + + if (( typeid(*rbnd) == typeid(*this)) && rbnd->rubberbandIsAlive()) + { + rubberbandOfSameTypeIsActive = true; + break; + } + } + + if (rubberbandOfSameTypeIsActive) + { + // disable dispatcher, so the rubberband is not alive yet. + if (_viewPortEventDispatcher) + { + _viewPortEventDispatcher -> onDo_kickTheBucket(); + _viewPortEventDispatcher -> onDo_delete(); + } + if (_windowEventDispatcher) _windowEventDispatcher->onDo_delete(); + _viewPortEventDispatcher = nullptr; + _windowEventDispatcher = nullptr; + } + else + { + SituationMap::RB::activeRubberbands()[map].append( this ); + if (_windowEventDispatcher) + { + _windowEventDispatcher->setCursor(QCursor(Qt::CrossCursor)); + _windowEventDispatcher->grab(); + } + if (_viewPortEventDispatcher) + { + _viewPortEventDispatcher->setCursor(QCursor(Qt::CrossCursor)); + _viewPortEventDispatcher->grab(); + } + } + + if (map) + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Sending signals of RubberbandFactory to inform about rubberband creation and destroy would be + * better placed in RubberbandFactory source (rubberband_factory.cpp) but there it's a lot of + * more writing stuff ... + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + SituationMap::RB::RubberbandFactory & rbndFactory = map -> rubberbandFactory(); + emit rbndFactory.rubberbandCreated(); + + connect( this, &QObject::destroyed, &rbndFactory, &SituationMap::RB::RubberbandFactory::rubberbandDestroyed ); + } + + } /* RubberbandBase::RubberbandBase */ + + + RubberbandBase::~RubberbandBase() + { + if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_kickTheBucket(); + if (_viewPortEventDispatcher) _viewPortEventDispatcher -> onDo_delete(); + if (_windowEventDispatcher) _windowEventDispatcher -> onDo_delete(); + + _viewPortEventDispatcher = nullptr; + _windowEventDispatcher = nullptr; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Test if map has been already destroyed. If map exists all is nice ... + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + if (_baseInternal->_map) + { + SituationMap::RB::activeRubberbands()[_baseInternal->_map].removeOne( this ); + emit finished( this ); + } + else + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * ... otherwise we have to search the rubberband + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + const auto rubberbands = SituationMap::RB::activeRubberbands(); + for (auto rubberList : rubberbands) + rubberList.removeOne(this); + } + + delete _baseInternal; + _baseInternal = nullptr; + + } /* RubberbandBase::~RubberbandBase */ + + + QPointF RubberbandBase::projectCoordinateToMap( CoordinateSystem cs, const QPointF & loc ) const + { + return (cs != CoordinateSystem::Geo || !_baseInternal->_mapCtFunctor) ? loc : _baseInternal->_mapCtFunctor->geographicToMap( loc ); + } /* RubberbandBase::projectCoordinateToMap */ + + + QPointF RubberbandBase::projectCoordinateFromMap( CoordinateSystem cs, const QPointF & loc ) const + { + return (cs != CoordinateSystem::Geo || !_baseInternal->_mapCtFunctor) ? loc : _baseInternal->_mapCtFunctor->mapToGeographic( loc ); + } /* RubberbandBase::projectCoordinateFromMap */ + + + QPointF RubberbandBase::projectCoordinateFromMap( CoordinateSystem cs, const Esri::ArcGISRuntime::Point & loc ) const + { + return (cs != CoordinateSystem::Geo || !_baseInternal->_mapCtFunctor) ? QPointF(loc.x(), loc.y()) : _baseInternal->_mapCtFunctor->mapToGeographic( loc ); + } /* RubberbandBase::projectCoordinateFromMap */ + + + + void RubberbandBase::activate() + { + if (!_baseInternal->_map) return; + + if (!_baseInternal->_isActive) + { + QGraphicsView * view = _baseInternal->_map->getQGraphicsView(); + if (view) + { + _viewPortEventDispatcher = new EventDispatcher(view->viewport(), this); + _windowEventDispatcher = new EventDelegater(view, _viewPortEventDispatcher); + _baseInternal->_isActive = true; + + MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); + + connect(_viewPortEventDispatcher.data(), &EventDispatcher::mouseMovedEvent, + mapImpl, &MapImpl::onHandleMouseMove); + } + } + } /* RubberbandBase::activate */ + + + bool RubberbandBase::autoSelectAction(int x, int y) + { + _viewPortEventDispatcher->_leftPressHandled = true; + return selectAction(nullptr,x,y); + } /* RubberbandBase::autoSelectAction */ + + + void RubberbandBase::installExternalUpdate(const std::function & updateFunctor) + { + _baseInternal->_updateFunctorList << updateFunctor; + } // void RubberbandBase::installExternalUpdate( const std::function & updateFunctor) + + + void RubberbandBase::externalUpdate() + { + for (const auto &update : std::as_const(_baseInternal->_updateFunctorList)) + { + if (!update(this)) + break; + } + } // void RubberbandBase::externalUpdate() + + + + void RubberbandBase::updateButtonPosition() + { + if ((_baseInternal->_lastClickPosition.x() < 0) || (_baseInternal->_lastClickPosition.y() < 0)) + _baseInternal->_buttonBoxSceneAnchor = sceneBbox().bottomRight(); + else + _baseInternal->_buttonBoxSceneAnchor = _baseInternal->_lastClickPosition; + + // _internal->_mapView->update(); + } /* RubberbandBase::updateButtonPosition */ + + + void RubberbandBase::setFixedButtonBoxPosition( QPoint widgetPos ) + { + _baseInternal->_buttonBoxFixPosition = widgetPos; + activateButtons(); + } /* RubberbandBase::setFixedButtonBoxPosition */ + + + void RubberbandBase::unsetFixedButtonBoxPosition() + { + _baseInternal->_buttonBoxFixPosition = QPoint(-1,-1); + + if (_baseInternal->_buttonProxy) + { + _baseInternal->_buttonProxy->setPos( _baseInternal->_buttonBoxSceneAnchor ); + } + } /* RubberbandBase::unsetFixedButtonBoxPosition */ + + + bool RubberbandBase::isButtonBoxPositionFixed() { return ((_baseInternal->_buttonBoxFixPosition.x() >= 0) && (_baseInternal->_buttonBoxFixPosition.y() >= 0)); } + + + void RubberbandBase::onDo_updateButtonFixCoordinates() + { + if (!_baseInternal->_buttonProxy) return; + + if (isButtonBoxPositionFixed()) + { + // Don't use bottom right corner of rubberband bbox to place the buttons, + // rather than use fixed window position. + + const QPoint & pos = _baseInternal->_buttonBoxFixPosition; + _baseInternal->_buttonProxy->setPos( pixel2ScenePosition(pos.x(),pos.y()) ); + } + } /* RubberbandStateSelect::onDo_updateButtonFixCoordinates */ + + + void RubberbandBase::activateButtons() + { + DEBUG_LOG(_baseInternal->_logger) << LOGFUNCTION; + if (!_baseInternal->_buttonProxy) + { + _baseInternal->_buttonProxy = new RubberbandHandleProxy( _baseInternal->_map->getQGraphicsView(), _baseInternal->_buttonBox ); + _baseInternal->_buttonProxy->setRestoreCursor( _baseInternal->_restoreCursor ); // workaround Qt bug. remove when fixed + + connect( _baseInternal->_buttonProxy.get(), &RubberbandHandleProxy::adjusted, this, [this]() + { + this->_baseInternal->_buttonBoxSceneAnchor = this->_baseInternal->_buttonProxy->getCoordinate()._sceneCoordinate; + } ); + } + + _baseInternal->enableAndShowOnlyPossibleButtons(); + + if (isButtonBoxPositionFixed()) + { + const QPoint & pos = _baseInternal->_buttonBoxFixPosition; + _baseInternal->_buttonProxy->setPos( pixel2ScenePosition(pos.x(),pos.y()) ); + _baseInternal->_buttonProxy->setSceneOffset( QPointF(0,0) ); + + } + else + { + _baseInternal->_buttonProxy->setPos( _baseInternal->_buttonBoxSceneAnchor ); + _baseInternal->_buttonProxy->setSceneOffset( QPointF(_frameHandleMargin+5,_frameHandleMargin+5) ); + } + + _baseInternal->_buttonProxy->show(); + + } /* Rubberband::activateButtons */ + + + void RubberbandBase::deactivateButtons() + { + DEBUG_LOG(_baseInternal->_logger) << LOGFUNCTION; + if (isButtonBoxPositionFixed()) return; + if (_baseInternal->_buttonProxy) _baseInternal->_buttonProxy->hide(); + } /* RubberbandBase::deactivateButtons() */ + + + void RubberbandBase::adjustButtons() + { + if (_baseInternal->_buttonProxy) + { + activateButtons(); + } + } /* RubberbandBase::adjustButtons */ + + + void RubberbandBase::applyButtonClicked() + { + completeStateAction( _viewPortEventDispatcher, 0.0, 0.0 ); + } /* RubberbandBase::applyButtonClicked */ + + + void RubberbandBase::cancelButtonClicked() + { + abortAction( _viewPortEventDispatcher, 0.0, 0.0 ); + } /* RubberbandBase::cancelButtonClicked */ + + + void RubberbandBase::undoButtonClicked() + { + undoAction( _viewPortEventDispatcher, 0.0, 0.0 ); + } /* RubberbandBase::undoButtonClicked */ + + + void RubberbandBase::resetIconsToDefault() + { + if (BaseInternalData::_applyIcon) { delete BaseInternalData::_applyIcon; BaseInternalData::_applyIcon = nullptr; } + if (BaseInternalData::_abortIcon) { delete BaseInternalData::_abortIcon; BaseInternalData::_abortIcon = nullptr; } + if (BaseInternalData::_undoIcon ) { delete BaseInternalData::_undoIcon; BaseInternalData::_undoIcon = nullptr; } + } /* RubberbandBase::resetIconsToDefault */ + + + void RubberbandBase::setIconApply(const QPixmap & icon) + { + if (BaseInternalData::_applyIcon == nullptr) BaseInternalData::_applyIcon = new QPixmap(icon); + else *BaseInternalData::_applyIcon = icon; + } /* RubberbandBase::setIconApply */ + + + void RubberbandBase::setIconAbort(const QPixmap & icon) + { + if (BaseInternalData::_abortIcon == nullptr) BaseInternalData::_abortIcon = new QPixmap(icon); + else *BaseInternalData::_abortIcon = icon; + } /* RubberbandBase::setIconAbort */ + + + void RubberbandBase::setIconUndo(const QPixmap & icon) + { + if (BaseInternalData::_undoIcon == nullptr) BaseInternalData::_undoIcon = new QPixmap(icon); + else *BaseInternalData::_undoIcon = icon; + } /* RubberbandBase::setIconUndo */ + + + QPointF RubberbandBase::pixel2ScenePosition( int x, int y ) const + { + QGraphicsView * view = _baseInternal->_map->getQGraphicsView(); + return (view) ? view->mapToScene(x, y) : QPointF(x,y); + } /* RubberbandBase::pixel2ScenePosition */ + + + bool RubberbandBase::buttonPositionAction(AbstractEventDispatcher *, int x, int y) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (!_baseInternal->buttonProxyActive(scenePos)) + { + _baseInternal->_buttonBoxSceneAnchor = scenePos; + adjustButtons(); + } + return false; + } /* bool RubberbandBase::buttonPositionAction(AbstractEventDispatcher * causer, int x, int y) */ + + + void RubberbandBase::unlinkEventDispatcher( AbstractEventDispatcher* ) + { + _viewPortEventDispatcher = nullptr; + _windowEventDispatcher = nullptr; + + } /* RubberbandBase::unlinkEventDispatcher */ + + + bool RubberbandBase::abortAction( AbstractEventDispatcher * causer, int, int ) + { + _baseInternal->_buttonBox->hide(); + + if (causer) causer->onDo_kickTheBucket(); + + emit abort( this ); + doAutoDelete(); + return true; + } /* RubberbandBase::abortAction */ + + + bool RubberbandBase::applyAndEndAction( AbstractEventDispatcher * causer, int,int ) + { + _baseInternal->_buttonBox->hide(); + + if (causer) causer->onDo_kickTheBucket(); + + emit apply( this ); + doAutoDelete(); + return true; + } /* RubberbandBase::applyAndEndAction */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.h new file mode 100755 index 000000000..d43ea7ca5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base.h @@ -0,0 +1,500 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include "event_dispatcher.h" +#include "event_delegater.h" + +#include +#include + +#include +#include +#include + +#include + +#include + + +namespace Esri { +namespace ArcGISRuntime { + class MapGraphicsView; + class Point; +} } + + + +namespace SituationMap +{ + +/** + Namespace for all rubberband stuff (is part of namespace SituationMap) +*/ +namespace RB +{ + /** + */ + enum class CoordinateSystem + { + Map, + Geo + }; + + + /** + @class RubberbandBase rubberband_base.h + @brief Base class of all Rubberbands + + Rubberbands are used to create or modifiy geometries like lines, circles, polygons etc. by processing + Qt input events like keyboard and mouse events. To create a rubberband the RubberbandFactory has to be used. + At end of rubberband processing the signal \c abort or\c apply are emitted. To delete the rubberband the + function deleteMySelf must be called. To use the delete operator is dangerous because at end of signal + processing some program code may be performed. + + Example to how to use rubberband: + + @code + + SituationMap::Map & map = ... + + const RubberbandFactory & rubberbandFactory = map.rubberbandFactory(); + + RubberbandPointFeature * rubberband = rubberbandFactory.create(); + if (rubberband != nullptr) + { + QObject::connect( rubberband, &SiMapRubBnd::Rubberband::apply, [=](SiMapRubBnd::Rubberband * rubberband ) + { + // rubberband has finished input + if (rubberband) + { + const QVector & vertexList = rubberband->coordinateVector(); + + // do something with vertexList + + rubberband->deleteMySelf(); // notice don't call here delete rubberband directly !!! + } + } ); + + QObject::connect( rubberband, &SiMapRubBnd::Rubberband::abort, this, [=](SiMapRubBnd::Rubberband * rubberband ) + { + // Rubberband has been aborted by user + if (rubberband) + { + rubberband->deleteMySelf(); // notice don't call here delete rubberband directly !!! + } + } ); + } + + @endcode + + Following keyboard commands are available in active rubberbands: + + | Key | Modifier | Funktion | + | :----: |:-------: | :--------------------------------------------------------------------------- | + | Esc | | Rubberband abbrechen | + | Return | | Rubberband abschließen und Eingabe übernehmen wenn möglich, sonst Abbruch | + | Left | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach links schieben | + | Right | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach rechts schieben | + | Up | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach oben schieben | + | Down | | Selektierten Handle (Punkt,Frame) um 1 Pixel nach unten schieben | + | Left | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach links schieben | + | Right | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach rechts schieben | + | Up | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach oben schieben | + | Down | Shift | Selektierten Handle (Punkt,Frame) um 10 Pixel nach unten schieben | + + @see EventDispatcher + @see Rubberband, RubberbandPointFeature, RubberbandPointCluster, RubberbandMultiPointFeature, RubberbandRectangle, RubberbandEllipse, RubberbandCorridor, RubberbandArrow + Rubberband3D, RubberbandPointFeature3D, RubberbandMultiPointFeature3D + + */ + + class SITUATIONMAP_EXPORT RubberbandBase : public QObject, public EventDispatcher::EventActionInterface + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Because class EventDispatcher::EventActionInterface is a pure virtual interface function the classic way of declaration would be + * + * SITUATIONMAP_EXPORT Rubberband : public QObject, public virtual EventDispatcher::EventActionInterface + * + * But with Visual Studio 2013 virtual inheritance combined with constructor delegation doesn't work and may crash the program. In + * this case the virtual inheritance of EventActionInterface isn't need because in the derived classes multiple inheritance will + * not be used. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + Q_OBJECT + + public: + + /** + @name Construction & Destruction + @{ + */ + + public: + + /** + Constructs a Rubberband for a situation map. Use \c RubberbandFactor to create a rubberband. + In case of the user actions the autoDelete mechanism can be used to delete the rubberband. Sometimes + the application wants to finish a running rubberband. This can be done by calling \c deleteMySelf. The + rubberband will be destroyed also automatically if the parent QObject or the map (see + SituationMap::Map::beginDestroy signal) will be destroyed. + + @param map Pointer to SituationMap::Map + @param parent Pointer to QObject to use as parent. + @isActive If set to false the Rubberband is not active and have to be actived to start the input. + @see RubberbandFactory + */ + RubberbandBase( SituationMap::Map * map, QObject * parent, bool isActive = true ); + + ~RubberbandBase(); + /** @} */ + + /** + Activate rubberband if it has been created with \c isActive = false. + */ + void activate(); + + /** + Provides a list of active rubberbands for a situation map. + */ + static QList activeRubberbands( SituationMap::Map * map ); + + /** + Delete active rubberbands for a situation map. + */ + static void deleteAllActiveRubberbands( SituationMap::Map * map ); + + /** + Use deleteMySelft instead of delete operator to avoid dangling this pointerin Qt signal processing. It calls delete this; later on. + */ + void deleteMySelf(); + + /** + If setting auto delete flag to true the delete will be done automatically after sending signal abort or apply. + The default value of auto delete flag is false. + @{ + */ + inline void setAutoDelete(bool on) { _autoDelete = on; } + + + inline bool autoDelete() const { return _autoDelete; } + /** @} */ + + /** + Returns true if the rubberband is already alive. Should be tested after creation of a rubberband because it is + possibled to create not living rubberbands, e.g. a rubberband was create twice and the first one has not finished + */ + inline bool rubberbandIsAlive() const { return !_viewPortEventDispatcher.isNull(); } + + /** A single user action step has finished. Interim results can be used to show a preview now. + */ + void userActionFinished() override { emit interimResultsChanged(this); } + + /** + @name ColorSettings Methodes to define visual behaviour of rubberband + @{ + */ + inline void setDrawColor (const QColor & col ) { _drawColor = col; update(); } ///< set color for general drawing + inline void setFillColor (const QColor & col ) { _fillColor = col; update(); } ///< set color for general filling + inline void setHandleDrawColor (const QColor & col ) { _handleDrawColor = col; update(); } ///< set color for drawing handles + inline void setHandleFillColor (const QColor & col ) { _handleFillColor = col; update(); } ///< set color for filling handles + inline void setHandleSelectionColor(const QColor & col ) { _handleSelectionColor = col; update(); } ///< set handle selection color + inline void setFrameColor (const QColor & col ) { _frameColor = col; update(); } ///< set frame handle outline color + inline void setFrameFillColor (const QColor & col ) { _frameFillColor = col; update(); } ///< set frame handle fill color + inline void setFrameHandleMargin (int margin ) { _frameHandleMargin = margin; update(); } ///< set margin of frame in pixel + + inline void setPickingRatio (unsigned int size ) { _pickingRatio = size; update(); } ///< set size of point handles + inline void setLineHandleWidth (unsigned int width ) { _lineHandleWidth = width; update(); } ///< set width of line handles + + inline const QColor getDrawColor () const { return _drawColor; } ///< get color for general drawing + inline const QColor getFillColor () const { return _fillColor; } ///< get color for general filling + inline const QColor getHandleDrawColor() const { return _handleDrawColor; } ///< get color for drawing handles + inline const QColor getHandleFillColor() const { return _handleFillColor; } ///< get color for filling handles + inline const QColor getHandleSelectionColor() const { return _handleSelectionColor; } ///< get handle selection color + inline const QColor getFrameColor() const { return _frameColor; } ///< get frame handle outline color + inline const QColor getFrameFillColor() const { return _frameFillColor; } ///< get frame handle fill color + inline int getFrameHandleMargin() const { return _frameHandleMargin; } ///< get margin of frame in pixel + + inline unsigned int getPickingRatio() const { return _pickingRatio; } ///< get size of point handles + /** @} */ + + bool applyAndEndAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool abortAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + /** + @name Set icons for rubberband buttons + + In case of the application doesn't use the default rubberband icons other icons can be set with the setIcon functions. This redefine + of the button icons have to be done before creating a rubberband. + @{ + */ + static void setIconApply( const QPixmap & ); + static void setIconAbort( const QPixmap & ); + static void setIconUndo ( const QPixmap & ); + + static void resetIconsToDefault(); + /** @} */ + + /** + Start Rubberband mouse input automatically by program + */ + bool autoSelectAction( int x, int y ); + + /** + If set to true the rubberband will send apply signal after leaving construction state and ends + otherwise it continues in modification state. Default value is true. + */ + inline void enableFinishAfterConstruction(bool flag) { _finishAfterConstruction = flag; } + + + /** + Defines fixed widget position of rubberband button box + */ + void setFixedButtonBoxPosition( QPoint widgetPos ); + + + /** + Undefines fixed widget position. The rubberband button box will be placed at the bottom right coner of + the rubberbands bounding box. + */ + void unsetFixedButtonBoxPosition(); + + /** + Returns true if a fixed widget position of rubberband button box is used. + */ + bool isButtonBoxPositionFixed(); + + /** + @name EventHandlerInterface Methodes of AbstractEventDispatcher::EventActionInterface + The interface between EventDispatcher and Rubberband are defined in class AbstractEventDispatcher. + To Implement the interface these methodes have to be override again in the derived classes of the Rubberband class. */ + /** + @{ + */ + + bool buttonPositionAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + void unlinkEventDispatcher ( AbstractEventDispatcher * causer ) override; + + /** @} */ + + + /** + Returns true if the coordinates are valid elsewise false + */ + virtual bool coordinatesAreValid() const { return true; } + + + /** + Returns count of coordinates + */ + virtual unsigned int coordinateCount() const = 0; + + /** + Returns constructed or modified coordinates + @param cs Defines the coordinate system of returned coordinates. + */ + virtual QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const = 0; + + /** + Returns a single coordinate from rubberbands coordinate list + @param idx index of coordinate + @param cs Defines the coordinate system of returned coordinate. + */ + virtual QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const = 0; + + /** + Sets coordinates to modify to internal coordinate list + + @param pntList List of coordinates to modify + @param cs Defines the coordinate system of input coordinates. + */ + virtual void setCoordinateVector( const QVector & pntList, CoordinateSystem cs = CoordinateSystem::Geo ) = 0; + + /** + Set user defined informations to this rubberband + */ + inline void setUserData(void * p) + { + _userData = p; + } + + /** + returns the user defined informations, which are set with methode userData(void*) + */ + inline void * userData() { return _userData; } + + + /** + Draw rubberband manually by calling the update function. In general it is not neccessary to call draw function. + But in some cases, for example the color setting functions, the graphic view update will not be done automatically. + Here calling draw forces an update of graphic view. + */ + inline void draw() { update(); } + + + /** + Sets call back function to realize an external update. The updateFunctor should return false to abort update process. In other case it have to return true. + */ + void installExternalUpdate(const std::function & updateFunctor); + + + /** + Returns true if there is a active rubberband on map. + @param map Pointer to map + */ + static bool isRubberbandActive( SituationMap::Map * map ); + + /** + Abort all active rubberbands on map. + @param map Pointer to map + */ + static void abortActiveRubberbands( SituationMap::Map * map ); + + + protected: + + Q_REQUIRED_RESULT QPointF projectCoordinateToMap( CoordinateSystem cs, const QPointF &loc ) const; + Q_REQUIRED_RESULT QPointF projectCoordinateFromMap( CoordinateSystem cs, const QPointF & loc ) const; + Q_REQUIRED_RESULT QPointF projectCoordinateFromMap( CoordinateSystem cs, const Esri::ArcGISRuntime::Point & loc ) const; + + inline void doAutoDelete() { if (_autoDelete) deleteMySelf(); } + + void updateButtonPosition(); + + QPointF pixel2ScenePosition( int x, int y ) const; + + virtual void update() = 0; + + void externalUpdate(); + + /* + Forward declaration of internal container class for member variables. The derived classes of Rubberband + often have also a declaration of InternalData for there own internal members. + */ + class BaseInternalData; + + BaseInternalData *_baseInternal; + + QColor _drawColor; + QColor _fillColor; + QColor _handleDrawColor; + QColor _handleFillColor; + QColor _handleSelectionColor; + QColor _frameColor; + QColor _frameFillColor; + QColor _frameSelectionColor; + int _frameHandleMargin; + + unsigned int _lineHandleWidth; + + unsigned int _pickingRatio; + + bool _finishAfterConstruction; + bool _autoDelete; + + QPointer _viewPortEventDispatcher; + QPointer _windowEventDispatcher; + + virtual QRectF sceneBbox() const = 0; + + virtual void activateButtons(); + virtual void deactivateButtons(); + virtual void adjustButtons(); + virtual void applyButtonClicked(); + virtual void cancelButtonClicked(); + virtual void undoButtonClicked(); + + protected slots: + + void onDo_applyButtonClicked() { applyButtonClicked(); } + void onDo_cancelButtonClicked() { cancelButtonClicked(); } + void onDo_undoButtonClicked() { undoButtonClicked(); } + + void onDo_updateButtonFixCoordinates(); + + private: + + void * _userData; + + friend class RubberbandFactory; + + static void * operator new(size_t); + + /** + The function \c allowMultipleRubberbands set a flag to allow new to create more than one rubberband + per situation map for the next new operator call. The function is used in class RubberbandFactory. The + value will be resetted to \c false at end of new operator. + */ + static void allowMultipleRubberbands( bool ok ); + + /** + The function \c currentFactoryMap defines the calling situation map for the next new operator call. The + function is used in class RubberbandFactory.The value will be resetted to \c nullptr at end of new operator. + */ + static void currentFactoryMap( SituationMap::Map * map ); + + + signals: + + /** + The abort signal will be emitted, when the abortAction has been called by one of the attached event handlers. + Before emtting the signal the event handler are closed and deleted. To avoid memory leaks the application has to + ensure that the delete function is called for this rubberband. See also example code at beginning. + + @param rbnd pointer to this Rubberband + */ + void abort( RubberbandBase * rbnd ); + + /** + The apply signal will be emitted, when the applyAndEndAction has been called by one of the attached event handler. The connected slot can now + read the geometry information. Before emtting the signal the event handler are closed and deleted.To avoid memory leaks the application has to + ensure that the delete function is called for this rubberband. See also example code at beginning. + + @param rbnd pointer to this Rubberband. + */ + void apply( RubberbandBase * rbnd ); + + /** + The finished signal will be emitted, when the rubberband is going to terminate and all user actions are done. + + @param rbnd pointer to this Rubberband. + */ + void finished( RubberbandBase * rbnd ); + + /** + The coordinatesChanged signal will be emitted , when the user has changed, removed or added coordinates. Can be used to implement + a preview of interim results. + */ + void interimResultsChanged( RubberbandBase * rbnd ); + + }; /* Rubberband */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base_internal.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base_internal.h new file mode 100755 index 000000000..61039acca --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_base_internal.h @@ -0,0 +1,118 @@ +/** +** +** @RDE_HEADER@ +** Definition of internal data of base class for rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Joerg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 16.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include +#include + +#include +#include + +#include "rubberband_base.h" + + + +class QHBoxLayout; +class QWidget; +class QPushButton; +class QGraphicsScene; +class QGraphicsView; + + +namespace Esri { +namespace ArcGISRuntime { + class MapGraphicsView; +} } + + +namespace SituationMap +{ + class Map; +} + +namespace SituationMap { namespace RB +{ + + class RubberbandHandleProxy; + + /** + @class Rubberband::BaseInternalData rubberband_base_internal.h + @brief Definition of internal data of rubberband base class. + */ + class RubberbandBase::BaseInternalData + { + public: + + static QPixmap * _applyIcon; + static QPixmap * _abortIcon; + static QPixmap * _undoIcon; + + QPointer _map; + + SituationMap::CoordTranslationFunctor * _mapCtFunctor; + + bool _isActive; + + bool _applyIsPossible; + bool _undoIsPossible; + bool _hasUndoButton; + + QPointF _lastClickPosition; + + QPointF _buttonBoxSceneAnchor; + QPoint _buttonBoxFixPosition; + SituationMap::RefPointer _buttonProxy; + QPointer _buttonBox; + QHBoxLayout * _buttonLayout; + QPushButton * _undoButton; + QPushButton * _cancelButton; + QPushButton * _applyButton; + + QCursor _restoreCursor; // workaround Qt bug. remove when fixed + + QVector> _updateFunctorList; + + LOGGER_T _logger; + + explicit BaseInternalData( SituationMap::Map * map, RubberbandBase * me ); + + virtual ~BaseInternalData(); + + bool buttonProxyActive( const QPointF & pos ) const; + + void enableAndShowOnlyPossibleButtons(); + + BaseInternalData( const BaseInternalData & ) = delete; + BaseInternalData & operator = ( const BaseInternalData & ) = delete; + + }; /* BaseInternalData */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.cpp new file mode 100755 index 000000000..9a9f3248d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.cpp @@ -0,0 +1,991 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define multi point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_corridor.h" +#include "rubberband_drawable.h" +#include "rubberband_frame_handle.h" +#include "rubberband_handle_proxy.h" +#include "rubberband_line_handle.h" +#include "rubberband_point_handle.h" + +#include "drawable_painter_path.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include + +#include +#include + + +namespace SituationMap { namespace RB +{ + + struct RubberbandCorridor::InternalData + { + SituationMap::RefPointer _widthHandle; + qreal _corridorSceneWidth = 0.0; + SituationMap::RefPointer _widthLine; + SituationMap::RefPointer _geoBufferItem; + SituationMap::RefPointer _centerLineItem; + QRectF _sceneBbox; + + }; /* class RubberbandCorridor::InternalData */ + + + RubberbandCorridor::RubberbandCorridor( SituationMap::Map * map, QObject * parent ) : RubberbandCorridor( map, InputMode::InputBoundary, 0, parent ) {;} + + + RubberbandCorridor::RubberbandCorridor( SituationMap::Map * map, InputMode mode, qreal width, QObject * parent ) + : RubberbandMultiPointFeature(map, false, parent) // Map*, bool complexGeometry, QObject* parent + , _internal(new InternalData) + , _inputMode(mode) + , _corridorWidth(width) + , _minimumCorridorWidth( 0.0 ) + , _maximumCorridorWidth( std::numeric_limits::infinity() ) + , _corridorBorderColor("black") + , _corridorFillColor("yellow") + { + _corridorFillColor.setAlphaF( 0.5 ); + _isClosed = false; + + setCheckMaxAngle(160.0 / 180.0*M_PI); // restrict angle between line segments + } /* RubberbandCorridor::RubberbandCorridor */ + + + RubberbandCorridor::~RubberbandCorridor() + { + delete _internal; + _internal = nullptr; + } /* RubberbandCorridor::~RubberbandCorridor */ + + + void RubberbandCorridor::minimumCorridorWidth( qreal min ) + { + min = fabs(min); + if (min < _maximumCorridorWidth) + { + _minimumCorridorWidth = min; + if ((_minimumCorridorWidth) > _corridorWidth) + { + _corridorWidth = _minimumCorridorWidth; + constructWidthHandle(); + } + } + } /* RubberbandCorridor::minimumCorridorWidth */ + + + void RubberbandCorridor::maximumCorridorWidth( qreal max ) + { + max = fabs(max); + if (max > _minimumCorridorWidth) + { + _maximumCorridorWidth = max; + if ((_maximumCorridorWidth) < _corridorWidth) + { + _corridorWidth = _maximumCorridorWidth; + constructWidthHandle(); + } + } + } /* RubberbandCorridor::maximumCorridorWidth */ + + + bool RubberbandCorridor::isEnabledConstructionWidthState() const + { + return _state.testFlag( RubberbandState::ConstructionWidthState ); + } /* RubberbandCorridor::isEnabledConstructionWidthState */ + + void RubberbandCorridor::enableConstructionWidthState() + { + _state &= ~QFlags(RubberbandState::ModificationState); + _state |= RubberbandState::ConstructionWidthState; + } /* RubberbandCorridor::enableConstructionWidthState */ + + void RubberbandCorridor::disenableConstructionWidthState() + { + _state &= ~QFlags(RubberbandState::ConstructionWidthState); + _state |= RubberbandState::ModificationState; + } /* RubberbandCorridor::disenableConstructionWidthState */ + + + qreal RubberbandCorridor::corridorWidth() const + { + return _corridorWidth; + } /* RubberbandCorridor::corridorWidth */ + + + void RubberbandCorridor::setCoordinateVector(const QVector & coordinateVec, CoordinateSystem cs ) + { + setCoordinateVectorWidth( coordinateVec, 0.0, cs ); + } /* RubberbandCorridor::setCoordinateVector */ + + + void RubberbandCorridor::setCoordinateVectorWidth(const QVector & coordinateVec, qreal corridorWidth, CoordinateSystem cs ) + { + if (coordinateVec.size() < 2) return; + + if (corridorWidth < 0) + { + QVector reverseVec; + reverseVec.reserve(coordinateVec.size()); + for (const auto &point : coordinateVec) + { + reverseVec.push_front(point); + } + RubberbandMultiPointFeature::setCoordinateVector(reverseVec, cs); + } + else RubberbandMultiPointFeature::setCoordinateVector( coordinateVec, cs ); + + _corridorWidth = fabs(corridorWidth); + + enableModificationState(); + constructWidthHandle(); + + _baseInternal->_applyIsPossible = true; + + activateButtons(); + update(); + + } /* RubberbandCorridor::setCoordinateVectorWidth */ + + + + QVector RubberbandCorridor::centerLine( CoordinateSystem cs ) + { + if (_inputMode == InputMode::InputCenterLine) + { + return RubberbandMultiPointFeature::coordinateVector( cs ); + } + + QVector cLine; + QVector cLineRet; + + // getCenterLineFromHandleBorder( cLine, _corridorWidth * ((_internal->_corridorSceneWidth >= 0) ? 0.5 : -0.5), true ); + getCenterLineFromHandleBorder( cLine, _corridorWidth * 0.5, true ); + cLineRet.reserve(cLine.size()); + for (const auto &point : std::as_const(cLine)) + cLineRet.push_back(projectCoordinateFromMap( cs, point )); + return cLineRet; + } /* RubberbandCorridor::centerLine */ + + + void RubberbandCorridor::setCenterLine( const QVector & coordinateVec, qreal corridorWidth, CoordinateSystem cs ) + { + if (_inputMode == InputMode::InputBoundary) + { + const QVector &borderLine = getCenterLineFromBorder( coordinateVec, corridorWidth*-0.5,cs); + setCoordinateVectorWidth( borderLine, corridorWidth, CoordinateSystem::Map); + return; + } + + setCoordinateVectorWidth( coordinateVec, corridorWidth, cs ); + } /* RubberbandCorridor::setCenterLine */ + + + QVector RubberbandCorridor::frontLine(CoordinateSystem cs) + { + if (_inputMode == InputMode::InputBoundary) + { + const QVector & fLine = RubberbandMultiPointFeature::coordinateVector(cs); + if (_internal->_corridorSceneWidth >= 0) return fLine; + // reverse order of coordinates + QVector fLineRet; + fLineRet.reserve(fLine.size()); + for (const auto & point : fLine) + { + fLineRet.push_front(point); + } + return fLineRet; + } + + // In case of InputMode::InputCenterLine construct centerLine + + QVector fLine; + getCenterLineFromHandleBorder(fLine, _corridorWidth*-1.0, true); + + if (_internal->_corridorSceneWidth >= 0) + { + for (QPointF & point : fLine) + { + point = projectCoordinateFromMap(cs, point); + } + return fLine; + } + + // reverse order of coordinates + QVector fLineRet; + fLineRet.reserve(fLine.size()); + for (const auto & point : std::as_const(fLine)) + { + fLineRet.push_front( projectCoordinateFromMap(cs,point) ); + } + + return fLineRet; + + } /* RubberbandCorridor::frontLine */ + + + void RubberbandCorridor::setFrontLine(const QVector & coordinateVec, qreal corridorWidth, CoordinateSystem cs) + { + if (_inputMode == InputMode::InputBoundary) + { + setCoordinateVectorWidth(coordinateVec, corridorWidth, cs); + } + + // In case of InputMode::InputCenterLine construct centerLine + + const QVector ¢erLine = getCenterLineFromBorder( coordinateVec, corridorWidth*0.5, cs); + setCoordinateVectorWidth(centerLine, corridorWidth, CoordinateSystem::Map); + + } /* RubberbandCorridor::setFrontLine */ + + + void RubberbandCorridor::constructWidth( const QPointF & scenePos ) + { + if (Rubberband::_internal->_handleList.size() < 2) return; + + QPointF p0 = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._sceneCoordinate; + QPointF p1 = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate; + + QPointF rv( p0.y()-p1.y(), p1.x()-p0.x() ); + rv /= sqrt(rv.x()*rv.x()+rv.y()*rv.y()); + + QPointF pScene = scenePos - p1; + + qreal scalarProduct = pScene.x()*rv.x() + pScene.y()*rv.y(); + + if (_inputMode == InputMode::InputCenterLine) scalarProduct *= 2; + + _internal->_corridorSceneWidth = scalarProduct; + + rv *= scalarProduct; + + RubberbandHandle::RubberbandCoordinate pointW( Rubberband::_internal->_mapView, p1+rv ); + + rv = QPointF( pointW._mapCoordinate.x() - static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._mapCoordinate.x() + , pointW._mapCoordinate.y() - static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._mapCoordinate.y() ); + + +// _corridorWidth = sqrt(rv.x()*rv.x()+rv.y()*rv.y()); + _corridorWidth = (scalarProduct > 0 ) ? sqrt(rv.x()*rv.x()+rv.y()*rv.y()) : - sqrt(rv.x()*rv.x()+rv.y()*rv.y()); + + bool constrainsComplied = true; + + if (_corridorWidth != 0.0) + { + qreal constraintFactor = 1.0; + if (_minimumCorridorWidth > fabs(_corridorWidth)) { constraintFactor = _minimumCorridorWidth / _corridorWidth; constrainsComplied = false; } + if (_maximumCorridorWidth < fabs(_corridorWidth)) { constraintFactor = _maximumCorridorWidth / fabs(_corridorWidth); constrainsComplied = false; } + _corridorWidth *= constraintFactor; + _internal->_corridorSceneWidth *= constraintFactor; + } + else + { + if (_minimumCorridorWidth > 0) + { + _corridorWidth = _minimumCorridorWidth; + + const QPointF & whp = calcWidthHandlePosition(); + Esri::ArcGISRuntime::MapGraphicsView * mapView = Rubberband::_internal->_mapView; + RubberbandDrawable::RubberbandCoordinate widthCoordinate( mapView, convert( whp, mapView->map()->spatialReference() )); + + const QPointF & p0 = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate; + const QPointF & p1 = widthCoordinate._sceneCoordinate; + + QPointF rv( p0.y()-p1.y(), p1.x()-p0.x() ); + + _internal->_corridorSceneWidth = sqrt(rv.x()*rv.x()+rv.y()*rv.y()); + if (_inputMode == InputMode::InputCenterLine) _internal->_corridorSceneWidth *= 2; + } + } + + markFailedConstrains( scenePos, !constrainsComplied ); + + } /* RubberbandCorridor::constructWidth */ + + + QPointF RubberbandCorridor::getWidthHandleScenePosition() const + { + QPointF p0 = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._sceneCoordinate; + QPointF p1 = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate; + + QPointF rv( p0.y()-p1.y(), p1.x()-p0.x() ); + + rv /= sqrt(rv.x()*rv.x()+rv.y()*rv.y()); + + if ((std::numeric_limits::infinity() != rv.x()) && (std::numeric_limits::infinity() != rv.y())) + { + rv *= _internal->_corridorSceneWidth; + if (_inputMode == InputMode::InputCenterLine) rv *= 0.5; + return p1 + rv; + } + return p1; + + } /* RubberbandCorridor::getWidthHandleScenePosition */ + + + QPointF RubberbandCorridor::calcWidthHandlePosition() + { + qreal p0X = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._mapCoordinate.x(); + qreal p0Y = static_cast(Rubberband::_internal->_handleList.end()[-2].get())->getCoordinate()._mapCoordinate.y(); + qreal p1X = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._mapCoordinate.x(); + qreal p1Y = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._mapCoordinate.y(); + + QPointF p( p1Y-p0Y, p1X-p0X ); + qreal lp = _corridorWidth / sqrt(p.x()*p.x()+p.y()*p.y()); + + if (std::numeric_limits::infinity() != lp) + { + p *= lp; + if (_inputMode == InputMode::InputCenterLine) p *= 0.5; + p = QPointF(p1X,p1Y) + p; + } + else p = QPointF(p1X,p1Y); + return p; + } /* RubberbandCorridor::calcWidthHandlePosition */ + + + void RubberbandCorridor::constructWidthHandle() + { + if (Rubberband::_internal->_handleList.size() < 2) return; + + if ((fabs(_corridorWidth) <= _minimumCorridorWidth) || (fabs(_corridorWidth) >= _maximumCorridorWidth)) + { + enableConstructionWidthState(); + deactivateButtons(); + } + + const QPointF & p = calcWidthHandlePosition(); + const Esri::ArcGISRuntime::Point mapP( convert(p, Rubberband::_internal->_mapView->map()->spatialReference()) ); + + _internal->_widthHandle = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP ); + + qreal pX = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._mapCoordinate.x(); + qreal pY = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._mapCoordinate.y(); + + _internal->_widthLine = new RubberbandLineHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, QPointF(pX,pY), p, 2, true ); + + pX = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate.x(); + pY = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate.y(); + + const QPointF & pScene = _internal->_widthHandle->getCoordinate()._sceneCoordinate; + + pX -= pScene.x(); + pY -= pScene.y(); + + _internal->_corridorSceneWidth = sqrt(pX*pX+pY*pY); + if (_inputMode == InputMode::InputCenterLine) _internal->_corridorSceneWidth *= 2; + + // connect signal to call updateBufferGeometry when zooming or resizing the window + // connect( _internal->_widthHandle.get(), &RubberbandHandle::adjusted, [this]() { this->updateBufferGeometry(); } ); + // doesn't work because not all handles are adjusted this time ... + + // but we have to ensure that coordinateSceneWidth have all ways the correct value: + connect( _internal->_widthHandle.get(), &RubberbandHandle::adjusted, this, &RubberbandCorridor::onDo_adjustCorridorSceneWidth ); + + } /* RubberbandCorridor::constructWidthHandle */ + + + void RubberbandCorridor::onDo_adjustCorridorSceneWidth() + { + if (Rubberband::_internal->_handleList.size() > 1) + { + qreal pX = static_cast(this->Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate.x(); + qreal pY = static_cast(this->Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate.y(); + const QPointF & pScene = this->_internal->_widthHandle->getCoordinate()._sceneCoordinate; + + pX -= pScene.x(); + pY -= pScene.y(); + + qreal sceneWidth = sqrt(pX*pX+pY*pY); + if (_inputMode == InputMode::InputCenterLine) sceneWidth *= 2; + + _internal->_corridorSceneWidth = (_internal->_corridorSceneWidth >= 0) ? sceneWidth : -sceneWidth; + } + } /* RubberbandCorridor::onDo_adjustCorridorSceneWidth */ + + + void RubberbandCorridor::updateHandleScenePosition() + { + if (!_internal->_widthHandle) return; + + QPointF pos = getWidthHandleScenePosition(); + + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " old handle Position = " << _internal->_widthHandle->getCoordinate()._sceneCoordinate.x() + << " ; " << _internal->_widthHandle->getCoordinate()._sceneCoordinate.y() + << " new handle position = " << pos.x() << " ; " << pos.y() + << " width = " << _corridorWidth + << " scene width = " << _internal->_corridorSceneWidth; + + _internal->_widthHandle->updateCoordinate( pos ); + + const QPointF & lp1 = static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate; + + _internal->_widthLine->updateCoordinate( 0, lp1 ); + _internal->_widthLine->updateCoordinate( 1, pos ); + + } /* RubberbandCorridor::updateHandleScenePosition */ + + + bool RubberbandCorridor::getCenterLineFromHandleBorder( QVector & centerLine, qreal corridorWidth, bool mapCoordinates ) + { + ClipperLib::ClipperOffset pathOffset; + ClipperLib::Path borderPath; + ClipperLib::Path offsetPath; + int precision = (mapCoordinates) ? 1000 : 10; + qreal baseX = 0.0; + qreal baseY = 0.0; + bool initBase = true; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * pointHandlePtr = dynamic_cast(handle.get()); + const RubberbandPointHandle::RubberbandCoordinate & pointCoordinate = pointHandlePtr->getCoordinate(); + + qreal x = (mapCoordinates) ? pointCoordinate._mapCoordinate.x() : pointCoordinate._sceneCoordinate.x(); + qreal y = (mapCoordinates) ? pointCoordinate._mapCoordinate.y() : -pointCoordinate._sceneCoordinate.y(); + + if (mapCoordinates) + { + // use relative coordinates to avoid to large numbers + if (initBase) + { + baseX = x; baseY = y; x = 0.0; y = 0.0; + initBase = false; + } + else { x -= baseX; y -= baseY; } + } + borderPath << ClipperLib::IntPoint(int(x*precision),int(y*precision)); + } + + bool ok = pathOffset.PathOffset( borderPath, offsetPath, int(corridorWidth*precision) ); + + ClipperLib::Path::const_iterator it = offsetPath.begin(); + + if (mapCoordinates) + { + for (; it != offsetPath.end(); ++it) + { + centerLine << QPointF(qreal(it->X)/qreal(precision)+baseX, qreal(it->Y)/qreal(precision)+baseY); + } + } + else + { + for (; it != offsetPath.end(); ++it) + { + centerLine << QPointF(qreal(it->X)/qreal(precision)+baseX, qreal(-it->Y)/qreal(precision)-baseY); + } + } + + return ok; + } /* RubberbandCorridor::getCenterLineFromHandleBorder */ + + + QVector RubberbandCorridor::getCenterLineFromBorder(const QVector & borderLine, qreal corridorWidth, CoordinateSystem cs) + { + const int precision = 1000; + + QVector centerLine; + + ClipperLib::ClipperOffset pathOffset; + ClipperLib::Path borderPath; + ClipperLib::Path offsetPath; + + qreal baseX = 0.0; + qreal baseY = 0.0; + bool initBase = true; + + for (const auto &point : borderLine) + { + const QPointF & mapPoint = projectCoordinateToMap(cs, point); + + qreal x = mapPoint.x(); + qreal y = mapPoint.y(); + + // use relative coordinates to avoid to large numbers + if (initBase) + { + baseX = x; baseY = y; x = 0.0; y = 0.0; + initBase = false; + } + else { x -= baseX; y -= baseY; } + + borderPath << ClipperLib::IntPoint(int(x*precision), int(y*precision)); + } + + if (pathOffset.PathOffset(borderPath, offsetPath, int(corridorWidth*precision))) + { + centerLine.reserve(100); + for (const auto &p : offsetPath) + { + centerLine << QPointF( qreal(p.X)/qreal(precision)+baseX, qreal(p.Y)/qreal(precision)+baseY ); + } + } + return centerLine; + } // RubberbandCorridor::getCenterLineFromBorder + + + + void RubberbandCorridor::updateBufferGeometry() + { + if (!_internal->_widthHandle) return; + + QVector centerLine; + qreal corridorWidth = 0.0; + bool centerLineOk = true; + + centerLine.reserve(Rubberband::_internal->_handleList.size() ); + + switch (_inputMode) + { + case InputMode::InputBoundary: + { + corridorWidth = _internal->_corridorSceneWidth * 0.5; + centerLineOk = getCenterLineFromHandleBorder( centerLine, corridorWidth, false ); + if (_internal->_centerLineItem == nullptr) + { + _internal->_centerLineItem = new DrawablePainterPath( Rubberband::_internal->_mapView ); + } + _internal -> _centerLineItem -> setDrawColor( centerLineOk ? _drawColor : QColor(255,0,0) ); + _internal -> _centerLineItem -> setFillColor( QColor(0,0,0,0) ); // turn off filling + _internal -> _centerLineItem -> dottedPath( centerLineOk ); + _internal -> _centerLineItem -> setVisibility( true ); + _internal -> _centerLineItem -> enableClosedLine( !centerLineOk ); + _internal -> _centerLineItem -> setPath( centerLine ); + + } break; + + case InputMode::InputCenterLine: + { + corridorWidth = _internal->_corridorSceneWidth * 0.5; + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * pointHandlePtr = dynamic_cast(handle.get()); + const RubberbandPointHandle::RubberbandCoordinate & pointCoordinate = pointHandlePtr->getCoordinate(); + + qreal x = pointCoordinate._sceneCoordinate.x(); + qreal y = pointCoordinate._sceneCoordinate.y(); + + centerLine.append( QPointF(x,y) ); + } + _internal -> _centerLineItem = nullptr; + } break; + } + + ClipperLib::ClipperOffset pathOffset; + ClipperLib::Path borderPath; + ClipperLib::Paths offsetPathVec; + QVector vertexSet; + + for (auto point : std::as_const(centerLine)) + borderPath << ClipperLib::IntPoint(int(point.x()*10),int(-point.y()*10)); + + pathOffset.AddPath( borderPath, ClipperLib::JoinType::jtSquare, ClipperLib::EndType::etOpenButt); + pathOffset.Execute( offsetPathVec, int(fabs(corridorWidth)*10) ); + + if (centerLineOk) + { + if (_internal->_geoBufferItem) _internal->_geoBufferItem->setVisibility(false); + + for (auto path : offsetPathVec) + { + ClipperLib::Path::const_iterator it = path.begin(); + + for (; it != path.end(); ++it) + { + vertexSet.append( RubberbandHandle::RubberbandCoordinate( Rubberband::_internal->_mapView + , QPointF(qreal(it->X)/10.0,qreal(-it->Y)/10.0) )); + } + + if (!vertexSet.empty()) + { + if (!_internal->_geoBufferItem) + { + _internal->_geoBufferItem = new DrawablePainterPath( Rubberband::_internal->_mapView ); + } + + _internal -> _geoBufferItem -> setVisibility( true ); + _internal -> _geoBufferItem -> enableClosedLine( true ); + _internal -> _geoBufferItem -> setDrawColor( _corridorBorderColor ); + _internal -> _geoBufferItem -> setFillColor( _corridorFillColor ); + _internal -> _geoBufferItem -> setPath( vertexSet ); + } + break; + } + } + else if (_internal->_geoBufferItem) _internal->_geoBufferItem->setVisibility(false); + + RubberbandFrameHandle * frameHandle = getFrameHandle(); + + if (frameHandle) + { + if (vertexSet.isEmpty()) + { + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * pointHandlePtr = dynamic_cast(handle.get()); + + vertexSet.append(pointHandlePtr->getCoordinate() ); + } + + if (_internal->_widthHandle != nullptr) + { + vertexSet.append( _internal->_widthHandle->getCoordinate() ); + } + } + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " frameHandle->updateFrameShape( vertexSet );"; + frameHandle->updateFrameShape( vertexSet ); + } + + if (!vertexSet.isEmpty()) + { + qreal minX = vertexSet.first()._sceneCoordinate.x(); + qreal maxX = minX; + qreal minY = vertexSet.first()._sceneCoordinate.y(); + qreal maxY = minY; + + for (const auto &v : std::as_const(vertexSet)) + { + const QPointF & p = v._sceneCoordinate; + if (p.x() < minX) minX = p.x(); + if (p.y() < minY) minY = p.y(); + if (p.x() > maxX) maxX = p.x(); + if (p.y() > maxY) maxY = p.y(); + } + _internal->_sceneBbox.setCoords( minX, minY, maxX, maxY ); + + if (!isButtonBoxPositionFixed()) + { + RubberbandBase::_baseInternal->_buttonBoxSceneAnchor = QPointF(maxX,maxY); + } + } + else _internal->_sceneBbox = QRectF(); + } /* RubberbandCorridor::updateBufferGeometry*/ + + + bool RubberbandCorridor::bufferPolygon( QVector & coordinateVec, CoordinateSystem cs ) + { + coordinateVec.clear(); + + const QVector & cLine = centerLine( CoordinateSystem::Map ); + + if (cLine.size() < 2) return false; + + ClipperLib::ClipperOffset pathOffset; + ClipperLib::Path borderPath; + ClipperLib::Paths offsetPathVec; + int precision = 1000; + const QPointF & basePoint = cLine.first(); + + for (const auto &point : cLine) + { + borderPath << ClipperLib::IntPoint(int((point.x()-basePoint.x())*precision), + int((point.y()-basePoint.y())*precision)); + } + + pathOffset.AddPath( borderPath, ClipperLib::JoinType::jtSquare, ClipperLib::EndType::etOpenButt); + pathOffset.Execute( offsetPathVec, int(fabs(_corridorWidth*0.5)*precision) ); + + if (offsetPathVec.empty()) return false; + + const ClipperLib::Path & bufferCoordinates = offsetPathVec.front(); + + if (bufferCoordinates.empty()) return false; + + coordinateVec.reserve(bufferCoordinates.size()); + + for (const ClipperLib::IntPoint & p : bufferCoordinates) + { + const QPointF & vertex = QPointF( qreal(p.X)/qreal(precision), qreal(p.Y)/qreal(precision) ) + basePoint; + coordinateVec << projectCoordinateFromMap( cs, vertex ); + } + return true; + } /* RubberbandCorridor::bufferPolygon */ + + + void RubberbandCorridor::resetAction(AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive()) + { + if (!isEnabledConstructionWidthState()) + { + if (isEnabledConstructionState()) + { + RubberbandMultiPointFeature::resetAction(causer,x,y); + return; + } + } + } + RubberbandMultiPointFeature::resetAction(causer,x,y); + } /* RubberbandCorridor::resetAction */ + + + bool RubberbandCorridor::selectAction( AbstractEventDispatcher * causer, int x, int y ) + { + Q_UNUSED(causer); + + if (!rubberbandIsAlive()) return false; + + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (_baseInternal->buttonProxyActive(scenePos)) return false; + + _baseInternal->_lastClickPosition = scenePos; + + if (!isEnabledConstructionWidthState()) + { + if (isEnabledConstructionState()) return RubberbandMultiPointFeature::selectAction(causer,x,y); + if (_internal->_widthHandle->isPositionInside(scenePos)) + { + _internal->_widthHandle->setSelection(); + eventHandled = true; + } + else return RubberbandMultiPointFeature::selectAction(causer,x,y); + } + else eventHandled = true; + + + if (eventHandled) + { + update(); + deactivateButtons(); + } + return eventHandled; + } /* RubberbandCorridor::selectAction */ + + + bool RubberbandCorridor::setCoordinateAction(AbstractEventDispatcher * causer, int x, int y) + { + Q_UNUSED(causer); + + markFailedConstrains( QPointF(), false ); + + if (!rubberbandIsAlive()) return false; + + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (isEnabledConstructionWidthState()) + { + Rubberband::_internal->_lastClickPosition = scenePos; + constructWidth( scenePos ); + updateHandleScenePosition(); + disenableConstructionWidthState(); + enableModificationState(); + eventHandled = true; + markFailedConstrains( QPointF(), false ); + } + else + { + if (isEnabledConstructionState()) + { + bool handled = RubberbandMultiPointFeature::setCoordinateAction(causer,x,y); + if (isEnabledConstructionWidthState()) + { + // guarantee that buttons are of + deactivateButtons(); + } + return handled; + } + + if (_internal->_widthHandle->isSelected()) + { + _internal->_widthHandle->setSelection(false); + constructWidth( scenePos ); + markFailedConstrains( QPointF(), false ); + eventHandled = true; + RubberbandMultiPointFeature::setCoordinateAction(causer,x,y); + } + else return RubberbandMultiPointFeature::setCoordinateAction(causer,x,y); + } + + if (eventHandled) update(); + + updateButtonPosition(); + + activateButtons(); + + return eventHandled; + } /* RubberbandCorridor::setCoordinateAction */ + + + bool RubberbandCorridor::movePickedAction(AbstractEventDispatcher * causer, int x, int y ) + { + adjustConnectedPreviewPixmap(x,y); + + if (!rubberbandIsAlive()) return false; + + if ((isEnabledConstructionWidthState()) || ((_internal->_widthHandle) && (_internal->_widthHandle->isSelected()))) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + constructWidth( scenePos ); + update(); + return false; + } + + return RubberbandMultiPointFeature::movePickedAction(causer,x,y); + + } /* RubberbandCorridor::movePickedAction */ + + + bool RubberbandCorridor::moveAction(AbstractEventDispatcher * causer, int x, int y) + { + adjustConnectedPreviewPixmap(x,y); + + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (isEnabledConstructionWidthState()) + { + constructWidth( scenePos ); + update(); + return false; + } + + if (_internal->_widthHandle) + { + if (_internal->_widthHandle->isPositionInside(scenePos)) + { + _internal->_widthHandle->setSelection(true); + return true; + } + _internal->_widthHandle->setSelection(false); + } + return RubberbandMultiPointFeature::moveAction(causer,x,y); + } /* RubberbandCorridor::moveAction */ + + + bool RubberbandCorridor::completeStateAction(AbstractEventDispatcher * causer, int x, int y) + { + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (isEnabledConstructionWidthState()) + { + if (!_finishAfterConstruction) + { + disenableSelectedState(); + enableModificationState(); + update(); + return true; + } + } + if (isEnabledConstructionState()) + { + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + { + disenableConstructionState(); + + if ((fabs(_corridorWidth) <= _minimumCorridorWidth) || (fabs(_corridorWidth) >= _maximumCorridorWidth)) + { + enableConstructionWidthState(); + + deSelectHandles(); + disenableSelectedState(); + + deactivateButtons(); + + updatePreviewLine(QPointF(0,0)); + + constructWidth( static_cast(Rubberband::_internal->_handleList.end()[-1].get())->getCoordinate()._sceneCoordinate ); + constructWidthHandle(); + + if (_internal->_widthHandle) + { + _internal->_widthHandle->setSelection(true); + } + } + else + { + if (_finishAfterConstruction) + { + return applyAndEndAction(causer,x,y); + } + else + { + deSelectHandles(); + constructWidthHandle(); + disenableSelectedState(); + enableModificationState(); + } + } + } + update(); + return true; + } + return applyAndEndAction(causer,x,y); + } + return false; + } /* RubberbandCorridor::completeStateAction */ + + + void RubberbandCorridor::activateButtons() + { + if (isEnabledConstructionWidthState()) + { + _baseInternal->_applyIsPossible = false; + _baseInternal->_undoIsPossible = false; + RubberbandMultiPointFeature::activateButtons(); + } + else RubberbandMultiPointFeature::activateButtons(); + + } /* RubberbandCorridor::activateButtons */ + + + QRectF RubberbandCorridor::sceneBbox() const + { + if (_internal->_sceneBbox.isValid()) return _internal->_sceneBbox; + else return Rubberband::sceneBbox(); + } /* RubberbandCorridor::sceneBbox */ + + + void RubberbandCorridor::update() + { + RubberbandMultiPointFeature::update(); + + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION; + + _isClosed = false; + + if (_internal->_widthHandle) + { + _internal->_widthHandle -> setDrawColor(_handleDrawColor); + _internal->_widthHandle -> setFillColor(_handleFillColor); + _internal->_widthHandle -> setSelectionColor(_handleSelectionColor); + _internal->_widthHandle -> setPickingRatio( _pickingRatio ); + + updateHandleScenePosition(); + updateBufferGeometry(); + } + + } /* RubberbandCorridor::update */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.h new file mode 100755 index 000000000..9879a3238 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_corridor.h @@ -0,0 +1,239 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multi point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_multi_point_feature.h" + + +namespace SituationMap { namespace RB +{ + /** + @class RubberbandCorridor rubberband_corridor.h + @brief Implementation of a rubberband for corridors. + @see RubberbandFactory, Rubberband, RubberbandMultiPointFeature + + Implementation of rubberbands used to input corridors. There are two different modes \c InputCenterLine and \c InputBoundary. In the + InputCenterLine mode the center line of the corridor has to be defined. The rubberband displays then the border geometry. + In the InputBoundary mode one side of the border has to be defined. The \c coordinateVector methodes always working on the input geometry. + */ + class SITUATIONMAP_EXPORT RubberbandCorridor : public RubberbandMultiPointFeature + { + Q_OBJECT + + public: + + enum class InputMode + { + InputCenterLine, + InputBoundary + }; /* InputMethode */ + + + /** + @name Construction & Destruction + @{ + + Construction of one instance of class RubberbandCorridor. + + @param map Pointer to SituationMap class instance + @param mode Two mode are distinguished. In \c InputCenterLine mode the coordinates will be interpreted as center line of the corridor. + In \c InputBoundary the coordinates are defining one border. + @param width With of a corridor in meters. In \c InputBoundary the coordinates are defining one border. If \c corridorWidth is negative + the coordinates are the right border otherwise the left one. + */ + + private: + + friend class RubberbandFactory; + + explicit RubberbandCorridor( SituationMap::Map * map, QObject * parent ); + explicit RubberbandCorridor( SituationMap::Map * map, InputMode mode = InputMode::InputBoundary, qreal width = 0, QObject * parent = nullptr ); + + + public: + + ~RubberbandCorridor(); + /** @} */ + + /** + @addtogroup States Methodes to query and modify rubberband states + @{ + */ + bool isEnabledConstructionWidthState() const; + void enableConstructionWidthState(); + void disenableConstructionWidthState(); + /** @} */ + + + + inline bool isEnabledInputBoundary() const { return _inputMode == InputMode::InputBoundary; } + inline void setInputBoundary() { _inputMode = InputMode::InputBoundary; } + inline void setInputCenterline() { _inputMode = InputMode::InputCenterLine; } + + /** + Sets coordinates to modify to internal coordinate list. + + @param coordinateVec List of coordinates to modify + @param cs Defines the coordinate system of returned coordinates. + */ + void setCoordinateVector( const QVector & coordinateVec, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + /** + Sets coordinates to modify to internal coordinate list. In \c InputCenterLine mode the coordinates will be interpreted as + center line of the corridor. In \c InputBoundary the coordinates are defining one border. If \c corridorWidth is negative + the coordinates are the right border otherwise the left one. + + @param coordinateVec List of coordinates to modify + @param corridorWidth Width of corridor in meters. + @param cs Defines the coordinate system of returned coordinates. + */ + void setCoordinateVectorWidth(const QVector & coordinateVec, qreal corridorWidth, CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + Returns the center line of the corridor + + @param cs Defines the coordinate system of returned coordinates. + */ + QVector centerLine( CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + Sets the center line of the corridor. + + @param coordinateVec List of coordinates of border to modify + @param corridorWidth Width of corridor in meters + @param cs Defines the coordinate system of returned coordinates. + */ + void setCenterLine( const QVector & coordinateVec, qreal corridorWidth, CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + Returns the front line of the rubberband. + + @param cs Defines the coordinate system of returned coordinates. + */ + QVector frontLine(CoordinateSystem cs = CoordinateSystem::Geo); + + /** + Sets the front line of the corridor. + + @param coordinateVec List of coordinates of border to modify + @param corridorWidth Width of corridor in meters + @param cs Defines the coordinate system of returned coordinates. + */ + void setFrontLine(const QVector & coordinateVec, qreal corridorWidth, CoordinateSystem cs = CoordinateSystem::Geo); + + /** + Provides the buffer polygon (outline) of the corridor. + + @param coordinateVec Coordinate list of buffer polygon + @param cs Defines the coordinate system of returned coordinates. + @return true on success otherwise false + */ + bool bufferPolygon( QVector & coordinateVec, CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + Return the width of the corridor. + */ + qreal corridorWidth() const; + + /** + Set the width of the corridor. + */ + inline void setCorridorWidth(qreal width) { _corridorWidth = width;} + + /** + @name Constrains for corridor width + @{ + */ + void minimumCorridorWidth( qreal min ); + void maximumCorridorWidth( qreal max ); + + qreal minimumCorridorWidth() const { return _minimumCorridorWidth; } + qreal maximumCorridorWidth() const { return _maximumCorridorWidth; } + /** @} */ + + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + void resetAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + QColor getCorridorBorderColor() const { return _corridorBorderColor; } + QColor getCorridorFillColor() const { return _corridorFillColor; } + + void setCorridorBorderColor( const QColor & color ) { _corridorBorderColor = color; } + void setCorridorFillColor ( const QColor & color ) { _corridorFillColor = color; } + + protected: + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + + InputMode _inputMode; + qreal _corridorWidth; + + qreal _minimumCorridorWidth; + qreal _maximumCorridorWidth; + + QColor _corridorBorderColor; + QColor _corridorFillColor; + + void activateButtons() override; + + QRectF sceneBbox() const override; + + void update() override; + + QPointF calcWidthHandlePosition(); + + void constructWidth( const QPointF & ); + void constructWidthHandle(); + + QPointF getWidthHandleScenePosition() const; + + bool getCenterLineFromHandleBorder(QVector & centerLine, qreal corridorWidth, bool mapCoordinates); + + QVector getCenterLineFromBorder(const QVector & borderLine, qreal corridorWidth, CoordinateSystem cs ); + + void updateHandleScenePosition(); + void updateBufferGeometry(); + + private slots: + + void onDo_adjustCorridorSceneWidth(); + + }; /* RubberbandCorridor */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.cpp new file mode 100755 index 000000000..6b53338f1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.cpp @@ -0,0 +1,155 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_drawable.h" + + +// ESRI +#include + + + +namespace SituationMap { namespace RB +{ + + Esri::ArcGISRuntime::Point convert(const QPointF &point, const Esri::ArcGISRuntime::SpatialReference& spatialReference) + { + return Esri::ArcGISRuntime::Point(point.x(), point.y(), spatialReference); + } // convert + + + RubberbandDrawable::RubberbandDrawable( QGraphicsView * view ) + : _scene((view) ? view->scene() : nullptr) + , _mapView() + , _isVisible(true) + , _lineWidth(1) + , _zBufferValue((unsigned int) ZBufferPlane::Drawables) + , _logger(getLogger("RubberbandDrawable")) + + { + doConnecting(); + } /* RubberbandDrawable::RubberbandDrawable */ + + RubberbandDrawable::RubberbandDrawable( Esri::ArcGISRuntime::MapGraphicsView * mapView ) + : _scene(mapView->scene()) + , _mapView(mapView) + , _isVisible(true) + , _lineWidth(1) + , _zBufferValue((unsigned int) ZBufferPlane::Drawables) + , _logger(getLogger("RubberbandDrawable")) + + { + doConnecting(); + } /* RubberbandDrawable::RubberbandDrawable */ + + RubberbandDrawable::RubberbandDrawable( Esri::ArcGISRuntime::MapGraphicsView * mapView, QGraphicsScene * scene) + : _scene(scene) + , _mapView(mapView) + , _isVisible(true) + , _lineWidth(1) + , _zBufferValue((unsigned int) ZBufferPlane::Drawables) + , _logger(getLogger("RubberbandDrawable")) + + { + doConnecting(); + } /* RubberbandDrawable::RubberbandDrawable */ + + +#ifdef ADJUST_COORDINATES_EVERY_PAINT_EVENT + bool RubberbandDrawable::eventFilter( QObject*, QEvent * e ) + { + if (e) + { + if (e->type() == QEvent::Paint) + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Sometimes the signals extentChanges and rectChanged of Esri Map Widget are not emitted. In this case + * the position update of handles and drawabled fails and they are drawn at wrong positions on map. As + * workaround updates coordinates at every paint event. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + DEBUG_LOG(_logger) << LOGFUNCTION << " paint event"; + onDo_adjustCoordinates(); + } + } + return false; + } /* RubberbandDrawable::eventFilter */ +#endif + + void RubberbandDrawable::doConnecting() + { + if (nullptr == _mapView) return; + + #ifdef ADJUST_COORDINATES_EVERY_PAINT_EVENT + _view -> installEventFilter( this ); + #endif + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * It is really important to specify the connnection type as following. Because the onDo_adjustCoordinates + * should be call as soon as possible to get smooth changes of map. The connection type is DirectConnnection here. + * The rectChanged signal is some times emitted, when the calculation of screen coordinates to map coordinates + * and vice versa has not been updated to the new map window size. In this case all calculations provides wrong + * results. Using the connection type QueuedConnection defers the call of onDo_adjustWindow. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + // Check Quartz + connect( _mapView.data(), &Esri::ArcGISRuntime::MapGraphicsView::viewpointChanged, + this, &RubberbandDrawable::onDo_adjustCoordinates, Qt::ConnectionType::DirectConnection ); + connect( _mapView.data(), &Esri::ArcGISRuntime::MapGraphicsView::rectChanged , + this, &RubberbandDrawable::onDo_adjustWindow , Qt::ConnectionType::QueuedConnection ); + + } /* RubberbandDrawable::doConnecting */ + + + void RubberbandDrawable::onDo_adjustCoordinates() + { + adjustCoordinates(); + forceUpdateViewWidget(); + } /* RubberbandDrawable::onDo_adjustCoordinates */ + + + void RubberbandDrawable::onDo_adjustWindow( const QRectF &r ) + { + adjustWindow(r); + forceUpdateViewWidget(); + } /* RubberbandDrawable::onDo_adjustWindow */ + + + void RubberbandDrawable::makeLineStyle( bool dottedLine, QPen & lineStyle ) + { + lineStyle.setColor( _drawColor ); + lineStyle.setWidth( int(_lineWidth) ); + + if (dottedLine) lineStyle.setStyle( Qt::DashDotLine ); + else lineStyle.setStyle( Qt::SolidLine ); + } /* DrawablePainterPathItem::makeLineStyle */ + + + RubberbandDrawable::~RubberbandDrawable() { if (_mapView) _mapView->removeEventFilter(this); } + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.h new file mode 100755 index 000000000..01ae5ec2a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_drawable.h @@ -0,0 +1,214 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberband drawing objects +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include + +#include + +#include +#include + +#include +#include + + +class QGraphicsScene; + +namespace Esri { namespace ArcGISRuntime { class MapGraphicsView; } } + +namespace SituationMap { namespace RB +{ + + Esri::ArcGISRuntime::Point convert(const QPointF &point, const Esri::ArcGISRuntime::SpatialReference& spatialReference); + + + + /** + @class RubberbandDrawable rubberband_drawable.h + @brief Base class of all rubberband drawables and handles for rubberband internal use. + */ + class RubberbandDrawable : public QObject, public SituationMap::Referenced + { + Q_OBJECT + public: + + enum class ZBufferPlane + { + FrameHandle = 10, + Drawables = 20, + LineHandle = 30, + ArrowSymbol = 31, + PointHandle = 40, + Widget = 100 + }; /* InputMethode */ + + + /** + Manages scene and map coordinates. + */ + struct RubberbandCoordinate + { + QPointF _sceneCoordinate; + Esri::ArcGISRuntime::Point _mapCoordinate; + + + RubberbandCoordinate() + {} + + explicit RubberbandCoordinate( const QPointF & sceneCoordinate) + { + updateSceneCoordinate( sceneCoordinate ); + } /* RubberbandCoordinate */ + + explicit RubberbandCoordinate( Esri::ArcGISRuntime::MapGraphicsView * mapView, const QPointF & sceneCoordinate) + { + updateSceneCoordinate( mapView, sceneCoordinate ); + } /* RubberbandCoordinate */ + + + explicit RubberbandCoordinate( Esri::ArcGISRuntime::MapGraphicsView * mapView, const Esri::ArcGISRuntime::Point & mapCoordinate) + : _mapCoordinate(mapCoordinate) + { + mapToScene( mapView ); + } /* RubberbandCoordinate */ + + + inline QPointF mapToScene(Esri::ArcGISRuntime::MapGraphicsView * mapView) + { + if (!_mapCoordinate.isValid()) + return QPointF(); + const QPointF pixelCoordinate = mapView->locationToScreen(_mapCoordinate); + _sceneCoordinate = mapView->mapToScene( pixelCoordinate.x(), pixelCoordinate.y() ); + return _sceneCoordinate; + } /* QPointF mapToScene(Esri::ArcGISRuntime::MapGraphicsView * mapView) */ + + + inline void updateSceneCoordinate( Esri::ArcGISRuntime::MapGraphicsView * mapView, const QPointF & sceneCoordinate) + { + if (mapView) + { + _sceneCoordinate = sceneCoordinate; + QPoint pixelCoordinate = mapView->mapFromScene(sceneCoordinate); + _mapCoordinate = mapView->screenToLocation( pixelCoordinate.x(), pixelCoordinate.y() ); + } + else updateSceneCoordinate( sceneCoordinate ); + } /* void updateSceneCoordinate( Esri::ArcGISRuntime::MapGraphicsView * mapView, const QPointF & sceneCoordinate) */ + + + inline void updateSceneCoordinate( const QPointF & sceneCoordinate ) + { + _sceneCoordinate = sceneCoordinate; + _mapCoordinate = Esri::ArcGISRuntime::Point(); + } /* void updateSceneCoordinate( const QPointF & sceneCoordinate ) */ + + bool isNull() const + { + return _sceneCoordinate.isNull(); + } + + }; /* RubberbandCoordinate */ + + /** + @name Construction & Destruction + @{ + */ + explicit RubberbandDrawable( QGraphicsView *view ); + explicit RubberbandDrawable( Esri::ArcGISRuntime::MapGraphicsView * ); + explicit RubberbandDrawable( Esri::ArcGISRuntime::MapGraphicsView *, QGraphicsScene * ); + ~RubberbandDrawable(); + /** @} */ + + inline bool isVisible() const { return _isVisible; } + inline void setVisibility(bool on = true) { _isVisible = on; updateGraphic(); } + + inline void setDrawColor (const QColor & col) { _drawColor = col; updateGraphic(); } + inline void setFillColor (const QColor & col) { _fillColor = col; updateGraphic(); } + inline void setLineWidth(unsigned int width) { _lineWidth = width; updateGraphic(); } + + inline void zBufferPlane( ZBufferPlane zPlane ) { _zBufferValue = (unsigned int) zPlane; } + + inline void setZValue(qreal zValue) { _zValue = zValue; updateGraphic(); } + inline qreal zValue() const { return _zValue; } + + protected: + + QPointer _scene; + QPointer _mapView; + + bool _isVisible; + QColor _drawColor; + QColor _fillColor; + unsigned int _lineWidth; + qreal _zBufferValue; + qreal _zValue{0}; // combine _zBufferValue and _zValue??? + + LOGGER_T _logger; + + #ifdef ADJUST_COORDINATES_EVERY_PAINT_EVENT + bool eventFilter(QObject*, QEvent*) override; + #endif + + virtual void updateGraphic() = 0; + + void makeLineStyle( bool dottedLine, QPen & lineStyle ); + + inline void forceUpdateViewWidget() { _mapView->viewport()->update(); } + + virtual void adjustCoordinates() = 0; + virtual void adjustWindow( const QRectF& ) = 0; + + + QColor transparentFillColor() const + { + QColor fillColor(_fillColor); + + if (fillColor.alphaF() > 0.85) + { + fillColor.setAlphaF( fillColor.alphaF()*0.5 ); + } + return fillColor; + } // QColor transparentFillColor() + + + protected slots: + + void onDo_adjustCoordinates(); + void onDo_adjustWindow(const QRectF & ); + + private: + + + + void doConnecting(); + + }; /* RubberbandDrawable */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.cpp new file mode 100755 index 000000000..5d03f1dd5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.cpp @@ -0,0 +1,455 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_ellipse.h" +#include "drawable_ellipse.h" +#include "rubberband_point_handle.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include +#include +#include + +namespace SituationMap { namespace RB +{ + + struct RubberbandEllipse::InternalData + { + SituationMap::RefPointer _ellipseSymbol; + int _alphaFill = 50; // alpha value used for fill color of ellipse + + QPointF _sectorOStart = QPointF(1,0); + QPointF _sectorOEnd = QPointF(1,0); + SituationMap::RefPointer _sectorStartHandle; + SituationMap::RefPointer _sectorEndHandle; + + bool _sectorEnabled = false; + bool _sectorFixedAngleEnabled = false; + + }; /* RubberbandEllipse::InternalData */ + + + RubberbandEllipse::RubberbandEllipse( SituationMap::Map * map, QObject * parent ) + : RubberbandRectangle(map,parent), _internal(new InternalData) + { + useDottedLineHandles(true); + } /* RubberbandEllipse::RubberbandEllipse */ + + + RubberbandEllipse::~RubberbandEllipse() + { + delete _internal; + _internal = nullptr; + } /* RubberbandEllipse::~RubberbandEllipse */ + + + QPointF RubberbandEllipse::getRotatedPoint(QPointF sector) const + { + qreal sinRect = 0, cosRect = 1; + getSceneRotation( sinRect, cosRect); + double angleRad = SituationMap::MathUtils::getMathAngle(QPointF(cosRect,sinRect));// angle rad + //double angleDeg = SituationMap::MathUtils::getMathAngle(QPointF(cosRect,sinRect)) * LtsCoordTrans::rad2deg(); + const double clockwiseAngle = ((360*LtsCoordTrans::deg2rad()) - angleRad);// angle in rad; + + // map clockwise rotation + QPointF point = sector; + point.setX( sector.x()*std::cos(clockwiseAngle) + sector.y()*std::sin(clockwiseAngle)); + point.setY( -sector.x()*std::sin(clockwiseAngle) + sector.y()*std::cos(clockwiseAngle)); + + return point; + } + + void RubberbandEllipse::setSectorStart(qreal sinPhi, qreal cosPhi) + { + _internal->_sectorOStart = QPointF( cosPhi, sinPhi ); + update(); + } /* RubberbandEllipse::setSectorStart */ + + + void RubberbandEllipse::setSectorEnd(qreal sinPhi, qreal cosPhi) + { + _internal->_sectorOEnd = QPointF( cosPhi, sinPhi ); + update(); + } /* RubberbandEllipse::setSectorEnd */ + + + QPointF RubberbandEllipse::sectorStart() const + { + return getRotatedPoint(_internal->_sectorOStart); + } /* RubberbandEllipse::sectorStart */ + + + QPointF RubberbandEllipse::sectorEnd() const + { + return getRotatedPoint(_internal->_sectorOEnd); + } /* RubberbandEllipse::sectorEnd */ + + + bool RubberbandEllipse::hasSector() const + { + auto getAngle = [](qreal sinPhi, qreal cosPhi) -> int + { + qreal phi = acos( cosPhi ); + if (sinPhi < 0) phi = MathConst::pi_two - phi; + return int( (phi * 180.0 / MathConst::pi) * 16); + }; // getAngle + + int phiStart = getAngle(_internal->_sectorOStart.y(),_internal->_sectorOStart.y()); + int phiEnd = getAngle( _internal->_sectorOEnd.y(), _internal->_sectorOEnd.y()); + return phiStart != phiEnd; + } /* RubberbandEllipse::hasSector */ + + void RubberbandEllipse::enableSector(bool on) { _internal -> _sectorEnabled = on; } + bool RubberbandEllipse::isSectorEnabled() const { return _internal -> _sectorEnabled; } + + void RubberbandEllipse::setSectorAngleFixed(bool on) + { + _internal -> _sectorFixedAngleEnabled = on; + } + + bool RubberbandEllipse::isSectorFixedAngeEnabled() const + { + return _internal->_sectorFixedAngleEnabled; + } + + + bool RubberbandEllipse::selectAction(AbstractEventDispatcher * causer, int x, int y) + { + if (rubberbandIsAlive()) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + _baseInternal->_lastClickPosition = scenePos; + + if (_internal -> _sectorEnabled) + { + if ((_internal->_sectorEndHandle.valid()) && (_internal->_sectorEndHandle->isPositionInside(scenePos))) + { + _internal->_sectorEndHandle->setSelection(true); + enableSelectedState(); + update(); + deactivateButtons(); + return true; + } + + if ((_internal->_sectorStartHandle.valid()) && (_internal->_sectorStartHandle->isPositionInside(scenePos))) + { + _internal->_sectorStartHandle->setSelection(true); + enableSelectedState(); + update(); + deactivateButtons(); + return true; + } + } + } + return RubberbandRectangle::selectAction(causer,x,y); + } /* RubberbandEllipse::selectAction */ + + + bool RubberbandEllipse::moveAction(AbstractEventDispatcher * causer, int x, int y) + { + if (rubberbandIsAlive()) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + if (_internal -> _sectorEnabled) + { + if (_internal->_sectorEndHandle.valid()) + { + if (_internal->_sectorEndHandle->isPositionInside(scenePos)) + { + _internal->_sectorEndHandle->setSelection(true); + update(); + return false; + } + else _internal->_sectorEndHandle->setSelection(false); + } + + if (_internal->_sectorStartHandle.valid()) + { + if (_internal->_sectorStartHandle->isPositionInside(scenePos)) + { + _internal->_sectorStartHandle->setSelection(true); + update(); + return false; + } + else _internal->_sectorStartHandle->setSelection(false); + } + } + } + return RubberbandRectangle::moveAction(causer,x,y); + } /* RubberbandEllipse::moveAction */ + + + bool RubberbandEllipse::setCoordinateAction(AbstractEventDispatcher * causer, int x, int y) + { + if (rubberbandIsAlive()) + { + if (_internal -> _sectorEnabled) + { + if ((_internal->_sectorEndHandle.valid()) && (_internal->_sectorEndHandle->isSelected())) + { + _internal->_sectorEndHandle->setSelection(false); + update(); + activateButtons(); + return true; + } + + if ((_internal->_sectorStartHandle.valid()) && (_internal->_sectorStartHandle->isSelected())) + { + _internal->_sectorStartHandle->setSelection(false); + update(); + activateButtons(); + return true; + } + } + } + return RubberbandRectangle::setCoordinateAction(causer,x,y); + } /* RubberbandEllipse::setCoordinateAction */ + + + bool RubberbandEllipse::movePickedAction(AbstractEventDispatcher * causer, int x, int y) + { + if (rubberbandIsAlive()) + { + auto updateHandle = [this](RubberbandPointHandle * handle, const RubberbandHandle::RubberbandCoordinate & pos, QPointF & oVector ) -> bool + { + if (handle == nullptr) return false; + if (!handle->isSelected()) return false; + if (isSectorFixedAngeEnabled()) return false; + + qreal sinRect = 0; + qreal cosRect = 1; + const QPointF & center = getCenter(); + + getSceneRotation( sinRect, cosRect); + + qreal cosA = pos._mapCoordinate.x() - center.x(); + qreal sinA = pos._mapCoordinate.y() - center.y(); + qreal len = sqrt(cosA*cosA+sinA*sinA); + + cosA /= len; + sinA /= len; + + oVector.setX( cosA*cosRect + sinA*sinRect ); + oVector.setY( sinA*cosRect - cosA*sinRect ); + update(); + return true; + }; // updateHandle + + QPointF scenePos(pixel2ScenePosition(x,y)); + + RubberbandHandle::RubberbandCoordinate pos(Rubberband::_internal->_mapView, scenePos ); + + if (updateHandle( _internal->_sectorEndHandle.get() , pos, _internal->_sectorOEnd )) return false; + if (updateHandle( _internal->_sectorStartHandle.get(), pos, _internal->_sectorOStart )) return false; + } + return RubberbandRectangle::movePickedAction(causer,x,y); + } /* RubberbandEllipse::movePickedAction */ + + + void RubberbandEllipse::updateSectorHandles() + { + + auto ellipse = []( qreal a, qreal b, qreal sinA, qreal cosA ) -> QPointF + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Qt does not use the paramater equation of a ellipse to draw sectors. Instead of a simpler term + * is used. For simpleness we use the same of. + * + qreal e2 = 1 - (b*b) / (a*a); + qreal cos2 = cosA * cosA; + qreal f = b / sqrt(1-e2*cos2); + return QPointF( f*cosA, f*sinA ); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + return QPointF(a*cosA,b*sinA); + }; + + auto rotate = [] ( const QPointF & p, qreal sinA, qreal cosA ) -> QPointF { return QPointF( p.x()*cosA - p.y()*sinA, p.y()*cosA + p.x()*sinA ); }; + + qreal a = getWidth() * 0.5; + qreal b = getHeight() * 0.5; + QPointF startP = ellipse(a,b,_internal->_sectorOStart.y(),_internal->_sectorOStart.x() ); + QPointF endP = ellipse(a,b,_internal->_sectorOEnd.y() ,_internal->_sectorOEnd.x() ); + + qreal sinRect = 0; + qreal cosRect = 1; + + getSceneRotation( sinRect, cosRect); + + startP = rotate(startP, sinRect, cosRect) + getCenter(); + endP = rotate( endP, sinRect, cosRect) + getCenter(); + + const Esri::ArcGISRuntime::Point &mapStartP = convert( startP, Rubberband::_internal->_mapView->map()->spatialReference() ); + const Esri::ArcGISRuntime::Point &mapEndP = convert( endP, Rubberband::_internal->_mapView->map()->spatialReference() ); + + if (!_internal->_sectorStartHandle.valid()) + { + _internal->_sectorStartHandle = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapStartP ); + } + else _internal->_sectorStartHandle->setCoordinate( RubberbandHandle::RubberbandCoordinate(Rubberband::_internal->_mapView,mapStartP) ); + + _internal->_sectorStartHandle -> setVisibility((isSectorEnabled() && !isSectorFixedAngeEnabled())/*_internal->_sectorEnabled*/); + _internal->_sectorStartHandle -> setDrawColor(_handleDrawColor); + _internal->_sectorStartHandle -> setFillColor( _handleFillColor ); + _internal->_sectorStartHandle -> setSelectionColor(_handleSelectionColor); + + if (!_internal->_sectorEndHandle.valid()) + { + _internal->_sectorEndHandle = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapEndP ); + } + else _internal->_sectorEndHandle->setCoordinate( RubberbandHandle::RubberbandCoordinate(Rubberband::_internal->_mapView,mapEndP) ); + + _internal->_sectorEndHandle -> setVisibility(isSectorEnabled() && !isSectorFixedAngeEnabled()/*_internal->_sectorEnabled*/); + _internal->_sectorEndHandle -> setDrawColor(_handleDrawColor); + _internal->_sectorEndHandle -> setFillColor( _handleFillColor ); + _internal->_sectorEndHandle -> setSelectionColor(_handleSelectionColor); + + } /* RubberbandEllipse::updateSectorHandles */ + + + void RubberbandEllipse::update() + { + + RubberbandRectangle::update(); + + if (isEnabledModificationState()) + { + updateSectorHandles(); + + QPointF center = getCenter(); + qreal width2 = getWidth()*0.5; + qreal height2 = getHeight()*0.5; + qreal sinRect = 0.0; + qreal cosRect = 1.0; + + getSceneRotation( sinRect, cosRect ); + + if (_internal->_ellipseSymbol == nullptr) + { + _internal->_ellipseSymbol = new DrawableEllipse( Rubberband::_internal->_mapView ); + } + + QColor drawColor = _drawColor; + + _internal->_ellipseSymbol->setDrawColor( drawColor ); + drawColor.setAlpha(_internal->_alphaFill); + if (_internal -> _sectorEnabled){ + _fillColor.setAlpha(_internal->_alphaFill+10); + _internal->_ellipseSymbol->setFillColor( _fillColor ); + } + else + { + _fillColor.setAlpha(0); + _internal->_ellipseSymbol->setFillColor( _fillColor ); + } + + _internal->_ellipseSymbol->setRect( center, width2, height2, sinRect, cosRect, _internal->_sectorOStart, _internal->_sectorOEnd ); + + } + } /* RubberbandEllipse::update */ + + + void RubberbandEllipse::buildTmpConstruction( SituationMap::RefPointer &tmpGraphic, QPointF &start, QPointF &end ) + { + if (!tmpGraphic.valid()) + { + tmpGraphic = new DrawableEllipse( Rubberband::_internal->_mapView ); + tmpGraphic -> setDrawColor( _drawColor ); + tmpGraphic -> setFillColor( _fillColor ); + tmpGraphic -> setLineWidth( 1 ); + tmpGraphic -> zBufferPlane( RubberbandDrawable::ZBufferPlane::Drawables ); + } + + QVector path; + QPointF sceneCenter; + qreal sceneWidth; + qreal sceneHeight; + qreal sinMap; + qreal cosMap; + + getMapRotation( sinMap, cosMap ); + + auto rotate = [sinMap, cosMap](qreal x, qreal y) -> QPointF + { + qreal px = x*cosMap - y*sinMap; + qreal py = y*cosMap + x*sinMap; + return QPointF(px,py); + }; // rotate + + buildTmpConstructionPath( start-end + start, end, path, sceneCenter, sceneWidth, sceneHeight ); + + DrawableEllipse * tmpEllipse = static_cast(tmpGraphic.get()); + + RubberbandHandle::RubberbandCoordinate mapCenter( Rubberband::_internal->_mapView, start ); + RubberbandHandle::RubberbandCoordinate mapRadius( Rubberband::_internal->_mapView, start+rotate(sceneWidth*0.5,sceneHeight*0.5) ); + + qreal w = mapRadius._mapCoordinate.x() - mapCenter._mapCoordinate.x(); + qreal h = mapRadius._mapCoordinate.y() - mapCenter._mapCoordinate.y(); + + tmpEllipse->setRect( QPointF(mapCenter._mapCoordinate.x(),mapCenter._mapCoordinate.y()), w, h, 0, 1 ); + } /* RubberbandEllipse::buildTmpConstruction */ + + + void RubberbandEllipse::getTmpConstructionParameter(const QPointF & start, const QPointF & end, QPointF & sceneCenter, qreal & sceneWidth, qreal & sceneHeight) + { + QVector path; + qreal sinMap; + qreal cosMap; + + getMapRotation( sinMap, cosMap ); + + auto rotate = [sinMap, cosMap]( qreal x, qreal y ) -> QPointF + { + qreal px = x*cosMap - y*sinMap; + qreal py = y*cosMap + x*sinMap; + return QPointF(px,py); + }; // rotate + + auto length = [](qreal x, qreal y) -> qreal { return sqrt(x*x+y*y); }; + + buildTmpConstructionPath( start, end, path, sceneCenter, sceneWidth, sceneHeight ); + + RubberbandHandle::RubberbandCoordinate mapCenter ( Rubberband::_internal->_mapView, start ); + RubberbandHandle::RubberbandCoordinate mapRadiusX( Rubberband::_internal->_mapView, start+rotate(sceneWidth,0) ); + RubberbandHandle::RubberbandCoordinate mapRadiusY( Rubberband::_internal->_mapView, start+rotate(0,sceneHeight) ); + + qreal w = length( mapRadiusX._sceneCoordinate.x()-mapCenter._sceneCoordinate.x(), mapRadiusX._sceneCoordinate.y()-mapCenter._sceneCoordinate.y() ); + qreal h = length( mapRadiusY._sceneCoordinate.x()-mapCenter._sceneCoordinate.x(), mapRadiusY._sceneCoordinate.y()-mapCenter._sceneCoordinate.y() ); + + sceneCenter = start; + sceneWidth = 2*w; + sceneHeight = 2*h; + } /* RubberbandEllipse::getTmpConstructionParameter */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.h new file mode 100755 index 000000000..a92f0aa6a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse.h @@ -0,0 +1,95 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_rectangle.h" + + +namespace SituationMap { namespace RB +{ + + + /** + @class RubberbandEllipse rubberband_ellipse.h + @brief Implementation of a rubberband to input ellipses and circles + @see Rubberband, RubberbandRectangle + */ + class SITUATIONMAP_EXPORT RubberbandEllipse : public RubberbandRectangle + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + explicit RubberbandEllipse( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + + virtual ~RubberbandEllipse(); + + RubberbandEllipse(const RubberbandEllipse &) = delete; + RubberbandEllipse & operator = (const RubberbandEllipse &) = delete; + /** @} */ + + void setSectorStart( qreal sinPhi, qreal cosPhi ); + void setSectorEnd( qreal sinPhi, qreal cosPhi ); + + QPointF getRotatedPoint(QPointF sector)const; + bool hasSector() const; + QPointF sectorStart() const; + QPointF sectorEnd() const; + + void enableSector( bool on = true ); + bool isSectorEnabled() const; + void setSectorAngleFixed(bool on); // true: start and end sectors positon can't be modifed + bool isSectorFixedAngeEnabled() const; + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + protected: + + void updateSectorHandles(); + + void update() override; + + struct InternalData; + + InternalData * _internal; + + void buildTmpConstruction( SituationMap::RefPointer & tmpGraphic, QPointF & start, QPointF & end ) override; + void getTmpConstructionParameter( const QPointF & start, const QPointF & end, QPointF & sceneCenter, qreal & sceneWidth, qreal & sceneHeight ) override; + + }; /* RubberbandPointFeature */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.cpp new file mode 100755 index 000000000..c5a1cbb6f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.cpp @@ -0,0 +1,236 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_ellipse_handle.h" +#include + +#include +#include + +#include +#include + +#include + +namespace SituationMap { namespace RB +{ + +RubberbandEllipseHandle::RubberbandEllipseHandle(Esri::ArcGISRuntime::MapGraphicsView * mapView, QGraphicsScene *scene) + : RubberbandHandle(mapView, scene) +{ + zBufferPlane( ZBufferPlane::LineHandle ); + updateGraphic(); +} + +RubberbandEllipseHandle::~RubberbandEllipseHandle() +{ + if (!_scene) + return; + + if (_ellipseSymbol) + { + _scene->removeItem(_ellipseSymbol); + delete _ellipseSymbol; + } +} + +void RubberbandEllipseHandle::adjustCoordinates() +{ + _center.mapToScene(_mapView); + updateGraphic(); + + emit adjusted(); +} + + +void RubberbandEllipseHandle::adjustWindow( const QRectF& ) +{ + onDo_adjustCoordinates(); +} + +qreal length(const QPointF &p) +{ + return sqrt(p.x()*p.x()+p.y()*p.y()); +} + + +void RubberbandEllipseHandle::updateGraphic() +{ + if (_scene) + { + if (!_ellipseSymbol) + { + _ellipseSymbol = new QGraphicsEllipseItem(); + _scene->addItem(_ellipseSymbol); + _ellipseSymbol->setFlags( QGraphicsItem::ItemIgnoresTransformations ); + } + + _ellipseSymbol->setZValue(_zValue); + + // Style + makeLineStyle( _dottedLine, _lineStyle ); + + if (_isSelected) + { + _lineStyle.setWidth( _lineStyle.width() + 1 ); + _lineStyle.setColor( _selectionColor ); + } + + _ellipseSymbol->setPen(_lineStyle); + + if (isSelected()) + _ellipseSymbol->setBrush( QBrush(_selectionColor) ); + else + _ellipseSymbol->setBrush( QBrush(_fillColor) ); + + _ellipseSymbol->setVisible(_isVisible); + + // geometry: + { + _center.mapToScene(_mapView); + + QPointF mapCenter( _center._mapCoordinate.x(), _center._mapCoordinate.y() ); + + qreal mapRotatePhi = _mapView->mapRotation() * MathConst::pi / 180.0; + + qreal sinMap = sin(mapRotatePhi); + qreal cosMap = cos(mapRotatePhi); + + qreal sinPhi = _sinRect * cosMap + _cosRect * sinMap; + qreal cosPhi = _cosRect * cosMap - _sinRect * sinMap; + + + RubberbandCoordinate x0y0(_mapView, convert( mapCenter+QPointF(-_radiusX,-_radiusY), _mapView->map()->spatialReference()) ); + RubberbandCoordinate x1y0(_mapView, convert( mapCenter+QPointF( _radiusX,-_radiusY), _mapView->map()->spatialReference()) ); + RubberbandCoordinate x0y1(_mapView, convert( mapCenter+QPointF(-_radiusX, _radiusY), _mapView->map()->spatialReference()) ); + + const qreal width = length(x1y0._sceneCoordinate-x0y0._sceneCoordinate); + const qreal height = length(x0y1._sceneCoordinate-x0y0._sceneCoordinate); + + // honor sector?? + + if (_sectorStart != _sectorEnd) + { + auto getAngle = [](qreal sinPhi, qreal cosPhi) -> int + { + qreal phi = acos( cosPhi ); + if (sinPhi < 0) phi = MathConst::pi_two - phi; + return int( (phi * 180.0 / MathConst::pi) * 16); + }; // getAngle + + int startAngle = getAngle(_sectorStart.y(), _sectorStart.x()); + int endAngle = getAngle( _sectorEnd.y(), _sectorEnd.x()); + int spanAngle = (endAngle > startAngle) ? endAngle-startAngle : 5760-startAngle+endAngle; + + QColor fillColor = transparentFillColor(); + _ellipseSymbol->setRect(-width*0.5, -height*0.5, width, height); + _ellipseSymbol->setPen(_lineStyle ); + _ellipseSymbol->setBrush( (fillColor.alpha() > 0) ? QBrush(fillColor) : QBrush() ); + _ellipseSymbol->setTransform( QTransform( cosPhi, -sinPhi, sinPhi, cosPhi, _center._sceneCoordinate.x(), _center._sceneCoordinate.y() )); + _ellipseSymbol->setStartAngle(startAngle ); + _ellipseSymbol->setSpanAngle(spanAngle); + _ellipseSymbol->setVisible(true); + } + + _ellipseSymbol->setRect(-width*0.5, -height*0.5, width, height); + _ellipseSymbol->setTransform( QTransform( cosPhi, -sinPhi, sinPhi, cosPhi, _center._sceneCoordinate.x(), _center._sceneCoordinate.y()) ); + } + } + +} + +QPointF RubberbandEllipseHandle::getRadius() +{ + return {_radiusX, _radiusY}; +} + + +void RubberbandEllipseHandle::setRect(const RubberbandCoordinate ¢er, qreal radiusX, qreal radiusY, + qreal sin, qreal cos, + const QPointF §orStart, const QPointF §orEnd) +{ + // p1,p2 = corner points of unrotated ellipse in scene coordinates + _center = center; + _radiusX = radiusX; + _radiusY = radiusY; + + _sinRect = sin; + _cosRect = cos; + + _sectorStart = sectorStart; + _sectorEnd = sectorEnd; + + updateGraphic(); +} + + +bool RubberbandEllipseHandle::isPositionInside(const QPointF &scenePosition) const +{ + if (_ellipseSymbol) + { + if (_ellipseSymbol->contains( scenePosition )) + { + return true; + } +// else +// { +// // Ok, Qt's methodes doesn't work safely +// // if it fails do some own calculations + +// QPointF V0 = _coordinates[0]._sceneCoordinate; +// QPointF V1 = _coordinates[1]._sceneCoordinate; +// QPointF R = V1-V0; +// QPointF P = scenePosition-V0; +// qreal lenR = R.x() * R.x() + R.y() * R.y(); +// qreal lenP = P.x() * P.x() + P.y() * P.y(); + +// if ((lenR == 0) || (lenP > lenR)) return false; // scene Position is to far away + +// // now check the direction + +// lenR = sqrt(lenR); +// lenP = sqrt(lenP); + +// R /= lenR; +// P /= lenP; + +// qreal cosPhi = QPointF::dotProduct(R,P); + +// if (cosPhi < 0) return false; + +// qreal epsilon = sqrt(1.0-cosPhi*cosPhi) * lenR; + +// return (epsilon < qreal(_pickingRatio)); +// } + } + + return false; +} + +} } diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.h new file mode 100755 index 000000000..d4e5e7ce6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_ellipse_handle.h @@ -0,0 +1,96 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include + +#include "rubberband_handle.h" + +class QGraphicsEllipse; +class QGraphicsScene; + +namespace SituationMap +{ +namespace RB +{ + +/** + @class RubberbandEllipseHandle rubberband_line_handle.h + @brief Implementation of a line handle for rubberband internal use. + @see RubberbandHandle + */ +class RubberbandEllipseHandle : public RubberbandHandle +{ + Q_OBJECT +public: + + RubberbandEllipseHandle(Esri::ArcGISRuntime::MapGraphicsView * mapView ,QGraphicsScene * scene); + ~RubberbandEllipseHandle(); + + bool isPositionInside(const QPointF &scenePosition) const override; + +// virtual void updateCoordinates(const QVector &scenePositionVector); +// virtual void updateCoordinate(unsigned index, const QPointF &scenePosition); + + QPointF getRadius(); + + void setRect(const RubberbandDrawable::RubberbandCoordinate ¢er, + qreal radiusX, qreal radiusY, + qreal sin, qreal cos, + const QPointF & sectorStart = QPointF(), const QPointF & sectorEnd = QPointF()); + + + void dottedLine(bool on = true) { _dottedLine = on; } + +protected: + + QGraphicsEllipseItem *_ellipseSymbol{nullptr}; + QPen _lineStyle; + RubberbandDrawable::RubberbandCoordinate _center; + qreal _radiusX{0.0}; + qreal _radiusY{0.0}; + qreal _sinRect{0.0}; + qreal _cosRect{0.0}; + QPointF _sectorStart; + QPointF _sectorEnd; + + bool _dottedLine{false}; + + void updateGraphic() override; + + void adjustCoordinates() override; + void adjustWindow(const QRectF&) override; + + +}; // RubberbandEllipseHandle + +} +} // namespace SituationMap::RB diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.cpp new file mode 100755 index 000000000..1b6d114ed --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.cpp @@ -0,0 +1,634 @@ +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** 30.09.2015, JT : New create method for creating rubberbands fitting to a special sidc code +** +** ----------------------------------------------------------------------------------------------- +*/ + + +#include "rubberband_factory.h" +#include "rubberband_corridor.h" +#include + +#include +#include + +namespace SituationMap { namespace RB +{ + + class RubberbandFactory::Defaults + { + public: + + QColor _rubberbandDrawColor = QColor(); + QColor _rubberbandFillColor = QColor(); + QColor _rubberbandHandleDrawColor = QColor(); + QColor _rubberbandHandleFillColor = QColor(); + QColor _rubberbandSelectionColor = QColor(); + QColor _rubberbandFrameColor = QColor(); + QColor _rubberbandFrameFillColor = QColor(); + int _rubberbandFrameHandleMargin = 0; + QPoint _rubberbandPreviewOffset = QPoint(); + unsigned int _rubberbandPreviewSize = 0; + unsigned int _rubberbandPickingRatio = 0; + unsigned int _rubberbandLineHandleWidth = 0; + + }; // RubberbandFactory::Defaults + + + RubberbandFactory::RubberbandFactory( SituationMap::Map * map ) + : QObject() + , _map(map) + , _defaults(new Defaults()) + , _allowMultipleRubberbands(false) + , _logger(getLogger("RubberbandFactory")) + {} + + RubberbandFactory::~RubberbandFactory() { delete _defaults; } + + + class SymbolConstruction + { + public: + + enum class Mode + { + Unknown = 0, + + Point, + + ArcCurve, + Triangle, + MultiPoint, + + PolyLine, + PolyLineWidth, + + Polygon, + Rectangle, + Circular, + Corridor + }; // Mode + + Mode _mode; + unsigned int _minPoints; + unsigned int _maxPoints; + + explicit SymbolConstruction( const AdvancedSymbology::GeometryType & geomType, const QString & gctString ) + : _mode(Mode::Unknown), _minPoints(-1), _maxPoints(-1) + { + if (QLatin1String("gct_arrow") == gctString) { _mode = Mode::PolyLine; _minPoints = 2; _maxPoints = 0; } + if (QLatin1String("gct_arrow_with_offset") == gctString) { _mode = Mode::PolyLineWidth; _minPoints = 2; _maxPoints = 0; } + if (QLatin1String("gct_arrow_with_tail") == gctString) { _mode = Mode::Triangle; _minPoints = 3; _maxPoints = 3; } + + if (QLatin1String("gct_circle") == gctString) { _mode = Mode::MultiPoint; _minPoints = 2; _maxPoints = 2; } + if (QLatin1String("gct_circular") == gctString) { _mode = Mode::Circular; _minPoints = 1; _maxPoints = 1; } + + if (QLatin1String("gct_freehand_arrow") == gctString) { _mode = Mode::ArcCurve; _minPoints = 3; _maxPoints = 3; } + if (QLatin1String("gct_freehand_line") == gctString) { _mode = Mode::ArcCurve; _minPoints = 3; _maxPoints = 3; } + if (QLatin1String("gct_freehand_reverse_arrow") == gctString) { _mode = Mode::ArcCurve; _minPoints = 3; _maxPoints = 3; } + if (QLatin1String("gct_freehand_u") == gctString) { _mode = Mode::ArcCurve; _minPoints = 4; _maxPoints = 4; } + + if (QLatin1String("gct_hook") == gctString) { _mode = Mode::MultiPoint; _minPoints = 3; _maxPoints = 3; } + if (QLatin1String("gct_horns") == gctString) { _mode = Mode::MultiPoint; _minPoints = 4; _maxPoints = 4; } + + if (QLatin1String("gct_open_triangle") == gctString) { _mode = Mode::Triangle; _minPoints = 3; _maxPoints = 3; } + + if (QLatin1String("gct_parallel_lines") == gctString) { _mode = Mode::PolyLineWidth; _minPoints = 2; _maxPoints = 2; } + if (QLatin1String("gct_parallel_lines_mid_line") == gctString) { _mode = Mode::PolyLineWidth; _minPoints = 2; _maxPoints = 2; } + + if (QLatin1String("gct_parallel_lines_with_ticks") == gctString) + { + _mode = Mode::MultiPoint; + // ESRI implmentation seems to differ from symbology standard and takes always 4 input + // coordinates here to construct the symbol! + // + // if (AdvancedSymbology::GeometryType::Polygon == geomType) _minPoints = _maxPoints = 4; + // else _minPoints = _maxPoints = 3; + _minPoints = _maxPoints = 4; + } + + if (QLatin1String("gct_rectangular") == gctString) { _mode = Mode::Corridor; _minPoints = 2; _maxPoints = 2; } + if (QLatin1String("gct_rectangular_1_pt") == gctString) { _mode = Mode::Rectangle; _minPoints = 1; _maxPoints = 1; } + + if (QLatin1String("gct_t") == gctString) { _mode = Mode::MultiPoint; _minPoints = 3; _maxPoints = 3; } + if (QLatin1String("gct_triple_arrow") == gctString) { _mode = Mode::MultiPoint; _minPoints = 3; _maxPoints = 3; } + + if (QLatin1String("gct_two_line") == gctString) { _mode = Mode::Triangle; _minPoints = 3; _maxPoints = 3; } + if (QLatin1String("gct_two_line_3_or_4_pt") == gctString) { _mode = Mode::MultiPoint; _minPoints = 3; _maxPoints = 4; } + + if (QLatin1String("gct_u_or_t_shape") == gctString) { _mode = Mode::MultiPoint; _minPoints = 3; _maxPoints = 3; } + + if (Mode::Unknown == _mode) + { + // gct string is empty or not supported yet + switch (geomType) + { + case AdvancedSymbology::GeometryType::Point : { _mode = Mode::Point; _minPoints = 1; _maxPoints = 1; } break; + case AdvancedSymbology::GeometryType::Polyline : { _mode = Mode::PolyLine; _minPoints = 2; _maxPoints = 0; } break; + case AdvancedSymbology::GeometryType::Polygon : { _mode = Mode::Polygon; _minPoints = 3; _maxPoints = 0; } break; + case AdvancedSymbology::GeometryType::Undefined : break; + default : break; + } // switch + } + } // SymbolConstruction( const QString & gctString ) + + ~SymbolConstruction() = default; + + Mode mode() const { return _mode; } + unsigned int minPoints() const { return _minPoints; } + unsigned int maxPoints() const { return _maxPoints; } + + bool unlimitedMaxPoints() const { return (_maxPoints == 0); } + + }; // SymbolConstruction + + + struct ActivateRubberbandInfo + { + Rubberband * _rubberband; + SymbolConstruction _symbolConstruction; + + explicit ActivateRubberbandInfo( Rubberband * r, const SymbolConstruction & sc ) : _rubberband(r), _symbolConstruction(sc) {;} + ~ActivateRubberbandInfo() = default; + + ActivateRubberbandInfo() = delete; + + }; // ActivateRubberbandInfo + + + QString toString( SymbolConstruction::Mode mode ) + { + switch (mode) + { + case SymbolConstruction::Mode::Unknown : return QStringLiteral("Unknown Rubberband"); break; + case SymbolConstruction::Mode::Point : return QStringLiteral("Point Rubberband"); break; + case SymbolConstruction::Mode::PolyLine : return QStringLiteral("Polyline Rubberband"); break; + case SymbolConstruction::Mode::PolyLineWidth : return QStringLiteral("PolylineWidth Rubberband"); break; + case SymbolConstruction::Mode::Polygon : return QStringLiteral("Polygon Rubberband"); break; + case SymbolConstruction::Mode::Triangle : return QStringLiteral("Triangle Rubberband"); break; + case SymbolConstruction::Mode::ArcCurve : return QStringLiteral("ArcCurve Rubberband"); break; + case SymbolConstruction::Mode::Rectangle : return QStringLiteral("Rectangle Rubberband"); break; + case SymbolConstruction::Mode::Circular : return QStringLiteral("Circular Rubberband"); break; + case SymbolConstruction::Mode::Corridor : return QStringLiteral("Corridor Rubberband"); break; + case SymbolConstruction::Mode::MultiPoint : return QStringLiteral("MultiPoint Rubberband"); break; + } + return QString(); + } // QString toString( SymbolConstruction::Mode mode ) + + + static bool defaultSymbolRubberbandCoordinates( Map * map, Rubberband * rbnd, const QPointF & pixelPos, const SymbolConstruction & symbolConstruction ); + + + Rubberband * RubberbandFactory::createSymbol(const QString & sidc, QObject * parent) const { return createSymbol(sidc,QPoint(-1,-1),parent); } + + Rubberband * RubberbandFactory::createSymbol( const QString & sidc, const QPoint & winPos, QObject * parent ) const + { + if (Mode::Mode_2D != _map->mode()) return nullptr; + + const QString & baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode( sidc ); + const AdvancedSymbology::SymbolProperties & symbolProperties = _map->symbolDictionary().symbolProperties( baseSymbolCode ); + const AdvancedSymbology::GeometryType & geomType = symbolProperties.geometryType(); + + SymbolConstruction symbolConstruction( geomType, symbolProperties.gctString() ); + + Rubberband::currentFactoryMap(_map); + Rubberband::allowMultipleRubberbands(_allowMultipleRubberbands); + Rubberband *rubberband = nullptr; + + DEBUG_LOG(_logger) << LOGFUNCTION << QStringLiteral(" - %1").arg(toString(geomType), 30); + DEBUG_LOG(_logger) << LOGFUNCTION << QStringLiteral(" - %1").arg(symbolProperties.gctString(), 30); + DEBUG_LOG(_logger) << LOGFUNCTION << QStringLiteral(" - %1").arg(toString(symbolConstruction.mode()),30); + + bool rubberbandConstructionMode = ((winPos.x() < 0) || (winPos.y() < 0)); + + try + { + switch (geomType) + { + case AdvancedSymbology::GeometryType::Point : + { + rubberband = new RubberbandPointFeature(_map, parent); + } break; + + case AdvancedSymbology::GeometryType::Polyline : + { + switch (symbolConstruction.mode()) + { + case SymbolConstruction::Mode::PolyLine : + { + RubberbandMultiPointFeature * r = new RubberbandMultiPointFeature(_map,false,parent); + if (r) + { + r->minimumPointCount( symbolConstruction.minPoints() ); + r->maximumPointCount( symbolConstruction.maxPoints() ); + r->enableFinishAfterConstruction(true); + rubberband = r; + } + } break; + + case SymbolConstruction::Mode::PolyLineWidth : + { + RubberbandMultiPointFeature * r = new RubberbandPolylineWidth(_map,parent); + if (r) + { + r->minimumPointCount( symbolConstruction.minPoints() ); + r->maximumPointCount( symbolConstruction.maxPoints() ); + r->enableFinishAfterConstruction(true); + rubberband = r; + } + } break; + + case SymbolConstruction::Mode::ArcCurve : + case SymbolConstruction::Mode::MultiPoint : + case SymbolConstruction::Mode::Triangle : + { + RubberbandPointCluster * r = new RubberbandPointCluster(_map,parent,false); + if (r) + { + r->minimumPointCount(symbolConstruction.minPoints()); + r->maximumPointCount(symbolConstruction.maxPoints()); + r->enableFinishAfterConstruction(true); + + rubberband = r; + + if ((!symbolConstruction.unlimitedMaxPoints()) && rubberbandConstructionMode) + { + // Rubberband is not active here because we need one position to initialize + // the coordinates + Rubberband::currentFactoryMap(_map); + + RubberbandPointFeature * askPosition = new RubberbandPointFeature(_map, parent); + + setDefaults(askPosition); + + askPosition -> enableFinishAfterConstruction(true); + askPosition -> setUserData( new ActivateRubberbandInfo(rubberband, symbolConstruction) ); + askPosition -> setPreviewSidc(sidc); + askPosition -> setAutoDelete(true); + + connect(askPosition, &SituationMap::RB::Rubberband::apply, this, &RubberbandFactory::onActivateRubberband ); + connect(askPosition, &SituationMap::RB::Rubberband::abort, this, &RubberbandFactory::onAbortRubberband ); + } + else rubberband->activate(); + } + } break; + + default: + { + rubberband = new RubberbandPointCluster(_map,parent); + if (rubberband) rubberband->activate(); + } break; + } // switch + } break; + + case AdvancedSymbology::GeometryType::Polygon : + { + switch (symbolConstruction.mode()) + { + case SymbolConstruction::Mode::Circular : + { + RubberbandEllipse * r = new RubberbandEllipse(_map, parent); + if (r) + { + r->enableSector(false); + r->constrainOrthogonal(true); + r->constrainQuadratic(true); + r->enableFinishAfterConstruction(true); + } + rubberband = r; + } break; + + case SymbolConstruction::Mode::Rectangle : + { + RubberbandRectangle * r = new RubberbandRectangle(_map, parent); + if (r) + { + r->constrainOrthogonal(false); + r->enableFinishAfterConstruction(true); + } + rubberband = r; + } break; + + case SymbolConstruction::Mode::Corridor : + { + RubberbandCorridor * r = new RubberbandCorridor(_map, RubberbandCorridor::InputMode::InputCenterLine, 0, parent); + if (r) + { + r->setCorridorBorderColor( QColor("Black") ); + r->setCorridorFillColor( QColor(255,255,0,100) ); + r->minimumPointCount( symbolConstruction.minPoints() ); + r->maximumPointCount( symbolConstruction.maxPoints() ); + r->enableFinishAfterConstruction(true); + + rubberband = r; + } + } break; + + case SymbolConstruction::Mode::MultiPoint : + { + RubberbandPointCluster * r = new RubberbandPointCluster(_map,parent,false); + if (r) + { + r->minimumPointCount(symbolConstruction.minPoints()); + r->maximumPointCount(symbolConstruction.maxPoints()); + r->enableFinishAfterConstruction(true); + + rubberband = r; + + if ((!symbolConstruction.unlimitedMaxPoints()) && rubberbandConstructionMode) + { + // Rubberband is not active here because we need one position to initialize + // the coordinates + Rubberband::currentFactoryMap(_map); + + RubberbandPointFeature * askPosition = new RubberbandPointFeature(_map, parent); + + setDefaults(askPosition); + + askPosition -> enableFinishAfterConstruction(true); + askPosition -> setUserData( new ActivateRubberbandInfo(rubberband, symbolConstruction) ); + askPosition -> setPreviewSidc(sidc); + askPosition -> setAutoDelete(true); + + connect(askPosition, &SituationMap::RB::Rubberband::apply, this, &RubberbandFactory::onActivateRubberband ); + connect(askPosition, &SituationMap::RB::Rubberband::abort, this, &RubberbandFactory::onAbortRubberband ); + } + else rubberband->activate(); + } + } break; + + case SymbolConstruction::Mode::Polygon : + default: + { + RubberbandMultiPointFeature * rubberArea = new RubberbandMultiPointFeature( _map, parent ); + if (rubberArea) + { + rubberArea -> enableClosed(true); + } + rubberband = rubberArea; + } break; + } // switch + } break; + + default : break; + + } //switch + } + catch (const SituationMap::Exception &) {;} + + if (rubberband) + { + setDefaults( rubberband ); + rubberband -> setAutoDelete(true); + rubberband -> setPreviewSidc( sidc ); + + if (!rubberbandConstructionMode) + { + QPointF pixelPos( winPos.x(), winPos.y() ); + if (!defaultSymbolRubberbandCoordinates(_map, rubberband, pixelPos, symbolConstruction)) + { + ERROR_LOG(_logger) << LOGFUNCTION << "Can't init symbol rubberband coordinate list, undefined proposal."; + rubberband->deleteMySelf(); + rubberband = nullptr; + } + } + } + + return rubberband; + } /* RubberbandFactory::create */ + + + static bool defaultSymbolRubberbandCoordinates( Map * map, Rubberband * rbnd, const QPointF & pixelPos, const SymbolConstruction & symbolConstruction ) + { + QVector pointList; + + pointList.reserve( symbolConstruction.maxPoints() ); + + switch (symbolConstruction.mode()) + { + case SymbolConstruction::Mode::ArcCurve: + { + if (4 == symbolConstruction.maxPoints()) + { + pointList.append(pixelPos + QPointF(-150,-100)); + pointList.append(pixelPos + QPointF(-100,-100)); + pointList.append(pixelPos + QPointF(-100, 100)); + pointList.append(pixelPos + QPointF(-150, 100)); + } + + if (3 == symbolConstruction.maxPoints()) + { + pointList.append(pixelPos + QPointF(-150, 100)); + pointList.append(pixelPos + QPointF(+150, -20)); + pointList.append(pixelPos); + } + } break; + + case SymbolConstruction::Mode::MultiPoint: + { + if (4 == symbolConstruction.maxPoints()) + { + pointList.append(pixelPos + QPointF(-100, 100)); + pointList.append(pixelPos + QPointF( 100, 100)); + pointList.append(pixelPos + QPointF(-150,-100)); + pointList.append(pixelPos + QPointF( 150,-100)); + } + if (3 == symbolConstruction.maxPoints()) + { + pointList.append(pixelPos + QPointF(-150, 0)); + pointList.append(pixelPos + QPointF( 150, 0)); + pointList.append(pixelPos + QPointF(-150,-100)); + } + if (2 == symbolConstruction.maxPoints()) + { + pointList.append(pixelPos); + pointList.append(pixelPos + QPointF(150, 150)); + } + } break; + + case SymbolConstruction::Mode::Polygon: + case SymbolConstruction::Mode::Triangle: + { + pointList.append(pixelPos); + pointList.append(pixelPos + QPointF(-150, +150)); + pointList.append(pixelPos + QPointF(+150, +150)); + } break; + + case SymbolConstruction::Mode::Point: + { + pointList.append(pixelPos); + } break; + + case SymbolConstruction::Mode::Circular: + case SymbolConstruction::Mode::Rectangle: + { + pointList.append(pixelPos + QPointF(-150, 150)); + pointList.append(pixelPos + QPointF( 150, 150)); + pointList.append(pixelPos + QPointF( 150,-150)); + pointList.append(pixelPos + QPointF(-150,-150)); + } break; + + case SymbolConstruction::Mode::PolyLineWidth: + { + pointList.append(pixelPos + QPointF( 50, 0)); + pointList.append(pixelPos + QPointF(-150, 0)); + pointList.append(pixelPos + QPointF( 0, +50)); + } break; + + case SymbolConstruction::Mode::Corridor: + case SymbolConstruction::Mode::PolyLine: + { + pointList.append(pixelPos); + pointList.append(pixelPos + QPointF(150, 150)); + } break; + + case SymbolConstruction::Mode::Unknown: + default: + { + // empty + } break; + + } // switch + + if (!pointList.isEmpty()) + { + for (int i = 0; i < pointList.size(); ++i) { pointList[i] = map->viewPortWindowPointToGeoCoordinate(pointList[i]); } + + if (SymbolConstruction::Mode::Corridor == symbolConstruction.mode()) + { + SituationMap::CoordTranslationFunctor * cf = map->coordTranslationFunctor(); + QPointF p1 = pointList[0]; + QPointF p2 = pointList[1]; + + if (cf) + { + p1 = cf->geographicToMap(p1); + p2 = cf->geographicToMap(p2); + } + p1 -= p2; + qreal wCorr = sqrt(p1.x()*p1.x()+p1.y()*p1.y()) * 0.15; + RubberbandCorridor * rbndCorr = static_cast(rbnd); + rbndCorr->setCoordinateVectorWidth(pointList,wCorr); + } + else rbnd->setCoordinateVector(pointList); + return true; + } + return false; + } /* RubberbandFactory::defaultSymbolRubberbandCoordinates */ + + + void RubberbandFactory::onActivateRubberband( RubberbandBase * rubberband ) + { + if (nullptr == rubberband) return; + + ActivateRubberbandInfo * arbInfo = static_cast( rubberband->userData() ); + + if (nullptr == arbInfo) return; + + const QPointF & position = rubberband->singleCoordinate(); + const QPointF & pixelPos = _map->geoCoordinateToViewPortWindowPoint(position); + + if (defaultSymbolRubberbandCoordinates( _map, arbInfo->_rubberband, pixelPos, arbInfo->_symbolConstruction)) + { + arbInfo->_rubberband->activate(); + } + else + { + arbInfo->_rubberband->deleteMySelf(); + ERROR_LOG(_logger) << LOGFUNCTION << "Can't init symbol rubberband coordinate list, undefined proposal."; + } + delete arbInfo; + rubberband->setUserData(nullptr); + } /* RubberbandFactory::onActivateRubberband */ + + + void RubberbandFactory::onAbortRubberband( RubberbandBase * rubberband ) + { + if (nullptr == rubberband) return; + + ActivateRubberbandInfo * arbInfo = static_cast(rubberband->userData()); + if (arbInfo) + { + arbInfo->_rubberband->deleteMySelf(); + delete arbInfo; + } + rubberband->setUserData(nullptr); + } /* RubberbandFactory::onAbortRubberband */ + + + void RubberbandFactory::setRubberbandDrawColor ( const QColor & c ) { _defaults->_rubberbandDrawColor = c; } + void RubberbandFactory::setRubberbandFillColor ( const QColor & c ) { _defaults->_rubberbandFillColor = c; } + void RubberbandFactory::setRubberbandHandleDrawColor ( const QColor & c ) { _defaults->_rubberbandHandleDrawColor = c; } + void RubberbandFactory::setRubberbandHandleFillColor ( const QColor & c ) { _defaults->_rubberbandHandleFillColor = c; } + void RubberbandFactory::setRubberbandSelectionColor ( const QColor & c ) { _defaults->_rubberbandSelectionColor = c; } + void RubberbandFactory::setRubberbandFrameColor ( const QColor & c ) { _defaults->_rubberbandFrameColor = c; } + void RubberbandFactory::setRubberbandFrameFillColor ( const QColor & c ) { _defaults->_rubberbandFrameFillColor = c; } + void RubberbandFactory::setRubberbandFrameHandleMargin ( int m ) { _defaults->_rubberbandFrameHandleMargin = m; } + void RubberbandFactory::setRubberbandPreviewOffset ( const QPoint & o ) { _defaults->_rubberbandPreviewOffset = o; } + void RubberbandFactory::setRubberbandPreviewSize ( unsigned int s ) { _defaults->_rubberbandPreviewSize = s; } + void RubberbandFactory::setRubberbandPickingRatio ( unsigned int s ) { _defaults->_rubberbandPickingRatio = s; } + void RubberbandFactory::setRubberbandLineHandleWidth ( unsigned int s ) { _defaults->_rubberbandLineHandleWidth = s; } + + + void RubberbandFactory::setDefaults(RubberbandBase * rband) const + { + if (nullptr == rband) return; + + if ( _defaults->_rubberbandDrawColor.isValid() ) rband->setDrawColor ( _defaults->_rubberbandDrawColor ); + if ( _defaults->_rubberbandFillColor.isValid() ) rband->setFillColor ( _defaults->_rubberbandFillColor ); + if ( _defaults->_rubberbandHandleDrawColor.isValid()) rband->setHandleDrawColor ( _defaults->_rubberbandHandleDrawColor ); + if ( _defaults->_rubberbandHandleFillColor.isValid()) rband->setHandleFillColor ( _defaults->_rubberbandHandleFillColor ); + if ( _defaults->_rubberbandSelectionColor.isValid() ) rband->setHandleSelectionColor( _defaults->_rubberbandSelectionColor ); + if ( _defaults->_rubberbandFrameColor.isValid() ) rband->setFrameColor ( _defaults->_rubberbandFrameColor ); + if ( _defaults->_rubberbandFrameFillColor.isValid() ) rband->setFrameFillColor ( _defaults->_rubberbandFrameFillColor ); + + rband->setFrameHandleMargin( _defaults->_rubberbandFrameHandleMargin ); + } /* RubberbandFactory::setDefaults */ + + + void RubberbandFactory::setDefaults(Rubberband * rband) const + { + if (nullptr == rband) return; + + setDefaults( static_cast(rband) ); + if (_defaults->_rubberbandPreviewSize > 0) rband->setPreviewSize ( _defaults->_rubberbandPreviewSize ); + if (_defaults->_rubberbandPickingRatio > 0) rband->setPickingRatio ( _defaults->_rubberbandPickingRatio ); + + if (!_defaults->_rubberbandPreviewOffset.isNull()) rband->setPreviewOffset( _defaults->_rubberbandPreviewOffset ); + + if (_defaults->_rubberbandLineHandleWidth > 0) rband->setLineHandleWidth ( _defaults->_rubberbandLineHandleWidth ); + + RubberbandCorridor *rbCorridor = qobject_cast(rband); + if (rbCorridor) + { + rbCorridor->setCorridorBorderColor(rband->getDrawColor()); + rbCorridor->setCorridorFillColor(rband->getFillColor()); + } + + } /* RubberbandFactory::setDefaults */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.h new file mode 100755 index 000000000..61e777124 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_factory.h @@ -0,0 +1,290 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "event_dispatcher.h" +#include "event_delegater.h" + +#include "rubberband_base.h" +#include "rubberband.h" +#include "rubberband_arrow.h" +#include "rubberband_corridor.h" +#include "rubberband_ellipse.h" +#include "rubberband_free_hand.h" +#include "rubberband_multi_point_feature.h" +#include "rubberband_multi_ellipse_feature.h" +#include "rubberband_point_cluster.h" +#include "rubberband_point_feature.h" +#include "rubberband_polyline_width.h" +#include "rubberband_rectangle.h" +#include "rubberband_smooth_line.h" +#include "rubberband_state_select.h" +#include "rubberband_transform.h" +#include "rubberband_transform_move.h" +#include "rubberband_transform_rotate.h" +#include "rubberband_transform_scale.h" + +#include "rubberband_3d.h" +#include "rubberband_point_feature_3d.h" +#include "rubberband_multi_point_feature_3d.h" + +#include +#include + +#include + +namespace SituationMap { namespace RB +{ + /** + @brief Factory class to create rubberbands + + Rubberbands are used to create or modifiy 2D geometries like lines, circles, polygons etc. on a 2D map view + + Rubberbands are used to create or modifiy 2D geometries like lines, circles, polygons etc. by processing + Qt input events like keyboard and mouse events. Following rubberbands are available: + + | Class Name | Description | + | :----------------------------- | : --------------------------------------------------------------------------------------- | + | RubberbandPointFeature | Rubberband to edit a single position for a point feature. | + | RubberbandPointCluster | Rubberband to edit multiple positions to define a cluster of point features. | + | RubberbandMultiPointFeature | Rubberband to edit a polyline or polygon to define line or areal features. | + | RubberbandSmoothLine | Like RubberbandMultiPointFeature with rounded edges. | + | RubberbandPolylineWidth | Rubberband like RubberbandMultiPointFeature for symbols with additional width coordinate. | + | RubberbandArrow | Rubberband to edit a polyline with an arrow at the end. | + | RubberbandCorridor | Rubberband to edit corridors by defining a polyline and a width. | + | RubberbandRectangle | Rubberband to edit a rectangle or square. | + | RubberbandEllipse | Rubberband to edit a ellipse or circle. | + | RubberbandFreeHand | Rubberband to input a free hand line. | + | RubberbandStateSelect | Rubberband to set states to coordinates. | + | RubberbandTransform | Rubberband to manipulate moving, rotating and scaling in a single step | + | RubberbandTransformMove | Rubberband to define moving transformations | + | RubberbandTransformRotate | Rubberband to define rotating transformations | + | RubberbandTransformScale | Rubberband to define scaling transformations | + + On a 3D map scene following rubberbands are available: + + | Class Name | Description | + | :----------------------------- | : --------------------------------------------------------------------------------------- | + | RubberbandPointFeature3D | Rubberband to edit a single position for a point feature. | + | RubberbandMultiPointFeature3D | Rubberband to edit a polyline or polygon to define line or areal features. | + + + @see Rubberband, RubberbandPointFeature, RubberbandPointCluster, RubberbandMultiPointFeature, RubberbandSmoothLine, RubberbandPolylineWidth + RubberbandRectangle, RubberbandEllipse, RubberbandCorridor, RubberbandArrow, RubberbandFreeHand, RubberbandStateSelect + RubberbandTransform, RubberbandTransformMove, RubberbandTransformRotate, RubberbandTransformScale, + Rubberband3D, RubberbandPointFeature3D, RubberbandMultiPointFeature3D + + */ + class SITUATIONMAP_EXPORT RubberbandFactory : public QObject + { + Q_OBJECT + + public: + + explicit RubberbandFactory( SituationMap::Map * map ); + virtual ~RubberbandFactory(); + + /** + @brief template methode to create a rubberband instance + + Creates with new a rubberband from type RubberbandClass. RubberbandClass must be inherited + from class SiMapRubBnd::Rubberband otherwise a nullptr will be retruned. After use the + pointer have to be freed with delete. + + Expample to create a rubberband: + @code + + SituationMap::Map & map = ... + + const RubberbandFactory & rubberbandFactory = map.rubberbandFactory(); + + RubberbandPointFeature * rubberband = rubberbandFactory.create(); + if (rubberband != nullptr) doSomething(rubberband); + + @endcode + + @param parent Parent of rubberband object. If parent is nullptr \c _map will be used as parent + + @see Rubberband, RubberbandPointFeature, RubberbandPointCluster, RubberbandMultiPointFeature, RubberbandRectangle, RubberbandEllipse, RubberbandCorridor, RubberbandArrow + */ + template RubberbandClass * create( QObject * parent = nullptr ) const + { + RubberbandClass * rubberband = nullptr; + + if ((std::is_base_of::value) && (Mode::Mode_2D == _map->mode())) + { + // be careful it is not thread safe here + Rubberband::currentFactoryMap(_map); + Rubberband::allowMultipleRubberbands(_allowMultipleRubberbands); + + try + { + rubberband = new RubberbandClass( _map, parent ); + } + catch (const SituationMap::Exception &) {;} + setDefaults( rubberband ); + } + return rubberband; + } + + /** + Creates a new corridor rubberband and defines the input method of the rubberband + @param inputMethode Defines the input method of the rubberband + @param width With of a corridor in meters. + @param parent Parent of rubberband object. If parent is nullptr \c _map will be used as parent + @see RubberbandCorridor + */ + RubberbandCorridor * create( RubberbandCorridor::InputMode inputMode, qreal width = 0, QObject * parent = nullptr ) const + { + if (!_map->isReady() || (Mode::Mode_2D != _map->mode()) ) + return nullptr; + + Rubberband::currentFactoryMap(_map); + Rubberband::allowMultipleRubberbands(_allowMultipleRubberbands); + + RubberbandCorridor * rubberband = nullptr; + try + { + rubberband = new RubberbandCorridor( _map, inputMode, width, parent ); + } + catch (const SituationMap::Exception &) {;} + setDefaults( rubberband ); + return rubberband; + } + + + /** + Creates a new rubberband fitting to the sidc code + @param sidc sidc of symbol that will be created with rubberband. + + @see RubberbandPointFeature, RubberbandMultiPointFeature + */ + Rubberband * createSymbol( const QString & sidc, QObject * parent = nullptr ) const; + + /** + Creates a new rubberband fitting to the sidc code + @param sidc sidc of symbol that will be created with rubberband. + @param winPos Default position of the rubberband on window + + @see RubberbandPointFeature, RubberbandMultiPointFeature + */ + Rubberband * createSymbol( const QString & sidc, const QPoint & winPos, QObject * parent = nullptr ) const; + + + /** + @brief template methode to create a rubberband instance in 3D maps + + Creates with new a rubberband from type RubberbandClass. RubberbandClass must be inherited + from class SiMapRubBnd::Rubberband otherwise a nullptr will be retruned. After use the + pointer have to be freed with delete. + + Expample to create a rubberband: + @code + + SituationMap::Map & map = ... + + const RubberbandFactory & rubberbandFactory = map.rubberbandFactory(); + + RubberbandPointFeature * rubberband = rubberbandFactory.create(); + if (rubberband != nullptr) doSomething(rubberband); + + @endcode + + @param parent Parent of rubberband object. If parent is nullptr \c _map will be used as parent + + @see Rubberband, RubberbandPointFeature, RubberbandPointCluster, RubberbandMultiPointFeature, RubberbandRectangle, RubberbandEllipse, RubberbandCorridor, RubberbandArrow + */ + template RubberbandClass * create3D( QObject * parent = nullptr ) const + { + RubberbandClass * rubberband = nullptr; + + if ((std::is_base_of::value) && (Mode::Mode_3D == _map->mode())) + { + // be careful it is not thread safe here + RubberbandBase::currentFactoryMap(_map); + RubberbandBase::allowMultipleRubberbands(_allowMultipleRubberbands); + + try + { + rubberband = new RubberbandClass( _map, parent ); + } + catch (const SituationMap::Exception &) {;} + setDefaults( rubberband ); + } + return rubberband; + }; + + /** + If pass \c true multiple rubberbands per time can be created per map. Creating a rubberbad of the same type twice per time will return one + rubberband that is not alive. Default is \c false. In this case creating a second rubberband before the first has finsihed (rubberband is not alive) + will retrun \c nullptr. + */ + void allowMultipleRubberbands(bool on) { _allowMultipleRubberbands = on; } + + bool allowMultipleRubberbands() const { return _allowMultipleRubberbands; } + + void setRubberbandDrawColor( const QColor & ); + void setRubberbandFillColor( const QColor & ); + void setRubberbandHandleDrawColor( const QColor & ); + void setRubberbandHandleFillColor( const QColor & ); + void setRubberbandSelectionColor( const QColor & ); + void setRubberbandFrameColor( const QColor & ); + void setRubberbandFrameFillColor( const QColor & ); + void setRubberbandFrameHandleMargin( int ); + void setRubberbandPreviewOffset( const QPoint & ); + void setRubberbandPreviewSize( unsigned int ); + void setRubberbandPickingRatio( unsigned int ); + void setRubberbandLineHandleWidth( unsigned int ); + + signals: + + void rubberbandCreated(); + void rubberbandDestroyed(); + + private: + + class Defaults; + + SituationMap::Map * _map; + Defaults * _defaults; + bool _allowMultipleRubberbands; + + LOGGER_T _logger; + + void setDefaults( RubberbandBase * ) const; + void setDefaults( Rubberband * ) const; + + private slots: + + void onActivateRubberband( RubberbandBase * rubberband ); + void onAbortRubberband ( RubberbandBase * rubberband ); + + }; /* RubberbandFactory */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.cpp new file mode 100755 index 000000000..9b6847bca --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.cpp @@ -0,0 +1,314 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberband frame handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_frame_handle.h" + +#include + +#include +#include + +#include + + +namespace SituationMap { namespace RB +{ + + + + + static QGraphicsPolygonItem * createFrameGraphic( QGraphicsScene * mapScene, qreal zBufferValue ) + { + if (mapScene != nullptr) + { + QGraphicsPolygonItem * frameSymbol = new QGraphicsPolygonItem(); + + frameSymbol -> setZValue( zBufferValue ); + frameSymbol -> setFlags( QGraphicsItem::ItemIgnoresTransformations ); + + mapScene -> addItem( frameSymbol ); + + return frameSymbol; + } + return nullptr; + } /* createFrameGraphic */ + + + class FramePoint : public QPointF + { + public: + Q_DECL_CONSTEXPR FramePoint(const QPointF &p) : QPointF(p) {} + Q_DECL_CONSTEXPR FramePoint(qreal x, qreal y) : QPointF(x, y) {} + + FramePoint rotate(const QPointF ¢er, qreal sinA, qreal cosA) const + { + qreal cx = x() - center.x(); + qreal cy = y() - center.y(); + qreal px = cx*cosA - cy*sinA + center.x(); + qreal py = cy*cosA + cx*sinA + center.y(); + return FramePoint(px, py); + } // rotate( const QPointF & center, qreal sin qreal cosA ) + + FramePoint rotate(const RubberbandHandle::RubberbandCoordinate & center, qreal sinA, qreal cosA) const + { + return rotate(QPointF(center._mapCoordinate.x(), center._mapCoordinate.y()), sinA, cosA); + } /* rotate */ + + FramePoint rotate(qreal sinA, qreal cosA) const + { + return rotate(QPointF(), sinA, cosA); + } // rotate( const QPointF & center, qreal sin qreal cosA ) + + + }; /* FramePoint */ + + + RubberbandFrameHandle::RubberbandFrameHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView + , QGraphicsScene * scene + , qreal sceneMargin ) + : RubberbandHandle(mapView,scene) + , _frameSymbol(nullptr) + , _frameSceneMargin(sceneMargin) + , _frameRotationSin(0) + , _frameRotationCos(1) + , _frameWidth(0) + , _frameHeight(0) + , _frameCenterCoordinate() + , _mapRotateSin(0) + , _mapRotateCos(1) + , _drawDisabled(false) + , _frameLineHandleList() + { + qreal mapRotatePhi = _mapView->mapRotation() * MathConst::pi / 180.0; + + _mapRotateSin = sin(mapRotatePhi); + _mapRotateCos = cos(mapRotatePhi); + + zBufferPlane( ZBufferPlane::FrameHandle ); + + _frameSymbol = createFrameGraphic( _scene, _zBufferValue ); + if (_drawDisabled) _frameSymbol->hide(); + updateGraphic(); + } /* RubberbandFrameHandle::RubberbandFrameHandle */ + + + + RubberbandFrameHandle::~RubberbandFrameHandle() + { + if (_scene) + { + if (_frameSymbol) + { + _scene->removeItem(_frameSymbol); + delete _frameSymbol; + } + } + _frameSymbol = nullptr; + } /* RubberbandFrameHandle::~RubberbandFrameHandle */ + + + void RubberbandFrameHandle::buildFrameVertexCoordinates() + { + auto length = [](qreal x, qreal y) -> qreal { return sqrt(x*x+y*y); }; + + QPointF center( _frameCenterCoordinate._mapCoordinate.x(), _frameCenterCoordinate._mapCoordinate.y()); + RubberbandCoordinate marginP( _mapView, _frameCenterCoordinate._sceneCoordinate+QPointF(_frameSceneMargin,0) ); + qreal margin = length( marginP._mapCoordinate.x()-center.x(), marginP._mapCoordinate.y()-center.y() ); + + QPointF vertex[4]; + + qreal frameWidth2 = _frameWidth * 0.5; + qreal frameHeight2 = _frameHeight * 0.5; + + vertex[0] = QPointF( center.x() - frameWidth2 - margin, center.y() - frameHeight2 - margin ); + vertex[1] = QPointF( center.x() + frameWidth2 + margin, center.y() - frameHeight2 - margin ); + vertex[2] = QPointF( center.x() + frameWidth2 + margin, center.y() + frameHeight2 + margin ); + vertex[3] = QPointF( center.x() - frameWidth2 - margin, center.y() + frameHeight2 + margin ); + + for (int i = 0; i < 4; ++i) + { + const FramePoint & v = FramePoint(vertex[i]).rotate(_frameCenterCoordinate, -_frameRotationSin, _frameRotationCos ); + _frameVertexCoordinate[i] = RubberbandCoordinate(_mapView, convert(v, _mapView->map()->spatialReference())); + } + // Now we have the vertex coordinates in map coordinate system + + } /* RubberbandFrameHandle::buildFrameVertexCoordinates */ + + + void RubberbandFrameHandle::adjustCoordinates() + { + qreal mapRotatePhi = _mapView->mapRotation() * MathConst::pi / 180.0; + + _mapRotateSin = sin(mapRotatePhi); + _mapRotateCos = cos(mapRotatePhi); + + _frameCenterCoordinate.mapToScene(_mapView); + + buildFrameVertexCoordinates(); + updateFramePolygonItem(); + + emit adjusted(); + + } /* RubberbandFrameHandle::adjustCoordinates */ + + + void RubberbandFrameHandle::adjustWindow( const QRectF& ) + { + onDo_adjustCoordinates(); + } /* RubberbandFrameHandle::adjustWindow */ + + + void RubberbandFrameHandle::updateFrameShape( const QVector & vertexList, qreal sinA, qreal cosA ) + { + DEBUG_LOG(_logger) << LOGFUNCTION << "( const QVector & vertexList, qreal sinA, qreal cosA )"; + + qreal sinPhi = sinA * _mapRotateCos - cosA * _mapRotateSin; // Addition-Theorem get rotation of the frame without map rotation + qreal cosPhi = cosA * _mapRotateCos + sinA * _mapRotateSin; + + qreal minX = std::numeric_limits::infinity(); + qreal maxX = -std::numeric_limits::infinity(); + qreal minY = std::numeric_limits::infinity(); + qreal maxY = -std::numeric_limits::infinity(); + + for (const RubberbandCoordinate & vertex : vertexList) + { + // eliminate rotation from vertex scene coordinate + FramePoint xy = FramePoint(vertex._sceneCoordinate.x(),vertex._sceneCoordinate.y()).rotate( -sinPhi, cosPhi ); + qreal x = xy.x(); + qreal y = xy.y(); + + if (minX > x) minX = x; + if (minY > y) minY = y; + if (maxX < x) maxX = x; + if (maxY < y) maxY = y; + } + + _frameRotationSin = sinA; + _frameRotationCos = cosA; + + // calculate rotated coordinates (map rotation) of (minX,minY), (maxX,maxY) and frame scene center + FramePoint frameSceneV0 = FramePoint( minX , minY ).rotate( sinPhi, cosPhi ); + FramePoint frameSceneVW = FramePoint( maxX , minY ).rotate( sinPhi, cosPhi ); + FramePoint frameSceneVH = FramePoint( minX , maxY ).rotate( sinPhi, cosPhi ); + FramePoint frameSceneCenter = FramePoint( (minX+maxX)*0.5, (minY+maxY)*0.5 ).rotate( sinPhi, cosPhi ); + + _frameCenterCoordinate.updateSceneCoordinate( _mapView, frameSceneCenter ); + + // Calculate map coordinates of bounding rectangle + RubberbandCoordinate frameV0( _mapView, QPointF(frameSceneV0.x(),frameSceneV0.y()) ); + RubberbandCoordinate frameVW( _mapView, QPointF(frameSceneVW.x(),frameSceneVW.y()) ); + RubberbandCoordinate frameVH( _mapView, QPointF(frameSceneVH.x(),frameSceneVH.y()) ); + + + auto length = []( qreal x1, qreal y1, qreal x2, qreal y2 ) -> qreal + { + qreal dx = x2-x1; + qreal dy = y2-y1; + return sqrt(dx*dx+dy*dy); + }; + + // get frame width and height in map coordinates + + _frameWidth = length( frameV0._mapCoordinate.x(), frameV0._mapCoordinate.y(), frameVW._mapCoordinate.x(), frameVW._mapCoordinate.y() ); + _frameHeight = length( frameV0._mapCoordinate.x(), frameV0._mapCoordinate.y(), frameVH._mapCoordinate.x(), frameVH._mapCoordinate.y() ); + + buildFrameVertexCoordinates(); + updateFramePolygonItem(); + + } /* RubberbandFrameHandle::updateFrameShape */ + + + void RubberbandFrameHandle::updateFrameShape() + { + DEBUG_LOG(_logger) << LOGFUNCTION; + + buildFrameVertexCoordinates(); + updateFramePolygonItem(); + + } /* RubberbandFrameHandle::updateFrameShape */ + + + void RubberbandFrameHandle::updateFramePolygonItem() + { + QPolygonF framePolygon; + + framePolygon.reserve( 4 ); + for (auto vertex : _frameVertexCoordinate) + { + framePolygon.append(vertex.mapToScene(_mapView)); + } + framePolygon.append(_frameVertexCoordinate[0]._sceneCoordinate); + + _frameSymbol->setPolygon(framePolygon); + + } /* RubberbandFrameHandle::updateFramePolygonItem */ + + + void RubberbandFrameHandle::updateGraphic() + { + if (_frameSymbol) + { + _frameSymbol -> setPen( QPen(QColor("black"),1,Qt::PenStyle::DashDotLine) ); + _frameSymbol -> setVisible( _isVisible ); + + if (isSelected()) + _frameSymbol -> setBrush( QBrush(_selectionColor) ); + else + _frameSymbol -> setBrush( QBrush(_fillColor) ); + } + } /* RubberbandFrameHandle::updateGraphic */ + + + void RubberbandFrameHandle::updateCoordinate(const QPointF & scenePosition) + { + _frameCenterCoordinate.updateSceneCoordinate( _mapView, scenePosition ); + updateFrameShape(); + } /* RubberbandFrameHandle::updateCoordinate */ + + + void RubberbandFrameHandle::moveCoordinate(const QPointF & sceneDelta) + { + _frameCenterCoordinate.updateSceneCoordinate( _mapView, _frameCenterCoordinate._sceneCoordinate+sceneDelta ); + updateFrameShape(); + } /* RubberbandFrameHandle::moveCoordinate */ + + + RubberbandHandle::RubberbandCoordinate RubberbandFrameHandle::getCoordinate() const { return _frameCenterCoordinate; } + + + bool RubberbandFrameHandle::isPositionInside( const QPointF & scenePosition ) const + { + if (_frameSymbol) + { + return _frameSymbol->contains( scenePosition ); + } + return false; + } /* RubberbandFrameHandle::isPositionInside */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.h new file mode 100755 index 000000000..227d0d0c1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_frame_handle.h @@ -0,0 +1,153 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberband frame handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include +#include + +#include + +#include "rubberband_handle.h" +#include "rubberband_line_handle.h" + +class QGraphicsScene; + +namespace SituationMap { namespace RB +{ + + + /** + @class RubberbandFrameHandle rubberband_frame_handle.h + @brief Implementation of a frame handle for rubberband internal use. + @see RubberbandHandle + */ + class RubberbandFrameHandle : public RubberbandHandle + { + Q_OBJECT + public: + + RubberbandFrameHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView + , QGraphicsScene * scene + , qreal sceneMargin ); + + ~RubberbandFrameHandle(); + + inline void updateFrameShape(const QVector & vertexList) + { + updateFrameShape( vertexList, 0.0, 1.0 ); + } /* updateFrameShape */ + + + void updateFrameShape( const QVector & vertexList, qreal sinA, qreal cosA ); + + bool isPositionInside( const QPointF & scenePosition ) const override; + + virtual void updateCoordinate( const QPointF & scenePosition ); + virtual void moveCoordinate ( const QPointF & sceneDelta ); + + virtual RubberbandCoordinate getCoordinate() const; + + inline qreal frameRotationAngle() const + { + qreal phi = acos(_frameRotationCos); + return (_frameRotationSin < 0.0) ? -phi : phi; + } /* frameRotationAngle */ + + + inline void frameRotationAngle( qreal & sinA, qreal & cosA ) + { + sinA = _frameRotationSin; + cosA = _frameRotationCos; + } /* frameRotationAngle */ + + inline qreal frameSceneMargin() const { return _frameSceneMargin; } + + inline void updateFrameRotationAngle( qreal sinA, qreal cosA ) + { + _frameRotationSin = sinA; + _frameRotationCos = cosA; + updateFrameShape(); + } /* updateFrameRotationAngle */ + + + inline void updateFrameRotationAngle( qreal x ) + { + _frameRotationSin = sin(x); + _frameRotationCos = cos(x); + updateFrameShape(); + } /* updateFrameRotationAngle */ + + + inline void updateFrameSceneMargin(qreal x) { _frameSceneMargin = x; updateFrameShape(); } + + + inline void drawDisabled(bool v) + { + _drawDisabled = v; + if (_drawDisabled) _frameSymbol->hide(); + else _frameSymbol->show(); + } /* drawDisabled */ + + + inline bool drawDisabled() const { return _drawDisabled; } + + + protected: + + QGraphicsPolygonItem * _frameSymbol; + qreal _frameSceneMargin; + qreal _frameRotationSin; + qreal _frameRotationCos; + qreal _frameWidth; + qreal _frameHeight; + RubberbandCoordinate _frameCenterCoordinate; + RubberbandCoordinate _frameVertexCoordinate[4]; + qreal _mapRotateSin; + qreal _mapRotateCos; + bool _drawDisabled; + + QList> _frameLineHandleList; + + void updateGraphic() override; + + void updateFrameShape(); + void updateFramePolygonItem(); + + void buildFrameVertexCoordinates(); + + void adjustCoordinates() override; + void adjustWindow( const QRectF& ) override; + + QPointF getMapRotation() { return QPointF( _mapRotateCos, _mapRotateSin ); } + + }; /* RubberbandFrameHandle */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.cpp new file mode 100755 index 000000000..6391cd1c1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.cpp @@ -0,0 +1,267 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_free_hand.h" +#include "rubberband_point_handle.h" +#include "rubberband_drawable.h" +#include "drawable_line.h" +#include "drawable_painter_path.h" +#include "rubberband_handle_proxy.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include + +namespace SituationMap { namespace RB +{ + + struct RubberbandFreeHand::InternalData + { + QVector _coordinateList; + QVector _inOneGoIndex; + QPoint _lastWindowPos; + bool _coordinateRecordingActive = false; + SituationMap::RefPointer _cursorPreviewLine; + SituationMap::RefPointer _cursorPreviewCloseLine; + SituationMap::RefPointer _pathItem; + }; /* RubberbandFreeHand::InternalData */ + + + RubberbandFreeHand::RubberbandFreeHand( SituationMap::Map * map, QObject * parent ) + : Rubberband(map,parent) + , _internal(new RubberbandFreeHand::InternalData) + , _isClosed(false) + { + } /* RubberbandFreeHand::RubberbandFreeHand */ + + + RubberbandFreeHand::~RubberbandFreeHand() + { + delete _internal; + _internal = nullptr; + } /* RubberbandFreeHand::~RubberbandFreeHand */ + + + bool RubberbandFreeHand::selectAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (Rubberband::selectAction(causer, x, y)) + { + deactivateButtons(); + _internal->_coordinateRecordingActive = true; + _internal->_inOneGoIndex << _internal->_coordinateList.size(); + return movePickedAction(causer,x,y); + } + return false; + } /* RubberbandFreeHand::selectAction */ + + + bool RubberbandFreeHand::movePickedAction( AbstractEventDispatcher *, int x, int y ) + { + if (_internal->_coordinateRecordingActive) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + if (_internal->_coordinateList.isEmpty()) + { + _internal->_lastWindowPos.setX(x); + _internal->_lastWindowPos.setY(y); + _internal->_coordinateList.append(RubberbandDrawable::RubberbandCoordinate(Rubberband::_internal->_mapView,scenePos)); + return true; + } + + if ((std::abs(_internal->_lastWindowPos.x() - x) > 2) || (std::abs(_internal->_lastWindowPos.y() - y) > 2)) + { + _internal->_lastWindowPos.setX(x); + _internal->_lastWindowPos.setY(y); + + _internal->_coordinateList.append(RubberbandDrawable::RubberbandCoordinate(Rubberband::_internal->_mapView,scenePos)); + } + + update(); + + return true; + } + return false; + } /* RubberbandFreeHand::movePickedAction */ + + + bool RubberbandFreeHand::moveAction( AbstractEventDispatcher *, int x, int y ) + { + if (!_internal->_coordinateList.isEmpty()) + { + const RubberbandDrawable::RubberbandCoordinate & firstPos = _internal->_coordinateList.first(); + const RubberbandDrawable::RubberbandCoordinate & lastPos = _internal->_coordinateList.last(); + + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (!_internal->_cursorPreviewLine) _internal->_cursorPreviewLine = new DrawableLine( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene ); + if ((isClosedEnabled()) && (!_internal->_cursorPreviewCloseLine)) + { + _internal->_cursorPreviewCloseLine = new DrawableLine( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene ); + } + _internal -> _cursorPreviewLine -> setVisibility( true ); + _internal -> _cursorPreviewLine -> setDrawColor( _handleSelectionColor ); + _internal -> _cursorPreviewLine -> setLine( lastPos, RubberbandDrawable::RubberbandCoordinate( Rubberband::_internal->_mapView, scenePos )); + + if (_internal->_cursorPreviewCloseLine) + { + _internal -> _cursorPreviewCloseLine -> setVisibility( true ); + _internal -> _cursorPreviewCloseLine -> setDrawColor( _handleSelectionColor ); + _internal -> _cursorPreviewCloseLine -> setLine( firstPos, RubberbandDrawable::RubberbandCoordinate( Rubberband::_internal->_mapView, scenePos )); + } + } + return false; + } /* RubberbandFreeHand::moveAction */ + + + bool RubberbandFreeHand::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) + { + _internal->_coordinateRecordingActive = false; + + if (Rubberband::setCoordinateAction(causer,x,y)) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + activateButtons(); + _baseInternal->buttonProxyActive( scenePos ); + } + return false; + } /* RubberbandFreeHand::setCoordinateAction */ + + + void RubberbandFreeHand::activateButtons() + { + _baseInternal->_hasUndoButton = true; + _baseInternal->_applyIsPossible = true; + _baseInternal->_undoIsPossible = !_internal->_inOneGoIndex.isEmpty(); + + Rubberband::activateButtons(); + } /* RubberbandFreeHand::activateButtons */ + + + bool RubberbandFreeHand::undoAction( AbstractEventDispatcher*, int, int ) + { + if (!_internal->_inOneGoIndex.isEmpty()) + { + unsigned int coordinateIdx = _internal->_inOneGoIndex.takeLast(); + + while (coordinateIdx < (unsigned int)_internal->_coordinateList.size()) + { + _internal->_coordinateList.removeLast(); + } + update(); + activateButtons(); + } + return true; + } /* RubberbandFreeHand::undoAction */ + + + bool RubberbandFreeHand::completeStateAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive()) + { + return applyAndEndAction(causer,x,y); + } + return false; + } /* RubberbandFreeHand::completeStateAction */ + + + unsigned int RubberbandFreeHand::coordinateCount() const + { + return _internal->_coordinateList.size(); + } /* RubberbandFreeHand::coordinateCount */ + + + QVector RubberbandFreeHand::coordinateVector( CoordinateSystem cs ) const + { + QVector coordinateVector; + coordinateVector.reserve(_internal->_coordinateList.size()); + + for (const RubberbandDrawable::RubberbandCoordinate &pos : std::as_const(_internal->_coordinateList)) + coordinateVector.push_back(projectCoordinateFromMap( cs, pos._mapCoordinate )); + + return coordinateVector; + + } /* RubberbandFreeHand::coordinateVector */ + + + QPointF RubberbandFreeHand::singleCoordinate(unsigned int idx, CoordinateSystem cs ) const + { + if (idx >= (unsigned int)_internal->_coordinateList.size()) return QPointF(); + + const Esri::ArcGISRuntime::Point & pos = _internal->_coordinateList.at(idx)._mapCoordinate; + + return projectCoordinateFromMap( cs, pos ); + + } /* RubberbandFreeHand::singleCoordinate */ + + + void RubberbandFreeHand::update() + { + if (_internal->_coordinateList.size() > 1) + { + if (!_internal->_pathItem) + { + _internal->_pathItem = new DrawablePainterPath( Rubberband::_internal->_mapView ); + } + _internal -> _pathItem -> setVisibility(true); + _internal -> _pathItem -> setLineWidth( 2 ); + _internal -> _pathItem -> enableClosedLine( _isClosed ); + _internal -> _pathItem -> setDrawColor( _drawColor ); + _internal -> _pathItem -> setFillColor( _fillColor ); + _internal -> _pathItem -> setPath( _internal->_coordinateList ); + } + else + { + if (_internal -> _pathItem) _internal->_pathItem->setVisibility(false); + if (_internal->_coordinateList.isEmpty()) + { + if (_internal -> _cursorPreviewLine ) _internal -> _cursorPreviewLine -> setVisibility(false); + if (_internal -> _cursorPreviewCloseLine) _internal -> _cursorPreviewCloseLine -> setVisibility(false); + } + } + if (!_internal->_coordinateList.isEmpty()) + { + QPointF bottomRight( std::numeric_limits::min(), std::numeric_limits::min() ); + + for (const RubberbandDrawable::RubberbandCoordinate &coordinate : std::as_const(_internal->_coordinateList)) + { + const QPointF & p = coordinate._sceneCoordinate; + if (bottomRight.x() < p.x()) bottomRight.setX(p.x()); + if (bottomRight.y() < p.y()) bottomRight.setY(p.y()); + } + + if (isButtonBoxPositionFixed()) + { + RubberbandBase::_baseInternal->_buttonBoxSceneAnchor = bottomRight; + } + } + } /* RubberbandFreeHand::update */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.h new file mode 100755 index 000000000..d5e07c370 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_free_hand.h @@ -0,0 +1,102 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class for free hand drawing +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 06.10.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband.h" + + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandPointCluster rubberband_point_cluster.h + @brief Implementation of a rubberband to input single coodinates for building point feature clusters + @see Rubberband, RubberbandPointFeature + */ + class SITUATIONMAP_EXPORT RubberbandFreeHand : public Rubberband + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + explicit RubberbandFreeHand( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + + ~RubberbandFreeHand(); + /** @} */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + unsigned int coordinateCount() const override; + + virtual QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + virtual QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + /** + If set to true a polygon the rubberband draws a polygon otherwise a polyline. + */ + inline void enableClosed( bool flag ) { _isClosed = flag; } + + /** + Gets the value of the close flag + */ + inline bool isClosedEnabled() const { return _isClosed; } + + + private: + + virtual void setCoordinateVector( const QVector & /*pntList*/, CoordinateSystem /*cs*/ = CoordinateSystem::Geo ) override {} + + protected: + + /* + Forward declaration of internal container class for member variables. The derived classes of Rubberband + often have also a declaration of InternalData for there own internal members. + */ + struct InternalData; + + InternalData * _internal; + bool _isClosed; + + void activateButtons() override; + void update() override; + + }; /* RubberbandFreeHand */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.cpp new file mode 100755 index 000000000..7d80c7da8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.cpp @@ -0,0 +1,70 @@ +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_handle.h" + +namespace SituationMap { namespace RB +{ + + + RubberbandHandle::RubberbandHandle( QGraphicsView *view ) + : RubberbandDrawable(view) + , _pickingRatio(5) + , _isSelected(false) + , _isSelectable(true) + , _userData(nullptr) + { + } /* RubberbandHandle::RubberbandHandle */ + + + + RubberbandHandle::RubberbandHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView ) + : RubberbandDrawable( mapView ) + , _pickingRatio(5) + , _isSelected(false) + , _isSelectable(true) + , _userData(nullptr) + { + } /* RubberbandHandle::RubberbandHandle */ + + + RubberbandHandle::RubberbandHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView, QGraphicsScene * scene) + : RubberbandDrawable( mapView, scene ) + , _pickingRatio(5) + , _isSelected(false) + , _isSelectable(true) + , _userData(nullptr) + { + } /* RubberbandHandle::RubberbandHandle */ + + + RubberbandHandle::~RubberbandHandle() + { + } /* RubberbandHandle::~Rubberbandhandle */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.h new file mode 100755 index 000000000..f8017e07f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle.h @@ -0,0 +1,100 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_drawable.h" + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandHandle rubberband_handle.h + @brief Base class of all rubberband handles for rubberband internal use. + */ + class RubberbandHandle : public RubberbandDrawable + { + Q_OBJECT + + public: + + /** + @name Construction & Destruction + @{ + */ + explicit RubberbandHandle( QGraphicsView * ); + explicit RubberbandHandle( Esri::ArcGISRuntime::MapGraphicsView * ); + explicit RubberbandHandle( Esri::ArcGISRuntime::MapGraphicsView *, QGraphicsScene * ); + + ~RubberbandHandle(); + /** @} */ + + + virtual bool isPositionInside( const QPointF & scenePosition ) const = 0; + + inline bool isSelected() const { return _isSelected; } + inline void setSelection( bool on = true ) { _isSelected = _isSelectable && on; updateGraphic(); } + + inline bool isSelectable() const { return _isSelectable; } + inline void setSelectable(bool on = true) { _isSelectable = on; if (!on) { _isSelected = false; updateGraphic(); } } + + inline void setSelectionColor( const QColor & col ) { _selectionColor = col; updateGraphic(); } + + inline void setUserData( void * ptr ) { _userData = ptr; } + inline void* userData() { return _userData; } + + inline unsigned int pickingRatio() const { return _pickingRatio; } + inline void setPickingRatio(unsigned int value) { if (value > 5) _pickingRatio = value; } + + inline bool isFirst() const { return _is_first; } + inline void setIsFirst(bool on = true) { _is_first = on; } + + protected: + + void updateGraphic() override = 0; + + void adjustCoordinates() override = 0; + void adjustWindow( const QRectF& ) override = 0; + + unsigned int _pickingRatio; + bool _isSelected; + bool _isSelectable; + QColor _selectionColor; + bool _is_first{false}; + + private: + + void * _userData; + + signals: + + void adjusted(); + + }; /* RubberbandHandle */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.cpp new file mode 100755 index 000000000..7b0912b5b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.cpp @@ -0,0 +1,167 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of class RubberbandHandleProxy to forward adjust signals from view +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 10.07.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_handle_proxy.h" + +#include + +#include + +#define BUG_iOSQz_000005 + + +namespace SituationMap { namespace RB +{ + + void RubberbandHandleProxy::init( QWidget * widget ) + { + zBufferPlane( ZBufferPlane::Widget ); + + _widgetsProxy = new QGraphicsProxyWidget(); + _widgetsProxy -> setWidget( widget ); + _widgetsProxy -> setZValue( _zBufferValue ); + +#ifdef BUG_iOSQz_000005 + // Bug iOSQz-000005 'QGraphicsProxyWidget will crash' + // Adding a widget will let the application crash + // Workaround: 'proxy->setOpacity(0.99);' + + _widgetsProxy->setOpacity(0.99); +#endif + + _scene->addItem( _widgetsProxy ); + + } /* RubberbandHandleProxy::init */ + + + RubberbandHandleProxy::RubberbandHandleProxy( QGraphicsView * view, QWidget * widget ) + : RubberbandHandle(view) + , _coordinate() + , _sceneOffset(0, 0) + , _rotateSceneOffset(true) + , _restoreView(view) + { + init( widget ); + } /* RubberbandHandleProxy::RubberbandHandleProxy */ + + + RubberbandHandleProxy::RubberbandHandleProxy( Esri::ArcGISRuntime::MapGraphicsView * view, QWidget * widget ) + : RubberbandHandle(view) + , _coordinate() + , _sceneOffset(0, 0) + , _rotateSceneOffset(true) + , _restoreView(view) + { + init( widget ); + } /* RubberbandHandleProxy::RubberbandHandleProxy */ + + + RubberbandHandleProxy::RubberbandHandleProxy( Esri::ArcGISRuntime::MapGraphicsView * view, QGraphicsScene * scene, QWidget * widget ) + : RubberbandHandle(view, scene) + , _coordinate() + , _sceneOffset(0, 0) + , _rotateSceneOffset(true) + , _restoreView(view) + { + init( widget ); + } /* RubberbandHandleProxy::RubberbandHandleProxy */ + + + RubberbandHandleProxy::~RubberbandHandleProxy() + { + if (_scene) + { + if (_widgetsProxy) + { + _widgetsProxy->setWidget(nullptr); + _scene->removeItem(_widgetsProxy); + delete _widgetsProxy; + } + } + _widgetsProxy = nullptr; + + if (_restoreView && _restoreView->viewport()) + _restoreView->viewport()->setCursor(_restoreCursor); // workaround Qt bug. remove when fixed + + } /* RubberbandHandleProxy::~RubberbandHandleProxy */ + + + QPointF RubberbandHandleProxy::getRotatedSceneOffset() const + { + if ((_mapView) && (_rotateSceneOffset)) + { + auto rotate = [](const QPointF & p, qreal sinA, qreal cosA) -> QPointF { return QPointF(p.x()*cosA+p.y()*sinA,p.y()*cosA-p.x()*sinA); }; + + qreal mapRotatePhi = _mapView->mapRotation() * MathConst::pi / 180.0; + + qreal sinMap = sin(mapRotatePhi); + qreal cosMap = cos(mapRotatePhi); + + return rotate( _sceneOffset, sinMap, cosMap ); + } + else return _sceneOffset; + } /* RubberbandHandleProxy::getRotatedSceneOffset */ + + + void RubberbandHandleProxy::setPos(const QPointF & pos) + { + _coordinate.updateSceneCoordinate( _mapView, pos ); + if (_widgetsProxy) + { + _widgetsProxy -> setPos(pos+getRotatedSceneOffset()); + _widgetsProxy -> setZValue( _zBufferValue ); + } + } /* RubberbandHandleProxy::setPos */ + + + bool RubberbandHandleProxy::isPositionInside( const QPointF & scenePosition ) const + { + const QRectF & bbox = _widgetsProxy->windowFrameGeometry(); + return bbox.contains(scenePosition); + } /* RubberbandHandleProxy::isPositionInside */ + + + void RubberbandHandleProxy::adjustCoordinates() + { + const QPointF & scenePosition = _coordinate.mapToScene(_mapView); + if (_widgetsProxy) + { + _widgetsProxy -> setZValue( _zBufferValue ); + _widgetsProxy -> setPos(scenePosition+getRotatedSceneOffset()); + } + emit adjusted(); + } /* RubberbandHandleProxy::adjustCoordinates */ + + + void RubberbandHandleProxy::adjustWindow( const QRectF & ) + { + onDo_adjustCoordinates(); + } /* RubberbandHandleProxy::adjustWindow */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.h new file mode 100755 index 000000000..c7a8ae069 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_handle_proxy.h @@ -0,0 +1,104 @@ +/** +** +** @RDE_HEADER@ +** Definition of class RubberbandHandleProxy to forward adjust signals from View +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 10.07.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_handle.h" + +#include + +class QGraphicsView; +class QGraphicsScene; + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandHandleProxy rubberband_handle_proxy.h + @brief Implementation of a procx to handle small menues and other widgets like buttons + @see RubberbandHandle + */ + class RubberbandHandleProxy : public RubberbandHandle + { + Q_OBJECT + + public: + + /** + @name Construction & Destruction + @{ + */ + RubberbandHandleProxy( QGraphicsView *view, QWidget * widget ); + + RubberbandHandleProxy( Esri::ArcGISRuntime::MapGraphicsView * view, QWidget * widget ); + RubberbandHandleProxy( Esri::ArcGISRuntime::MapGraphicsView * view, QGraphicsScene * scene, QWidget * widget ); + + ~RubberbandHandleProxy(); + /** @} */ + + bool isPositionInside( const QPointF & scenePosition ) const override; + + void setPos(const QPointF & pos); + + const RubberbandCoordinate & getCoordinate() const { return _coordinate; } + + inline void setSceneOffset(const QPointF & offset) { _sceneOffset = offset; } + + inline void show() { setVisibility(false); if (_widgetsProxy) { _widgetsProxy->show(); setVisibility(true); } } + inline void hide() { setVisibility(false); if (_widgetsProxy) { _widgetsProxy->hide(); setVisibility(false); } } + + inline void setRotateSceneOffset(bool on = true) { _rotateSceneOffset = on; } + + inline void setRestoreCursor( const QCursor & cursor ) { _restoreCursor = cursor; } // workaround Qt bug. remove when fixed + + protected: + + RubberbandCoordinate _coordinate; + QGraphicsProxyWidget * _widgetsProxy = nullptr; + QPointF _sceneOffset; + bool _rotateSceneOffset; + + QPointer _restoreView; + QCursor _restoreCursor; // workaround Qt bug. remove when fixed + + void updateGraphic() override {} + + void adjustCoordinates() override; + void adjustWindow( const QRectF& ) override; + + private: + + QPointF getRotatedSceneOffset() const; + + private: + + void init( QWidget * widget ); + + }; /* RubberbandHandleProxy */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.cpp new file mode 100755 index 000000000..36e486482 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.cpp @@ -0,0 +1,98 @@ +/** +** +** @RDE_HEADER@ +** Definition of internal data of base class for rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Joerg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 16.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_internal.h" +#include "rubberband.h" + +#include "rubberband_drawable.h" +#include "rubberband_handle.h" +#include "rubberband_handle_proxy.h" + +#include +#include + + + + +namespace SituationMap { namespace RB +{ + + Rubberband::InternalData::InternalData( SituationMap::Map * map ) + : _mapView(nullptr) + , _mapScene(nullptr) + , _handleList() + , _constructionObjectDraw(nullptr) + , _connectedPreviewPixmap(nullptr) + , _lastClickPosition(-1,-1) + , _constrainFailedMarker(nullptr) + , _logger(getLogger("Rubberband")) + { + SituationMap::MapImpl * mapImpl = dynamic_cast(map); + + if (mapImpl) + { + _mapView = mapImpl -> getEsriMapGraphicsView(); + + if (_mapView != nullptr) + { + _mapScene = _mapView -> scene(); + } + } + } /* InternalData */ + + Rubberband::InternalData::~InternalData() + { + /* Because of using shared RefPointer we don't need to call delete for every handle, + * just clear the handle-list. + for (auto handle : _handleList) + { + handle = nullptr;; + } + */ + _handleList.clear(); + + delete _constructionObjectDraw; + + _constructionObjectDraw = nullptr; + + if (_mapScene) + { + if (nullptr != _connectedPreviewPixmap) + { + _mapScene->removeItem( _connectedPreviewPixmap ); + delete _connectedPreviewPixmap; + _connectedPreviewPixmap = nullptr; + } + + delete _constrainFailedMarker; + } + _mapView = nullptr; + _mapScene = nullptr; + } /* ~InternalData */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.h new file mode 100755 index 000000000..aab73ea54 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_internal.h @@ -0,0 +1,98 @@ +/** +** +** @RDE_HEADER@ +** Definition of internal data of base class for rubberbands +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Joerg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 16.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + + +#include +#include + +#include +#include + +#include "rubberband.h" + +#include "rubberband_handle.h" +#include "rubberband_drawable.h" + + +class QHBoxLayout; +class QWidget; +class QPushButton; +class QGraphicsScene; + + +namespace Esri { +namespace ArcGISRuntime { + class MapGraphicsView; +} } + + +namespace SituationMap +{ + class Map; + class CoordTranslationFunctor; +} + +namespace SituationMap { namespace RB +{ + + class RubberbandHandleProxy; + + /** + @class Rubberband::InternalData rubberband_internal.h + @brief Definition of internal data of rubberband base class. + */ + struct Rubberband::InternalData + { + Q_DISABLE_COPY(InternalData) + + QPointer _mapView; + QPointer _mapScene; + + QList> _handleList; + RubberbandDrawable * _constructionObjectDraw; + QGraphicsPixmapItem * _connectedPreviewPixmap; + + + + QPointF _lastClickPosition; + + QGraphicsPixmapItem * _constrainFailedMarker; + + LOGGER_T _logger; + + explicit InternalData( SituationMap::Map * map ); + + ~InternalData(); + + void markFailedConstrains( QPoint pos, bool on ); + }; /* InternalData */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.cpp new file mode 100755 index 000000000..8b7f1539b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.cpp @@ -0,0 +1,231 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_line_handle.h" + +#include + +#include +#include + +#include + +namespace SituationMap { namespace RB +{ + + RubberbandLineHandle::RubberbandLineHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView + , QGraphicsScene * scene + , const QPointF & p1 + , const QPointF & p2 + , unsigned int lineWidth + , bool dottedLine ) + : RubberbandHandle(mapView,scene) + , _lineSymbol(nullptr) + , _lineStyle() + , _coordinates() + , _dottedLine(dottedLine) + { + _lineWidth = lineWidth; + + zBufferPlane( ZBufferPlane::LineHandle ); + _lineStyle.setWidth(_lineWidth); + + if (_scene != nullptr) + { + QGraphicsLineItem * line = new QGraphicsLineItem(); + + _lineSymbol = line; + + _coordinates.resize(2); + _coordinates[0].updateSceneCoordinate(mapView,p1); + _coordinates[1].updateSceneCoordinate(mapView,p2); + + if (_lineSymbol != nullptr) + { + _scene -> addItem( _lineSymbol ); + line->setLine( _coordinates[0]._sceneCoordinate.x(), _coordinates[0]._sceneCoordinate.y() + , _coordinates[1]._sceneCoordinate.x(), _coordinates[1]._sceneCoordinate.y() ); + + line -> setZValue(10); + line -> setFlags( QGraphicsItem::ItemIgnoresTransformations ); + + makeLineStyle( _dottedLine, _lineStyle ); + + line -> setPen( _lineStyle ); + } + } + } /* RubberbandLineHandle::RubberbandLineHandle */ + + + RubberbandLineHandle::~RubberbandLineHandle() + { + if (_scene) + { + if (_lineSymbol) + { + _scene->removeItem(_lineSymbol); + delete _lineSymbol; + } + } + _coordinates.clear(); + + } /* RubberbandHandle::~Rubberbandhandle */ + + void RubberbandLineHandle::adjustCoordinates() + { + if (_coordinates.size() >= 2) + { + _coordinates[0].mapToScene( _mapView ); + _coordinates[1].mapToScene( _mapView ); + + static_cast(_lineSymbol)->setLine( _coordinates[0]._sceneCoordinate.x(), _coordinates[0]._sceneCoordinate.y() + , _coordinates[1]._sceneCoordinate.x(), _coordinates[1]._sceneCoordinate.y() ); + } + + DEBUG_LOG(_logger) << LOGFUNCTION << " map-coordinate = " << QString::number(_coordinates[0]._mapCoordinate.x(),'f',15) + << " ; " << QString::number(_coordinates[0]._mapCoordinate.y(),'f',15) + << " scene-coordinate = " << _coordinates[1]._sceneCoordinate.x() << " ; " << _coordinates[1]._sceneCoordinate.y(); + emit adjusted(); + } /* RubberbandLineHandle::adjustCoordinates */ + + + void RubberbandLineHandle::adjustWindow( const QRectF& ) + { + onDo_adjustCoordinates(); + } /* RubberbandLineHandle::adjustWindow */ + + + void RubberbandLineHandle::updateGraphic() + { + if (_lineSymbol) + { + QGraphicsLineItem * line = static_cast( _lineSymbol); + + makeLineStyle( _dottedLine, _lineStyle ); + + if (_isSelected) + { + _lineStyle.setWidth( _lineStyle.width() + 1 ); + _lineStyle.setColor( _selectionColor ); + } + line -> setPen( _lineStyle ); + line -> setVisible( _isVisible ); + } + } /* RubberbandLineHandle::updateGraphic */ + + + void RubberbandLineHandle::updateCoordinates(const QVector & scenePositionVector) + { + if (scenePositionVector.size() == 2) + { + if (_coordinates.isEmpty()) _coordinates.resize(2); + _coordinates[0].updateSceneCoordinate(_mapView,scenePositionVector[0]); + _coordinates[1].updateSceneCoordinate(_mapView,scenePositionVector[1]); + + if (_lineSymbol != nullptr) + { + QGraphicsLineItem * line = static_cast( _lineSymbol); + line->setLine( _coordinates[0]._sceneCoordinate.x(), _coordinates[0]._sceneCoordinate.y() + , _coordinates[1]._sceneCoordinate.x(), _coordinates[1]._sceneCoordinate.y() ); + } + } + } /* RubberbandLineHandle::updateCoordinates */ + + + void RubberbandLineHandle::updateCoordinate( unsigned index, const QPointF & scenePosition ) + { + if (index < 2) + { + _coordinates[index].updateSceneCoordinate(_mapView,scenePosition); + QGraphicsLineItem * line = static_cast( _lineSymbol); + line->setLine( _coordinates[0]._sceneCoordinate.x(), _coordinates[0]._sceneCoordinate.y() + , _coordinates[1]._sceneCoordinate.x(), _coordinates[1]._sceneCoordinate.y() ); + } + } /* RubberbandLineHandle::updateCoordinate */ + + + QVector RubberbandLineHandle::getCoordinates() const + { + return _coordinates; + } /* RubberbandLineHandle::getCoordinates */ + + + RubberbandHandle::RubberbandCoordinate RubberbandLineHandle::getCoordinate( unsigned index ) const + { + if (index < 2) + { + return _coordinates[index]; + } + return RubberbandCoordinate(); + } /* RubberbandLineHandle::getCoordinate */ + + + bool RubberbandLineHandle::isPositionInside( const QPointF & scenePosition ) const + { + if (_lineSymbol) + { + if (_lineSymbol->contains( scenePosition )) + { + return true; + } + else + { + // Ok, Qt's methodes doesn't work safely + // if it fails do some own calculations + + QPointF V0 = _coordinates[0]._sceneCoordinate; + QPointF V1 = _coordinates[1]._sceneCoordinate; + QPointF R = V1-V0; + QPointF P = scenePosition-V0; + qreal lenR = R.x() * R.x() + R.y() * R.y(); + qreal lenP = P.x() * P.x() + P.y() * P.y(); + + if ((lenR == 0) || (lenP > lenR)) return false; // scene Position is to far away + + // now check the direction + + lenR = sqrt(lenR); + lenP = sqrt(lenP); + + R /= lenR; + P /= lenP; + + qreal cosPhi = QPointF::dotProduct(R,P); + + if (cosPhi < 0) return false; + + qreal epsilon = sqrt(1.0-cosPhi*cosPhi) * lenR; + + return (epsilon < qreal(_pickingRatio)); + } + } + return false; + } /* RubberbandLineHandle::isPositionInside */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.h new file mode 100755 index 000000000..d01f9e520 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_line_handle.h @@ -0,0 +1,81 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include + +#include "rubberband_handle.h" + +class QGraphicsItem; +class QGraphicsScene; + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandLineHandle rubberband_line_handle.h + @brief Implementation of a line handle for rubberband internal use. + @see RubberbandHandle + */ + class RubberbandLineHandle : public RubberbandHandle + { + Q_OBJECT + public: + + RubberbandLineHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView ,QGraphicsScene * scene , const QPointF & p1, const QPointF & p2, unsigned int lineWidth = 2, bool dottedLine = false ); + ~RubberbandLineHandle(); + + bool isPositionInside( const QPointF & scenePosition ) const override; + + virtual void updateCoordinates( const QVector & scenePositionVector ); + virtual void updateCoordinate( unsigned index, const QPointF & scenePosition ); + + virtual QVector getCoordinates() const; + virtual RubberbandCoordinate getCoordinate( unsigned index ) const; + + void dottedLine(bool on = true) { _dottedLine = on; } + + protected: + + QGraphicsItem * _lineSymbol; + QPen _lineStyle; + QVector _coordinates; + bool _dottedLine; + + void updateGraphic() override; + + void adjustCoordinates() override; + void adjustWindow( const QRectF& ) override; + + + }; /* RubberbandLineHandle */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.cpp new file mode 100755 index 000000000..805aab5d5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.cpp @@ -0,0 +1,1747 @@ +/** + * @RDE_HEADER@ + * Rubberband class to define multiple ellipse/circle + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "rubberband_multi_ellipse_feature.h" +#include "rubberband_frame_handle.h" +#include "rubberband_line_handle.h" +//#include "rubberband_ellipse.h" +#include "rubberband_ellipse_handle.h" +#include "rubberband_point_handle.h" +#include "rubberband_drawable.h" +#include "rubberband_handle_proxy.h" +#include "drawable_line.h" +#include "drawable_ellipse.h" +#include "drawable_painter_path.h" +#include "transient_tactical_symbol.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include "winkel_icon.xpm" + +//#include +#include +#include +#include + +#include + +#include + +namespace SituationMap +{ +namespace RB +{ + +struct RubberbandMultiEllipseFeature::InternalData +{ + Q_DISABLE_COPY(InternalData) + + QList> _ellipseHandleList; + SituationMap::RefPointer _frameHandle; + + // sector : + QPointF _sectorOStart{1, 0}; + QPointF _sectorOEnd{0, 1}; + SituationMap::RefPointer _sectorStartHandle; + SituationMap::RefPointer _sectorEndHandle; + bool _sectorEnabled = false; + bool _sectorFixedAngleEnabled{false}; + + QPointF _oVec{1,0}; // rb: // normalized orientation vector according to scene coordinate system + // : sector + + // rotationHandle : + SituationMap::RefPointer _rotationHandle; + unsigned int _rotationHandleSize{0}; + // : rotationHandle + + + // Coordinate matrix to describe the change of coordinates of the last user input + MathUtils::Matrix2D _lastCoordinateChange; + + + SituationMap::RefPointer _previewEllipse; + SituationMap::RefPointer _previewSymbol; + + QVector _preparedRadiiConstraints; + + bool _complexGeometry; + bool _addVertexAtEnd; + bool _showCursorPreviewEllipse; + bool _constrainQuadratic{true}; + + + InternalData(QGraphicsScene *scene, bool complexGeometry) + : _complexGeometry(complexGeometry) + , _addVertexAtEnd(false) + , _showCursorPreviewEllipse(false) + { + Q_UNUSED(scene); + } // InternalData +}; // RubberbandMultiEllipseFeature::InternalData + +QPointF rotateVertex(const QPointF ¢er, qreal x, qreal y, qreal sinA, qreal cosA) +{ + return QPointF(x*cosA-y*sinA,y*cosA+x*sinA) + center; +} + + +RubberbandMultiEllipseFeature::RubberbandMultiEllipseFeature(SituationMap::Map *map, QObject *parent) + : RubberbandMultiEllipseFeature(map, false, parent) +{ +} + + +RubberbandMultiEllipseFeature::RubberbandMultiEllipseFeature(SituationMap::Map *map, bool complexGeometry, QObject *parent) + : Rubberband(map, parent) + , _internal(new InternalData(Rubberband::_internal->_mapScene, complexGeometry)) + , _isClosed(false) + , _minimumPointCount(2) + , _maximumPointCount(0) + , _maximumSegmentLength(0.0) + , _maximumLength(0.0) +{ +} + + +RubberbandMultiEllipseFeature::~RubberbandMultiEllipseFeature() +{ + delete _internal; +} + +RubberbandFrameHandle *RubberbandMultiEllipseFeature::getFrameHandle() const +{ + return _internal->_frameHandle.get(); +} + +QPointF RubberbandMultiEllipseFeature::centerCoordinate(CoordinateSystem cs) const +{ + if (Rubberband::_internal->_handleList.isEmpty()) + return {}; + RubberbandPointHandle *handlePointCenter = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + + if (!handlePointCenter) + return {}; + + return projectCoordinateFromMap(cs, handlePointCenter->getCoordinate()._mapCoordinate); +} + +QVector RubberbandMultiEllipseFeature::radii() const +{ + QPointF center = centerCoordinate(CoordinateSystem::Map); + if (center.isNull()) + return {}; + if (_internal->_ellipseHandleList.isEmpty()) + return {}; + + QVector ret; + ret.reserve(_internal->_ellipseHandleList.size()); + + for (SituationMap::RefPointer handle : std::as_const(_internal->_ellipseHandleList)) + { + if (!handle) + continue; + ret.append(handle->getRadius()); + } + return ret; +} + +QPointF RubberbandMultiEllipseFeature::getBiggestRadius() const +{ + QPointF max(0,0); + const QVector &radiiCont = radii(); + + for (const QPointF &item : std::as_const(radiiCont)) + { + max.setX( std::max(max.x(), item.x()) ); + max.setY( std::max(max.y(), item.y()) ); + } + return max; +} + +bool RubberbandMultiEllipseFeature::setCenterAndRadii(const QPointF ¢er, CoordinateSystem cs, + const QVector &radii) +{ + if (center.isNull() || radii.isEmpty()) + return false; + + const QPointF &myCenter = projectCoordinateToMap(cs, center); + + QVector coordinateVector; + coordinateVector.reserve(radii.size()+1); + + // Center: + coordinateVector.append(myCenter); + + // radii: + for (const QPointF& radius : std::as_const(radii)) + coordinateVector.append(myCenter + radius); + + setCoordinateVector(coordinateVector, CoordinateSystem::Map); + + return true; +} + +bool RubberbandMultiEllipseFeature::setCenterAndRadii2(const QPointF ¢er, CoordinateSystem cs, + const QVector &radiiWithConstraints) +{ + if (center.isNull() || radiiWithConstraints.isEmpty()) + return false; + + const QPointF &myCenter = projectCoordinateToMap(cs, center); + + QVector coordinateVector; + coordinateVector.reserve(radiiWithConstraints.size()+1); + + // Center: + coordinateVector.append(myCenter); + + // Center with the radii: + for (const RubberbandMultiEllipseFeature::RadiusWithContraints& radiusItem : std::as_const(radiiWithConstraints)) + if (radiusItem.m_radius > 0) + coordinateVector.append(myCenter + QPointF(radiusItem.m_radius, radiusItem.m_radius)); + + + Rubberband::_internal->_handleList.clear(); + for (const auto &p : coordinateVector) + constructPoint(p, CoordinateSystem::Map); + + // are we still in construction or in modification? + // from scratch: constraints are provided, but no radius + // predefined: radius is provided, maybe constraints + if (coordinateVector.size() > 1) + enableModificationState(); + update(); + activateButtons(); + + return true; +} + + +void RubberbandMultiEllipseFeature::prepareFixedNumberOfRadiiWithConstraints(const QVector &radiiContraints) +{ + // for creation from scratch, just min/max should be prepared. + // for creation with predefined values, the m_radius is relevant. + _internal->_preparedRadiiConstraints = radiiContraints; +} + + +QPointF RubberbandMultiEllipseFeature::getRotatedPoint(const QPointF §or) const +{ + // Rotation of entire Rubberband + qreal sinRect = 0, cosRect = 1; + getSceneRotation(sinRect, cosRect); + const double angleRad = SituationMap::MathUtils::getMathAngle(QPointF(cosRect, sinRect)); // angle rad + const double clockwiseAngleRad = ((2*LtsCoordTrans::pi()) - angleRad); // angle in rad; + + // map clockwise rotation + QPointF point; + point.setX( sector.x()*std::cos(clockwiseAngleRad) + sector.y()*std::sin(clockwiseAngleRad)); + point.setY( -sector.x()*std::sin(clockwiseAngleRad) + sector.y()*std::cos(clockwiseAngleRad)); + + return point; +} + +void RubberbandMultiEllipseFeature::enableSector(bool on) +{ + _internal->_sectorEnabled = on; +} +bool RubberbandMultiEllipseFeature::isSectorEnabled() const +{ + return _internal->_sectorEnabled; +} + +QPointF RubberbandMultiEllipseFeature::sectorStart() const +{ + return getRotatedPoint(_internal->_sectorOStart); +} + +qreal RubberbandMultiEllipseFeature::sectorStartDegMath() const +{ + const QPointF &point = sectorStart(); + return MathUtils::getMathAngle(point) * LtsCoordTrans::rad2deg(); +} + +QPointF RubberbandMultiEllipseFeature::sectorEnd() const +{ + return getRotatedPoint(_internal->_sectorOEnd); +} + +qreal RubberbandMultiEllipseFeature::sectorEndDegMath() const +{ + const QPointF &point = sectorEnd(); + return MathUtils::getMathAngle(point) * LtsCoordTrans::rad2deg(); +} + +void RubberbandMultiEllipseFeature::setSectorStart(qreal sinPhi, qreal cosPhi) +{ + _internal->_sectorOStart = QPointF(cosPhi, sinPhi); + enableSelectedState(); + update(); +} + +void RubberbandMultiEllipseFeature::setSectorEnd(qreal sinPhi, qreal cosPhi) +{ + _internal->_sectorOEnd = QPointF(cosPhi, sinPhi); + enableSelectedState(); + update(); +} + +void RubberbandMultiEllipseFeature::setSectorAngleFixed(bool on) +{ + _internal->_sectorFixedAngleEnabled = on; + update(); +} + +bool RubberbandMultiEllipseFeature::isSectorFixedAngeEnabled() const +{ + return _internal->_sectorFixedAngleEnabled; +} + +QVector RubberbandMultiEllipseFeature::coordinateVector(CoordinateSystem cs) const +{ + QVector coordinateVector; + coordinateVector.reserve(Rubberband::_internal->_handleList.size()); + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + + coordinateVector.push_back(projectCoordinateFromMap(cs, handlePointPtr->getCoordinate()._mapCoordinate)); + } + return coordinateVector; +} + +QPointF RubberbandMultiEllipseFeature::singleCoordinate(unsigned int idx, CoordinateSystem cs ) const +{ + if (idx < (unsigned int)Rubberband::_internal->_handleList.size()) + { + const RubberbandPointHandle * handle = dynamic_cast(Rubberband::_internal->_handleList[idx].get()); + + return projectCoordinateFromMap( cs, handle->getCoordinate()._mapCoordinate ); + } + return QPointF(); +} + +void RubberbandMultiEllipseFeature::setCoordinateVector(const QVector &coordinateVector, CoordinateSystem cs) +{ + Rubberband::_internal->_handleList.clear(); + + for (const auto &p : coordinateVector) + constructPoint(p, cs); + enableModificationState(); + update(); + activateButtons(); +} + +bool RubberbandMultiEllipseFeature::getRadiusXY(const QPointF &scenePos, qreal &radiusX_out, qreal &radiusY_out) +{ + if (Rubberband::_internal->_handleList.isEmpty()) + return false; + + RubberbandPointHandle *handlePointCenter = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if (!handlePointCenter) + return false; + + const RubberbandHandle::RubberbandCoordinate &mapCenter = handlePointCenter->getCoordinate(); + RubberbandHandle::RubberbandCoordinate currentMapPos(Rubberband::_internal->_mapView, scenePos); + + radiusX_out = fabs(mapCenter._mapCoordinate.x() - currentMapPos._mapCoordinate.x()); + radiusY_out = fabs(mapCenter._mapCoordinate.y() - currentMapPos._mapCoordinate.y()); + return true; +} + +void RubberbandMultiEllipseFeature::constructPoint(const QPointF &point, CoordinateSystem cs) +{ + const QPointF &mapPos = projectCoordinateToMap(cs, point); + + RubberbandHandle::RubberbandCoordinate coordinate(Rubberband::_internal->_mapView, convert(mapPos, Rubberband::_internal->_mapView->map()->spatialReference())); + RubberbandHandle *handlePtr = constructPoint(coordinate._sceneCoordinate); + if (handlePtr) + { + static_cast(handlePtr)->setCoordinate(coordinate); + handlePtr->setSelection(false); + } +} + +RubberbandHandle *RubberbandMultiEllipseFeature::constructPoint(const QPointF &scenePos) +{ + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION + << " RubberbandMultiEllipseFeature::constructPoint, _handleList.size " << Rubberband::_internal->_handleList.size(); + RubberbandHandle *handlePtr{nullptr}; + + if ((_maximumPointCount>0) && ((unsigned int)Rubberband::_internal->_handleList.size() >= _maximumPointCount)) + { + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " skip due to _maximumPointCount (_sectorInputIsRunning)"; + return nullptr; + } + + + if (!Rubberband::_internal->_handleList.empty()) + { + handlePtr = Rubberband::_internal->_handleList.first().get(); + if (handlePtr) + { + // center == first + const RubberbandHandle::RubberbandCoordinate &firstPosCenterPos = static_cast(handlePtr)->getCoordinate(); + + qreal radiusX_out, radiusY_out; + if (getRadiusXY(scenePos, radiusX_out, radiusY_out)) + { + bool minMaxOk = checkMinMaxRadiusConstraint(radiusX_out, radiusY_out); + if (minMaxOk) + { + RubberbandEllipseHandle *temp = new RubberbandEllipseHandle(Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene); + + if (_internal->_constrainQuadratic) + radiusX_out = radiusY_out = std::max(radiusX_out, radiusY_out); + + temp->setRect(firstPosCenterPos, radiusX_out, radiusY_out, 0, 1); + + QColor fillColor = _fillColor; + fillColor.setAlphaF(_fillColor.alphaF()*0.5); + temp->setFillColor(fillColor); + + QColor selectionColor = _handleSelectionColor; + selectionColor.setAlphaF(_handleSelectionColor.alphaF()*0.5); + temp->setSelectionColor(selectionColor); + + _internal->_ellipseHandleList.append(RefPointer(static_cast(temp))); + } else { + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION + << " SKIP RubberbandMultiEllipseFeature::constructPoint, " + << ", radiusX_out" << radiusX_out << ", radiusY_out" << radiusY_out; + return nullptr; + } + } + } + } + + // + // RubberbandPointHandle einfügen + // + handlePtr = new RubberbandPointHandle(Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, scenePos); + handlePtr->setSelection(); + handlePtr->setIsFirst(Rubberband::_internal->_handleList.empty()); // mark centerPoint with 'isFirst' to simplify later recognation + Rubberband::_internal->_handleList.append(SituationMap::RefPointer(handlePtr)); + + // activateButtons? + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + activateButtons(); + + if ((!_maximumPointCount) || ((unsigned int)Rubberband::_internal->_handleList.size() < _maximumPointCount)) + { + if (!_internal->_previewEllipse) + _internal->_previewEllipse = new DrawableEllipse(Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene); + _internal->_previewEllipse->setVisibility(true); + } + else + { + _internal->_previewEllipse = nullptr; + } + return handlePtr; +} + +bool RubberbandMultiEllipseFeature::checkLengthConstrains(const QPointF &scenePos, const QPointF &deltaScenePos) +{ + Q_UNUSED(scenePos); + Q_UNUSED(deltaScenePos); + return true; + +/* + if (_internal->_complexGeometry) + { + markFailedConstrains(QPointF(), false); + return true; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Checking constrains for moving selected coordinates (handles) + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + bool allHandlesSelected = true; + QVector coordinateVector; + coordinateVector.reserve(Rubberband::_internal->_handleList.size()); + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + if (handlePointPtr) + { + coordinateVector.append(handlePointPtr->getCoordinate()); + if (handlePointPtr->isSelected()) + { + RubberbandHandle::RubberbandCoordinate &coordinate = coordinateVector.back(); + coordinate.updateSceneCoordinate(Rubberband::_internal->_mapView, coordinate._sceneCoordinate + deltaScenePos); + } + else allHandlesSelected = false; + } + } + + if ((allHandlesSelected) || ((_internal->_frameHandle) && (_internal->_frameHandle->isSelected()))) + { + markFailedConstrains(QPointF(), false); + return true; + } + + // + // check totalLength + // + if (_maximumLength > 0.0) + { + qreal totalLength = 0.0; + const Esri::ArcGISRuntime::Point *lastMapCoord = nullptr; // Check Quartz + for (int i = 0; i < coordinateVector.size(); ++i) + { + const RubberbandHandle::RubberbandCoordinate &coordinate = coordinateVector[i]; + if (lastMapCoord) + { + qreal x = coordinate._mapCoordinate.x() - lastMapCoord->x(); + qreal y = coordinate._mapCoordinate.y() - lastMapCoord->y(); + + totalLength += sqrt(x * x + y * y); + } + lastMapCoord = &coordinateVector.at(i)._mapCoordinate; + } + if (totalLength > _maximumLength) + { + markFailedConstrains(scenePos, true); + return false; + } + } + + // + // check maximumSegmentLength + // + const RubberbandHandle::RubberbandCoordinate *mapPointPtr = nullptr; + bool lastSelected = false; + auto handleIter = Rubberband::_internal->_handleList.constBegin(); + + for (int i = 0; i < coordinateVector.size(); ++i) + { + const RubberbandHandle::RubberbandCoordinate &coordinate = coordinateVector[i]; + bool isSelected = (*handleIter)->isSelected(); + + if ((isSelected || lastSelected) && (mapPointPtr)) + { + if (_maximumSegmentLength > 0.0) + { + qreal x = coordinate._mapCoordinate.x() - mapPointPtr->_mapCoordinate.x(); + qreal y = coordinate._mapCoordinate.y() - mapPointPtr->_mapCoordinate.y(); + + if (sqrt(x * x + y * y) > _maximumSegmentLength) + { + markFailedConstrains(scenePos, true); + return false; + } + } + // looking for identical coordinate with last point + qreal x = coordinate._sceneCoordinate.x() - mapPointPtr->_sceneCoordinate.x(); + qreal y = coordinate._sceneCoordinate.y() - mapPointPtr->_sceneCoordinate.y(); + + if ((x * x + y * y) <= 1) + { + markFailedConstrains(scenePos, true); + return false; + } + } + ++handleIter; + mapPointPtr = &coordinateVector.at(i); + lastSelected = isSelected; + } // for + + markFailedConstrains(QPointF(), false); + return true; +*/ +} + +bool RubberbandMultiEllipseFeature::checkMinMaxRadiusConstraint(const QPointF &curScenePos) +{ + qreal radiusX_out, radiusY_out; + if (!getRadiusXY(curScenePos, radiusX_out, radiusY_out)) + return true; + + if (_internal->_constrainQuadratic) + radiusX_out = radiusY_out = std::max(radiusX_out, radiusY_out); + + return checkMinMaxRadiusConstraint(radiusX_out, radiusY_out); +} + +bool RubberbandMultiEllipseFeature::checkMinMaxRadiusConstraint(qreal radiusX, qreal radiusY, int idxOfConstraint) +{ + // Which radius is relevant? + if (idxOfConstraint < 0) + idxOfConstraint = Rubberband::_internal->_handleList.size() - 1; // '-1' - keep handle for center (isFirst) in mind + + if ((idxOfConstraint < 0) || (idxOfConstraint >= _internal->_preparedRadiiConstraints.size())) + return true; + + const RadiusWithContraints &curConstraints = _internal->_preparedRadiiConstraints[idxOfConstraint]; + +// qCritical() << " radiusX = " << radiusX; +// qCritical() << " radiusY = " << radiusY; + + bool bMinOk = true; + double minRadius = curConstraints.m_minRadius; + if (minRadius > 0) + bMinOk = ((radiusX >= minRadius) && (radiusY >= minRadius)); + + bool bMaxOk = true; + double maxRadius = curConstraints.m_maxRadius; + if (maxRadius > 0) + bMaxOk = ((radiusX <= maxRadius) && (radiusY <= maxRadius)); + + if (!bMinOk) + INFO_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " checkMinMaxRadiusConstraintNew false by bMinOk" + << ", idxOfConstraint=" << idxOfConstraint + << ", radiusX=" << radiusX << ", radiusY=" << radiusY + << ", minRadius=" << minRadius << ", maxRadius=" << maxRadius; + if (!bMaxOk) + INFO_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " checkMinMaxRadiusConstraintNew false by bMaxOk" + << ", idxOfConstraint=" << idxOfConstraint + << ", radiusX=" << radiusX << ", radiusY=" << radiusY + << ", minRadius=" << minRadius << ", maxRadius=" << maxRadius; + return bMinOk && bMaxOk; +} + +bool RubberbandMultiEllipseFeature::checkLengthConstrains(unsigned int checkHandleIdx, const QPointF &scenePos) +{ + Q_UNUSED(checkHandleIdx); + + if (!checkMinMaxRadiusConstraint(scenePos)) + { + markFailedConstrains(scenePos, true); + return false; + } + markFailedConstrains(QPoint(), true); + + return true; + + // return true -> construct point + // return false -> don not construct point + +/* + if (_internal->_complexGeometry) + { + markFailedConstrains(QPointF(), false); + return true; + } + + bool markFailedConstrainsFlag = false; + + // calculate pixel size for later use + RubberbandDrawable::RubberbandCoordinate pi1(Rubberband::_internal->_mapView, scenePos); + RubberbandDrawable::RubberbandCoordinate pi2(Rubberband::_internal->_mapView, scenePos + QPointF(1, 1)); + + qreal piX = pi2._mapCoordinate.x() - pi1._mapCoordinate.x(); + qreal piY = pi2._mapCoordinate.y() - pi1._mapCoordinate.y(); + qreal pixelSize = sqrt(piX * piX + piY * piY); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Checking constrains for adding single coordinate at end of polyline + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + RubberbandHandle::RubberbandCoordinate mapPos(Rubberband::_internal->_mapView, scenePos); + + if ((!Rubberband::_internal->_handleList.empty()) && (_maximumLength > 0.0)) + { + qreal totalLength = 0.0; + RubberbandHandle::RubberbandCoordinate mapPoint; + + QList>::iterator it = Rubberband::_internal->_handleList.begin(); + + for (; it != Rubberband::_internal->_handleList.end(); ++it) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(it->get()); + if (handlePointPtr) + { + if (it != Rubberband::_internal->_handleList.begin()) + { + qreal x = handlePointPtr->getCoordinate()._mapCoordinate.x() - mapPoint._mapCoordinate.x(); + qreal y = handlePointPtr->getCoordinate()._mapCoordinate.y() - mapPoint._mapCoordinate.y(); + + totalLength += sqrt(x * x + y * y); + } + mapPoint = handlePointPtr->getCoordinate(); + } + } + if (totalLength >= _maximumLength) + { + markFailedConstrains(scenePos, true); + return false; + } + + qreal x = mapPos._mapCoordinate.x() - mapPoint._mapCoordinate.x(); + qreal y = mapPos._mapCoordinate.y() - mapPoint._mapCoordinate.y(); + qreal l = sqrt(x * x + y * y); + + qreal d = _maximumLength - totalLength - l - pixelSize; + if (d < 0.0) + { + qreal scale = (1.0 + d / l); + const Esri::ArcGISRuntime::Point newMapCoordinate(mapPoint._mapCoordinate.x() + x * scale, + mapPoint._mapCoordinate.y() + y * scale, + mapPos._mapCoordinate.spatialReference()); + mapPos._mapCoordinate = newMapCoordinate; + markFailedConstrainsFlag = true; + scenePos = mapPos.mapToScene(Rubberband::_internal->_mapView); + } + } + + // check radius + if (!checkMinMaxRadiusConstraint(scenePos)) + { + markFailedConstrains(scenePos, true); + return false; + +// markFailedConstrainsFlag = true; +// return true; + } + + markFailedConstrains(scenePos, markFailedConstrainsFlag); + + return true; +*/ +} + +bool RubberbandMultiEllipseFeature::selectHandles(int x, int y, const QPointF &scenePos) +{ + bool eventHandled = false; + RubberbandHandle *handlePtr = pickHandle(x, y); + + _internal->_addVertexAtEnd = false; + + if (handlePtr && handlePtr->isVisible()) + { + handlePtr->setSelection(); + enableSelectedState(); + eventHandled = true; + } + + if (!isEnabledSelectedState()) + { + // ok doesn't hit any point or line handle now looking to frame handle + if (_internal->_frameHandle && _internal->_frameHandle->isPositionInside(scenePos)) + { + _internal->_frameHandle->setSelection(true); + enableSelectedState(); + eventHandled = true; + } + + if (_internal->_rotationHandle && _internal->_rotationHandle->isPositionInside(scenePos)) + { + _internal->_rotationHandle->setSelection(true); + enableSelectedState(); + eventHandled = true; + } + } + return eventHandled; +} + +void RubberbandMultiEllipseFeature::updateHandles(const QPointF &scenePos, const QPointF &deltaMove) +{ + Q_UNUSED(deltaMove); + bool constrainsComplied = true; + + if (Rubberband::_internal->_handleList.size() < 2) + return; + + if ((_internal->_rotationHandle.valid()) && _internal->_rotationHandle->isSelected()) + { + const RubberbandPointHandle *handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if (!handlePointPtr) + return; + const RubberbandHandle::RubberbandCoordinate &mapCenter = handlePointPtr->getCoordinate(); + + + // _internal->_centerScene --> centerScene + const QPointF ¢erScene = mapCenter._sceneCoordinate; + const Esri::ArcGISRuntime::Point ¢erMapCoords = mapCenter._mapCoordinate; + const QPointF centerMapCoordsAsPointF(centerMapCoords.x(), centerMapCoords.y()); + + qreal sinA = scenePos.x() - centerScene.x(); // _internal->_centerScene.x(); + qreal cosA = - scenePos.y() + centerScene.y(); + qreal len = sqrt(cosA*cosA+sinA*sinA); + + cosA /= len; + sinA /= len; + +// for circle it´s not really helpfull to rotate the handles -> skip here +// for (auto handle : std::as_const(Rubberband::_internal->_handleList)) +// { +// RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); +// if (!handlePointPtr) +// continue; + +// QPointF p = MathUtils::rotate( handlePointPtr->getCoordinate()._sceneCoordinate, centerScene, -_internal->_oVec.y(), _internal->_oVec.x() ); +// p = MathUtils::rotate( p, centerScene, sinA, cosA ); +// handlePointPtr->updateCoordinate( p ); + +// p.setX( handlePointPtr->getCoordinate()._mapCoordinate.x() ); +// p.setY( handlePointPtr->getCoordinate()._mapCoordinate.y() ); +// } // for + + qreal dSin = sinA*_internal->_oVec.x() - cosA*_internal->_oVec.y(); // addition theorem + qreal dCos = cosA*_internal->_oVec.x() + sinA*_internal->_oVec.y(); + + _internal->_lastCoordinateChange.setToIdentity(); // clear matrix + _internal->_lastCoordinateChange.putTranslation( -centerMapCoordsAsPointF ); // move center to (0,0) + _internal->_lastCoordinateChange.putRotate( QPointF(dCos, dSin) ); // do rotation + _internal->_lastCoordinateChange.putTranslation( centerMapCoordsAsPointF ); // move center back + + _internal->_oVec.setX(cosA); + _internal->_oVec.setY(sinA); + + _baseInternal->_lastClickPosition = scenePos; + } + + markFailedConstrains( scenePos, !constrainsComplied ); + + update(); +} + +void RubberbandMultiEllipseFeature::resetAction(AbstractEventDispatcher *causer, int x, int y) +{ + if (rubberbandIsAlive()) + { + if (isEnabledConstructionState()) + { + if (_internal->_addVertexAtEnd) + { + Rubberband::_internal->_handleList.removeLast(); + //_internal->_lineHandleList.removeLast(); + } + } + } + Rubberband::resetAction(causer, x, y); +} + +bool RubberbandMultiEllipseFeature::selectAction(AbstractEventDispatcher *, int x, int y) +{ + if (rubberbandIsAlive()) + { + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " selectAction"; + + QPointF scenePos(pixel2ScenePosition(x, y)); + + // starting a rubberband with radii prepared but without center? + if (!_internal->_preparedRadiiConstraints.isEmpty() && Rubberband::_internal->_handleList.isEmpty()) + { + RubberbandDrawable::RubberbandCoordinate rbC(Rubberband::_internal->_mapView, scenePos); + setCenterAndRadii2(QPointF(rbC._mapCoordinate.x(), rbC._mapCoordinate.y()), CoordinateSystem::Map, + _internal->_preparedRadiiConstraints); + enableSelectedState(); + + _internal->_addVertexAtEnd = true; + _internal->_showCursorPreviewEllipse = true; + + return true; + } + + + if (_baseInternal->buttonProxyActive(scenePos)) + return false; + + if (isEnabledConstructionState()) + disenableMultiSelectedState(); + + _baseInternal->_lastClickPosition = scenePos; + + bool eventHandled = selectHandles(x, y, scenePos); + if ((!eventHandled) && (isEnabledConstructionState())) + { + _internal->_addVertexAtEnd = true; + _internal->_showCursorPreviewEllipse = true; + + deSelectHandles(); + + if (checkLengthConstrains(1, scenePos)) + { + constructPoint(scenePos); + enableSelectedState(); + _baseInternal->_lastClickPosition = scenePos; + } + + eventHandled = true; + } + else + _internal->_addVertexAtEnd = false; + + if (eventHandled) + { + update(); + // deactivateButtons(); + } + + return eventHandled; + } + return false; +} + +bool RubberbandMultiEllipseFeature::setCoordinateAction(AbstractEventDispatcher *causer, int x, int y) +{ + Q_UNUSED(causer); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + QPointF scenePos(pixel2ScenePosition(x, y)); + Rubberband::_internal->_lastClickPosition = scenePos; + + if (Rubberband::_internal->_handleList.isEmpty()) + { + disenableSelectedState(); + disenableMultiSelectedState(); + return false; + } + } + return false; + + + // ----------------------------------- +/* + markFailedConstrains(QPointF(), false); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x, y)); + + Rubberband::_internal->_lastClickPosition = scenePos; + + if (_internal->_addVertexAtEnd) // isEnabledConstructionState()) + { + if (Rubberband::_internal->_handleList.isEmpty()) + { + disenableSelectedState(); + disenableMultiSelectedState(); + return false; + } + + if ((!isEnabledSelectedState()) && (_baseInternal->buttonProxyActive(scenePos))) + { + deSelectHandles(); + return false; + } + + if (checkLengthConstrains(2, scenePos)) + { + QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && ((handlePointPtr->isSelected()))) + { + handlePointPtr->setSelection(false); + handlePointPtr->moveCoordinate(deltaMove); + eventHandled = true; + } + } + } + markFailedConstrains(QPointF(), false); + + if ((_maximumPointCount > 0) && ((unsigned int)Rubberband::_internal->_handleList.size() >= _maximumPointCount)) + { + INFO_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " do Sector now..."; + + _internal->_showCursorPreviewEllipse = false; + // continue contructionState, so we are able to move the whole object by moving the first handle (center) + + // reset _lastClickPosition to improve ButtonPosition + _baseInternal->_lastClickPosition = QPointF(-1,-1); + + // remove former selection: + for (SituationMap::RefPointer handle : std::as_const(_internal->_ellipseHandleList)) + if (handle) + handle->setSelection(false); + deSelectHandles(); + + update(); + + // do not complete + // return false; + // completeStateAction(causer, x, y); + } + if (eventHandled) + update(); + if (_baseInternal->buttonProxyActive(scenePos)) + return false; + updateButtonPosition(); + activateButtons(); + } + else + { + if (isEnabledSelectedState()) + { + bool frameIsSelected(((_internal->_frameHandle != nullptr) && (_internal->_frameHandle->isSelected()))); + + QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; + + if (!checkLengthConstrains(scenePos, deltaMove)) + { + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + if (handlePointPtr) + handlePointPtr->setSelection(false); + } + if (_internal->_frameHandle) + _internal->_frameHandle->setSelection(false); + updateButtonPosition(); + activateButtons(); + disenableSelectedState(); + disenableMultiSelectedState(); + markFailedConstrains(QPointF(), false); + return true; + } + markFailedConstrains(QPointF(), false); + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && ((handlePointPtr->isSelected()) || (frameIsSelected))) + { + handlePointPtr->setSelection(false); + handlePointPtr->moveCoordinate(deltaMove); + eventHandled = true; + } + } + if (frameIsSelected) + { + _internal->_frameHandle->setSelection(false); + eventHandled = true; + } + + disenableSelectedState(); + disenableMultiSelectedState(); + } + + if (isEnabledConstructionState()) + { + _internal->_addVertexAtEnd = true; + _internal->_showCursorPreviewEllipse = true; + + updatePreviewEllipse(scenePos); + } + else if (_baseInternal->buttonProxyActive(scenePos)) + return false; + + if (eventHandled) + update(); + updateButtonPosition(); + activateButtons(); + + } + return eventHandled; + } + return false; +*/ +} + +void RubberbandMultiEllipseFeature::updatePreviewEllipse(const QPointF &scenePosition) +{ + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " START"; +// qCritical() << " RubberbandMultiEllipseFeature::updatePreviewEllipse" +// << ", scenePosition " << scenePosition +// << ", _addVertexAtEnd " << _internal->_addVertexAtEnd +// << ", isEnabledConstructionState " << isEnabledConstructionState() +// << " _internal->_handleList.size() " <_handleList.size() +// << " _ellipseHandleList.size() " <<_internal->_ellipseHandleList.size(); + + if (isEnabledConstructionState() && (!Rubberband::_internal->_handleList.isEmpty()) && (_internal->_previewEllipse)) + { + // center / first point + RubberbandPointHandle *handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if ((handlePointPtr) && (_internal->_addVertexAtEnd)) + { + RubberbandHandle::RubberbandCoordinate mapCenter = handlePointPtr->getCoordinate(); + RubberbandDrawable::RubberbandCoordinate rbCoordinate(Rubberband::_internal->_mapView, scenePosition); + + _internal->_previewEllipse->setVisibility(_internal->_showCursorPreviewEllipse); + _internal->_previewEllipse->setDrawColor(_handleSelectionColor); + + int distX = fabs(mapCenter._mapCoordinate.x() - rbCoordinate._mapCoordinate.x()); + int distY = fabs(mapCenter._mapCoordinate.y() - rbCoordinate._mapCoordinate.y()); + if (_internal->_constrainQuadratic) + distX = distY = std::max(distX, distY); + _internal->_previewEllipse->setRect(QPointF(mapCenter._mapCoordinate.x(), mapCenter._mapCoordinate.y()), + distX, distY, 0, 1); + + // sync FrameHandle: + updateFrameHandle(scenePosition); + } + else + { + _internal->_previewEllipse->setVisibility(false); + } + return; + } + else + { + _internal->_previewEllipse = nullptr; + } +} + +bool RubberbandMultiEllipseFeature::moveAction(AbstractEventDispatcher *, int x, int y) +{ + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " START"; + + adjustConnectedPreviewPixmap(x, y); + + if (rubberbandIsAlive()) + { + QPointF scenePosition(pixel2ScenePosition(x, y)); + + //------------------------------ + // sectorStartHand, _sectorEndHandle + { + if (_internal->_sectorStartHandle.valid()) + { + if (_internal->_sectorStartHandle->isPositionInside(scenePosition)) + { + _internal->_sectorStartHandle->setSelection(true); + _internal->_frameHandle = nullptr; + update(); + return false; + } + else + _internal->_sectorStartHandle->setSelection(false); + } + if (_internal->_sectorEndHandle.valid()) + { + if (_internal->_sectorEndHandle->isPositionInside(scenePosition)) + { + _internal->_sectorEndHandle->setSelection(true); + _internal->_frameHandle = nullptr; + update(); + return false; + } + else + _internal->_sectorEndHandle->setSelection(false); + } + } + //------------------------------ + //------------------------------ + //------------------------------ + + //------------------------------ + if (_internal->_rotationHandle.valid()) + _internal->_rotationHandle->setSelection(_internal->_rotationHandle->isPositionInside(scenePosition)); + + + if (_baseInternal->buttonProxyActive(scenePosition)) + return false; + if (!Rubberband::_internal->_mapView) + return false; + + if (!isEnabledMultiSelectedState()) + deSelectHandles(); + + RubberbandHandle *handlePtr = pickHandle(x, y); + if (!handlePtr) + { + _internal->_showCursorPreviewEllipse = isEnabledConstructionState(); + } + else + { + _internal->_showCursorPreviewEllipse = false; + + // set handlePtr selected... + handlePtr->setSelection(); + + // and updateFrameHandle if not first handle: + RubberbandPointHandle *handlePointPtr = dynamic_cast(handlePtr); + if (handlePointPtr) + { + if (handlePointPtr->isFirst()) + updateFrameHandle({}); + else + updateFrameHandle(handlePointPtr->getCoordinate()._sceneCoordinate); + } + } + + if (isEnabledConstructionState()) + { + if (checkLengthConstrains(1, scenePosition)) + { + updatePreviewEllipse(scenePosition); + + if (isSignalConnected(QMetaMethod::fromSignal(&RubberbandMultiEllipseFeature::currentCursorPositionChanged))) + { + RubberbandDrawable::RubberbandCoordinate mapCoord(Rubberband::_internal->_mapView, scenePosition); + emit currentCursorPositionChanged(Point3D(mapCoord._mapCoordinate.x(), mapCoord._mapCoordinate.y(), 0.0)); + } + } + } + return false; + } + return false; +} + +bool RubberbandMultiEllipseFeature::movePickedAction(AbstractEventDispatcher *, int x, int y) +{ + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " START" + << ", isEnabledSelectedState " << isEnabledSelectedState(); + + adjustConnectedPreviewPixmap(x, y); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + updateHandles(scenePos, scenePos-_baseInternal->_lastClickPosition); + + + if (isEnabledSelectedState()) + { + // sectorHandles: + if (!isSectorFixedAngeEnabled()) + { + auto updateHandle = [this](RubberbandPointHandle * handle, const RubberbandHandle::RubberbandCoordinate & pos, QPointF & oVector ) -> bool + { + if (!handle) + return false; + if (!handle->isSelected()) + return false; + + RubberbandPointHandle *handlePointCenter = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if (!handlePointCenter) + return false; + const RubberbandHandle::RubberbandCoordinate &mapCenter = handlePointCenter->getCoordinate(); + + qreal sinRect = 0; + qreal cosRect = 1; + getSceneRotation( sinRect, cosRect); + + qreal cosA = pos._mapCoordinate.x() - mapCenter._mapCoordinate.x(); + qreal sinA = pos._mapCoordinate.y() - mapCenter._mapCoordinate.y(); + qreal len = sqrt(cosA*cosA+sinA*sinA); + + cosA /= len; + sinA /= len; + + oVector.setX( cosA*cosRect + sinA*sinRect ); + oVector.setY( sinA*cosRect - cosA*sinRect ); + update(); + return true; + }; // updateHandle + + RubberbandHandle::RubberbandCoordinate pos(Rubberband::_internal->_mapView, scenePos); + updateHandle( _internal->_sectorStartHandle.get(), pos, _internal->_sectorOStart); + updateHandle( _internal->_sectorEndHandle.get() , pos, _internal->_sectorOEnd ); + } + // // return true; + //------------------------------ + //------------------------------ + //------------------------------ + + // look for (first) selected PointHandle: + RubberbandPointHandle *selectedPointHandlePtr{nullptr}; + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + if (!handlePointPtr) + continue; + if (handlePointPtr->isSelected()) + selectedPointHandlePtr = handlePointPtr; + } // for + const bool bFirstHandlePointSelected = (selectedPointHandlePtr && selectedPointHandlePtr->isFirst()); + + + RubberbandHandle::RubberbandCoordinate rcNewCorner; + + // nur wenn _constrainQuadratic UND nicht der 'isFirst'/centerPoint + if (_internal->_constrainQuadratic && !bFirstHandlePointSelected) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if (!handlePointPtr) + return false; + + const RubberbandHandle::RubberbandCoordinate &mapCenter = handlePointPtr->getCoordinate(); + + qreal radiusX, radiusY; + if (getRadiusXY(scenePos, radiusX, radiusY)) + radiusX = radiusY = std::max(radiusX, radiusY); + + + RefPointer refPtr(selectedPointHandlePtr); + int idx = Rubberband::_internal->_handleList.indexOf(refPtr); + if (idx > 0) + { + if (!checkMinMaxRadiusConstraint(radiusX, radiusY, idx-1)) + { + update(); // show/create preview!? + return false; + } + } + + // adjusted coord: (prefer topRight) + Esri::ArcGISRuntime::Point newCorner(mapCenter._mapCoordinate.x()+radiusX, + mapCenter._mapCoordinate.y()+radiusY, + mapCenter._mapCoordinate.spatialReference()); + + rcNewCorner = RubberbandHandle::RubberbandCoordinate(Rubberband::_internal->_mapView, newCorner); + } + + + QPointF deltaMove(scenePos - _baseInternal->_lastClickPosition); + if (checkLengthConstrains(scenePos, deltaMove)) + { + const bool frameIsSelected(((_internal->_frameHandle) && (_internal->_frameHandle->isSelected()))); + const bool moveAll = bFirstHandlePointSelected; + + _baseInternal->_lastClickPosition = scenePos; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && ((handlePointPtr->isSelected()) || (frameIsSelected||moveAll))) + { + if (rcNewCorner.isNull()) + handlePointPtr->moveCoordinate(deltaMove); + else + handlePointPtr->setCoordinate(rcNewCorner); + } + } + updatePreviewEllipse(scenePos); + update(); + } + return true; + } + + } + return false; +} + +bool RubberbandMultiEllipseFeature::deleteAction(AbstractEventDispatcher *, int x, int y) +{ + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (((unsigned int)Rubberband::_internal->_handleList.size() > _minimumPointCount) && (isEnabledSelectedState())) + { + QList> savedHandleList= Rubberband::_internal->_handleList; + + QPointF scenePosition = pixel2ScenePosition(x, y); + QList>::iterator handleIter = Rubberband::_internal->_handleList.begin(); + + while (handleIter != Rubberband::_internal->_handleList.end()) + { + if (handleIter->get()->isPositionInside(scenePosition)) + { + handleIter = Rubberband::_internal->_handleList.erase(handleIter); + updateFrameHandle({}); + continue; + } + ++handleIter; + } // while handleIter + + if (!checkLengthConstrains(scenePosition, QPointF(0, 0))) + { + // constrain failed, do not delete yet + Rubberband::_internal->_handleList = savedHandleList; + } + update(); + activateButtons(); + + disenableSelectedState(); + disenableMultiSelectedState(); + } + return true; + } + return false; +} + +bool RubberbandMultiEllipseFeature::contextMenuAction(AbstractEventDispatcher *, int, int) +{ + return false; +} + + +bool RubberbandMultiEllipseFeature::completeStateAction(AbstractEventDispatcher *causer, int x, int y) +{ + markFailedConstrains(QPointF(), false); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (isEnabledConstructionState()) + { + if (!_finishAfterConstruction) + { + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + { + enableModificationState(); + _internal->_previewEllipse = nullptr; // remove preview + } + update(); + return true; + } + } + + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + return applyAndEndAction(causer, x, y); + } + return false; +} + +bool RubberbandMultiEllipseFeature::undoAction(AbstractEventDispatcher * /*causer*/, int, int) +{ + if (rubberbandIsAlive()) + { + if (!Rubberband::_internal->_handleList.empty()) + { + if (isEnabledConstructionState()) + { + Rubberband::_internal->_handleList.pop_back(); + if (!_internal->_ellipseHandleList.empty()) + _internal->_ellipseHandleList.pop_back(); + + if (_internal->_previewEllipse) + _internal->_previewEllipse->setVisibility(false); + + update(); + activateButtons(); + } + } + return true; + } + return false; +} + +bool RubberbandMultiEllipseFeature::applyAndEndAction(AbstractEventDispatcher *causer, int x, int y) +{ + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + Rubberband::applyAndEndAction(causer, x, y); + else abortAction(causer, x, y); + return true; +} + +void RubberbandMultiEllipseFeature::constrainQuadratic(bool on) +{ + _internal->_constrainQuadratic = on; +} + +void RubberbandMultiEllipseFeature::activateButtons() +{ + _baseInternal->_hasUndoButton = isEnabledConstructionState(); + _baseInternal->_applyIsPossible = ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount); + _baseInternal->_undoIsPossible = (isEnabledConstructionState() && ((unsigned int)Rubberband::_internal->_handleList.size() > _minimumPointCount)); + + Rubberband::activateButtons(); +} + +void RubberbandMultiEllipseFeature::update() +{ + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " START"; + + Rubberband::update(); + + // update _ellipseHandleList according _handleList: (context modification) + + RubberbandHandle::RubberbandCoordinate centerCoord; + if (!Rubberband::_internal->_handleList.isEmpty()) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + centerCoord = handlePointPtr->getCoordinate(); + } + + // + // adjust EllipseHandles according size, selection, zOrder: + // + RubberbandPointHandle *selectedHandlePointPtrNotCenter{nullptr}; + int handleIdxMax = 0; + const int handleListSize = Rubberband::_internal->_handleList.size(); + for (int handleIdx=0; handleIdx < handleListSize; ++handleIdx) + { + if (handleIdx >= Rubberband::_internal->_handleList.size()) + continue; + + handleIdxMax = handleIdx; + + RubberbandPointHandle *pointHandle = dynamic_cast(Rubberband::_internal->_handleList[handleIdx].get()); + if (!pointHandle) + continue; + + if (pointHandle->isSelected() && handleIdx>0) + selectedHandlePointPtrNotCenter = pointHandle; + + if (handleIdx==0) + continue; + + // --- now we have finished the first iteration --- + + // 2 pointHandles are required for an elli: + SituationMap::RefPointer ellipseHandle = _internal->_ellipseHandleList[handleIdx-1]; + if (!ellipseHandle) + continue; + + const RubberbandHandle::RubberbandCoordinate &curCoord = pointHandle->getCoordinate(); + qreal distX = fabs(curCoord._mapCoordinate.x() - centerCoord._mapCoordinate.x()); + qreal distY = fabs(curCoord._mapCoordinate.y() - centerCoord._mapCoordinate.y()); + if (_internal->_constrainQuadratic) + distX = distY = std::max(distX, distY); + + if (isSectorEnabled()) + { + // _internal->_sectorOStart + _internal->_oVec + QPointF sectorStartRotated = getRotatedPoint(_internal->_sectorOStart); + QPointF sectorEndRotated = getRotatedPoint(_internal->_sectorOEnd); + + ellipseHandle->setRect(centerCoord, distX, distY, 0, 1, sectorStartRotated, sectorEndRotated); + } + else + ellipseHandle->setRect(centerCoord, distX, distY, 0, 1); + + // transfer selection: + ellipseHandle->setSelection(pointHandle->isSelected()); + + // update zOrder + ellipseHandle->setZValue(handleListSize-handleIdx); + + } // for + + // Maybe there has ist one RubberbandPointHandle deleted (deleteAction) + // remove superfluous ellipse handles + // like: 2 RubberbandPointHandle (handleIdxMax==1) -> 1 RubberbandEllipseHandle + while (_internal->_ellipseHandleList.size() > handleIdxMax) + _internal->_ellipseHandleList.removeLast(); + + if (selectedHandlePointPtrNotCenter) + updateFrameHandle(selectedHandlePointPtrNotCenter->getCoordinate()._sceneCoordinate); + + // transfer selection: + if (_internal->_frameHandle && selectedHandlePointPtrNotCenter) + _internal->_frameHandle->setSelection( selectedHandlePointPtrNotCenter->isSelected() ); + + updateSectorHandles(); + updateRotationHandle(); + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " END"; +} + + +void RubberbandMultiEllipseFeature::updateFrameHandle(const QPointF &sceneCoordinate) +{ + const RubberbandDrawable::RubberbandCoordinate vertex(Rubberband::_internal->_mapView, sceneCoordinate); + + if (//isEnabledModificationState() && + (!vertex.isNull()) && + !Rubberband::_internal->_handleList.isEmpty() + ) + { + if (!_internal->_frameHandle) + { + _internal->_frameHandle = SituationMap::RefPointer(new RubberbandFrameHandle(Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, _frameHandleMargin)); + _internal->_frameHandle->setSelectable(false); + + _internal->_frameHandle->setDrawColor(_frameColor); + _internal->_frameHandle->setFillColor(_frameFillColor); + _internal->_frameHandle->setSelectionColor(_frameSelectionColor); + _internal->_frameHandle->updateFrameSceneMargin(_frameHandleMargin); + _internal->_frameHandle->drawDisabled((_frameHandleMargin < 0)); + } + } else { + _internal->_frameHandle = nullptr; + } + + if (_internal->_frameHandle) + { + const RubberbandPointHandle *handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if (!handlePointPtr) + return; + + const RubberbandHandle::RubberbandCoordinate &mapCenter = handlePointPtr->getCoordinate(); + qreal radiusX, radiusY; + if (!getRadiusXY(sceneCoordinate, radiusX, radiusY)) + return; + + if (_internal->_constrainQuadratic) + radiusX = radiusY = std::max(radiusX, radiusY); + + RubberbandHandle::RubberbandCoordinate rcNewCorner1(Rubberband::_internal->_mapView, + Esri::ArcGISRuntime::Point(mapCenter._mapCoordinate.x()+(radiusX), + mapCenter._mapCoordinate.y()+(radiusY), + mapCenter._mapCoordinate.spatialReference())); + + RubberbandHandle::RubberbandCoordinate rcNewCorner2(Rubberband::_internal->_mapView, + Esri::ArcGISRuntime::Point(mapCenter._mapCoordinate.x()-(radiusX), + mapCenter._mapCoordinate.y()-(radiusY), + mapCenter._mapCoordinate.spatialReference())); + + _internal->_frameHandle->updateFrameShape({rcNewCorner1, rcNewCorner2}); + } +} + +void RubberbandMultiEllipseFeature::getSceneRotation(qreal &sinA, qreal &cosA) const +{ + sinA = -_internal->_oVec.y(); + cosA = _internal->_oVec.x(); +} + +Esri::ArcGISRuntime::Point addOffsetToMapCoordinate(const Esri::ArcGISRuntime::Point &mapCoordinate, + const QPointF &offset) +{ + return Esri::ArcGISRuntime::Point(mapCoordinate.x() + offset.x(), + mapCoordinate.y() + offset.y(), + mapCoordinate.spatialReference()); + +} + +void RubberbandMultiEllipseFeature::updateSectorHandles() +{ + if (Rubberband::_internal->_handleList.isEmpty()) + return; + + if ((unsigned int)Rubberband::_internal->_handleList.size() < _minimumPointCount) + return; + + if (!isSectorEnabled()) + return; + + auto ellipse = []( qreal a, qreal b, qreal sinA, qreal cosA ) -> QPointF + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Qt does not use the paramater equation of a ellipse to draw sectors. Instead of a simpler term + * is used. For simpleness we use the same of. + * + qreal e2 = 1 - (b*b) / (a*a); + qreal cos2 = cosA * cosA; + qreal f = b / sqrt(1-e2*cos2); + return QPointF( f*cosA, f*sinA ); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + return QPointF(a*cosA,b*sinA); + }; + + auto rotate = [] ( const QPointF & p, qreal sinA, qreal cosA ) -> QPointF { return QPointF( p.x()*cosA - p.y()*sinA, p.y()*cosA + p.x()*sinA ); }; + + + const QPointF &biggestRadius = getBiggestRadius(); + + qreal a = biggestRadius.x(); // getWidth() * 0.5; + qreal b = biggestRadius.y(); // getHeight() * 0.5; + QPointF startP = ellipse(a,b,_internal->_sectorOStart.y(),_internal->_sectorOStart.x() ); + QPointF endP = ellipse(a,b,_internal->_sectorOEnd.y() ,_internal->_sectorOEnd.x() ); + + qreal sinRect = 0; + qreal cosRect = 1; + + getSceneRotation(sinRect, cosRect); + + startP = rotate(startP, sinRect, cosRect) ; // + getCenter(); + endP = rotate( endP, sinRect, cosRect) ; // + getCenter(); + + RubberbandPointHandle *handlePointCenter = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if (!handlePointCenter) + return; + const RubberbandHandle::RubberbandCoordinate &mapCenter = handlePointCenter->getCoordinate(); + + // const Esri::ArcGISRuntime::Point &mapStartP = convert( startP, Rubberband::_internal->_mapView->map()->spatialReference() ); + // const Esri::ArcGISRuntime::Point &mapEndP = convert( endP, Rubberband::_internal->_mapView->map()->spatialReference() ); + const Esri::ArcGISRuntime::Point &mapStartP = addOffsetToMapCoordinate(mapCenter._mapCoordinate, startP); + const Esri::ArcGISRuntime::Point &mapEndP = addOffsetToMapCoordinate(mapCenter._mapCoordinate, endP); + + if (!_internal->_sectorStartHandle.valid()) + _internal->_sectorStartHandle = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapStartP ); + else + _internal->_sectorStartHandle->setCoordinate( RubberbandHandle::RubberbandCoordinate(Rubberband::_internal->_mapView, mapStartP) ); + + _internal->_sectorStartHandle->setVisibility((!isSectorFixedAngeEnabled())/*_internal->_sectorEnabled*/); + _internal->_sectorStartHandle->setDrawColor(Qt::red); + _internal->_sectorStartHandle->setFillColor( _handleFillColor ); + _internal->_sectorStartHandle->setSelectionColor(_handleSelectionColor); + _internal->_sectorStartHandle->setSelectable(true); + + if (!_internal->_sectorEndHandle.valid()) + _internal->_sectorEndHandle = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapEndP ); + else + _internal->_sectorEndHandle->setCoordinate( RubberbandHandle::RubberbandCoordinate(Rubberband::_internal->_mapView, mapEndP) ); + + _internal->_sectorEndHandle->setVisibility(!isSectorFixedAngeEnabled()/*_internal->_sectorEnabled*/); + _internal->_sectorEndHandle->setDrawColor(Qt::red); + _internal->_sectorEndHandle->setFillColor( _handleFillColor ); + _internal->_sectorEndHandle->setSelectionColor(_handleSelectionColor); + _internal->_sectorEndHandle->setSelectable(true); +} + +void RubberbandMultiEllipseFeature::updateRotationHandle() +{ + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " START" + << ", isSectorFixedAngeEnabled()=" << isSectorFixedAngeEnabled() + << ", _handleList.size=" << Rubberband::_internal->_handleList.size(); + + if (!isSectorEnabled() || !isSectorFixedAngeEnabled() || (Rubberband::_internal->_handleList.size()<2)) + { + _internal->_rotationHandle = nullptr; // destroy + } + else + { + static QPixmap *s_icon = nullptr; + if (!s_icon) + s_icon = new QPixmap( QPixmap( winkelIcon_xpm ).transformed(QTransform::fromScale(0.5,0.5)) ); + + if (!_internal->_rotationHandle) + { + _internal->_rotationHandleSize = std::max( s_icon->width(), s_icon->height() ) * 0.5; + + _internal->_rotationHandle = new RubberbandPointHandle(Rubberband::_internal->_mapView + , Rubberband::_internal->_mapScene + , QPointF(100, 100) // , _internal->getScenePositionRotationHandle(), + , *s_icon ); + + _internal->_rotationHandle->setDrawColor(_handleDrawColor); + _internal->_rotationHandle->setFillColor(_handleFillColor); + _internal->_rotationHandle->setSelectionColor(_handleSelectionColor); + } + + _internal -> _rotationHandle->setSceneOffset(getSceneOffsetRotationHandle( (_frameHandleMargin > 0) ? _frameHandleMargin : 0 ) ); + _internal -> _rotationHandle->updateCoordinate(getScenePositionRotationHandle()); + } + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION << " END"; +} + +QPointF RubberbandMultiEllipseFeature::getScenePositionRotationHandle() const +{ + if (Rubberband::_internal->_handleList.size()<2) + return {}; + + RubberbandPointHandle *handlePointCenter = dynamic_cast(Rubberband::_internal->_handleList.first().get()); + if (!handlePointCenter) + return {}; + const RubberbandHandle::RubberbandCoordinate &mapCenter = handlePointCenter->getCoordinate(); + + const QPointF &biggestRadius = getBiggestRadius(); + + Esri::ArcGISRuntime::Point handlePosPoint(mapCenter._mapCoordinate.x() + 0, + mapCenter._mapCoordinate.y() + biggestRadius.y(), + mapCenter._mapCoordinate.spatialReference()); + + RubberbandHandle::RubberbandCoordinate handlePos(Rubberband::_internal->_mapView, handlePosPoint); + + QPointF scenePoint (handlePos._sceneCoordinate); + scenePoint.ry() -= _internal->_rotationHandleSize; // keep center of sceneCoordSystem in mind + + qreal sinRect = 0; + qreal cosRect = 1; + getSceneRotation(sinRect, cosRect); + + scenePoint = MathUtils::rotate(scenePoint, mapCenter._sceneCoordinate, -sinRect, cosRect); // be carefull the scene coordinate system is contra clockwise -- sinA must be negated + + return scenePoint; +} + +QPointF RubberbandMultiEllipseFeature::getSceneOffsetRotationHandle(qreal margin) +{ + qreal sinPhi = _internal->_oVec.y(); // * _mapOVec.x() + _oVec.x() * _mapOVec.y(); // Addition-Theorem + qreal cosPhi = _internal->_oVec.x(); // * _mapOVec.x() - _oVec.y() * _mapOVec.y(); + return rotateVertex( QPointF(0,0), 0.0,-(margin+_internal->_rotationHandleSize), sinPhi, cosPhi ); +} + + +} +} // namespace SituationMap::RB diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.h new file mode 100755 index 000000000..4d80e0e42 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_ellipse_feature.h @@ -0,0 +1,254 @@ +/** + * @RDE_HEADER@ + * Rubberband class to define multiple ellipse/circle + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include "rubberband.h" + + +namespace SituationMap +{ +namespace RB +{ +class RubberbandHandle; +class RubberbandFrameHandle; +class DrawableEllipse; + +/** + @class RubberbandMultiEllipseFeature rubberband_multi_circle_feature.h + @brief Implementation of a rubberband to input multi point features, like areals or linears + @see Rubberband +*/ +class SITUATIONMAP_EXPORT RubberbandMultiEllipseFeature : public Rubberband +{ + Q_OBJECT +public: + + /** + @name Construction & Destruction + @{ + */ + +private: + + friend class RubberbandFactory; + friend class RubberbandArrow; + friend class RubberbandCorridor; + friend class RubberbandPolylineWidth; + friend class RubberbandSmoothLine; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Constructor delegation doesn't work with Visual Studio 2013 + + explicit RubberbandMultiEllipseFeature( SituationMap::Map * map ); + explicit RubberbandMultiEllipseFeature( SituationMap::Map * map, bool complexGeometry ); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + /** + Construction of RubberbandMultiEllipseFeature in most common cases. The parameter parent is + optional. + */ + explicit RubberbandMultiEllipseFeature(SituationMap::Map *map, QObject *parent = nullptr); + + /** + Construction of RubberbandMultiEllipseFeature in case of controlling complex geometry objects, like + splines or multi point symbols. In this case set complexGeometry true. + */ + explicit RubberbandMultiEllipseFeature(SituationMap::Map *map, bool complexGeometry, QObject *parent = nullptr); + +public: + + ~RubberbandMultiEllipseFeature(); + /** @} */ + + bool selectAction(AbstractEventDispatcher *causer, int x, int y) override; + bool setCoordinateAction(AbstractEventDispatcher *causer, int x, int y) override; + bool moveAction(AbstractEventDispatcher *causer, int x, int y) override; + bool movePickedAction(AbstractEventDispatcher *causer, int x, int y) override; + void resetAction(AbstractEventDispatcher *causer, int x, int y) override; + bool contextMenuAction(AbstractEventDispatcher *causer, int x, int y) override; + bool completeStateAction(AbstractEventDispatcher *causer, int x, int y) override; + bool deleteAction(AbstractEventDispatcher *causer, int x, int y) override; + bool undoAction(AbstractEventDispatcher *causer, int x, int y) override; + bool applyAndEndAction(AbstractEventDispatcher *causer, int x, int y) override; + + /** + * @brief If set to true only quadratic rectangles can be created. + */ + void constrainQuadratic( bool on ); + + + /** + @name Setting Constrains + @{ + */ + + /** + Defines minimum count of verticies that has to be defined by users input to complete the rubberband with apply signal. The default value is 2. + */ + inline void minimumRadiusCount(unsigned int x) + { + _minimumPointCount = x+1; + } + + + /** + Defines maximum count of verticies that can be aceppted by the rubberband. A values of 0 stands for undefined. The default is 0. + */ + inline void maximumRadiusCount(unsigned int x) + { + _maximumPointCount = x+1; + } + + + /** + Defines the maximum length of a segment (distance between to adjaced verticies). A value of 0 stands for no limit is set. The default is 0. + */ + inline void maximumSegmentLength(qreal x) { _maximumSegmentLength = (x > 0) ? x : 0.0; } + inline qreal getMaximumSegmentLength() const { return _maximumSegmentLength; } + + /** + Defines the maximum length of the complete polyline. \c enableClosed must be set to false. A value of 0 stands for no limit is set. The default is 0. + */ + inline void maximumLength(qreal x) { _maximumLength = (x > 0) ? x : 0.0; } + /** @} */ + inline qreal getMaximumLength() const { return _maximumLength; } + + + QPointF centerCoordinate(CoordinateSystem cs = CoordinateSystem::Geo) const; + + /** + * @brief radii get vector of radius (QPointF containing radiusX and radius Y in meter) + * @return radii + */ + QVector radii() const; + QPointF getBiggestRadius() const; + + + struct RadiusWithContraints + { + // to be able to supply int, avoid explicit: + RadiusWithContraints(double radius = 0.0, double minRadius = 0.0, double maxRadius = 0.0) + : m_radius(radius), + m_minRadius(minRadius), + m_maxRadius(maxRadius) + {} + double m_radius{0}; + double m_minRadius{0}; + double m_maxRadius{0}; + }; + + bool setCenterAndRadii(const QPointF ¢er, CoordinateSystem cs, const QVector &radii); // no constraints + + bool setCenterAndRadii2(const QPointF ¢er, CoordinateSystem cs, + const QVector &radiiWithConstraints); + + void prepareFixedNumberOfRadiiWithConstraints(const QVector &radiiContraints); + + + // get SectorStartEnd + QPointF getRotatedPoint(const QPointF §or)const; + + + void enableSector(bool on); + bool isSectorEnabled() const; + + /** + * @brief sectorStart in mathematical sense (starts counter-clockwise from 3 o'clock) + * @return sectorStart + */ + QPointF sectorStart() const; + + qreal sectorStartDegMath() const; // in mathematical sense (starts counter-clockwise from 3 o'clock) + + /** + * @brief sectorEnd in mathematical sense (starts counter-clockwise from 3 o'clock) + * @return sectorEnd + */ + QPointF sectorEnd() const; + + qreal sectorEndDegMath() const; // in mathematical sense (starts counter-clockwise from 3 o'clock) + + void setSectorStart(qreal sinPhi, qreal cosPhi); + void setSectorEnd(qreal sinPhi, qreal cosPhi); + + void setSectorAngleFixed(bool on); // true: start and end sectors positon can't be modifed + bool isSectorFixedAngeEnabled() const; + + + virtual QVector coordinateVector(CoordinateSystem cs = CoordinateSystem::Geo) const override; + virtual QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + virtual void setCoordinateVector(const QVector &, CoordinateSystem cs = CoordinateSystem::Geo) override; + +signals: + + /** + The signal will be emitted, when the mouse cursor has changed and the rubberband is in construction mode and is accepting new input coordinates. + */ + void currentCursorPositionChanged(const Point3D &mapCoord); + + +protected: + + /* + Forward declaration of InternalData to hold private member variables. Be careful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData *_internal; + + bool _isClosed{false}; + + unsigned int _minimumRadius = 0; + unsigned int _maximumRadius = 0; + + unsigned int _minimumPointCount{2}; // 2 Punkte == 1 Kreis + unsigned int _maximumPointCount{0}; + + qreal _maximumSegmentLength; + qreal _maximumLength; + + void activateButtons() override; + + void update() override; + void updateFrameHandle(const QPointF &sceneCoordinate); // Unfortunately, RubberbandCoordinate can´t be forwarded, so use sceneCoordinate + + void getSceneRotation(qreal &sinA, qreal &cosA) const; + void updateSectorHandles(); + + void updateRotationHandle(); + QPointF getScenePositionRotationHandle() const; + QPointF getSceneOffsetRotationHandle(qreal margin); + + + // some helper methodes + bool getRadiusXY(const QPointF &scenePos, qreal &radiusX_out, qreal &radiusY_out); + + RubberbandHandle *constructPoint(const QPointF &scenePos); + void constructPoint(const QPointF &point, CoordinateSystem cs); + + bool selectHandles(int x, int y, const QPointF &scenePos); + void updateHandles(const QPointF &scenePos, const QPointF &deltaMove); + + bool checkLengthConstrains(unsigned int checkHandleIdx, const QPointF &scenePos); + bool checkLengthConstrains(const QPointF &scenePos, const QPointF &deltaScenePos); + + + bool checkMinMaxRadiusConstraint(const QPointF &curScenePos); + bool checkMinMaxRadiusConstraint(qreal radiusX, qreal radiusY, int idxOfConstraint = -1); + + void updatePreviewEllipse(const QPointF &scenePosition); + + RubberbandFrameHandle *getFrameHandle() const; + +}; /* RubberbandMultiEllipseFeature */ + +} +} /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.cpp new file mode 100755 index 000000000..62fd79efe --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.cpp @@ -0,0 +1,1137 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define multi point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_multi_point_feature.h" +#include "rubberband_frame_handle.h" +#include "rubberband_line_handle.h" +#include "rubberband_point_handle.h" +#include "rubberband_drawable.h" +#include "rubberband_handle_proxy.h" +#include "drawable_line.h" +#include "drawable_painter_path.h" +#include "transient_tactical_symbol.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include + +#include + +namespace SituationMap { namespace RB +{ + + struct RubberbandMultiPointFeature::InternalData + { + Q_DISABLE_COPY(InternalData) + + QList> _lineHandleList; + SituationMap::RefPointer _frameHandle; + + SituationMap::RefPointer _fillPath; + SituationMap::RefPointer _cursorPreviewLine; + SituationMap::RefPointer _cursorPreviewCloseLine; + SituationMap::RefPointer _previewSymbol; + + bool _complexGeometry; + bool _addVertexAtEnd; + bool _showCursorPreviewLine; + double _checkMaxAngle; + + InternalData( QGraphicsScene * scene, bool complexGeometry ) + : _lineHandleList() + , _frameHandle() + , _fillPath() + , _cursorPreviewLine() + , _cursorPreviewCloseLine() + , _previewSymbol() + , _complexGeometry(complexGeometry) + , _addVertexAtEnd(false) + , _showCursorPreviewLine(false) + , _checkMaxAngle(0.0) + { + Q_UNUSED(scene); + } /* InternalData */ + }; /* RubberbandMultiPointFeature::InternalData */ + + RubberbandMultiPointFeature::RubberbandMultiPointFeature( SituationMap::Map * map, QObject * parent ) + : RubberbandMultiPointFeature(map, false, parent) + { + } /* RubberbandMultiPointFeature::RubberbandMultiPointFeature */ + + + RubberbandMultiPointFeature::RubberbandMultiPointFeature( SituationMap::Map * map, bool complexGeometry, QObject * parent ) + : Rubberband(map,parent) + , _internal(new InternalData(Rubberband::_internal->_mapScene, complexGeometry)) + , _isClosed(false) + , _minimumPointCount(2) + , _maximumPointCount(0) + , _maximumSegmentLength(0.0) + , _maximumLength(0.0) + { + } /* RubberbandMultiPointFeature::RubberbandMultiPointFeature */ + + + RubberbandMultiPointFeature::~RubberbandMultiPointFeature() + { + delete _internal; + } /* RubberbandMultiPointFeature::~RubberbandMultiPointFeature */ + + + void RubberbandMultiPointFeature::setCheckMaxAngle(double phi) + { + _internal->_checkMaxAngle = ((phi > 0.0) && (phi < M_PI)) ? phi : 0.0; + + } // RubberbandMultiPointFeature::setCheckMaxAngle + + + RubberbandFrameHandle * RubberbandMultiPointFeature::getFrameHandle() const { return _internal->_frameHandle.get(); } + + + QVector RubberbandMultiPointFeature::coordinateVector( CoordinateSystem cs ) const + { + QVector coordinateVector; + coordinateVector.reserve(Rubberband::_internal->_handleList.size()); + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + + coordinateVector.push_back(projectCoordinateFromMap( cs, handlePointPtr->getCoordinate()._mapCoordinate )); + } + return coordinateVector; + } /* RubberbandMultiPointFeature::coordinateVector */ + + + QPointF RubberbandMultiPointFeature::singleCoordinate(unsigned int idx, CoordinateSystem cs ) const + { + if (idx < (unsigned int)Rubberband::_internal->_handleList.size()) + { + const RubberbandPointHandle * handle = dynamic_cast(Rubberband::_internal->_handleList[idx].get()); + + return projectCoordinateFromMap( cs, handle->getCoordinate()._mapCoordinate ); + } + return QPointF(); + } /* RubberbandMultiPointFeature::singleCoordinate */ + + + void RubberbandMultiPointFeature::setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs ) + { + Rubberband::_internal->_handleList.clear(); + + for (const auto &p : coordinateVector) + { + constructPoint( p, cs ); + } + enableModificationState(); + update(); + activateButtons(); + } /* RubberbandMultiPointFeature::setCoordinateVector */ + + + void RubberbandMultiPointFeature::constructPoint( const QPointF &point, CoordinateSystem cs ) + { + const QPointF &mapPos = projectCoordinateToMap( cs, point ); + + RubberbandHandle::RubberbandCoordinate coordinate( Rubberband::_internal->_mapView, convert(mapPos, Rubberband::_internal->_mapView->map()->spatialReference()) ); + RubberbandHandle * handlePtr = constructPoint( coordinate._sceneCoordinate ); + if (handlePtr) + { + static_cast(handlePtr)->setCoordinate( coordinate ); + handlePtr->setSelection(false); + } + } /* RubberbandMultiPointFeature::constructPoint */ + + + RubberbandHandle * RubberbandMultiPointFeature::constructPoint( QPointF scenePos ) + { + RubberbandHandle * handlePtr; + + if (!Rubberband::_internal->_handleList.empty()) + { + handlePtr = Rubberband::_internal->_handleList.last().get(); + + if (handlePtr) + { + const RubberbandHandle::RubberbandCoordinate && lastPos = static_cast(handlePtr)->getCoordinate(); + + if (isClosedEnabled()) + { + handlePtr = Rubberband::_internal->_handleList.first().get(); // get pointer to first point + + if ((unsigned int)Rubberband::_internal->_handleList.size() == 2) + { + const RubberbandHandle::RubberbandCoordinate && firstPos = static_cast(handlePtr)->getCoordinate(); + + // create line from first point of polyline to new point to close the polyline + handlePtr = new RubberbandLineHandle( Rubberband::_internal->_mapView + , Rubberband::_internal->_mapScene + , scenePos + , firstPos._sceneCoordinate + , _lineHandleWidth + , _internal->_complexGeometry ); + _internal->_lineHandleList.insert( 0, SituationMap::RefPointer(static_cast(handlePtr)) ); + } + else if ((unsigned int)Rubberband::_internal->_handleList.size() > 2) + { + // update coordinates + handlePtr = _internal->_lineHandleList.first().get(); // get pointer to first line handle + static_cast(handlePtr)->updateCoordinate(0,scenePos); + } + } + + // create line from last point to new point + handlePtr = new RubberbandLineHandle( Rubberband::_internal->_mapView + , Rubberband::_internal->_mapScene + , lastPos._sceneCoordinate + , scenePos + , _lineHandleWidth + , _internal->_complexGeometry ); + _internal->_lineHandleList.append( SituationMap::RefPointer(static_cast(handlePtr)) ); + } + } + + handlePtr = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, scenePos ); + handlePtr -> setSelection(); + + Rubberband::_internal->_handleList.append( SituationMap::RefPointer(handlePtr) ); + + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + { + activateButtons(); + } + + if ((!_maximumPointCount) || ((unsigned int)Rubberband::_internal->_handleList.size() < _maximumPointCount)) + { + if (!_internal->_cursorPreviewLine) _internal->_cursorPreviewLine = new DrawableLine( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene ); + if ((isClosedEnabled()) && (!_internal->_cursorPreviewCloseLine)) + { + _internal->_cursorPreviewCloseLine = new DrawableLine( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene ); + } + _internal->_cursorPreviewLine -> setVisibility( true ); + if (_internal->_cursorPreviewCloseLine) _internal -> _cursorPreviewCloseLine -> setVisibility( true ); + } + else + { + _internal->_cursorPreviewLine = nullptr; + _internal->_cursorPreviewCloseLine = nullptr; + } + return handlePtr; + } /* RubberbandMultiPointFeature::constructPoint */ + + + + bool RubberbandMultiPointFeature::checkLengthConstrains( const QPointF & scenePos, const QPointF & deltaScenePos ) + { + if (_internal->_complexGeometry) + { + markFailedConstrains( QPointF(), false ); + return true; + } + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Checking constrains for moving selected coordinates (handles) + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + bool allHandlesSelected = true; + QVector coordinateVector; + + coordinateVector.reserve( Rubberband::_internal->_handleList.size() ); + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if (handlePointPtr) + { + coordinateVector.append(handlePointPtr->getCoordinate()); + if (handlePointPtr->isSelected()) + { + RubberbandHandle::RubberbandCoordinate & coordinate = coordinateVector.back(); + coordinate.updateSceneCoordinate( Rubberband::_internal->_mapView , coordinate._sceneCoordinate + deltaScenePos ); + } + else allHandlesSelected = false; + } + } + + if ((allHandlesSelected) || ((_internal->_frameHandle) && (_internal->_frameHandle->isSelected()))) + { + markFailedConstrains( QPointF(), false ); + return true; + } + + if (_internal->_checkMaxAngle > 0.0) + { + // Check angle of line segments first + + for (int i = 2; i < coordinateVector.size(); ++i) + { + const RubberbandHandle::RubberbandCoordinate & p1 = coordinateVector[i - 2]; + const RubberbandHandle::RubberbandCoordinate & p2 = coordinateVector[i - 1]; + const RubberbandHandle::RubberbandCoordinate & p3 = coordinateVector[i]; + + // build tangent vectors + QPointF t1 = QPointF(p2._mapCoordinate.x() - p1._mapCoordinate.x(), p2._mapCoordinate.y() - p1._mapCoordinate.y()); + QPointF t2 = QPointF(p3._mapCoordinate.x() - p2._mapCoordinate.x(), p3._mapCoordinate.y() - p2._mapCoordinate.y()); + double l1 = sqrt(t1.x()*t1.x() + t1.y()*t1.y()); + double l2 = sqrt(t2.x()*t2.x() + t2.y()*t2.y()); + + if (qFuzzyIsNull(l1) || qFuzzyIsNull(l2)) + { + continue; + } + + t1 /= l1; + t2 /= l2; + + double phi = acos(t1.x()*t2.x() + t1.y()*t2.y()); + if (phi > _internal->_checkMaxAngle) + { + markFailedConstrains(scenePos, true); + return false; + } + } + } + + + if ((!isClosedEnabled()) && (_maximumLength > 0.0)) + { + qreal totalLength = 0.0; + const Esri::ArcGISRuntime::Point * mapPointPtr = nullptr; // Check Quartz + + for (int i = 0; i < coordinateVector.size(); ++i) + { + const RubberbandHandle::RubberbandCoordinate & coordinate = coordinateVector[i]; + if (mapPointPtr) + { + qreal x = coordinate._mapCoordinate.x() - mapPointPtr->x(); + qreal y = coordinate._mapCoordinate.y() - mapPointPtr->y(); + + totalLength += sqrt(x*x+y*y); + } + mapPointPtr = &coordinateVector.at(i)._mapCoordinate; + } + if (totalLength > _maximumLength) + { + markFailedConstrains( scenePos, true ); + return false; + } + } + + const RubberbandHandle::RubberbandCoordinate * mapPointPtr = nullptr; + bool lastSelected = false; + auto handleIter = Rubberband::_internal->_handleList.constBegin(); + + for (int i = 0; i < coordinateVector.size(); ++i) + { + const RubberbandHandle::RubberbandCoordinate & coordinate = coordinateVector[i]; + bool isSelected = (*handleIter)->isSelected(); + + if ((isSelected || lastSelected) && (mapPointPtr)) + { + if ((!isClosedEnabled()) && (_maximumSegmentLength > 0.0)) + { + qreal x = coordinate._mapCoordinate.x() - mapPointPtr->_mapCoordinate.x(); + qreal y = coordinate._mapCoordinate.y() - mapPointPtr->_mapCoordinate.y(); + + if (sqrt(x*x+y*y) > _maximumSegmentLength) + { + markFailedConstrains( scenePos, true ); + return false; + } + } + // looking for identical coordinate with last point + qreal x = coordinate._sceneCoordinate.x() - mapPointPtr->_sceneCoordinate.x(); + qreal y = coordinate._sceneCoordinate.y() - mapPointPtr->_sceneCoordinate.y(); + + if ((x*x + y*y) <= 1) + { + markFailedConstrains( scenePos, true ); + return false; + } + } + ++handleIter; + mapPointPtr = &coordinateVector.at(i); + lastSelected = isSelected; + } // for + + markFailedConstrains( QPointF(), false ); + return true; + } /* RubberbandMultiPointFeature::checkLengthConstrains */ + + + bool RubberbandMultiPointFeature::checkLengthConstrains( unsigned int checkHandleIdx, QPointF & scenePos ) + { + if (_internal->_complexGeometry) + { + markFailedConstrains( QPointF(), false ); + return true; + } + + bool markFailedConstrainsFlag = false; + + // calculate pixel size for later use + + RubberbandDrawable::RubberbandCoordinate pi1( Rubberband::_internal->_mapView,scenePos ); + RubberbandDrawable::RubberbandCoordinate pi2( Rubberband::_internal->_mapView,scenePos+QPointF(1,1) ); + + qreal piX = pi2._mapCoordinate.x() - pi1._mapCoordinate.x(); + qreal piY = pi2._mapCoordinate.y() - pi1._mapCoordinate.y(); + qreal pixelSize = sqrt(piX*piX+piY*piY); + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Checking constrains for adding single coordinate at end of polyline + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + RubberbandHandle::RubberbandCoordinate mapPos( Rubberband::_internal->_mapView, scenePos ); + + if ((!Rubberband::_internal->_handleList.empty()) && (_maximumLength > 0.0) && (!isClosedEnabled())) + { + qreal totalLength = 0.0; + RubberbandHandle::RubberbandCoordinate mapPoint; + + QList>::iterator it = Rubberband::_internal->_handleList.begin(); + + for (; it != Rubberband::_internal->_handleList.end(); ++it) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(it->get()); + if (handlePointPtr) + { + if (it != Rubberband::_internal->_handleList.begin()) + { + qreal x = handlePointPtr->getCoordinate()._mapCoordinate.x() - mapPoint._mapCoordinate.x(); + qreal y = handlePointPtr->getCoordinate()._mapCoordinate.y() - mapPoint._mapCoordinate.y(); + + totalLength += sqrt(x*x+y*y); + } + mapPoint = handlePointPtr->getCoordinate(); + } + } + if (totalLength >= _maximumLength) + { + markFailedConstrains( scenePos, true ); + return false; + } + + qreal x = mapPos._mapCoordinate.x() - mapPoint._mapCoordinate.x(); + qreal y = mapPos._mapCoordinate.y() - mapPoint._mapCoordinate.y(); + qreal l = sqrt(x*x+y*y); + + qreal d = _maximumLength - totalLength - l - pixelSize; + if (d < 0.0) + { + qreal scale = (1.0 + d/l); + const Esri::ArcGISRuntime::Point newMapCoordinate(mapPoint._mapCoordinate.x() + x * scale, + mapPoint._mapCoordinate.y() + y * scale, + mapPos._mapCoordinate.spatialReference()); + mapPos._mapCoordinate = newMapCoordinate; + markFailedConstrainsFlag = true; + scenePos = mapPos.mapToScene( Rubberband::_internal->_mapView ); + } + } + + if (checkHandleIdx <= (unsigned int)Rubberband::_internal->_handleList.size()) + { + unsigned int handleListIdx = Rubberband::_internal->_handleList.size()-checkHandleIdx; + const RubberbandPointHandle * handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.at(handleListIdx).get()); + + if ((!isClosedEnabled()) && (_maximumSegmentLength > 0.0)) + { + qreal x = mapPos._mapCoordinate.x() - handlePointPtr->getCoordinate()._mapCoordinate.x(); + qreal y = mapPos._mapCoordinate.y() - handlePointPtr->getCoordinate()._mapCoordinate.y(); + qreal l = sqrt(x*x+y*y); + qreal d = _maximumSegmentLength - l - pixelSize; + + if (d < 0.0) + { + qreal scale = (1.0 + d/l); + const Esri::ArcGISRuntime::Point newMapCoordinate(handlePointPtr->getCoordinate()._mapCoordinate.x() + x * scale, + handlePointPtr->getCoordinate()._mapCoordinate.y() + y * scale, + mapPos._mapCoordinate.spatialReference()); + + mapPos._mapCoordinate = newMapCoordinate; + markFailedConstrainsFlag = true; + scenePos = mapPos.mapToScene( Rubberband::_internal->_mapView ); + } + } + // looking for identical coordinate with last point + qreal x = handlePointPtr->getCoordinate()._sceneCoordinate.x() - scenePos.x(); + qreal y = handlePointPtr->getCoordinate()._sceneCoordinate.y() - scenePos.y(); + + if ((x*x+y*y) <= 1) + { + markFailedConstrains( scenePos, true ); + return false; + } + } + + markFailedConstrains( scenePos, markFailedConstrainsFlag ); + + return true; + } /* RubberbandMultiPointFeature::checkLengthConstrains */ + + + bool RubberbandMultiPointFeature::selectHandles(int x, int y, const QPointF & scenePos) + { + bool eventHandled = false; + RubberbandHandle * handlePtr = pickHandle(x,y); + + _internal -> _addVertexAtEnd = false; + + if (handlePtr) + { + handlePtr->setSelection(); + enableSelectedState(); + eventHandled = true; + } + else + { + if ((_maximumPointCount < 1) || ((unsigned int)Rubberband::_internal->_handleList.size() < _maximumPointCount)) + { + unsigned int lineCounter = 0; + + for (auto lineHandle : std::as_const(_internal->_lineHandleList)) + { + if (lineHandle->isSelected()) + { + lineHandle->setSelection(false); + + handlePtr = new RubberbandLineHandle( Rubberband::_internal->_mapView + , Rubberband::_internal->_mapScene + , lineHandle->getCoordinate(0)._sceneCoordinate + , scenePos + , _lineHandleWidth + , _internal->_complexGeometry ); + + _internal->_lineHandleList.insert( lineCounter, SituationMap::RefPointer(static_cast(handlePtr)) ); + lineHandle->updateCoordinate( 0, scenePos ); + + handlePtr = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, scenePos ); + handlePtr -> setSelection(); + + unsigned int insertHandleIndex = lineCounter; + + if (!isClosedEnabled()) insertHandleIndex += 1; + Rubberband::_internal->_handleList.insert( insertHandleIndex, SituationMap::RefPointer(handlePtr) ); + + enableSelectedState(); + eventHandled = true; + break; + } + ++lineCounter; + } + } + } + + if (!isEnabledSelectedState()) + { + // ok doesn't hit any point or line handle now looking to frame handle + if (_internal->_frameHandle != nullptr) + { + if (_internal->_frameHandle->isPositionInside(scenePos)) + { + _internal->_frameHandle->setSelection(true); + enableSelectedState(); + eventHandled = true; + } + } + } + return eventHandled; + } /* RubberbandMultiPointFeature::selectHandles */ + + + void RubberbandMultiPointFeature::resetAction(AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive()) + { + if (isEnabledConstructionState()) + { + if (_internal->_addVertexAtEnd) + { + Rubberband::_internal->_handleList.removeLast(); + _internal->_lineHandleList.removeLast(); + } + } + } + Rubberband::resetAction(causer,x,y); + } /* RubberbandMultiPointFeature::resetAction */ + + + bool RubberbandMultiPointFeature::selectAction( AbstractEventDispatcher* , int x, int y ) + { + if (rubberbandIsAlive()) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (_baseInternal->buttonProxyActive(scenePos)) return false; + + if (isEnabledConstructionState()) disenableMultiSelectedState(); + + _baseInternal->_lastClickPosition = scenePos; + + bool eventHandled = selectHandles( x, y, scenePos ); + + if ((!eventHandled) && (isEnabledConstructionState())) + { + _internal -> _addVertexAtEnd = true; + _internal -> _showCursorPreviewLine = true; + + deSelectHandles(); + + if (checkLengthConstrains(1,scenePos)) + { + constructPoint( scenePos ); + enableSelectedState(); + _baseInternal->_lastClickPosition = scenePos; + } + + eventHandled = true; + } + else _internal->_addVertexAtEnd = false; + + if (eventHandled) + { + update(); + deactivateButtons(); + } + + return eventHandled; + } + return false; + } /* RubberbandMultiPointFeature::selectAction */ + + + bool RubberbandMultiPointFeature::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) + { + markFailedConstrains( QPointF(), false ); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + Rubberband::_internal->_lastClickPosition = scenePos; + + if (_internal->_addVertexAtEnd) // isEnabledConstructionState()) + { + if (Rubberband::_internal->_handleList.isEmpty()) + { + disenableSelectedState(); + disenableMultiSelectedState(); + return false; + } + + if ((!isEnabledSelectedState()) && (_baseInternal->buttonProxyActive(scenePos))) + { + deSelectHandles(); + return false; + } + + if (checkLengthConstrains(2, scenePos)) + { + QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && ((handlePointPtr->isSelected()))) + { + handlePointPtr->setSelection(false); + handlePointPtr->moveCoordinate( deltaMove ); + eventHandled = true; + } + } + } + markFailedConstrains( QPointF(), false ); + + if ((_maximumPointCount >0) && ((unsigned int)Rubberband::_internal->_handleList.size() >= _maximumPointCount)) + { + completeStateAction(causer,x,y); + } + if (eventHandled) update(); + if (_baseInternal->buttonProxyActive(scenePos)) return false; + updateButtonPosition(); + activateButtons(); + } + else + { + if (isEnabledSelectedState()) + { + bool frameIsSelected( ((_internal->_frameHandle != nullptr) && (_internal->_frameHandle->isSelected())) ); + + QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; + + if (!checkLengthConstrains(scenePos, deltaMove)) + { + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if (handlePointPtr) handlePointPtr->setSelection(false); + } + if (_internal->_frameHandle) _internal->_frameHandle->setSelection(false); + updateButtonPosition(); + activateButtons(); + disenableSelectedState(); + disenableMultiSelectedState(); + markFailedConstrains( QPointF(), false ); + return true; + } + markFailedConstrains( QPointF(), false ); + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && ((handlePointPtr->isSelected()) || (frameIsSelected))) + { + handlePointPtr->setSelection(false); + handlePointPtr->moveCoordinate( deltaMove ); + eventHandled = true; + } + } + if (frameIsSelected) + { + _internal->_frameHandle->setSelection(false); + eventHandled = true; + } + + disenableSelectedState(); + disenableMultiSelectedState(); + } + + if (isEnabledConstructionState()) + { + _internal->_addVertexAtEnd = true; + _internal->_showCursorPreviewLine = true; + + updatePreviewLine( scenePos ); + } + else if (_baseInternal->buttonProxyActive(scenePos)) return false; + + if (eventHandled) update(); + updateButtonPosition(); + activateButtons(); + + } + return eventHandled; + } + return false; + } /* RubberbandMultiPointFeature::setCoordinateAction */ + + + void RubberbandMultiPointFeature::updatePreviewLine( const QPointF & scenePosition ) + { + if (isEnabledConstructionState() && (!Rubberband::_internal->_handleList.isEmpty()) && (_internal->_cursorPreviewLine)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.last().get()); + if ((handlePointPtr) && (_internal->_addVertexAtEnd)) + { + RubberbandDrawable::RubberbandCoordinate rbCoordinate( Rubberband::_internal->_mapView, scenePosition ); + + _internal->_cursorPreviewLine -> setVisibility( _internal->_showCursorPreviewLine ); + _internal->_cursorPreviewLine -> setDrawColor( _handleSelectionColor ); + + _internal->_cursorPreviewLine->setLine( handlePointPtr->getCoordinate(), rbCoordinate ); + + if (_internal->_cursorPreviewCloseLine) + { + _internal->_cursorPreviewCloseLine -> setVisibility( _internal->_showCursorPreviewLine ); + _internal->_cursorPreviewCloseLine -> setDrawColor( _handleSelectionColor ); + + handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList.front().get()); + + if (handlePointPtr) + { + _internal->_cursorPreviewCloseLine->setLine( handlePointPtr->getCoordinate(), rbCoordinate ); + } + } + } + else + { + _internal->_cursorPreviewLine -> setVisibility( false ); + if (_internal->_cursorPreviewCloseLine) _internal->_cursorPreviewCloseLine -> setVisibility( false ); + } + return; + } + + _internal->_cursorPreviewLine = nullptr; + _internal->_cursorPreviewCloseLine = nullptr; + + } /* RubberbandMultiPointFeature::updatePreviewLine */ + + + bool RubberbandMultiPointFeature::moveAction( AbstractEventDispatcher*, int x, int y ) + { + adjustConnectedPreviewPixmap(x,y); + + if (rubberbandIsAlive()) + { + QPointF scenePosition(pixel2ScenePosition(x,y)); + + if (_baseInternal->buttonProxyActive(scenePosition)) return false; + if (Rubberband::_internal->_mapView == nullptr) return false; + + if (!isEnabledMultiSelectedState()) deSelectHandles(); + + RubberbandHandle * handlePtr = pickHandle(x,y); + if (handlePtr == nullptr) + { + _internal->_showCursorPreviewLine = isEnabledConstructionState(); + for (auto handle : std::as_const(_internal->_lineHandleList)) + { + handle->setSelection(false); + if (handle->isPositionInside(scenePosition)) + handle->setSelection(); + } + } + else + { + _internal->_showCursorPreviewLine = false; + handlePtr->setSelection(); + } + + if (isEnabledConstructionState()) + { + + if (checkLengthConstrains(1,scenePosition)) + { + updatePreviewLine( scenePosition ); + + if (isSignalConnected(QMetaMethod::fromSignal(&RubberbandMultiPointFeature::currentCursorPositionChanged))) + { + RubberbandDrawable::RubberbandCoordinate mapCoord(Rubberband::_internal->_mapView, scenePosition); + emit currentCursorPositionChanged( Point3D(mapCoord._mapCoordinate.x(),mapCoord._mapCoordinate.y(),0.0) ); + } + } + } + + return false; + } + return false; + } /* RubberbandMultiPointFeature::moveAction */ + + + bool RubberbandMultiPointFeature::movePickedAction( AbstractEventDispatcher*, int x, int y ) + { + adjustConnectedPreviewPixmap(x,y); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (isEnabledSelectedState()) + { + QPointF scenePos( pixel2ScenePosition(x,y) ); + QPointF deltaMove( scenePos-_baseInternal->_lastClickPosition ); + + if (checkLengthConstrains(scenePos, deltaMove)) + { + bool frameIsSelected( ((_internal->_frameHandle != nullptr) && (_internal->_frameHandle->isSelected())) ); + + _baseInternal->_lastClickPosition = scenePos; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && ((handlePointPtr->isSelected()) || (frameIsSelected))) + { + handlePointPtr->moveCoordinate( deltaMove ); + } + } + updatePreviewLine(scenePos); + update(); + } + return true; + } + } + return false; + } /* RubberbandMultiPointFeature::movePickedAction */ + + + bool RubberbandMultiPointFeature::deleteAction(AbstractEventDispatcher*, int x, int y) + { + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (((unsigned int)Rubberband::_internal->_handleList.size() > _minimumPointCount) && (isEnabledSelectedState())) + { + QList> savedHandleList = Rubberband::_internal->_handleList; + QList> savedLineHandleList = _internal->_lineHandleList; + + QPointF scenePosition = pixel2ScenePosition(x,y); + QList>::iterator handleIter = Rubberband::_internal->_handleList.begin(); + QList>::iterator lineHandleIter = _internal->_lineHandleList.begin(); + + while (handleIter != Rubberband::_internal->_handleList.end()) + { + if (handleIter->get()->isPositionInside(scenePosition)) + { + handleIter = Rubberband::_internal->_handleList.erase(handleIter); + lineHandleIter = _internal->_lineHandleList.erase(lineHandleIter); + if (lineHandleIter != _internal->_lineHandleList.end()) + { + if (lineHandleIter != _internal->_lineHandleList.begin()) + { + lineHandleIter->get()->updateCoordinate( 0, (lineHandleIter-1)->get()->getCoordinate(1)._sceneCoordinate ); + } + } + continue; + } + ++handleIter; + if ((lineHandleIter != _internal->_lineHandleList.end()) + && (lineHandleIter+1 != _internal->_lineHandleList.end())) + { + ++lineHandleIter; + } + } /* while handleIter */ + + if (!checkLengthConstrains(scenePosition, QPointF(0,0))) + { + // constrain failed, do not delete yet + Rubberband::_internal->_handleList = savedHandleList; + _internal->_lineHandleList = savedLineHandleList; + } + update(); + activateButtons(); + + disenableSelectedState(); + disenableMultiSelectedState(); + } + return true; + } + return false; + } /* RubberbandMultiPointFeature::deleteAction */ + + + bool RubberbandMultiPointFeature::contextMenuAction( AbstractEventDispatcher*, int,int ) + { + return false; + } /* RubberbandMultiPointFeature::contextMenuAction */ + + + bool RubberbandMultiPointFeature::completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) + { + markFailedConstrains( QPointF(), false ); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (isEnabledConstructionState()) + { + if (!_finishAfterConstruction) + { + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + { + enableModificationState(); + _internal->_cursorPreviewLine = nullptr; // remove preview lines + _internal->_cursorPreviewCloseLine = nullptr; + } + update(); + return true; + } + } + + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) + { + return applyAndEndAction(causer,x,y); + } + } + return false; + } /* RubberbandMultiPointFeature::completeStateAction */ + + + bool RubberbandMultiPointFeature::undoAction( AbstractEventDispatcher * /*causer*/, int,int ) + { + if (rubberbandIsAlive()) + { + if (!Rubberband::_internal->_handleList.empty()) + { + if (isEnabledConstructionState()) + { + Rubberband::_internal->_handleList.pop_back(); + if (!_internal->_lineHandleList.empty()) _internal->_lineHandleList.pop_back(); + + if (_internal->_cursorPreviewLine) _internal->_cursorPreviewLine -> setVisibility( false ); + if (_internal -> _cursorPreviewCloseLine) _internal -> _cursorPreviewCloseLine -> setVisibility( false ); + + update(); + activateButtons(); + } + } + return true; + } + return false; + } /* RubberbandMultiPointFeature::undoAction */ + + + bool RubberbandMultiPointFeature::applyAndEndAction( AbstractEventDispatcher * causer, int x, int y) + { + if ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount) Rubberband::applyAndEndAction( causer,x,y ); + else abortAction(causer,x,y); + return true; + } /* RubberbandMultiPointFeature::applyAndEndAction */ + + + void RubberbandMultiPointFeature::activateButtons() + { + _baseInternal->_hasUndoButton = isEnabledConstructionState(); + _baseInternal->_applyIsPossible = ((unsigned int)Rubberband::_internal->_handleList.size() >= _minimumPointCount); + _baseInternal->_undoIsPossible = (isEnabledConstructionState() && ((unsigned int)Rubberband::_internal->_handleList.size() > _minimumPointCount)); + + Rubberband::activateButtons(); + + } /* RubberbandMultiPointFeature::activateButtons */ + + + void RubberbandMultiPointFeature::update() + { + Rubberband::update(); + + DEBUG_LOG(Rubberband::_internal->_logger) << LOGFUNCTION; + + auto handleLine = _internal->_lineHandleList.begin(); + bool closePolygon = ((unsigned int)Rubberband::_internal->_handleList.size() > 2) && (isClosedEnabled()); + bool firstLoop = !closePolygon; + + QColor lineHandleDrawColor = _drawColor; + QColor lineHandleFillColor = _drawColor; + + if (_internal->_complexGeometry) + { + lineHandleDrawColor.setAlphaF( lineHandleDrawColor.alphaF()*0.5 ); + lineHandleFillColor.setAlphaF( lineHandleFillColor.alphaF()*0.5 ); + } + else + { + if (!_previewSidc.isEmpty()) + { + lineHandleDrawColor.setAlphaF( 0.0 ); + lineHandleFillColor.setAlphaF( 0.0 ); + } + } + + QVector coordinateList; + coordinateList.reserve( Rubberband::_internal->_handleList.size() ); + + for (auto handlePoint : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handlePoint.get()); + if (handlePointPtr) + { + if (handleLine != _internal->_lineHandleList.end()) + { + RubberbandHandle::RubberbandCoordinate && pos = handlePointPtr->getCoordinate(); + RubberbandLineHandle * handleLinePtr = dynamic_cast(handleLine->get()); + + if (!firstLoop) + { + if (handleLinePtr) + { + handleLinePtr -> updateCoordinate( 1, pos._sceneCoordinate ); + + handleLinePtr -> setDrawColor(lineHandleDrawColor); + handleLinePtr -> setFillColor(lineHandleFillColor); + handleLinePtr -> setSelectionColor(_handleSelectionColor); + handleLinePtr -> setPickingRatio( _pickingRatio ); + } + ++handleLine; + if (handleLine != _internal->_lineHandleList.end()) + { + handleLinePtr = dynamic_cast(handleLine->get()); + } + else handleLinePtr = nullptr; + } + else firstLoop = false; + + if (handleLinePtr) handleLinePtr->updateCoordinate( 0, pos._sceneCoordinate ); + } + + coordinateList.append( handlePointPtr->getCoordinate() ); + } + + } /* for handlePoint */ + + + // remove superfluous line handles + while (handleLine != _internal->_lineHandleList.end()) + { + handleLine = _internal->_lineHandleList.erase( handleLine ); + } + + + if (closePolygon) + { + SituationMap::RefPointer & handleLinePtr = _internal->_lineHandleList.first(); + SituationMap::RefPointer & handlePointPtr = Rubberband::_internal->_handleList.last(); + RubberbandHandle::RubberbandCoordinate && pos = dynamic_cast(handlePointPtr.get())->getCoordinate(); + + handleLinePtr->updateCoordinate( 0, pos._sceneCoordinate ); + + if (_fillColor.alpha()) + { + if (!_internal->_fillPath) + { + _internal->_fillPath = new DrawablePainterPath( Rubberband::_internal->_mapView ); + _internal->_fillPath -> setDrawColor( QColor() ); + } + _internal -> _fillPath -> setVisibility( true ); + _internal -> _fillPath -> setFillColor(_fillColor ); + _internal -> _fillPath -> setPath( coordinateList ); + } + else _internal->_fillPath = nullptr; + } + else if (_internal->_fillPath) _internal->_fillPath->setVisibility( false ); + + if ((isEnabledModificationState()) && (coordinateList.size() >= 2) && (_internal->_frameHandle == nullptr)) + { + _internal->_frameHandle = SituationMap::RefPointer( new RubberbandFrameHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, _frameHandleMargin )); + } + + + if (_internal->_frameHandle != nullptr) + { + if (coordinateList.size() >= 2) + { + _internal->_frameHandle -> setDrawColor(_frameColor); + _internal->_frameHandle -> setFillColor(_frameFillColor); + _internal->_frameHandle -> setSelectionColor(_frameSelectionColor); + _internal->_frameHandle -> updateFrameSceneMargin( _frameHandleMargin ); + _internal->_frameHandle -> updateFrameShape( coordinateList ); + _internal->_frameHandle -> drawDisabled( (_frameHandleMargin < 0) ); + } + else _internal->_frameHandle = nullptr; + } + + if (!_previewSidc.isEmpty()) + { + if (!_internal->_previewSymbol) + { + _internal->_previewSymbol = new TransientTacticalSymbol( static_cast(_baseInternal->_map.data()), _previewSidc ); + } + _internal->_previewSymbol->update( coordinateList ); + } + else _internal->_previewSymbol = nullptr; + + } /* RubberbandMultiPointFeature::update */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.h new file mode 100755 index 000000000..9d23ecf98 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature.h @@ -0,0 +1,209 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multi point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 10.04.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband.h" + + +namespace SituationMap { namespace RB +{ + class RubberbandHandle; + class RubberbandFrameHandle; + + /** + @class RubberbandMultiPointFeature rubberband_multi_point_feature.h + @brief Implementation of a rubberband to input multi point features, like areals or linears + @see Rubberband + */ + class SITUATIONMAP_EXPORT RubberbandMultiPointFeature : public Rubberband + { + Q_OBJECT + public: + + /** + @name Construction & Destruction + @{ + */ + + private: + + friend class RubberbandFactory; + friend class RubberbandArrow; + friend class RubberbandCorridor; + friend class RubberbandPolylineWidth; + friend class RubberbandSmoothLine; + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Constructor delegation doesn't work with Visual Studio 2013 + + explicit RubberbandMultiPointFeature( SituationMap::Map * map ); + explicit RubberbandMultiPointFeature( SituationMap::Map * map, bool complexGeometry ); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + /** + Construction of RubberbandMultiPointFeature in most common cases. The parameter parent is + optional. + */ + explicit RubberbandMultiPointFeature( SituationMap::Map * map, QObject * parent = nullptr ); + + /** + Construction of RubberbandMultiPointFeature in case of controlling complex geometry objects, like + splines or multi point symbols. In this case set complexGeometry true. + */ + explicit RubberbandMultiPointFeature( SituationMap::Map * map, bool complexGeometry, QObject * parent = nullptr ); + + public: + + ~RubberbandMultiPointFeature(); + /** @} */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + void resetAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool applyAndEndAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + /** + If set to true a polygon the rubberband draws a polygon otherwise a polyline. + */ + inline void enableClosed( bool flag ) + { + _isClosed = flag; + if (( _isClosed) && (_minimumPointCount < 3)) _minimumPointCount = 3; + if ((!_isClosed) && (_minimumPointCount < 2)) _minimumPointCount = 2; + } /* enableClosed */ + + /** + Gets the value of the close flag + */ + inline bool isClosedEnabled() const { return _isClosed; } + + /** + @name Setting Constrains + @{ + */ + + /** + Defines minimum count of verticies that has to be defined by users input to complete the rubberband with apply signal. The default value is 0. + */ + inline void minimumPointCount( unsigned int x ) + { + if (( _isClosed) && (x < 3)) x = 3; + if ((!_isClosed) && (x < 2)) x = 2; + _minimumPointCount = x; + } /* minimumPointCount */ + + + /** + Defines maximum count of verticies that can be aceppted by the rubberband. A values of 0 stands for undefined. The default is 0. + */ + inline void maximumPointCount( unsigned int x ) + { + if (x > 0) + { + if (( _isClosed) && (x < 3)) x = 3; + if ((!_isClosed) && (x < 2)) x = 2; + } + _maximumPointCount = x; + } /* maximumPointCount */ + + + /** + Defines the maximum length of a segment (distance between to adjaced verticies). A value of 0 stands for no limit is set. The default is 0. + */ + inline void maximumSegmentLength( qreal x ) { _maximumSegmentLength = (x > 0) ? x : 0.0; } + inline qreal getMaximumSegmentLength() const{ return _maximumSegmentLength; } + + /** + Defines the maximum length of the complete polyline. \c enableClosed must be set to false. A value of 0 stands for no limit is set. The default is 0. + */ + inline void maximumLength( qreal x ) { _maximumLength = (x > 0) ? x : 0.0; } + /** @} */ + inline qreal getMaximumLength() const { return _maximumLength; } + + + virtual QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + virtual QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + virtual void setCoordinateVector( const QVector &, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + signals: + + /** + The signal will be emitted, when the mouse cursor has changed and the rubberband is in construction mode and is accepting new input coordinates. + */ + void currentCursorPositionChanged(const Point3D &mapCoord); + + + protected: + + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + + bool _isClosed; + + unsigned int _minimumPointCount; + unsigned int _maximumPointCount; + + qreal _maximumSegmentLength; + qreal _maximumLength; + + void activateButtons() override; + + void update() override; + + // some helper methodes + RubberbandHandle * constructPoint( QPointF scenePos ); + + void constructPoint(const QPointF &point, CoordinateSystem cs ); + + bool selectHandles( int x, int y, const QPointF & scenePos ); + + bool checkLengthConstrains( unsigned int checkHandleIdx, QPointF & scenePos ); + bool checkLengthConstrains( const QPointF & scenePos, const QPointF & deltaScenePos ); + void updatePreviewLine( const QPointF & scenePosition ); + + RubberbandFrameHandle * getFrameHandle() const; + + void setCheckMaxAngle( double phi ); + + }; /* RubberbandMultiPointFeature */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.cpp new file mode 100755 index 000000000..cd86bc68a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.cpp @@ -0,0 +1,660 @@ +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define point feature geometry in 3d +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include + +#include "rubberband_multi_point_feature_3d.h" +#include "rubberband_base_internal.h" + +#include "event_mouse_navigator.h" + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +namespace SituationMap { namespace RB +{ + + class RubberbandMultiPointFeature3D::InternalData + { + public: + + QVector m_marker; + GraphicalObjectPath *m_path; + + QVector m_coordinates; + + int m_currentIndex; + int m_selectionIndex; + QFuture m_pickingTask; + QFuture m_selectionTask; + QPoint m_pickingPoint; + QPointF * m_startMove; + ScreenToLocationHelper * m_screenToMap; + QPointer m_toElevation; + PositionCont m_positionList3D; + bool m_doCompleteAction; + + + InternalData() + : m_marker() + , m_path(nullptr) + , m_coordinates() + , m_currentIndex(-1) + , m_selectionIndex(-1) + , m_pickingTask() + , m_selectionTask() + , m_pickingPoint() + , m_startMove(nullptr) + , m_screenToMap() + , m_toElevation(nullptr) + , m_positionList3D() + , m_doCompleteAction(false) + { + m_marker.reserve(100); + m_coordinates.reserve(100); + } // InternalData() + + ~InternalData() + { + delete m_startMove; + for (auto marker : std::as_const(m_marker)) + delete marker; + delete m_path; + delete m_screenToMap; + } // ~InternalData() + + }; // RubberbandPointFeature3D::InternalData + + + RubberbandMultiPointFeature3D::RubberbandMultiPointFeature3D( SituationMap::Map * map, QObject * parent, bool isActive ) + : Rubberband3D(map,parent,isActive) + , _internal( new RubberbandMultiPointFeature3D::InternalData() ) + , _isClosed(false) + , _isCircular(false) + { + MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); + Esri::ArcGISRuntime::SceneGraphicsView *view = mapImpl->getEsriSceneGraphicsView(); + + connect( view, &Esri::ArcGISRuntime::SceneGraphicsView::identifyGraphicsOverlaysCompleted, this, &RubberbandMultiPointFeature3D::onGetPickingResult ); + _internal->m_screenToMap = new ScreenToLocationHelper(view); + connect( _internal->m_screenToMap, &ScreenToLocationHelper::mouseScenePositionChanged, this, &RubberbandMultiPointFeature3D::onMapPosition ); // ToDO + + } // RubberbandMultiPointFeature3D::RubberbandMultiPointFeature3D( SituationMap::Map * map, QObject * parent = nullptr, bool isActive ) + + + RubberbandMultiPointFeature3D::~RubberbandMultiPointFeature3D() + { + delete _internal; + _internal = nullptr; + } // RubberbandMultiPointFeature3D::~RubberbandMultiPointFeature3D() + + void RubberbandMultiPointFeature3D::activateButtons() + { + _baseInternal->_hasUndoButton = false; + _baseInternal->_undoIsPossible = false; + _baseInternal->_applyIsPossible = (_internal->m_coordinates.size() > ((_isClosed && !_isCircular) ? 2 : 1)); + + RubberbandBase::activateButtons(); + + } /* RubberbandMultiPointFeature3D::activateButtons */ + + + void circleShape( GraphicalObjectPath *pathObj, const PositionCont &positionList ) + { + if (positionList.size() < 2) return; + + PositionCont circleShape; + + double cx = positionList[0].x(); + double cy = positionList[0].y(); + double rx = positionList[1].x()-cx; + double ry = positionList[1].y()-cy; + + double corrLon = cos(cy / 57.2957795147); // correct factor from nautic simulation to get a real circle + double dPhi = MathConst::pi / 50; // delta angle to rasterize the circle to a polyline + + auto sqr = [](double x) -> double { return x * x; }; + double r = sqrt(sqr(rx*corrLon) + sqr(ry) ); + + double phi = 0.0; + while (phi < MathConst::pi_two) + { + double sinPhi = sin(phi); + double cosPhi = cos(phi); + double px = cx + r * cosPhi / corrLon; + double py = cy + r*sinPhi; + + circleShape.append(Point3D(px, py, 0.0)); + phi += dPhi; + } + + pathObj->setPositions(circleShape); + + } // circleShape + + + void RubberbandMultiPointFeature3D::onMapPosition(const Point3D &mapCoordinate) + { + if (_internal->m_currentIndex < 0) return; + + if (qIsNaN(mapCoordinate.x())) return; // illegal map position + if (qIsNaN(mapCoordinate.y())) return; + + if ((!_internal->m_toElevation) && (RubberbandBase::_baseInternal->_map->layerFactory().elevationAvailable())) + { + _internal->m_toElevation = RubberbandBase::_baseInternal->_map->layerFactory().locationToElevation(); // layerFactory has ownership + } + + if ((std::numeric_limits::max()) == _internal->m_currentIndex) + { + // move all coordinates + if (_internal->m_startMove) + { + QPointF deltaMove = mapCoordinate.toPointF() - *(_internal->m_startMove); + + _internal->m_positionList3D.clear(); + _internal->m_positionList3D.reserve(_internal->m_coordinates.size()); + + for (int i = 0; i < _internal->m_coordinates.size(); ++i) + { + const QPointF & pos = _internal->m_coordinates[i] += deltaMove; + _internal->m_marker[i]->setOrigin( pos ); + + if (_internal->m_toElevation) + { + QMetaObject::Connection conn; + + conn = connect(_internal->m_toElevation.data(), &SituationMap::LocationToElevationHelper::elevationCompleted, this, [this,pos,conn](double elevation) + { + disconnect(conn); + _internal->m_positionList3D << Point3D(pos.x(),pos.y(),elevation); + if (_internal->m_positionList3D.size() == _internal->m_coordinates.size()) + { + if (_internal->m_path) _internal->m_path->setPositions( _internal->m_positionList3D ); + update(); + } + + // qCritical() << "elevation at (" << pos.x() << " , " << pos.y() << ") : " << elevation; + + }); + _internal->m_toElevation->locationToElevation(pos); + } + else + { + _internal->m_positionList3D << Point3D::fromQPointF(pos); + } + } + + if (_internal->m_toElevation) + { + if (_internal->m_path) _internal->m_path->setPositions( _internal->m_positionList3D ); + update(); + } + } + else _internal->m_startMove = new QPointF(mapCoordinate.toPointF()); + return; + } + + if (_internal->m_currentIndex >= _internal->m_coordinates.size()) return; + + MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); + Esri::ArcGISRuntime::SceneGraphicsView *view = mapImpl->getEsriSceneGraphicsView(); + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = mapImpl->getGraphicsLayer(mapImpl->layerFactory().rubberbandLayer()); + + if (nullptr == view) return; + + for (int idx = _internal->m_positionList3D.size(); idx <= _internal->m_currentIndex; ++idx) + { + _internal->m_positionList3D << mapCoordinate; + } + _internal->m_coordinates[_internal->m_currentIndex] = mapCoordinate.toPointF(); + _internal->m_positionList3D[_internal->m_currentIndex] = mapCoordinate; + + // qCritical() << "elevation idx=" << _internal->m_currentIndex <<" at (" << mapCoordinate.x() << " , " << mapCoordinate.y() << ") : " << mapCoordinate.z(); + + GraphicalObjectPictureMarker *&marker = _internal->m_marker[_internal->m_currentIndex]; + + if (nullptr == marker) + { + marker = new GraphicalObjectPictureMarker( view, graphicsLayer, QStringLiteral("RubberbandMultiPointFeature3D"), INVALID_MAP_ID ); + + marker->setImage(markerImage()); + marker->setOrigin( mapCoordinate ); + marker->updateView(); + marker->setPickable(true); + + Esri::ArcGISRuntime::Graphic *graphic = marker->graphic(); + if (graphic) + { + Esri::ArcGISRuntime::AttributeListModel *attLisModel = graphic->attributes(); + if (attLisModel) + { + // qCritical() << "Rubberband3D-Item =" << _internal->m_currentIndex; + attLisModel->insertAttribute(QStringLiteral("Rubberband3D-Item"),_internal->m_currentIndex); + } + } + } + else marker->setOrigin( mapCoordinate ); + + if (_internal->m_coordinates.size() > ((_isClosed && !_isCircular) ? 2 : 1)) + { + + if (nullptr == _internal->m_path) + { + bool isPolygon = (_isClosed || _isCircular); + + _internal->m_path = new GraphicalObjectPath(isPolygon ? GraphicalObjectPathType::Polygon : GraphicalObjectPathType::Polyline, view, graphicsLayer, QStringLiteral("RubberbandMultiPointFeature3D"), INVALID_MAP_ID); + + _internal->m_path->rLineStyle().setLineColor(_handleDrawColor); + _internal->m_path->rLineStyle().setLinePattern(LinePattern::Solid); + _internal->m_path->rLineStyle().setLineWidth(3); + _internal->m_path->setPickable(false); + if (isPolygon) + { + _internal->m_path->rFillStyle().setFillColor(_frameFillColor); + } + + if (_isCircular) + { + circleShape(_internal->m_path, _internal->m_positionList3D); + } + else _internal->m_path->setPositions(_internal->m_positionList3D); + + _internal->m_path->updateView(); + } + else + { + if (_isCircular) + { + circleShape(_internal->m_path, _internal->m_positionList3D); + } + else _internal->m_path->setPositions(_internal->m_positionList3D); + } + } + + if (_isCircular && (0 == _internal->m_currentIndex) && (1 == _internal->m_coordinates.size())) + { + _internal->m_coordinates << _internal->m_coordinates.first(); + _internal->m_positionList3D << _internal->m_positionList3D.first(); + _internal->m_marker << nullptr; + _internal->m_currentIndex++; + + // qCritical() << QCursor::pos() << " != " << view->viewport()->mapToGlobal(_internal->m_pickingPoint); + + _internal->m_doCompleteAction = (QCursor::pos() != view->viewport()->mapToGlobal(_internal->m_pickingPoint)); + + if (!_internal->m_doCompleteAction) + { + // Click with out moving the mouse, set a default size of the circle and let the user do some editing + // The rubberband doesn't complete automatically in this case. + _internal->m_screenToMap->screenToLocation(_internal->m_pickingPoint.x()+100, _internal->m_pickingPoint.y()); + } + } + + update(); + + if (!QGuiApplication::mouseButtons().testFlag(Qt::MouseButton::LeftButton)) + { + if (_isCircular && _internal->m_doCompleteAction && (1 == _internal->m_currentIndex)) + { + completeStateAction(nullptr, 0, 0); + } + else + { + updateButtonPosition(); + activateButtons(); + } + } + + } // void RubberbandMultiPointFeature3D::onMapPosition(const QPointF &mapCoordinate) + + + void RubberbandMultiPointFeature3D::onGetPickingResult(const QUuid &taskId, const QList &identifyResults) + { + if (_internal->m_pickingTask.taskId() == taskId) + { + for (auto iResult : identifyResults) + { + const QList &gResults = iResult->graphics(); + for (auto pickedGraphic : gResults) + { + if (pickedGraphic->attributes()) + { + bool ok = false; + int pickedIndex = pickedGraphic->attributes()->attributeValue(QStringLiteral("Rubberband3D-Item")).toInt(&ok); + + if (ok) + { + qCritical() << "Picking index " << pickedIndex; + _internal->m_currentIndex = pickedIndex; + _internal->m_screenToMap->screenToLocation(_internal->m_pickingPoint.x(), _internal->m_pickingPoint.y()); + + if (pickedIndex < _internal->m_marker.size()) + { + GraphicalObjectPictureMarker *&marker = _internal->m_marker[pickedIndex]; + + if (marker) + { + marker->setImage(markerSelectionImage()); + marker->updateView(); + } + } + return; + } + } + + } // foreach pickedGraphic + } // foreach iResult + + if ((!_isCircular) || (_internal->m_coordinates.size() < 2)) + { + // add new coordinate and calculate map location + _internal->m_currentIndex = _internal->m_marker.size(); + _internal->m_marker << nullptr; + _internal->m_coordinates << QPointF(); + + _internal->m_screenToMap->screenToLocation(_internal->m_pickingPoint.x(), _internal->m_pickingPoint.y()); + } + } + + if (_internal->m_selectionTask.taskId() == taskId) + { + for (auto iResult : identifyResults) + { + const QList &gResults = iResult->graphics(); + for (auto pickedGraphic : gResults) + { + if (pickedGraphic->attributes()) + { + bool ok = false; + int pickedIndex = pickedGraphic->attributes()->attributeValue(QStringLiteral("Rubberband3D-Item")).toInt(&ok); + + if ((ok) && (pickedIndex < _internal->m_marker.size())) + { + GraphicalObjectPictureMarker *&marker = _internal->m_marker[pickedIndex]; + + if (marker) + { + _internal->m_selectionIndex = pickedIndex; + marker->setImage(markerSelectionImage()); + marker->updateView(); + return; + } + } + } + } + } + } + if ((_internal->m_selectionIndex >= 0) && (_internal->m_selectionIndex < _internal->m_marker.size())) + { + GraphicalObjectPictureMarker *&marker = _internal->m_marker[_internal->m_selectionIndex]; + + if (marker) + { + marker->setImage(markerImage()); + marker->updateView(); + } + _internal->m_selectionIndex = -1; + } + } // void RubberbandMultiPointFeature3D::onGetPickingResult(const QUuid &taskId, const QList &identifyResults) + + + bool RubberbandMultiPointFeature3D::selectAction(AbstractEventDispatcher *, int x, int y) + { + delete _internal->m_startMove; + _internal->m_startMove = nullptr; + + if (rubberbandIsAlive()) + { + if (_baseInternal->buttonProxyActive(QPointF(x,y))) return false; + + MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); + + _internal->m_pickingTask.cancel(); // cancel old task + _internal->m_pickingPoint = QPoint(x,y); + _internal->m_pickingTask = mapImpl->getEsriGeoView()->identifyGraphicsOverlays(x,y,EventMouseNavigator::pickTolerance(),false,std::numeric_limits::max()); + _internal->m_currentIndex = -1; + + return true; + } + return false; + } // bool RubberbandMultiPointFeature3D::selectAction(AbstractEventDispatcher *, int x, int y) + + + bool RubberbandMultiPointFeature3D::setCoordinateAction(AbstractEventDispatcher *, int x, int y) + { + delete _internal->m_startMove; + _internal->m_startMove = nullptr; + + if (rubberbandIsAlive()) + { + _baseInternal->_lastClickPosition = QPointF(x,y); + + if (_baseInternal->buttonProxyActive(_baseInternal->_lastClickPosition)) return false; + + _internal->m_screenToMap->screenToLocation(x,y); + + if ((_internal->m_currentIndex >= 0) && (_internal->m_currentIndex < _internal->m_marker.size())) + { + GraphicalObjectPictureMarker *&marker = _internal->m_marker[_internal->m_currentIndex]; + + if (marker) + { + marker->setImage(markerImage()); + marker->updateView(); + } + } + + return true; + } + return false; + } // bool RubberbandMultiPointFeature3D::setCoordinateAction(AbstractEventDispatcher *, int x, int y) + + + bool RubberbandMultiPointFeature3D::movePickedAction(AbstractEventDispatcher *, int x, int y) + { + if (rubberbandIsAlive()) + { + _internal->m_screenToMap->screenToLocation(x,y); + return true; + } + return false; + } // bool RubberbandMultiPointFeature3D::movePickedAction(AbstractEventDispatcher *, int x, int y) + + + bool RubberbandMultiPointFeature3D::moveAction( AbstractEventDispatcher*, int x, int y ) + { + if (rubberbandIsAlive()) + { + if (_baseInternal->buttonProxyActive(QPointF(x,y))) return false; + + MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); + + _internal->m_selectionTask.cancel(); // cancel old task + _internal->m_selectionTask = mapImpl->getEsriGeoView()->identifyGraphicsOverlays(x,y,EventMouseNavigator::pickTolerance(),false,std::numeric_limits::max()); + + if ((_internal->m_selectionIndex >= 0) && (_internal->m_selectionIndex < _internal->m_marker.size())) + { + GraphicalObjectPictureMarker *&marker = _internal->m_marker[_internal->m_selectionIndex]; + + if (marker) + { + marker->setImage(markerImage()); + marker->updateView(); + } + } + _internal->m_selectionIndex = -1; + } + return false; + } // bool RubberbandMultiPointFeature3D::moveAction(AbstractEventDispatcher*,int,int) + + + + bool RubberbandMultiPointFeature3D::completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) + { + if (_internal->m_coordinates.size() > ((_isClosed && !_isCircular) ? 2 : 1)) + { + return applyAndEndAction(causer,x,y); + } + return false; + } // bool RubberbandMultiPointFeature3D::completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) + + + bool RubberbandMultiPointFeature3D::contextMenuAction ( AbstractEventDispatcher*,int,int ) { return rubberbandIsAlive(); } + bool RubberbandMultiPointFeature3D::deleteAction ( AbstractEventDispatcher*,int,int ) { return rubberbandIsAlive(); } + bool RubberbandMultiPointFeature3D::undoAction ( AbstractEventDispatcher*,int,int ) { return rubberbandIsAlive(); } + + + unsigned int RubberbandMultiPointFeature3D::coordinateCount() const + { + return (_internal->m_coordinates.size()); + } // unsigned int RubberbandMultiPointFeature3D::coordinateCount() const + + + void RubberbandMultiPointFeature3D::update() + { + for (auto marker : std::as_const(_internal->m_marker)) + if (marker) marker->updateView(); + if (_internal->m_path) + _internal->m_path->updateView(); + + externalUpdate(); + + } // RubberbandMultiPointFeature3D::update() + + + QVector RubberbandMultiPointFeature3D::coordinateVector( CoordinateSystem cs ) const + { + QVector coordinateVector; + coordinateVector.reserve(_internal->m_coordinates.size()); + + for (const auto &mapCoordinate : std::as_const(_internal->m_coordinates)) + coordinateVector << projectCoordinateFromMap( cs, mapCoordinate ); + return coordinateVector; + } // QVector RubberbandMultiPointFeature3D::coordinateVector( CoordinateSystem cs ) const + + + QPointF RubberbandMultiPointFeature3D::singleCoordinate( unsigned int idx, CoordinateSystem cs ) const + { + if (coordinateCount() > idx) + return coordinateVector(cs).at(idx); + + return QPointF(); + } // QPointF RubberbandMultiPointFeature3D::singleCoordinate( unsigned int idx, CoordinateSystem cs ) const + + + void RubberbandMultiPointFeature3D::setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs ) + { + _internal->m_coordinates.clear(); + for (const auto &geoCoordinate : coordinateVector) + _internal->m_coordinates << projectCoordinateToMap( cs, geoCoordinate ); + + if ((!_internal->m_toElevation) && (_baseInternal->_map->layerFactory().elevationAvailable())) + { + _internal->m_toElevation = _baseInternal->_map->layerFactory().locationToElevation(); // layerFactory has ownership + } + + // now get elevations + _internal->m_positionList3D.clear(); + for (const auto &pos : std::as_const(_internal->m_coordinates)) + { + if (_internal->m_toElevation) + { + QMetaObject::Connection conn; + + conn = connect(_internal->m_toElevation.data(), &SituationMap::LocationToElevationHelper::elevationCompleted, this, [this,pos,conn](double elevation) + { + disconnect(conn); + + _internal->m_positionList3D << Point3D(pos.x(),pos.y(),elevation); + if (_internal->m_positionList3D.size() == _internal->m_coordinates.size()) + { + if (_internal->m_path) _internal->m_path->setPositions( _internal->m_positionList3D ); + update(); + } + }); + _internal->m_toElevation->locationToElevation(pos); + } + else _internal->m_positionList3D << Point3D::fromQPointF(pos); + } + } // void RubberbandMultiPointFeature3D::setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs ) + + + void RubberbandMultiPointFeature3D::setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs) + { + _internal->m_coordinates.clear(); + _internal->m_positionList3D.clear(); + + for (const auto &geoCoordinate : coordinateVector) + { + const QPointF & projCoordinate = projectCoordinateToMap( cs, geoCoordinate.toPointF() ); + _internal->m_coordinates << projCoordinate; + _internal->m_positionList3D << Point3D(projCoordinate.x(),projCoordinate.y(),geoCoordinate.z()); + } + } // void RubberbandMultiPointFeature3D::setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs) + + + QVector RubberbandMultiPointFeature3D::coordinateVector3D(CoordinateSystem cs) const + { + QVector coordinateVector; + + coordinateVector.reserve(_internal->m_coordinates.size()); + + for (const auto &mapCoordinate : std::as_const(_internal->m_coordinates)) + coordinateVector << Point3D::fromQPointF( projectCoordinateFromMap(cs,mapCoordinate) ); + + return coordinateVector; + } // QVector RubberbandMultiPointFeature3D::coordinateVector3D(CoordinateSystem cs) const + + + Point3D RubberbandMultiPointFeature3D::singleCoordinate3D(unsigned int idx, CoordinateSystem cs) const + { + if (coordinateCount() > idx) + { + return coordinateVector3D(cs).at(idx); + } + return Point3D(); + } // Point3D RubberbandMultiPointFeature3D::singleCoordinate3D(unsigned int idx, CoordinateSystem cs) const + + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.h new file mode 100755 index 000000000..5edca24f7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_multi_point_feature_3d.h @@ -0,0 +1,128 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multipoint feature geometry in 3d +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_3d.h" +#include "IdentifyGraphicsOverlayResult.h" + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandPointFeature rubberband_point_feature.h + @brief Implementation of a rubberband to input single coodinates for building point features + @see Rubberband3D + */ + class SITUATIONMAP_EXPORT RubberbandMultiPointFeature3D : public Rubberband3D + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + + explicit RubberbandMultiPointFeature3D( SituationMap::Map * map, QObject * parent = nullptr, bool isActive = true ); + + public: + + virtual ~RubberbandMultiPointFeature3D(); + + RubberbandMultiPointFeature3D(const RubberbandMultiPointFeature3D &) = delete; + RubberbandMultiPointFeature3D & operator = (const RubberbandMultiPointFeature3D &) = delete; + /** @} */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + inline void enableClosed( bool flag ) + { + _isClosed = flag; + } /* enableClosed */ + + /** + Gets the value of the close flag + */ + inline bool isClosedEnabled() const { return _isClosed; } + + unsigned int coordinateCount() const override; + + QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + void setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + Point3D singleCoordinate3D( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + QVector coordinateVector3D( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + void setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + inline void enableCircular(bool flag) + { + _isCircular = flag; + } // enableCircular + + inline bool isCircularEnabled() const { return _isCircular; } + + protected: + + virtual bool buttonProxyActive( const QPointF & scenePos) const { Q_UNUSED(scenePos); return false; } + + void activateButtons() override; + + void update() override; + + /* + Forward declaration of internal container class for member variables. The derived classes of Rubberband + often have also a declaration of InternalData for there own internal members. + */ + class InternalData; + + InternalData * _internal; + + bool _isClosed; + bool _isCircular; + + protected slots: + + void onGetPickingResult(const QUuid &taskId, const QList<::Esri::ArcGISRuntime::IdentifyGraphicsOverlayResult*> &identifyResults); + void onMapPosition( const Point3D &mapCoordinate ); + + }; /* RubberbandMultiPointFeature3D */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.cpp new file mode 100755 index 000000000..3ab843e1d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.cpp @@ -0,0 +1,133 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_point_cluster.h" +#include "rubberband_point_handle.h" +#include "rubberband_drawable.h" +#include "rubberband_handle_proxy.h" +#include "transient_tactical_symbol.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +namespace SituationMap { namespace RB +{ + + struct RubberbandPointCluster::InternalData + { + Q_DISABLE_COPY(InternalData) + InternalData() = default; + SituationMap::RefPointer _previewSymbol; + }; /* RubberbandMultiPointFeature::InternalData */ + + + RubberbandPointCluster::RubberbandPointCluster( SituationMap::Map * map, QObject * parent, bool isActive ) : RubberbandPointCluster( map, INT_MAX, parent, isActive ) {;} + + + RubberbandPointCluster::RubberbandPointCluster( SituationMap::Map * map, unsigned int maxCoordinates, QObject * parent, bool isActive ) + : RubberbandPointFeature(map,parent,isActive), _internal(new InternalData()), _minimalPointCount(1) + { + _maximalPointCount = maxCoordinates; + } /* RubberbandPointCluster::RubberbandPointCluster */ + + + RubberbandPointCluster::~RubberbandPointCluster() + { + delete _internal; + } /* RubberbandPointCluster::~RubberbandPointCluster */ + + + void RubberbandPointCluster::activateButtons() + { + _baseInternal->_hasUndoButton = isEnabledConstructionState(); + _baseInternal->_applyIsPossible = (static_cast(Rubberband::_internal->_handleList.size()) >= _minimalPointCount); + _baseInternal->_undoIsPossible = (isEnabledConstructionState() && (static_cast(Rubberband::_internal->_handleList.size()) > _minimalPointCount)); + Rubberband::activateButtons(); + } /* RubberbandMultiPointFeature::activateButton */ + + + void RubberbandPointCluster::setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs ) + { + RubberbandPointFeature::setCoordinateVector( coordinateVector, cs ); + enableModificationState(); + activateButtons(); + } /* RubberbandPointCluster::setCoordinateVector */ + + + bool RubberbandPointCluster::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) + { + bool ok = false; + + if (rubberbandIsAlive()) + { + ok = RubberbandPointFeature::setCoordinateAction(causer,x,y); + if (ok) activateButtons(); + } + return ok; + } /* RubberbandPointCluster::setCoordinateAction */ + + + void RubberbandPointCluster::update() + { + RubberbandPointFeature::update(); + if (!_previewSidc.isEmpty()) + { + if (!_internal->_previewSymbol) + { + _internal->_previewSymbol = new TransientTacticalSymbol( static_cast(_baseInternal->_map.data()), _previewSidc ); + } + + QVector coordinateList; + coordinateList.reserve( Rubberband::_internal->_handleList.size() ); + + for (auto handlePoint : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handlePoint.get()); + if (handlePointPtr) + { + coordinateList.append( handlePointPtr->getCoordinate() ); + } + } + _internal->_previewSymbol->update( coordinateList ); + } + else _internal->_previewSymbol = nullptr; + } /* RubberbandPointCluster::update */ + + + bool RubberbandPointCluster::deleteAction(AbstractEventDispatcher * causer, int x, int y) + { + if (rubberbandIsAlive()) + { + if (static_cast(RubberbandPointFeature::_internal->_handleList.size()) <= _minimalPointCount) return true; + return RubberbandPointFeature::deleteAction(causer,x,y); + } + return false; + } /* RubberbandPointCluster::deleteAction */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.h new file mode 100755 index 000000000..839206aa1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_cluster.h @@ -0,0 +1,102 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multiple point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 18.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include "rubberband_point_feature.h" + + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandPointCluster rubberband_point_cluster.h + @brief Implementation of a rubberband to input single coodinates for building point feature clusters + @see Rubberband, RubberbandPointFeature + */ + class SITUATIONMAP_EXPORT RubberbandPointCluster : public RubberbandPointFeature + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + + RubberbandPointCluster( SituationMap::Map * map, QObject * parent, bool isActive = true ); + + RubberbandPointCluster( SituationMap::Map * map + , unsigned int maxCoordinates = std::numeric_limits::max() + , QObject * parent = nullptr + , bool isActive = true ); + + public: + + virtual ~RubberbandPointCluster(); + + RubberbandPointCluster(const RubberbandPointCluster &) = delete; + RubberbandPointCluster & operator = (const RubberbandPointCluster &) = delete; + /** @} */ + + bool setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + /** + Defines the minimum number of coordinates to input. Default is 1 + */ + inline void minimumPointCount(unsigned int n) { _minimalPointCount = (n > 0) ? n : 1; } + + /** + Returns the setting for minimum number of coordinates to input. + */ + inline unsigned int getMinimumPointCount() { return _minimalPointCount; } + + void setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + protected: + + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + unsigned int _minimalPointCount; + + void activateButtons() override; + + void update() override; + + }; /* RubberbandPointCluster */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.cpp new file mode 100755 index 000000000..21a437a8c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.cpp @@ -0,0 +1,311 @@ + +/** +** +** @RDE_HEADER@ +** Implementationof rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_point_feature.h" +#include "rubberband_point_handle.h" +#include "rubberband_drawable.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include +#include + + +namespace SituationMap { namespace RB +{ + + RubberbandPointFeature::RubberbandPointFeature( SituationMap::Map * map, QObject * parent, bool isActive ) + : Rubberband(map,parent,isActive), _maximalPointCount(1) + { + } /* RubberbandPointFeature::RubberbandPointFeature */ + + + RubberbandPointFeature::~RubberbandPointFeature() + { + } /* RubberbandPointFeature::~RubberbandPointFeature */ + + + QVector RubberbandPointFeature::coordinateVector( CoordinateSystem cs ) const + { + QVector coordinateVector; + coordinateVector.reserve(_internal->_handleList.size()); + + for (auto handle : std::as_const(_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + + coordinateVector.push_back(projectCoordinateFromMap( cs, handlePointPtr->getCoordinate()._mapCoordinate )); + } + return coordinateVector; + } /* RubberbandPointFeature::coordinateVector */ + + QVector RubberbandPointFeature::coordinateVectorPixel() const + { + QVector coordinateVector; + coordinateVector.reserve(_internal->_handleList.size()); + + for (auto handle : std::as_const(_internal->_handleList)) + { + RubberbandPointHandle *handlePointPtr = dynamic_cast(handle.get()); + coordinateVector.push_back(handlePointPtr->getPixelCoordinate()); + } + return coordinateVector; + } + + + QPointF RubberbandPointFeature::singleCoordinate(unsigned int idx, CoordinateSystem cs ) const + { + if (idx < (unsigned int) _internal->_handleList.size()) + { + const RubberbandPointHandle * handle = dynamic_cast(_internal->_handleList[idx].get()); + + return projectCoordinateFromMap( cs, handle->getCoordinate()._mapCoordinate ); + } + return QPointF(); + } /* RubberbandPointFeature::singleCoordinate */ + + + void RubberbandPointFeature::setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs ) + { + _internal->_handleList.clear(); + + for (auto p : coordinateVector) + { + const QPointF &mapPos = projectCoordinateToMap( cs, p ); + + RubberbandPointHandle * handlePointPtr = new RubberbandPointHandle( _internal->_mapView + , _internal->_mapScene + , Esri::ArcGISRuntime::Point(mapPos.x(),mapPos.y(),Rubberband::_internal->_mapView->map()->spatialReference()) ); + _internal->_handleList.append( SituationMap::RefPointer(handlePointPtr) ); + } + update(); + } /* RubberbandPointFeature::setCoordinateVector */ + + + bool RubberbandPointFeature::selectAction( AbstractEventDispatcher * , int x, int y ) + { + if (rubberbandIsAlive()) + { + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (_baseInternal->buttonProxyActive(scenePos)) return false; + + _baseInternal->_lastClickPosition = scenePos; + + RubberbandHandle * handlePtr = pickHandle(x,y); + if (handlePtr) + { + handlePtr->setSelection(); + enableSelectedState(); + eventHandled = true; + } + + if (!isEnabledSelectedState()) + { + if (isEnabledConstructionState()) + { + if (!buttonProxyActive(scenePos)) + { + RubberbandPointHandle * handlePtr = new RubberbandPointHandle( _internal->_mapView, _internal->_mapScene, scenePos ); + + _internal->_handleList.append( SituationMap::RefPointer(handlePtr) ); + handlePtr->setSelection(); + enableSelectedState(); + eventHandled = true; + } + } + } + + if (eventHandled) + { + update(); + deactivateButtons(); + } + + + return eventHandled; + } + return false; + } /* RubberbandPointFeature::selectAction */ + + + + bool RubberbandPointFeature::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) + { + if ((rubberbandIsAlive()) && (_internal->_mapView) && (_internal->_mapScene)) + { + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + Rubberband::_internal->_lastClickPosition = scenePos; + + if (isEnabledSelectedState()) + { + if (_baseInternal->buttonProxyActive(scenePos)) return false; + for (auto handle : std::as_const(_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && (handlePointPtr->isSelected())) + { + handlePointPtr->setSelection(false); + handlePointPtr->moveCoordinate( scenePos - _baseInternal->_lastClickPosition ); + eventHandled = true; + } + } + disenableSelectedState(); + disenableMultiSelectedState(); + } + else if (_baseInternal->buttonProxyActive(scenePos)) return false; + + if (eventHandled) update(); + + if ((isEnabledConstructionState()) && (_internal->_handleList.size() >= static_cast(_maximalPointCount))) + { + completeStateAction( causer, x, y ); + eventHandled = true; + } + return eventHandled; + } + return false; + } /* RubberbandPointFeature::setCoordinateAction */ + + + bool RubberbandPointFeature::moveAction( AbstractEventDispatcher*, int x, int y ) + { + adjustConnectedPreviewPixmap(x,y); + + if (rubberbandIsAlive()) + { + if (!isEnabledMultiSelectedState()) deSelectHandles(); + + RubberbandHandle * handlePtr = pickHandle(x,y); + if (handlePtr) + { + handlePtr->setSelection(); + return false; + } + } + return false; + } /* RubberbandPointFeature::moveAction */ + + + bool RubberbandPointFeature::movePickedAction( AbstractEventDispatcher*, int x, int y ) + { + adjustConnectedPreviewPixmap(x,y); + + if ((rubberbandIsAlive()) && (_internal->_mapView) && (_internal->_mapScene)) + { + if (isEnabledSelectedState()) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + QPointF deltaMove(scenePos-_baseInternal->_lastClickPosition); + + _baseInternal->_lastClickPosition = scenePos; + + bool eventHandled = false; + + for (auto handle : std::as_const(_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && (handlePointPtr->isSelected())) + { + handlePointPtr->moveCoordinate( deltaMove ); + eventHandled = true; + } + } + if (eventHandled) update(); + } + return false; + } + return false; + } /* RubberbandPointFeature::movePickedAction */ + + + bool RubberbandPointFeature::contextMenuAction( AbstractEventDispatcher*, int , int ) + { + return rubberbandIsAlive(); + } /* RubberbandPointFeature::contextMenuAction */ + + + bool RubberbandPointFeature::completeStateAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (!_internal->_handleList.empty()) + { + return applyAndEndAction(causer,x,y); + } + return false; + } /* RubberbandPointFeature::completeStateAction */ + + + bool RubberbandPointFeature::undoAction( AbstractEventDispatcher*, int , int ) + { + if (rubberbandIsAlive()) + { + if (!_internal->_handleList.empty()) + { + _internal->_handleList.pop_back(); + update(); + activateButtons(); + } + return true; + } + return false; + } /* RubberbandPointFeature::undoAction */ + + + bool RubberbandPointFeature::deleteAction(AbstractEventDispatcher*, int x, int y) + { + if ((rubberbandIsAlive()) && (_internal->_mapView) && (_internal->_mapScene)) + { + if ((_internal->_handleList.size() > 1) && (isEnabledSelectedState())) + { + QPointF scenePosition = pixel2ScenePosition(x,y); + QList>::iterator handleIter = _internal->_handleList.begin(); + + while (handleIter != _internal->_handleList.end()) + { + if (handleIter->get()->isPositionInside(scenePosition)) + { + handleIter = _internal->_handleList.erase(handleIter); + continue; + } + ++handleIter; + } + update(); + activateButtons(); + } + return true; + } + return false; + } /* RubberbandPointFeature::deleteAction */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.h new file mode 100755 index 000000000..38d425bdc --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature.h @@ -0,0 +1,99 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include "rubberband.h" + + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandPointFeature rubberband_point_feature.h + @brief Implementation of a rubberband to input single coodinates for building point features + @see Rubberband + */ + class SITUATIONMAP_EXPORT RubberbandPointFeature : public Rubberband + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + friend class RubberbandPointCluster; + + explicit RubberbandPointFeature( SituationMap::Map * map, QObject * parent = nullptr, bool isActive = true ); + + public: + ~RubberbandPointFeature(); + /** @} */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + /** + Defines the maximum number of coordinates to input. Default is 1 + */ + inline void maximumPointCount(unsigned int n) { _maximalPointCount = (n > 0) ? n : std::numeric_limits::max(); } + + /** + Returns the setting for maximum number of coordinates to input. + */ + inline unsigned int getMaximumPointCount() { return _maximalPointCount; } + + + QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + QVector coordinateVectorPixel() const; + + QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + void setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + protected: + + virtual bool buttonProxyActive( const QPointF & scenePos) const { Q_UNUSED(scenePos); return false; } + + + unsigned int _maximalPointCount; + + + }; /* RubberbandPointFeature */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.cpp new file mode 100755 index 000000000..a10e4fbd8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.cpp @@ -0,0 +1,272 @@ +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define point feature geometry in 3d +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include + +#include "rubberband_point_feature_3d.h" +#include "rubberband_base_internal.h" + +#include +#include + +#include +#include +#include + +#include + +#include + + +namespace SituationMap { namespace RB +{ + + struct RubberbandPointFeature3D::InternalData + { + QPoint m_screenCordinate = QPoint(std::numeric_limits::min(),std::numeric_limits::min()); + Point3D m_position; + GraphicalObjectPictureMarker * m_marker = nullptr; + ScreenToLocationHelper * m_screenToMap = nullptr; + AbstractEventDispatcher * m_causer = nullptr; + bool m_modifyPosition = false; + bool m_positionReady = false; + + InternalData() = default; + + ~InternalData() + { + delete m_marker; + delete m_screenToMap; + } + }; // RubberbandPointFeature3D::InternalData + + + RubberbandPointFeature3D::RubberbandPointFeature3D( SituationMap::Map * map, QObject * parent, bool isActive ) + : Rubberband3D(map,parent,isActive) + , _internal( new RubberbandPointFeature3D::InternalData() ) + { + MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); + Esri::ArcGISRuntime::SceneGraphicsView *view = mapImpl->getEsriSceneGraphicsView(); + + _internal->m_screenToMap = new ScreenToLocationHelper(view); + connect( _internal->m_screenToMap, &ScreenToLocationHelper::mouseScenePositionChanged, this, &RubberbandPointFeature3D::onMapPosition ); + + } // RubberbandPointFeature3D::RubberbandPointFeature3D( SituationMap::Map * map, QObject * parent = nullptr, bool isActive ) + + + RubberbandPointFeature3D::~RubberbandPointFeature3D() + { + delete _internal; + _internal = nullptr; + } // RubberbandPointFeature3D::~RubberbandPointFeature3D() + + + void RubberbandPointFeature3D::enableModificationState() + { + _internal->m_modifyPosition = true; + _internal->m_positionReady = true; + } // void RubberbandPointFeature3D::enableModificationState() + + + void RubberbandPointFeature3D::onMapPosition(const Point3D &mapCoordinate) + { + if (qIsNaN(mapCoordinate.x())) return; // illegal map position + if (qIsNaN(mapCoordinate.y())) return; + + _internal->m_position = mapCoordinate; + + if (nullptr == _internal->m_marker) + { + MapImpl *mapImpl = static_cast(_baseInternal->_map.data()); + Esri::ArcGISRuntime::SceneGraphicsView *view = mapImpl->getEsriSceneGraphicsView(); + Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer = mapImpl->getGraphicsLayer(mapImpl->layerFactory().rubberbandLayer()); + + if (nullptr == view) return; + + _internal->m_marker = new GraphicalObjectPictureMarker( view, graphicsLayer, QStringLiteral("RubberbandPointFeature3D"), INVALID_MAP_ID ); + + _internal->m_marker->setImage(markerImage()); + } + _internal->m_marker->setOrigin( mapCoordinate ); + + update(); + + if (_internal->m_causer) + { + _internal->m_positionReady = true; + completeStateAction(_internal->m_causer,_internal->m_screenCordinate.x(), _internal->m_screenCordinate.y()); + } + } // void RubberbandPointFeature3D::onMapPosition(const QPointF &mapCoordinate) + + + bool RubberbandPointFeature3D::selectAction(AbstractEventDispatcher *, int x, int y) + { + if (rubberbandIsAlive()) + { + if (nullptr ==_internal->m_causer) + { + _internal->m_screenCordinate = QPoint(x,y); + _internal->m_screenToMap->screenToLocation(x,y); + } + return true; + } + return false; + } // bool RubberbandPointFeature3D::selectAction(AbstractEventDispatcher *, int x, int y) + + + bool RubberbandPointFeature3D::setCoordinateAction(AbstractEventDispatcher * causer, int x, int y) + { + if (rubberbandIsAlive()) + { + _internal->m_causer = (_internal->m_modifyPosition) ? nullptr : causer; + _internal->m_screenCordinate = QPoint(x,y); + _internal->m_screenToMap->screenToLocation(x,y); + + return true; + } + return false; + } // bool RubberbandPointFeature3D::setCoordinateAction(AbstractEventDispatcher * causer, int x, int y) + + + bool RubberbandPointFeature3D::movePickedAction(AbstractEventDispatcher *, int x, int y) + { + if (rubberbandIsAlive()) + { + if (nullptr == _internal->m_causer) + { + _internal->m_screenCordinate = QPoint(x,y); + _internal->m_screenToMap->screenToLocation(x,y); + } + return true; + } + return false; + } // bool RubberbandPointFeature3D::movePickedAction(AbstractEventDispatcher *, int x, int y) + + + bool RubberbandPointFeature3D::moveAction(AbstractEventDispatcher*,int,int) + { + if (rubberbandIsAlive()) + { + } + return false; + } // bool RubberbandPointFeature3D::moveAction(AbstractEventDispatcher*,int,int) + + + + bool RubberbandPointFeature3D::completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) + { + if (coordinateCount() > 0) + { + return applyAndEndAction(causer,x,y); + } + return false; + } // bool RubberbandPointFeature3D::completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) + + + bool RubberbandPointFeature3D::contextMenuAction ( AbstractEventDispatcher*,int,int ) { return rubberbandIsAlive(); } + bool RubberbandPointFeature3D::deleteAction ( AbstractEventDispatcher*,int,int ) { return rubberbandIsAlive(); } + bool RubberbandPointFeature3D::undoAction ( AbstractEventDispatcher*,int,int ) { return rubberbandIsAlive(); } + + + unsigned int RubberbandPointFeature3D::coordinateCount() const + { + return (_internal->m_positionReady) ? 1 : 0; + } // unsigned int RubberbandPointFeature3D::coordinateCount() const + + + void RubberbandPointFeature3D::update() + { + if (_internal->m_marker) _internal->m_marker->updateView(); + externalUpdate(); + } // RubberbandPointFeature3D::update() + + + QVector RubberbandPointFeature3D::coordinateVector( CoordinateSystem cs ) const + { + if (coordinateCount() > 0) + { + const QPointF &geoPos = projectCoordinateFromMap( cs, _internal->m_position.toPointF() ); + return QVector() << geoPos; + } + return QVector(); + } // QVector RubberbandPointFeature3D::coordinateVector( CoordinateSystem cs ) const + + + QPointF RubberbandPointFeature3D::singleCoordinate( unsigned int idx, CoordinateSystem cs ) const + { + Q_UNUSED(idx); + if (coordinateCount() > 0) + { + return coordinateVector(cs).value(0); + } + return QPointF(); + } // QPointF RubberbandPointFeature3D::singleCoordinate( unsigned int idx, CoordinateSystem cs ) const + + + void RubberbandPointFeature3D::setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs ) + { + if (coordinateVector.size() > 0) + { + const QPointF &mapPos = projectCoordinateToMap(cs,coordinateVector[0]); + onMapPosition( Point3D::fromQPointF(mapPos) ); + } + } // void RubberbandPointFeature3D::setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs ) + + + void RubberbandPointFeature3D::setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs ) + { + if (coordinateVector.size() > 0) + { + const QPointF &mapPos = projectCoordinateToMap(cs,coordinateVector[0].toPointF()); + _internal->m_position = Point3D(mapPos.x(),mapPos.y(),coordinateVector[0].z()); + } + } // void RubberbandPointFeature3D::setCoordinateVector(const QVector & coordinateVector, CoordinateSystem cs ) + + + QVector RubberbandPointFeature3D::coordinateVector3D( CoordinateSystem cs ) const + { + if (coordinateCount() > 0) + { + const QPointF &geoPos = projectCoordinateFromMap( cs, _internal->m_position.toPointF() ); + return QVector() << Point3D(geoPos.x(),geoPos.y(),_internal->m_position.z()); + } + return QVector(); + } // QVector RubberbandPointFeature3D::coordinateVector3D( CoordinateSystem cs ) const + + + Point3D RubberbandPointFeature3D::singleCoordinate3D(unsigned int idx , CoordinateSystem cs) const + { + Q_UNUSED(idx); + if (coordinateCount() > 0) + { + return coordinateVector3D(cs).value(0); + } + return Point3D(); + } // Point3D RubberbandPointFeature3D::singleCoordinate3D(unsigned int idx, CoordinateSystem cs) const + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.h new file mode 100755 index 000000000..ed3f980ee --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_feature_3d.h @@ -0,0 +1,104 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define point feature geometry in 3d +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_3d.h" + + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandPointFeature rubberband_point_feature.h + @brief Implementation of a rubberband to input single coodinates for building point features + @see Rubberband + */ + class SITUATIONMAP_EXPORT RubberbandPointFeature3D : public Rubberband3D + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + + explicit RubberbandPointFeature3D( SituationMap::Map * map, QObject * parent = nullptr, bool isActive = true ); + + public: + + ~RubberbandPointFeature3D(); + /** @} */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + unsigned int coordinateCount() const override; + + QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + void setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + Point3D singleCoordinate3D( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + QVector coordinateVector3D( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + void setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + void enableModificationState(); + + protected: + + virtual bool buttonProxyActive( const QPointF & scenePos) const { Q_UNUSED(scenePos); return false; } + + void update() override; + + /* + Forward declaration of internal container class for member variables. The derived classes of Rubberband + often have also a declaration of InternalData for there own internal members. + */ + struct InternalData; + + InternalData * _internal; + + protected slots: + + void onMapPosition( const Point3D &mapCoordinate ); + + }; /* RubberbandPointFeature3D */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.cpp new file mode 100755 index 000000000..55087de3e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.cpp @@ -0,0 +1,390 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_point_handle.h" + +#include + +#include + +#include +#include + +namespace SituationMap { namespace RB +{ + + RubberbandPointHandle::RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView + , QGraphicsScene * scene + , const QPointF & scenePosition ) + : RubberbandHandle(mapView,scene) + , _pointSymbol(nullptr) + , _coordinate( mapView, scenePosition ) + , _sceneOffset(0,0) + , _iconPixmap(nullptr) + , _boundingRect(nullptr) + { + zBufferPlane( ZBufferPlane::PointHandle ); + buildHandle( scenePosition ); + } /* RubberbandPointHandle::RubberbandPointHandle */ + + + RubberbandPointHandle::RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView + , QGraphicsScene * scene + , const Esri::ArcGISRuntime::Point & mapCoordinate ) + : RubberbandHandle(mapView,scene) + , _pointSymbol(nullptr) + , _coordinate( mapView, mapCoordinate ) + , _sceneOffset(0,0) + , _iconPixmap(nullptr) + , _boundingRect(nullptr) + { + zBufferPlane( ZBufferPlane::PointHandle ); + buildHandle( _coordinate._sceneCoordinate ); + } /* RubberbandPointHandle::RubberbandPointHandle */ + + + RubberbandPointHandle::RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView + , QGraphicsScene * scene + , const QPointF & scenePosition + , const QPixmap & icon ) + : RubberbandHandle(mapView,scene) + , _pointSymbol(nullptr) + , _coordinate( mapView, scenePosition ) + , _sceneOffset(0,0) + , _iconPixmap( new QPixmap(icon) ) + , _boundingRect(nullptr) + { + zBufferPlane( ZBufferPlane::PointHandle ); + buildHandle( scenePosition ); + } /* RubberbandPointHandle::RubberbandPointHandle */ + + + RubberbandPointHandle::RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView * mapView + , QGraphicsScene * scene + , const Esri::ArcGISRuntime::Point & mapCoordinate + , const QPixmap & icon ) + : RubberbandHandle(mapView,scene) + , _pointSymbol(nullptr) + , _coordinate( mapView, mapCoordinate ) + , _sceneOffset(0,0) + , _iconPixmap( new QPixmap(icon) ) + , _boundingRect(nullptr) + { + zBufferPlane( ZBufferPlane::PointHandle ); + buildHandle( _coordinate._sceneCoordinate ); + } /* RubberbandPointHandle::RubberbandPointHandle */ + + + void RubberbandPointHandle::setIcon( const QPixmap & icon ) + { + if (_iconPixmap == nullptr) + { + _iconPixmap = new QPixmap(icon); + if (_pointSymbol) + { + _scene->removeItem(_pointSymbol); + delete _pointSymbol; + _pointSymbol = nullptr; + } + buildHandle( _coordinate._sceneCoordinate ); + return; + } + + *_iconPixmap = icon; + + QGraphicsPixmapItem * iconItem = static_cast( _pointSymbol); + if (iconItem) + { + iconItem->setPixmap( *_iconPixmap ); + QRectF bbox = iconItem->boundingRect(); + bbox.moveCenter( _coordinate._sceneCoordinate ); + if (_boundingRect) _boundingRect->setRect(bbox); + } + else buildHandle( _coordinate._sceneCoordinate ); + } /* RubberbandPointHandle::setIcon */ + + + void RubberbandPointHandle::buildHandle( const QPointF & scenePosition ) + { + if (_scene != nullptr) + { + if (_iconPixmap != nullptr) + { + if (_iconPixmap->isNull()) + { + delete _iconPixmap; + _iconPixmap = nullptr; + } + } + + if (_iconPixmap == nullptr) + { + QGraphicsEllipseItem * kuller = new QGraphicsEllipseItem(); + + _pointSymbol = kuller; + + if (_pointSymbol != nullptr) + { + _scene -> addItem( _pointSymbol ); + + kuller -> setPos( scenePosition+_sceneOffset ); + kuller -> setRect( -qreal(_pickingRatio), -qreal(_pickingRatio), qreal(2*_pickingRatio), qreal(2*_pickingRatio) ); + kuller -> setZValue( _zBufferValue ); + + kuller -> setCacheMode(QGraphicsItem::CacheMode::NoCache, QSize(_pickingRatio*2,_pickingRatio*2) ); + kuller -> setFlags( QGraphicsItem::ItemIgnoresTransformations ); + kuller -> setVisible( _isVisible ); + } + } + else + { + QGraphicsPixmapItem * icon = new QGraphicsPixmapItem(*_iconPixmap); + + _pointSymbol = icon; + + if (_pointSymbol != nullptr) + { + _scene -> addItem( _pointSymbol ); + + qreal xOff = -(_iconPixmap->width() * 0.5); + qreal yOff = -(_iconPixmap->height() * 0.5); + + icon -> setPos( scenePosition+_sceneOffset ); + icon -> setZValue( _zBufferValue ); + icon -> setFlags( QGraphicsItem::ItemIgnoresTransformations ); + icon -> setVisible( _isVisible ); + icon -> setOffset( xOff, yOff ); + icon -> setShapeMode( QGraphicsPixmapItem::BoundingRectShape ); + + QRectF bbox = icon->boundingRect(); + bbox.moveCenter( scenePosition ); + + _boundingRect = new QGraphicsRectItem( bbox ); + _boundingRect -> setZValue(_zBufferValue-1); + _boundingRect -> setVisible( _isVisible ); + + _scene -> addItem( _boundingRect ); + + } + } + } + } /* RubberbandPointHandle::buildHandle */ + + + RubberbandPointHandle::~RubberbandPointHandle() + { + delete _iconPixmap; + if (_scene) + { + + if (nullptr != _boundingRect) + { + _scene->removeItem(_boundingRect); + delete _boundingRect; + } + + if (nullptr != _pointSymbol) + { + _scene->removeItem(_pointSymbol); + delete _pointSymbol; + } + } + + _pointSymbol = nullptr; + _iconPixmap = nullptr; + _boundingRect = nullptr; + + } /* RubberbandHandle::~Rubberbandhandle */ + + + QPointF RubberbandPointHandle::getRotatedSceneOffset() const + { + auto rotate = [](const QPointF & p, qreal sinA, qreal cosA) -> QPointF { return QPointF(p.x()*cosA+p.y()*sinA,p.y()*cosA-p.x()*sinA); }; + + qreal mapRotatePhi = _mapView->mapRotation() * MathConst::pi / 180.0; + + qreal sinMap = sin(mapRotatePhi); + qreal cosMap = cos(mapRotatePhi); + + return rotate( _sceneOffset, sinMap, cosMap ); + } /* RubberbandPointHandle::getRotatedSceneOffset */ + + + void RubberbandPointHandle::adjustCoordinates() + { + const QPointF & rotatedSceneOffset = getRotatedSceneOffset(); + + if (_iconPixmap == nullptr) + { + static_cast(_pointSymbol)->setPos(_coordinate.mapToScene(_mapView)+rotatedSceneOffset); + } + else + { + QGraphicsPixmapItem * icon = static_cast( _pointSymbol); + + const QPointF & scenePosition = _coordinate.mapToScene(_mapView); + icon -> setPos( scenePosition+rotatedSceneOffset ); + + QRectF bbox = icon->boundingRect(); + bbox.moveCenter( scenePosition+rotatedSceneOffset ); + _boundingRect->setRect(bbox); + } + + DEBUG_LOG(_logger) << LOGFUNCTION << " map-coordinate = " << QString::number(_coordinate._mapCoordinate.x(),'f',15) + << " ; " << QString::number(_coordinate._mapCoordinate.y(),'f',15) + << " scene-coordinate = " << _coordinate._sceneCoordinate.x() << " ; " << _coordinate._sceneCoordinate.y(); + emit adjusted(); + } /* RubberbandPointHandle::onDo_adjustCoordinates */ + + + void RubberbandPointHandle::adjustWindow( const QRectF& ) + { + onDo_adjustCoordinates(); + } /* RubberbandPointHandle::onDo_adjustWindow */ + + + void RubberbandPointHandle::updateGraphic() + { + if (_pointSymbol) + { + if (_pointSymbol != nullptr) + { + if (_iconPixmap == nullptr) + { + QGraphicsEllipseItem * kuller = static_cast( _pointSymbol); + + kuller -> setPen( QPen(_drawColor) ); + + QRadialGradient gradient(0, 0, _pickingRatio, 0, 0); + QColor gradientColor = (_isSelected) ? _selectionColor : _fillColor; + + gradient.setColorAt( 0, gradientColor); + gradient.setColorAt( 1, QColor(gradientColor.red(), gradientColor.green(), gradientColor.blue(), 0) ); + kuller -> setBrush( QBrush(gradient) ); + kuller -> setVisible( _isVisible ); + } + else + { + QGraphicsPixmapItem * icon = static_cast( _pointSymbol); + + _boundingRect -> setPen( QPen(_drawColor) ); + const QColor &backColor = (_isSelected) ? _selectionColor : _fillColor; + _boundingRect -> setBrush( QBrush(backColor) ); + _boundingRect -> setVisible( _isVisible ); + icon -> setVisible( _isVisible ); + } + } + } + } /* RubberbandPointHandle::updateGraphic */ + + + void RubberbandPointHandle::updateCoordinate(const QPointF & scenePosition) + { + const QPointF & rotatedSceneOffset = getRotatedSceneOffset(); + + _coordinate.updateSceneCoordinate( _mapView, scenePosition ); + + if (_pointSymbol != nullptr) + { + if (_iconPixmap == nullptr) + { + QGraphicsEllipseItem * kuller = static_cast( _pointSymbol); + kuller -> setPos( scenePosition+rotatedSceneOffset ); + } + else + { + QGraphicsPixmapItem * icon = static_cast( _pointSymbol); + icon -> setPos( scenePosition+rotatedSceneOffset ); + + QRectF bbox = icon->boundingRect(); + bbox.moveCenter( scenePosition+rotatedSceneOffset ); + + _boundingRect->setRect(bbox); + } + } + } /* RubberbandPointHandle::updateCoordinate */ + + + void RubberbandPointHandle::updateCoordinate() + { + const QPointF & rotatedSceneOffset = getRotatedSceneOffset(); + + if (_pointSymbol != nullptr) + { + if (_iconPixmap == nullptr) + { + QGraphicsEllipseItem * kuller = static_cast( _pointSymbol); + kuller -> setPos( _coordinate._sceneCoordinate+rotatedSceneOffset ); + } + else + { + QGraphicsPixmapItem * icon = static_cast( _pointSymbol); + icon -> setPos( _coordinate._sceneCoordinate+rotatedSceneOffset ); + + QRectF bbox = icon->boundingRect(); + + bbox.moveCenter( _coordinate._sceneCoordinate+rotatedSceneOffset ); + _boundingRect->setRect(bbox); + } + } + } /* RubberbandPointHandle::updateCoordinate */ + + + void RubberbandPointHandle::moveCoordinate(const QPointF & sceneDelta) + { + _coordinate.updateSceneCoordinate( _mapView, _coordinate._sceneCoordinate+sceneDelta ); + updateCoordinate(); + } /* RubberbandPointHandle::moveCoordinate */ + + + void RubberbandPointHandle::setCoordinate(const RubberbandHandle::RubberbandCoordinate & p) + { + _coordinate = p; + updateCoordinate(); + } /* RubberbandPointHandle::setCoordinate */ + + + RubberbandHandle::RubberbandCoordinate RubberbandPointHandle::getCoordinate() const { return _coordinate; } + + QPointF RubberbandPointHandle::getPixelCoordinate() const + { + return _coordinate._sceneCoordinate; + } + + + bool RubberbandPointHandle::isPositionInside( const QPointF & scenePosition ) const + { + if (_pointSymbol) + { + return _pointSymbol->contains( _coordinate._sceneCoordinate+getRotatedSceneOffset() - scenePosition ); + } + return false; + } /* RubberbandPointHandle::isPositionInside */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.h new file mode 100755 index 000000000..82601814f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_point_handle.h @@ -0,0 +1,116 @@ +/** +** +** @RDE_HEADER@ +** Definition of base class for rubberband handles +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include + +#include "rubberband_handle.h" + +class QGraphicsItem; +class QGraphicsScene; + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandPointHandle rubberband_point_handle.h + @brief Implementation of a point handle for rubberband internal use. + @see RubberbandHandle + */ + class RubberbandPointHandle : public RubberbandHandle + { + Q_OBJECT + public: + + /** + @name Construction & Destruction + @{ + */ + RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView* + , QGraphicsScene* + , const QPointF & scenePosition ); + + RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView* + , QGraphicsScene* + , const ::Esri::ArcGISRuntime::Point & mapCoordinate ); + + RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView* + , QGraphicsScene* + , const QPointF & scenePosition + , const QPixmap & icon ); + + RubberbandPointHandle( Esri::ArcGISRuntime::MapGraphicsView* + , QGraphicsScene* + , const ::Esri::ArcGISRuntime::Point & mapCoordinate + , const QPixmap & icon ); + + ~RubberbandPointHandle(); + /** @} */ + + void setIcon( const QPixmap & icon ); + + bool isPositionInside( const QPointF & scenePosition ) const override; + virtual void updateCoordinate( const QPointF & scenePosition ); + virtual void moveCoordinate( const QPointF & sceneDelta ); + + virtual void setCoordinate( const RubberbandCoordinate & ); + virtual RubberbandCoordinate getCoordinate() const; + + QPointF getPixelCoordinate() const; + + inline void setSceneOffset(const QPointF & offset) { _sceneOffset = offset; } + + protected: + + QGraphicsItem * _pointSymbol; + RubberbandCoordinate _coordinate; + QPointF _sceneOffset; + QPixmap * _iconPixmap; + QGraphicsRectItem * _boundingRect; + + void updateGraphic() override; + + virtual void updateCoordinate(); + + void adjustCoordinates() override; + void adjustWindow( const QRectF & ) override; + + private: + + QPointF getRotatedSceneOffset() const; + + void buildHandle( const QPointF & scenePosition ); + + + + }; /* RubberbandPointHandle */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.cpp new file mode 100755 index 000000000..686979853 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.cpp @@ -0,0 +1,318 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multi point feature geometry with an additional width coordinate +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Linux, Windows 7, Qt 5.7. ArcGISRuntimeQt 100 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 7.12.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_polyline_width.h" +#include "rubberband_point_handle.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include + +#include + + + +namespace SituationMap { namespace RB +{ + + struct RubberbandPolylineWidth::InternalData + { + SituationMap::RefPointer _widthItem; + }; /* RubberbandPolylineWidth::InternalData*/ + + + RubberbandPolylineWidth::RubberbandPolylineWidth( SituationMap::Map * map, QObject * parent ) + : RubberbandMultiPointFeature(map,true,parent), _internal( new InternalData() ) + { + } /* RubberbandPolylineWidth::RubberbandPolylineWidth */ + + + RubberbandPolylineWidth::~RubberbandPolylineWidth() + { + delete _internal; + } /* RubberbandPolylineWidth::~RubberbandPolylineWidth */ + + + void RubberbandPolylineWidth::buildWidthItem(const QPointF & mapPos1, const QPointF & mapPos2) const + { + double dx = mapPos2.x() - mapPos1.x(); + double dy = mapPos2.y() - mapPos1.y(); + QPointF handleMapPos = mapPos1 + QPointF(dx,dy)*0.35 + QPointF(dy,dx)*0.35; + + setUpWidthItem(handleMapPos); + } /* RubberbandPolylineWidth::buildWidthItem */ + + + void RubberbandPolylineWidth::setUpWidthItem(const QPointF & mapPos) const + { + RubberbandHandle::RubberbandCoordinate coordinate( Rubberband::_internal->_mapView, convert(mapPos,Rubberband::_internal->_mapView->map()->spatialReference()) ); + + _internal->_widthItem = new RubberbandPointHandle(Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, coordinate._sceneCoordinate); + } /* RubberbandPolylineWidth::setUpWidthItem */ + + + QVector RubberbandPolylineWidth::coordinateVector( CoordinateSystem cs ) const + { + QVector polylineCoords = RubberbandMultiPointFeature::coordinateVector(cs); + if (polylineCoords.size() > 1) + { + if (_internal->_widthItem == nullptr) + { + RubberbandPointHandle * pointHandle1Ptr = static_cast(Rubberband::_internal->_handleList[0].get()); + RubberbandPointHandle * pointHandle2Ptr = static_cast(Rubberband::_internal->_handleList[1].get()); + Esri::ArcGISRuntime::Point p1 = pointHandle1Ptr->getCoordinate()._mapCoordinate; + Esri::ArcGISRuntime::Point p2 = pointHandle2Ptr->getCoordinate()._mapCoordinate; + + buildWidthItem( QPointF(p1.x(),p1.y()), QPointF(p2.x(),p2.y()) ); + + } + polylineCoords += projectCoordinateFromMap(cs, _internal->_widthItem->getCoordinate()._mapCoordinate); + } + return polylineCoords; + } /* RubberbandPolylineWidth::coordinateVector */ + + + QPointF RubberbandPolylineWidth::singleCoordinate( unsigned int idx, CoordinateSystem cs ) const + { + if (static_cast(Rubberband::_internal->_handleList.size()) == idx) + { + return projectCoordinateFromMap(cs, _internal->_widthItem->getCoordinate()._mapCoordinate); + } + return RubberbandMultiPointFeature::singleCoordinate(idx, cs); + } /* RubberbandPolylineWidth::singleCoordinate */ + + + void RubberbandPolylineWidth::setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs ) + { + if (coordinateVector.size() > 2) + { + QVector lineCoords = coordinateVector; + setUpWidthItem( projectCoordinateToMap(cs, lineCoords.last()) ); + lineCoords.removeLast(); + RubberbandMultiPointFeature::setCoordinateVector(lineCoords, cs); + + return; + } + if (coordinateVector.size() == 2) + { + const QPointF & mapP1 = projectCoordinateToMap(cs, coordinateVector[0]); + const QPointF & mapP2 = projectCoordinateToMap(cs, coordinateVector[1]); + + buildWidthItem(mapP1, mapP2); + } + RubberbandMultiPointFeature::setCoordinateVector(coordinateVector,cs); + } /* RubberbandPolylineWidth::setCoordinateVector */ + + + bool RubberbandPolylineWidth::selectAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) + { + _internal->_widthItem->setSelection(false); + QPointF scenePos(pixel2ScenePosition(x, y)); + if (_baseInternal->buttonProxyActive(scenePos)) return false; + if (_internal->_widthItem->isPositionInside(scenePos)) + { + _internal->_widthItem->setSelection(); + _baseInternal->_lastClickPosition = scenePos; + update(); + return true; + } + } + return RubberbandMultiPointFeature::selectAction(causer,x,y); + } /* RubberbandPolylineWidth::selectAction */ + + + bool RubberbandPolylineWidth::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) + { + if (_internal->_widthItem->isSelected()) + { + QPointF scenePos(pixel2ScenePosition(x, y)); + QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; + + _internal->_widthItem->setSelection(false); + _internal->_widthItem->moveCoordinate(deltaMove); + update(); + return true; + } + } + return RubberbandMultiPointFeature::setCoordinateAction(causer, x, y); + } /* RubberbandPolylineWidth::setCoordinateAction */ + + + bool RubberbandPolylineWidth::movePickedAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) + { + if (_internal->_widthItem->isSelected()) + { + adjustConnectedPreviewPixmap(x, y); + + QPointF scenePos(pixel2ScenePosition(x, y)); + QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; + _baseInternal->_lastClickPosition = scenePos; + + _internal->_widthItem->moveCoordinate(deltaMove); + update(); + return false; + } + else + { + RubberbandPointHandle * handlePoint1Ptr = dynamic_cast(Rubberband::_internal->_handleList[0].get()); + RubberbandPointHandle * handlePoint2Ptr = dynamic_cast(Rubberband::_internal->_handleList[1].get()); + + if (handlePoint1Ptr && handlePoint2Ptr) + { + const RubberbandDrawable::RubberbandCoordinate & loc1Start = handlePoint1Ptr->getCoordinate(); + const RubberbandDrawable::RubberbandCoordinate & loc2Start = handlePoint2Ptr->getCoordinate(); + + bool ret = RubberbandMultiPointFeature::movePickedAction(causer, x, y); + + const RubberbandDrawable::RubberbandCoordinate & loc1End = handlePoint1Ptr->getCoordinate(); + const RubberbandDrawable::RubberbandCoordinate & loc2End = handlePoint2Ptr->getCoordinate(); + + QPointF deltaMove1 = loc1End._sceneCoordinate - loc1Start._sceneCoordinate; + QPointF deltaMove2 = loc2End._sceneCoordinate - loc2Start._sceneCoordinate; + + + if ((fabs(deltaMove1.x()) >= 1) || (fabs(deltaMove1.y()) >= 1) || (fabs(deltaMove2.x()) >= 1) || (fabs(deltaMove2.y()) >= 1)) + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * First or second point have been moved. Now recalculate the coordinate of the width handle point as follows: + * The width handle "C" and the first two coordinates ("A","B") are providing a triangle: + * B + * / \ + * / \ + * /sinB \ + * / cosB \ + * / \ + * / \ + * c / \ a + * / \ + * / \ + * / \ + * / \ + * / sinA sinC \ + * / cosA cosC \ + * A ------------------------- C + * b + * If the coordinates of "A" or "B" are changed, try to keep the ratio of angles and sides of the triangle constant. For this + * use some trigonometric identities. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + auto rotate = []( const QPointF & p, qreal sinPhi, qreal cosPhi ) -> QPointF + { + return QPointF( p.x()*cosPhi - p.y()*sinPhi, p.y()*cosPhi + p.x()*sinPhi ); + }; + + QPointF A = loc2Start._sceneCoordinate; + QPointF B = loc1Start._sceneCoordinate; + QPointF C = _internal->_widthItem->getCoordinate()._sceneCoordinate; + + qreal a = QLineF(B,C).length(); // side length of triangle + qreal b = QLineF(A,C).length(); + qreal c = QLineF(A,B).length(); + qreal cosA = (b*b+c*c-a*a)/(b*c*2); // cosin rule + qreal cosB = (a*a+c*c-b*b)/(a*c*2); + qreal cosC = (a*a+b*b-c*c)/(a*b*2); + qreal sinA = sqrt(1-cosA*cosA); // pythagorean trigonometric identity + qreal sinB = sqrt(1-cosB*cosB); + qreal sinC = sqrt(1-cosC*cosC); + + // now we have all angle of the triangle, set A and B to the new coordinates ... + + A = loc2End._sceneCoordinate; + B = loc1End._sceneCoordinate; + + // ... and calculate the coordinate of C with the same ratio of angles as before ... + + QPointF AB = B-A; + +// c = QLineF(QPointF(0,0),AB).length(); + + if (sinC != 0.0) + { + // ... rotate AB and scale it to the right length + C = A + rotate(AB,sinA,cosA) * sinB / sinC; + + // the RubberbandPointHandle dosn't has a set to set scene coordinate. Using the moveCoordinate methode instead. + + _internal->_widthItem->moveCoordinate( C - _internal->_widthItem->getCoordinate()._sceneCoordinate ); + } + // else could only happen when the triangle is degenrated to a line + update(); + } + return ret; + } + } + } + return RubberbandMultiPointFeature::movePickedAction(causer, x, y); + } /* RubberbandPolylineWidth::movePickedAction */ + + + bool RubberbandPolylineWidth::moveAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) + { + QPointF scenePos(pixel2ScenePosition(x, y)); + _internal->_widthItem->setSelection( _internal->_widthItem->isPositionInside(scenePos) ); + } + return RubberbandMultiPointFeature::moveAction( causer, x, y); + + } /* RubberbandPolylineWidth::moveAction */ + + + void RubberbandPolylineWidth::update() + { + if ((_internal->_widthItem.get() == nullptr) && (Rubberband::_internal->_handleList.size() > 1)) + { + RubberbandPointHandle * pointHandle1Ptr = static_cast(Rubberband::_internal->_handleList[0].get()); + RubberbandPointHandle * pointHandle2Ptr = static_cast(Rubberband::_internal->_handleList[1].get()); + Esri::ArcGISRuntime::Point p1 = pointHandle1Ptr->getCoordinate()._mapCoordinate; + Esri::ArcGISRuntime::Point p2 = pointHandle2Ptr->getCoordinate()._mapCoordinate; + + buildWidthItem(QPointF(p1.x(), p1.y()), QPointF(p2.x(), p2.y())); + } + + if (_internal->_widthItem) + { + Rubberband::_internal->_handleList += static_cast>(_internal->_widthItem); + RubberbandMultiPointFeature::update(); + Rubberband::_internal->_handleList.removeLast(); + } + else RubberbandMultiPointFeature::update(); + + } /* RubberbandPolylineWidth::update */ + + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.h new file mode 100755 index 000000000..759068155 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_polyline_width.h @@ -0,0 +1,88 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multi point feature geometry with an additional width coordinate +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Linux, Windows 7, Qt 5.7. ArcGISRuntimeQt 100 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 7.12.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_multi_point_feature.h" + + +namespace SituationMap { namespace RB +{ + + class RubberbandLineHandle; + class RubberbandFrameHandle; + + /** + @class RubberbandPolylineWidth rubberband_smooth_line.h + @brief Implementation of a multi point feature rubberband with rounded edges. + @see RubberbandFactory, Rubberband, RubberbandMultiPointFeature + + */ + class SITUATIONMAP_EXPORT RubberbandPolylineWidth : public RubberbandMultiPointFeature + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + + explicit RubberbandPolylineWidth( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + + virtual ~RubberbandPolylineWidth(); + /** @} */ + + virtual QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + virtual QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + virtual void setCoordinateVector( const QVector &, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + protected: + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + + void update() override; + + void buildWidthItem(const QPointF & mapPos1, const QPointF & mapPos2) const; + void setUpWidthItem(const QPointF & mapPos) const; + }; /* RubberbandSmoothLine */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.cpp new file mode 100755 index 000000000..b256868b2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.cpp @@ -0,0 +1,1913 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_rectangle.h" +#include "rubberband_point_handle.h" +#include "rubberband_frame_handle.h" +#include "rubberband_drawable.h" +#include "drawable_painter_path.h" +#include "rubberband_handle_proxy.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + +#include +#include + +#include +#include + +#include + + +namespace SituationMap { namespace RB +{ + + static bool epsilonEqual( qreal x1, qreal x2, qreal thresholdValue = 0.001 ) + { + return (fabs(x1-x2) < thresholdValue); + } /* epsilonEqual */ + + static qreal distance(const QPointF & p0, const QPointF & p1) + { + qreal dx = p1.x() - p0.x(); + qreal dy = p1.y() - p0.y(); + return sqrt(dx*dx+dy*dy); + } /* distance */ + + + struct RubberbandRectangle::InternalData + { + Q_DISABLE_COPY(InternalData) + + const int _initialRectPixelSize = 100; + + RubberbandRectangle * _me; + + bool _constrainQuadratic; + bool _constrainOrthogonal; + + qreal _minWidth; + qreal _maxWidth; + qreal _minHeight; + qreal _maxHeight; + + qreal _rectWidth; // referred to map coordinates + qreal _rectHeight; // referred to map coordinate + QPointF _center; // center refferd to map coordinates + + QPointF _oVec; // normalized orientation vector according to scene coordinate system + QPointF _screenCenter; // center of screen in scene coordinates + QPointF _mapOVec; // map rotation as normalized orientation vector + + qreal _rectWidthScene; // rectangle width in scene coordinates + qreal _rectHeightScene; // rectangle height in scene coordinates + QPointF _centerScene; // rectangle center in scene coordinates + + QPointF _constructionStart; + + unsigned int _rotationHandleSize; + bool _useDottedLineHandle; + + SituationMap::RefPointer _tmpConstruction; + + SituationMap::RefPointer _frameHandle; + SituationMap::RefPointer _pointHandle[4]; + SituationMap::RefPointer _lineHandle[4]; + SituationMap::RefPointer _rotateHandle; + + QVector _pointIndexVector; + + LOGGER_T _logger; + + ModificationMode _modificationMode; + MathUtils::Matrix2D & _lastCoordinateChange; + + InternalData( MathUtils::Matrix2D & lastCoordinateChange, RubberbandRectangle * me ) + : _me(me) + , _constrainQuadratic(false) + , _constrainOrthogonal(false) + , _minWidth(0.0) + , _maxWidth(0.0) + , _minHeight(0.0) + , _maxHeight(0.0) + , _rectWidth(0.0) + , _rectHeight(0.0) + , _center() + , _oVec(1,0) + , _screenCenter(0,0) + , _mapOVec(1,0) + , _rectWidthScene(0.0) + , _rectHeightScene(0.0) + , _centerScene() + , _constructionStart() + , _rotationHandleSize(0) + , _useDottedLineHandle(false) + , _tmpConstruction() + , _frameHandle() + , _pointHandle() + , _lineHandle() + , _rotateHandle() + , _pointIndexVector() + , _logger(getLogger("RubberbandRectangle")) + , _modificationMode( ModificationMode::NoneRestriction ) + , _lastCoordinateChange(lastCoordinateChange) + { + } + + inline static QPointF rotateVertex( const QPointF & center, qreal x, qreal y, qreal sinA, qreal cosA ) + { + return QPointF(x*cosA-y*sinA,y*cosA+x*sinA) + center; + } // rotateVertex + + + void setScreenCenter( Esri::ArcGISRuntime::MapGraphicsView * mapView ); + + void setRotatedFrame( Esri::ArcGISRuntime::MapGraphicsView * mapView ); + + void updateFrame( Esri::ArcGISRuntime::MapGraphicsView * mapView, QVector & points ); + + void updateFrame( Esri::ArcGISRuntime::MapGraphicsView * mapView, const QVector & coordinateList ); + + + inline QPointF getSceneOffsetRotationHandle(qreal margin) + { + qreal sinPhi = _oVec.y(); // * _mapOVec.x() + _oVec.x() * _mapOVec.y(); // Addition-Theorem + qreal cosPhi = _oVec.x(); // * _mapOVec.x() - _oVec.y() * _mapOVec.y(); + + return rotateVertex( QPointF(0,0), 0.0,-(margin+2*_rotationHandleSize), sinPhi, cosPhi ); + } /* getSceneOffsetRotationHandle */ + + + inline QPointF getScenePositionRotationHandle() + { + if ((_pointHandle[2]) && (_pointHandle[3])) + { + return (_pointHandle[2]->getCoordinate()._sceneCoordinate + _pointHandle[3]->getCoordinate()._sceneCoordinate) * 0.5; + } + + QPointF p( 0.0, -_rectHeightScene*0.5); + return rotateVertex(_centerScene, p.x(), p.y(), _oVec.y(), _oVec.x() ); + + } /* getScenePositionRotationHandle */ + + + inline void verifyConstrains() + { + if (_constrainQuadratic) + { + if ((_minWidth > 0) || (_minHeight > 0)) + { + if (_minWidth < _minHeight) _minWidth = _minHeight; + else _minHeight = _minWidth; + } + + if ((_maxWidth > 0) || (_maxHeight > 0)) + { + if (_maxWidth < _maxHeight) _maxHeight = _maxWidth; + else _maxWidth = _maxHeight; + } + } + } /* verifyConstrains */ + + + inline void setInitialRectSize(RubberbandRectangle * rubBnd, const QPointF & scenePos) + { + const QPoint & pixelPos = rubBnd->Rubberband::_internal->_mapView -> mapFromScene(scenePos); + setInitialRectSize( rubBnd, scenePos, pixelPos.x(), pixelPos.y() ); + } /* setInitialRectSize */ + + + + + inline void setInitialRectSize( RubberbandRectangle * rubBnd, const QPointF & scenePos, qreal pixelPosX, qreal pixelPosY ) + { + verifyConstrains(); + if ((_rectWidthScene <= 0.0) && (_rectHeightScene <= 0.0)) + { + QPointF rectSize(rubBnd->pixel2ScenePosition(pixelPosX+_initialRectPixelSize,pixelPosY+_initialRectPixelSize)); + + _rectWidthScene = fabs(rectSize.x() - scenePos.x()); + _rectHeightScene = fabs(rectSize.y() - scenePos.y()); + setInitialRectSize( rubBnd ); + } + } /* setInitialRectSize */ + + + inline void setInitialRectSize(RubberbandRectangle * rubBnd, qreal sceneWidth, qreal sceneHeight) + { + verifyConstrains(); + if ((_rectWidthScene <= 0.0) && (_rectHeightScene <= 0.0)) + { + _rectWidthScene = sceneWidth; + _rectHeightScene = sceneHeight; + setInitialRectSize( rubBnd ); + } + } /* setInitialRectSize */ + + + bool minimumSizeReached( int pointHandleIdx = -1, int lineHandleIdx = -1, const QPointF & loc = QPointF() ) + { + qreal min = 10; + bool result = false; + bool isPointHandle = ((pointHandleIdx >= 0) && (pointHandleIdx < 4)); + bool isLineHandle = ((lineHandleIdx >= 0) && (lineHandleIdx < 4)); + + if (isPointHandle ^ isLineHandle) + { + qreal h = loc.y() - _centerScene.y(); + qreal w = loc.x() - _centerScene.x(); + + + if (lineHandleIdx >=0) + { + switch (lineHandleIdx) + { + case 0 : if (h > min) result = true; break; + case 1 : if (w > min) result = true; break; + case 2 : if (h < -min) result = true; break; + case 3 : if (w < -min) result = true; break; + } + } + + if (isPointHandle) + { + switch (pointHandleIdx) + { + case 0 : if ((w < -min) && (h > min)) result = true; break; + case 1 : if ((w > min) && (h > min)) result = true; break; + case 2 : if ((w > min) && (h < -min)) result = true; break; + case 3 : if ((w < -min) && (h < -min)) result = true; break; + } + } + return result; + } + else + { + for (const auto &ph : _pointHandle) if (nullptr == ph.get()) return false; + + min -= 1; + if (QLineF( _pointHandle[0].get()->getCoordinate()._sceneCoordinate, _pointHandle[1].get()->getCoordinate()._sceneCoordinate ).length() < min) result = true; + if (QLineF( _pointHandle[0].get()->getCoordinate()._sceneCoordinate, _pointHandle[2].get()->getCoordinate()._sceneCoordinate ).length() < min) result = true; + if (QLineF( _pointHandle[0].get()->getCoordinate()._sceneCoordinate, _pointHandle[3].get()->getCoordinate()._sceneCoordinate ).length() < min) result = true; + if (QLineF( _pointHandle[1].get()->getCoordinate()._sceneCoordinate, _pointHandle[2].get()->getCoordinate()._sceneCoordinate ).length() < min) result = true; + if (QLineF( _pointHandle[2].get()->getCoordinate()._sceneCoordinate, _pointHandle[3].get()->getCoordinate()._sceneCoordinate ).length() < min) result = true; + } + return result; + } // minimumSizeReached + + private: + inline void setInitialRectSize( RubberbandRectangle * rubBnd ) + { + auto length = [](qreal x, qreal y) -> qreal { return sqrt(x*x+y*y); }; + + RubberbandHandle::RubberbandCoordinate mapPointCenter( rubBnd->Rubberband::_internal->_mapView, _centerScene ); + + const Esri::ArcGISRuntime::Point & mapCenter = mapPointCenter._mapCoordinate; + + const Esri::ArcGISRuntime::Point mapMaxW( mapCenter.x() + _maxWidth + , mapCenter.y() + , rubBnd->Rubberband::_internal->_mapView->map()->spatialReference() ); + + const Esri::ArcGISRuntime::Point mapMinW( mapCenter.x() + _minWidth + , mapCenter.y() + , rubBnd->Rubberband::_internal->_mapView->map()->spatialReference() ); + + const Esri::ArcGISRuntime::Point mapMaxH( mapCenter.x() + , mapCenter.y() + _maxHeight + , rubBnd->Rubberband::_internal->_mapView->map()->spatialReference() ); + + const Esri::ArcGISRuntime::Point mapMinH( mapCenter.x() + , mapCenter.y() + _minHeight + , rubBnd->Rubberband::_internal->_mapView->map()->spatialReference() ); + + RubberbandHandle::RubberbandCoordinate mapPointMaxW( rubBnd->Rubberband::_internal->_mapView, mapMaxW ); + RubberbandHandle::RubberbandCoordinate mapPointMinW( rubBnd->Rubberband::_internal->_mapView, mapMinW ); + RubberbandHandle::RubberbandCoordinate mapPointMaxH( rubBnd->Rubberband::_internal->_mapView, mapMaxH ); + RubberbandHandle::RubberbandCoordinate mapPointMinH( rubBnd->Rubberband::_internal->_mapView, mapMinH ); + + qreal maxSceneWidth = length(mapPointMaxW._sceneCoordinate.x()-_centerScene.x(), mapPointMaxW._sceneCoordinate.y()-_centerScene.y()); + qreal minSceneWidth = length(mapPointMinW._sceneCoordinate.x()-_centerScene.x(), mapPointMinW._sceneCoordinate.y()-_centerScene.y()); + qreal maxSceneHeight = length(mapPointMaxH._sceneCoordinate.x()-_centerScene.x(), mapPointMaxH._sceneCoordinate.y()-_centerScene.y()); + qreal minSceneHeight = length(mapPointMinH._sceneCoordinate.x()-_centerScene.x(), mapPointMinH._sceneCoordinate.y()-_centerScene.y()); + + if ((_maxWidth > 0) && (_rectWidthScene > maxSceneWidth )) _rectWidthScene = maxSceneWidth; + if ((_minWidth > 0) && (_rectWidthScene < minSceneWidth )) _rectWidthScene = minSceneWidth; + if ((_maxHeight > 0) && (_rectHeightScene > maxSceneHeight)) _rectHeightScene = maxSceneHeight; + if ((_minHeight > 0) && (_rectHeightScene < minSceneHeight)) _rectHeightScene = minSceneHeight; + + RubberbandHandle::RubberbandCoordinate mapRectWidth ( rubBnd->Rubberband::_internal->_mapView, _centerScene + QPointF( _rectWidthScene, 0.0 )); + RubberbandHandle::RubberbandCoordinate mapRectHeight( rubBnd->Rubberband::_internal->_mapView, _centerScene + QPointF( 0.0 , _rectHeightScene )); + + _rectWidth = length( mapRectWidth._mapCoordinate.x()-mapCenter.x(), mapRectWidth._mapCoordinate.y()-mapCenter.y() ); + _rectHeight = length( mapRectHeight._mapCoordinate.x()-mapCenter.x(), mapRectHeight._mapCoordinate.y()-mapCenter.y() ); + _center = QPointF(mapCenter.x(),mapCenter.y()); + + rubBnd->_rectangleIsValid = true; + } /* setInitialRectSize */ + + }; /* InternalData */ + + + void RubberbandRectangle::InternalData::setScreenCenter( Esri::ArcGISRuntime::MapGraphicsView * mapView ) + { + qreal mapRotatePhi = mapView->mapRotation() * MathConst::pi / 180.0; + + _mapOVec = QPointF( cos(mapRotatePhi), sin(mapRotatePhi) ); + _screenCenter = QPointF( mapView->viewport()->size().width()* 0.5, mapView->viewport()->size().height()* 0.5 ); + + } /* RubberbandRectangle::InternalData::setScreenCenter */ + + + void RubberbandRectangle::InternalData::setRotatedFrame( Esri::ArcGISRuntime::MapGraphicsView* ) + { + // _center and unrotated _rectWidth and _rectHeight is already set from setInitialRectSize() which has been executed before + + qreal sinA = -_oVec.y(); // be carefull the scene coordinate system is contra clockwise -- sinA must be negated + qreal cosA = _oVec.x(); + qreal w = _rectWidth * 0.5; + qreal h = _rectHeight * 0.5; + + QPointF p0 = MathUtils::rotate( _center + QPointF(-w,-h ), _center, sinA, cosA ); + QPointF p1 = MathUtils::rotate( _center + QPointF( w,-h ), _center, sinA, cosA ); + QPointF p2 = MathUtils::rotate( _center + QPointF( w, h ), _center, sinA, cosA ); + + _rectWidth = distance(p1,p0); + _rectHeight = distance(p2,p1); + + } /* RubberbandRectangle::InternalData::setRotatedFrame */ + + + void RubberbandRectangle::InternalData::updateFrame( Esri::ArcGISRuntime::MapGraphicsView * mapView, QVector & points ) + { + DEBUG_LOG(_logger) << LOGFUNCTION << " RubberbandRectangle::InternalData::updateFrame for map-coordinates " << _oVec.x() << " ; " << _oVec.y(); + + qreal cosA = _oVec.x(); + qreal sinA = _oVec.y(); // be carefull the scene coordinate system is contra clockwise -- sinA must be negated + + qreal xmin = std::numeric_limits::infinity(); + qreal xmax = - std::numeric_limits::infinity(); + qreal ymin = std::numeric_limits::infinity(); + qreal ymax = - std::numeric_limits::infinity(); + + for (unsigned int i = 0; i < 4; ++i) + { + const QPointF & p = MathUtils::rotate( points[i], points[0], sinA, cosA ); // rotate back to orthogonal position + const qreal & x = p.x(); + const qreal & y = p.y(); + + if (x < xmin) xmin = x; + if (x > xmax) xmax = x; + if (y < ymin) ymin = y; + if (y > ymax) ymax = y; + + DEBUG_LOG(_logger) << LOGFUNCTION << " point " << i << " : " << p.x() << " ; " << p.y(); + } + + // set center, keep in mind that the center point must be rotated to it's real position + _center = MathUtils::rotate( QPointF((xmin + xmax)*0.5, (ymin + ymax)*0.5), points[0], -sinA, cosA ); + + if (_constrainQuadratic) + { + double w = xmax-xmin; + double h = ymax-ymin; + + if (w > h) w = h; + if (h > w) h = w; + + if ((_modificationMode != ModificationMode::RotationOnly) && (_modificationMode != ModificationMode::TranslationOnly)) + { + // size of rectangle + _rectWidth = w; w *= 0.5; + _rectHeight = h; h *= 0.5; + } + + points[0] = MathUtils::rotate( _center + QPointF(-w,-h), _center, -sinA, cosA ); + points[1] = MathUtils::rotate( _center + QPointF( w,-h), _center, -sinA, cosA ); + points[2] = MathUtils::rotate( _center + QPointF( w, h), _center, -sinA, cosA ); + points[3] = MathUtils::rotate( _center + QPointF(-w, h), _center, -sinA, cosA ); + } + else + { + // size of rectangle + _rectWidth = xmax-xmin; + _rectHeight = ymax-ymin; + } + + QVector sceneCoordinates; + sceneCoordinates.reserve(4); + + for (unsigned int i = 0; i < 4; ++i) + { + const QPointF & p = MathUtils::rotate( points[i], points[0], sinA, cosA ); // rotate back to orthogonal position, keep in mind points are map coordinates + const qreal & x = p.x(); + const qreal & y = p.y(); + RubberbandHandle::RubberbandCoordinate point( mapView, Esri::ArcGISRuntime::Point( x, y, mapView->map()->spatialReference() )); + sceneCoordinates.append(point._sceneCoordinate ); + } + + RubberbandHandle::RubberbandCoordinate point( mapView, Esri::ArcGISRuntime::Point( _center.x(), _center.y(), mapView->map()->spatialReference() )); + + _centerScene = point._sceneCoordinate; + + xmin = std::numeric_limits::infinity(); + xmax = - std::numeric_limits::infinity(); + ymin = std::numeric_limits::infinity(); + ymax = - std::numeric_limits::infinity(); + + for (const QPointF & p : std::as_const(sceneCoordinates)) + { + const QPointF & unRotMap = MathUtils::rotate( p, _screenCenter, -_mapOVec.y(), _mapOVec.x() ); + const qreal & x = unRotMap.x(); + const qreal & y = unRotMap.y(); + + if (x < xmin) xmin = x; + if (x > xmax) xmax = x; + if (y < ymin) ymin = y; + if (y > ymax) ymax = y; + } + + + // size of scene rectangle + _rectWidthScene = xmax-xmin; + _rectHeightScene = ymax-ymin; + + + DEBUG_LOG(_logger) << LOGFUNCTION << " center = " << _centerScene.x() << " ; " << _centerScene.y() + << " _rectWidthScene = " << _rectWidthScene + << " _rectHeightScene = " << _rectHeightScene; + + } /* RubberbandRectangle::InternalData::updateFrame */ + + + void RubberbandRectangle::InternalData::updateFrame( Esri::ArcGISRuntime::MapGraphicsView*, const QVector & coordinateList ) + { + if (coordinateList.isEmpty()) return; + + DEBUG_LOG(_logger) << LOGFUNCTION << " RubberbandRectangle::InternalData::updateFrame for scene-coordinates " << _oVec.x() << " ; " << _oVec.y(); + + qreal cosA = _oVec.x(); + qreal sinA = _oVec.y(); + + qreal xmin = std::numeric_limits::infinity(); + qreal xmax = - std::numeric_limits::infinity(); + qreal ymin = std::numeric_limits::infinity(); + qreal ymax = - std::numeric_limits::infinity(); + + QVector sceneCoordinates; + sceneCoordinates.reserve(4); + + for (unsigned int i = 0; i < 4; ++i) + { + QPointF p = MathUtils::rotate( coordinateList[i]._sceneCoordinate, coordinateList[0]._sceneCoordinate, -sinA, cosA ); // rotate back to orthogonal position + + sceneCoordinates.append( p ); + + const QPointF & unRotMap = MathUtils::rotate( p, _screenCenter, -_mapOVec.y(), _mapOVec.x() ); + const qreal & x = unRotMap.x(); + const qreal & y = unRotMap.y(); + + if (x < xmin) xmin = x; + if (x > xmax) xmax = x; + if (y < ymin) ymin = y; + if (y > ymax) ymax = y; + } + + // set center, keep in mind that the center point must be rotated to it's real position + const QPointF & rotMap = MathUtils::rotate( QPointF((xmin + xmax)*0.5, (ymin + ymax)*0.5), _screenCenter, _mapOVec.y(), _mapOVec.x() ); + + _centerScene = MathUtils::rotate( rotMap, coordinateList[0]._sceneCoordinate, sinA, cosA ); + + // size of scene rectangle + _rectWidthScene = xmax - xmin; + _rectHeightScene = ymax - ymin; + + xmin = std::numeric_limits::infinity(); + xmax = -std::numeric_limits::infinity(); + ymin = std::numeric_limits::infinity(); + ymax = -std::numeric_limits::infinity(); + + QPointF p0(coordinateList[0]._mapCoordinate.x(), coordinateList[0]._mapCoordinate.y()); + + for (unsigned int i = 0; i < 4; ++i) + { + QPointF pi(coordinateList[i]._mapCoordinate.x(), coordinateList[i]._mapCoordinate.y()); + + QPointF p = MathUtils::rotate(pi, p0, sinA, cosA); // rotate back to orthogonal position + + const qreal & x = p.x(); + const qreal & y = p.y(); + + if (x < xmin) xmin = x; + if (x > xmax) xmax = x; + if (y < ymin) ymin = y; + if (y > ymax) ymax = y; + } + + qreal sRectWidth = _rectWidth; + qreal sRectHeight = _rectHeight; + + // size of rectangle + _rectWidth = xmax - xmin; + _rectHeight = ymax - ymin; + + QPointF lastCenter = _center; + + _center = MathUtils::rotate(QPointF((xmin + xmax)*0.5, (ymin + ymax)*0.5), p0, -sinA, cosA); + + DEBUG_LOG(_logger) << LOGFUNCTION << " centerScene = " << _centerScene.x() << " ; " << _centerScene.y() << " _rectWidthScene = " << _rectWidthScene << " _rectHeightScene = " << _rectHeightScene; + DEBUG_LOG(_logger) << LOGFUNCTION << " center = " << _center.x() << " ; " << _center.y() << " _rectWidth = " << _rectWidth << " _rectHeight = " << _rectHeight ; + + sRectWidth = _rectWidth / sRectWidth; + sRectHeight = _rectHeight / sRectHeight; + + if (!_lastCoordinateChange.isRotation()) // _lastCoordinateChange.isIdentity()) + { + // move center to origin + _lastCoordinateChange.putTranslation( -lastCenter ); + + // do scaling + _lastCoordinateChange.putScale( QPointF(sRectWidth,sRectHeight), QPointF(cosA,sinA) ); + + // move back to new center + _lastCoordinateChange.putTranslation( _center ); + } + + } /* RubberbandRectangle::InternalData::updateFrame */ + + + RubberbandRectangle::RubberbandRectangle(SituationMap::Map * map, QObject * parent) + : Rubberband(map,parent) + , _internal(new InternalData(_lastCoordinateChange,this)) + , _rectangleIsValid(false) + , _lastCoordinateChange() + { + } /* RubberbandRectangle::RubberbandRectangle */ + + + RubberbandRectangle::~RubberbandRectangle() + { + delete _internal; + } /* RubberbandRectangle::~RubberbandRectangle */ + + + void RubberbandRectangle::setModificationMode( ModificationMode mode ) { _internal->_modificationMode = mode; } + + + bool RubberbandRectangle::rectangleIsValid() const { return _rectangleIsValid; } + + + void RubberbandRectangle::minWidth(qreal x) { _internal->_minWidth = (x > 0) ? x : 0.0; } + void RubberbandRectangle::maxWidth(qreal x) { _internal->_maxWidth = (x > 0) ? x : 0.0; } + void RubberbandRectangle::minHeight(qreal x) { _internal->_minHeight = (x > 0) ? x : 0.0; } + void RubberbandRectangle::maxHeight(qreal x) { _internal->_maxHeight = (x > 0) ? x : 0.0; } + + + QPointF RubberbandRectangle::getSceneCenter() const { return _internal->_centerScene; } + qreal RubberbandRectangle::getSceneWidth() const { return _internal->_rectWidthScene; } + qreal RubberbandRectangle::getSceneHeight() const { return _internal->_rectHeightScene; } + + QPointF RubberbandRectangle::getCenter() const { return _internal->_center; } + qreal RubberbandRectangle::getWidth() const { return _internal->_rectWidth; } + qreal RubberbandRectangle::getHeight() const { return _internal->_rectHeight; } + + void RubberbandRectangle::getSceneRotation( qreal & sinA, qreal & cosA ) const { sinA = -_internal->_oVec.y() ; cosA = _internal->_oVec.x(); } + void RubberbandRectangle::getMapRotation ( qreal & sinA, qreal & cosA ) const { sinA = -_internal->_mapOVec.y(); cosA = _internal->_mapOVec.x(); } + + void RubberbandRectangle::useDottedLineHandles(bool on) + { + _internal->_useDottedLineHandle = on; + } /* RubberbandRectangle::useDottedLineHandles */ + + + void RubberbandRectangle::setButtonPosition() + { + QPointF buttonPos(0, 0); + + if (Rubberband::_internal->_handleList.size() == 4) + { + for (unsigned int i = 0; i < 4; ++i) + { + if (Rubberband::_internal->_handleList[i].valid()) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(Rubberband::_internal->_handleList[i].get()); + const QPointF & p = handlePointPtr->getCoordinate()._sceneCoordinate; + + if (i > 0) + { + if ((p.x() == buttonPos.x()) && (p.y() >= buttonPos.y())) + { + buttonPos = p; + } + else if (p.x() > buttonPos.x()) buttonPos = p; + } + else buttonPos = p; + } + } + } + else buttonPos = InternalData::rotateVertex( _internal->_centerScene, _internal->_rectWidthScene*0.5, _internal->_rectHeightScene*0.5, _internal->_oVec.y(), fabs(_internal->_oVec.x()) ); + + _baseInternal->_buttonProxy->setPos( QPointF(buttonPos.x(),buttonPos.y()) ); + } /* RubberbandRectangle::setButtonPosition */ + + + void RubberbandRectangle::activateButtons() + { + if (isEnabledModificationState()) + { + _baseInternal->_hasUndoButton = false; + _baseInternal->_applyIsPossible = true; + _baseInternal->_undoIsPossible = false; + + Rubberband::activateButtons(); + _baseInternal->_buttonProxy->setRotateSceneOffset(false); + setButtonPosition(); + _baseInternal->_buttonProxy->show(); + } + else Rubberband::deactivateButtons(); + } /* RubberbandRectangle::activateButtons */ + + + +#include "./Rubberbands/winkel_icon.xpm" + + void RubberbandRectangle::constructHandles() + { + _internal->setScreenCenter( Rubberband::_internal->_mapView ); + _internal->verifyConstrains(); + + // remove existing handles + Rubberband::_internal->_handleList.clear(); + + if ((_internal->_modificationMode != ModificationMode::RotationOnly) && (_internal->_modificationMode != ModificationMode::TranslationOnly)) + { + qreal w2 = _internal->_rectWidth * 0.5; + qreal h2 = _internal->_rectHeight * 0.5; + qreal sinA = _internal->_oVec.y(); + qreal cosA = _internal->_oVec.x(); + + QPointF p0 = InternalData::rotateVertex( _internal->_center, -w2, -h2, -sinA, cosA ); + QPointF p1 = InternalData::rotateVertex( _internal->_center, w2, -h2, -sinA, cosA ); + QPointF p2 = InternalData::rotateVertex( _internal->_center, w2, h2, -sinA, cosA ); + QPointF p3 = InternalData::rotateVertex( _internal->_center, -w2, h2, -sinA, cosA ); + + RubberbandHandle::RubberbandCoordinate mapP0( Rubberband::_internal->_mapView, Esri::ArcGISRuntime::Point( p0.x(), p0.y(), Rubberband::_internal->_mapView->map()->spatialReference() )); + RubberbandHandle::RubberbandCoordinate mapP1( Rubberband::_internal->_mapView, Esri::ArcGISRuntime::Point( p1.x(), p1.y(), Rubberband::_internal->_mapView->map()->spatialReference() )); + RubberbandHandle::RubberbandCoordinate mapP2( Rubberband::_internal->_mapView, Esri::ArcGISRuntime::Point( p2.x(), p2.y(), Rubberband::_internal->_mapView->map()->spatialReference() )); + RubberbandHandle::RubberbandCoordinate mapP3( Rubberband::_internal->_mapView, Esri::ArcGISRuntime::Point( p3.x(), p3.y(), Rubberband::_internal->_mapView->map()->spatialReference() )); + + _internal->_lineHandle[0] = new RubberbandLineHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP0._sceneCoordinate, mapP1._sceneCoordinate, _lineHandleWidth, _internal->_useDottedLineHandle ); + _internal->_lineHandle[1] = new RubberbandLineHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP1._sceneCoordinate, mapP2._sceneCoordinate, _lineHandleWidth, _internal->_useDottedLineHandle ); + _internal->_lineHandle[2] = new RubberbandLineHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP2._sceneCoordinate, mapP3._sceneCoordinate, _lineHandleWidth, _internal->_useDottedLineHandle ); + _internal->_lineHandle[3] = new RubberbandLineHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP3._sceneCoordinate, mapP0._sceneCoordinate, _lineHandleWidth, _internal->_useDottedLineHandle ); + + _internal->_pointHandle[0] = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP0._mapCoordinate ); + _internal->_pointHandle[1] = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP1._mapCoordinate ); + _internal->_pointHandle[2] = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP2._mapCoordinate ); + _internal->_pointHandle[3] = new RubberbandPointHandle( Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, mapP3._mapCoordinate ); + + for (auto ph : _internal->_pointHandle) + { + Rubberband::_internal->_handleList.append( SituationMap::RefPointer(ph.get()) ); + + ph -> setDrawColor(_handleDrawColor); + ph -> setFillColor( _handleFillColor ); + ph -> setSelectionColor(_handleSelectionColor); + } + + for (auto lh : _internal->_lineHandle) + { + lh -> setDrawColor(_handleDrawColor); + lh -> setFillColor( _handleFillColor ); + lh -> setSelectionColor(_handleSelectionColor); + } + } + + + if ((_internal->_modificationMode == ModificationMode::NoneRestriction) || (_internal->_modificationMode == ModificationMode::RotationOnly)) + { + static QPixmap * icon = nullptr; + + if (icon == nullptr) + { + icon = new QPixmap( QPixmap( winkelIcon_xpm ).transformed(QTransform::fromScale(0.5,0.5)) ); + } + + if (!_internal->_constrainOrthogonal) + { + _internal->_rotationHandleSize = std::max( icon->width(), icon->height() ) * 0.5; + + _internal->_rotateHandle = new RubberbandPointHandle( Rubberband::_internal->_mapView + , Rubberband::_internal->_mapScene + , _internal->getScenePositionRotationHandle(), *icon ); + + _internal->_rotateHandle -> setSceneOffset( _internal->getSceneOffsetRotationHandle( (_frameHandleMargin > 0) ? _frameHandleMargin : 0) ); + _internal->_rotateHandle -> setDrawColor(_handleDrawColor); + _internal->_rotateHandle -> setFillColor( _handleFillColor ); + _internal->_rotateHandle -> setSelectionColor(_handleSelectionColor ); + } + } + + _internal->_frameHandle = new RubberbandFrameHandle( Rubberband::_internal->_mapView + , Rubberband::_internal->_mapScene + , _frameHandleMargin ); + + + _internal->_frameHandle -> setDrawColor( _frameColor ); + _internal->_frameHandle -> setFillColor( _frameFillColor ); + _internal->_frameHandle -> setSelectionColor(_frameSelectionColor); + _internal->_frameHandle -> setSelectable((_internal->_modificationMode != ModificationMode::ScalingOnly) && (_internal->_modificationMode != ModificationMode::RotationOnly)); + _internal->_frameHandle -> drawDisabled( true ); + + // lambda slot to update screen center and map rotation + connect( _internal->_frameHandle.get(), &RubberbandFrameHandle::adjusted, this, [this]() + { + this->_internal->setScreenCenter( this->Rubberband::_internal->_mapView ); + this->setButtonPosition(); + } ); + + activateButtons(); + + } /* RubberbandRectangle::constructHandles */ + + + QVector RubberbandRectangle::coordinateVector( CoordinateSystem cs ) const { return rectangleVerticies(cs); } + QPointF RubberbandRectangle::singleCoordinate(unsigned int, CoordinateSystem cs ) const { return centerPoint(cs); } + + + void RubberbandRectangle::setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs ) + { + if (coordinateVector.size() == 4) + { + rectangleVerticies( coordinateVector, cs ); + } + } /* RubberbandRectangle::setCoordinateVector */ + + + QPointF RubberbandRectangle::centerPoint( CoordinateSystem cs ) const + { + return projectCoordinateFromMap( cs, _internal->_center ); + } /* RubberbandRectangle::centerPoint */ + + + void RubberbandRectangle::centerPoint(const QPointF ¢erPoint, CoordinateSystem cs ) + { + _internal->_center = projectCoordinateToMap( cs, centerPoint ); + + RubberbandHandle::RubberbandCoordinate point( Rubberband::_internal->_mapView, + Esri::ArcGISRuntime::Point( _internal->_center.x(), + _internal->_center.y(), + Rubberband::_internal->_mapView->map()->spatialReference() )); + _internal->_centerScene = point._sceneCoordinate; + + _internal->setInitialRectSize( this, _internal->_centerScene ); + + _internal->setRotatedFrame( Rubberband::_internal->_mapView ); + + disenableConstructionState(); + constructHandles(); + activateButtons(); + update(); + } /* RubberbandRectangle::centerPoint */ + + + + void RubberbandRectangle::rectangleParameter( QPointF & center, qreal & width, qreal & height, QPointF & direction, CoordinateSystem cs ) + { + center = projectCoordinateFromMap(cs,_internal->_center); + width = _internal->_rectWidth; + height = _internal->_rectHeight; + direction = QPointF(_internal->_oVec.x(),-_internal->_oVec.y()); + + // projectCoordinateFromMap( CoordinateSystem::Geo, center ); + } /* RubberbandRectangle::rectangleParameter */ + + + QVector RubberbandRectangle::rectangleVerticies( CoordinateSystem cs ) const + { + QVector points(4); + + for ( unsigned int i = 0; i < 4; ++i ) + { + // they may be emtpy (e.g. select action, move mouse but don't release button, hit 'ENTER' + if (!_internal->_pointHandle[i]) + return {}; + const Esri::ArcGISRuntime::Point & p = _internal->_pointHandle[i]-> getCoordinate()._mapCoordinate; + + if (_internal->_pointIndexVector.size() == 4) + points[_internal->_pointIndexVector[i]] = projectCoordinateFromMap( cs,p); + else + points[i] = projectCoordinateFromMap( cs,p); + } + return points; + } /* RubberbandRectangle::rectangleVerticies */ + + + void RubberbandRectangle::rectangleVerticies( const QVector & coordinateVector, CoordinateSystem cs ) + { + if (coordinateVector.size() == 4) + { + QVector rectPoints; + + // first do coordinate projection to map coordinate system + rectPoints.reserve(4); + for (auto loc : coordinateVector) + { + rectPoints.append( projectCoordinateToMap( cs, loc ) ); + } + + // here first sort the verticies + _internal->_pointIndexVector.clear(); + for (unsigned int i = 0; i< 4;++i) _internal->_pointIndexVector.append(i); + + // here first sort the verticies + + for (unsigned i = 0; i < 4; ++i) + { + for (unsigned k = i + 1; k < 4; ++k) + { + if (rectPoints[k].y() < rectPoints[i].y()) + { + std::swap(rectPoints[i], rectPoints[k]); + std::swap(_internal->_pointIndexVector[i],_internal->_pointIndexVector[k]); + } + } + } + + if (rectPoints[0].x() > rectPoints[1].x()) + { + std::swap(rectPoints[0], rectPoints[1]); + std::swap(_internal->_pointIndexVector[0],_internal->_pointIndexVector[1]); + } + + if (rectPoints[3].x() > rectPoints[2].x()) + { + std::swap(rectPoints[3], rectPoints[2]); + std::swap(_internal->_pointIndexVector[3],_internal->_pointIndexVector[2]); + } + + // Verticies are now sorted in counter clockwise order with beginning + // with bottom left verticies as shown in following sketch + // + // 3 2 + // X------------------------------------X + // | | + // | | + // | | + // X------------------------------------X + // 0 1 + // + + // now check input verticies for beeing a rectangle + + QPointF p1( (rectPoints[0] + rectPoints[3]) * 0.5); // construct 2 helpful points + QPointF p2( (rectPoints[1] + rectPoints[2]) * 0.5); + + qreal cosA = p2.x()-p1.x(); + qreal sinA = p2.y()-p1.y(); + + qreal len = sqrt(cosA*cosA+sinA*sinA); + + auto epsilonThreshold = [this](qreal distance) -> qreal + { + Esri::ArcGISRuntime::Point p1 = Rubberband::_internal->_mapView->screenToLocation(0, 0); + Esri::ArcGISRuntime::Point p2 = Rubberband::_internal->_mapView->screenToLocation(0, 1); + + qreal dx = p2.x() - p1.x(); + qreal dy = p2.y() - p1.y(); + + return sqrt(dx*dx + dy * dy) / distance; + }; + + qreal eps = epsilonThreshold(len); + + cosA /= len; + sinA /= len; + + qreal cosP = rectPoints[1].x()-rectPoints[0].x(); + qreal sinP = rectPoints[1].y()-rectPoints[0].y(); + + len = sqrt(cosP*cosP+sinP*sinP); + cosP /= len; + sinP /= len; + + _rectangleIsValid = (epsilonEqual(cosA,cosP,eps) && epsilonEqual(sinA,sinP,eps)); + + // be careful _oVec is relative to scene coordinate system and the scene coordinate system is counter clockwise -- sinA must be negated + + if (_internal->_constrainOrthogonal) + { + _internal->_oVec = QPointF(1,0); + if (_rectangleIsValid) + { + _rectangleIsValid = epsilonEqual( fabs(cosA-1), fabs(sinA), eps ); + } + } + else _internal->_oVec = QPointF( cosA, -sinA ); + + _internal->updateFrame( Rubberband::_internal->_mapView, rectPoints ); + + disenableConstructionState(); + constructHandles(); + activateButtons(); + update(); + + } + } /* RubberbandRectangle::rectangleVerticies */ + + + void RubberbandRectangle::buildFromRectangleParamter(const QPointF & center, qreal width, qreal height, const QPointF & direction, CoordinateSystem cs) + { + auto rotate = [](const QPointF & p, qreal sinA, qreal cosA) -> QPointF + { + return QPointF(p.x()*cosA-p.y()*sinA,p.y()*cosA+p.x()*sinA); + }; + + qreal dl = sqrt(direction.x()*direction.x()+direction.y()*direction.y()); + + qreal sinA = direction.y()/dl; + qreal cosA = direction.x()/dl; + + _internal->_constrainOrthogonal = false; + _internal->_oVec = QPointF( cosA, -sinA ); + + _internal->_pointIndexVector.clear(); + + QVector rectPoints(4); + + width *= 0.5; + height *= 0.5; + + QPointF centerMapCoordinate = projectCoordinateToMap( cs, center ); + + rectPoints[0] = centerMapCoordinate + rotate( QPointF(-width,-height), sinA, cosA ); + rectPoints[1] = centerMapCoordinate + rotate( QPointF( width,-height), sinA, cosA ); + rectPoints[2] = centerMapCoordinate + rotate( QPointF( width, height), sinA, cosA ); + rectPoints[3] = centerMapCoordinate + rotate( QPointF(-width, height), sinA, cosA ); + + _internal->updateFrame( Rubberband::_internal->_mapView, rectPoints ); + + disenableConstructionState(); + constructHandles(); + activateButtons(); + update(); + + } /* RubberbandRectangle::buildFromRectangleParamter */ + + + void RubberbandRectangle::buildRectangleRelativeToBottomLeftCorner(const QPointF & bottomLeft, qreal width, qreal height, const QPointF & direction, CoordinateSystem cs) + { + auto rotate = [](const QPointF & p, qreal sinA, qreal cosA) -> QPointF + { + return QPointF(p.x()*cosA-p.y()*sinA,p.y()*cosA+p.x()*sinA); + }; + + qreal dl = sqrt(direction.x()*direction.x()+direction.y()*direction.y()); + + qreal sinA = direction.y()/dl; + qreal cosA = direction.x()/dl; + + _internal->_constrainOrthogonal = false; + _internal->_oVec = QPointF( cosA, -sinA ); + + _internal->_pointIndexVector.clear(); + + QVector rectPoints(4); + + QPointF bottomLeftMapCoordinate = projectCoordinateToMap( cs, bottomLeft ); + + rectPoints[0] = bottomLeftMapCoordinate; + rectPoints[1] = bottomLeftMapCoordinate + rotate( QPointF(width, 0), sinA, cosA ); + rectPoints[2] = bottomLeftMapCoordinate + rotate( QPointF(width,height), sinA, cosA ); + rectPoints[3] = bottomLeftMapCoordinate + rotate( QPointF( 0,height), sinA, cosA ); + + _internal->updateFrame( Rubberband::_internal->_mapView, rectPoints ); + + disenableConstructionState(); + constructHandles(); + activateButtons(); + update(); + + } /* RubberbandRectangle::buildRectangleRelativeToBottomLeftCorner */ + + + void RubberbandRectangle::constrainQuadratic(bool on) + { + _internal->_constrainQuadratic = on; + if (isEnabledModificationState()) + { + constructHandles(); + activateButtons(); + update(); + } + } /* RubberbandRectangle::constrainQuadratic */ + + + bool RubberbandRectangle::constrainQuadratic() const { return _internal->_constrainQuadratic; } + + + void RubberbandRectangle::constrainOrthogonal(bool on) + { + _internal->_constrainOrthogonal = on; + + if (_internal->_constrainOrthogonal) + { + _internal->_rotateHandle = nullptr; + _internal->_oVec = QPointF(1,0); + } + } /* RubberbandRectangle::constrainQuadratic */ + + + bool RubberbandRectangle::constrainOrthogonal() const { return _internal->_constrainOrthogonal; } + + + bool RubberbandRectangle::multiSelectActionPress ( AbstractEventDispatcher * causer, int x, int y ) { return selectAction ( causer, x, y ); } + bool RubberbandRectangle::multiSelectActionRelease( AbstractEventDispatcher * causer, int x, int y ) { return setCoordinateAction( causer, x, y ); } + + + bool RubberbandRectangle::selectAction( AbstractEventDispatcher *, int x, int y ) + { + if (rubberbandIsAlive()) + { + bool eventHandled = false; + QPointF scenePos(pixel2ScenePosition(x,y)); + + _baseInternal->_lastClickPosition = scenePos; + + if (isEnabledConstructionState()) + { + _internal->_constructionStart = scenePos; + enableSelectedState(); + eventHandled = true; + } + else + { + if (_internal->minimumSizeReached()) return true; + + RubberbandHandle * handlePtr = pickHandle(x,y); + if (handlePtr) + { + handlePtr->setSelection(); + enableSelectedState(); + eventHandled = true; + } + else + { + for (auto lh : _internal->_lineHandle) + { + if ((lh.valid()) && (lh->isPositionInside(scenePos))) + { + lh->setSelection(true); + enableSelectedState(); + eventHandled = true; + } + } + + if ((_internal->_rotateHandle.valid()) && (_internal->_rotateHandle->isPositionInside(scenePos))) + { + _internal->_rotateHandle->setSelection(true); + enableSelectedState(); + eventHandled = true; + } + } + if (!isEnabledSelectedState()) + { + // ok doesn't hit any point or line handle now looking to frame handle + if (_internal->_frameHandle.valid()) + { + if ((_internal->_frameHandle->isSelectable()) && (_internal->_frameHandle->isPositionInside(scenePos))) + { + _internal->_frameHandle->setSelection(true); + enableSelectedState(); + eventHandled = true; + } + } + } + } + + if (eventHandled) + { + update(); + deactivateButtons(); + } + return eventHandled; + } + return false; + } /* RubberbandRectangle::selectAction */ + + + bool RubberbandRectangle::moveAction( AbstractEventDispatcher*, int x, int y ) + { + adjustConnectedPreviewPixmap(x,y); + + if (rubberbandIsAlive()) + { + if (!isEnabledSelectedState()) + { + deSelectHandles(); + + if (_internal->minimumSizeReached()) return true; + + if ((_internal->_pointHandle[0] == nullptr) && (_internal->_lineHandle[0] == nullptr) && (_internal->_rotateHandle == nullptr) && (_internal->_frameHandle == nullptr)) + { + // constructHandles has not been called jet. Leave function and eat event + return false; + } + + if (_internal->_rotateHandle.valid()) + { + _internal->_rotateHandle->setSelection(false); + } + RubberbandHandle * handlePtr = pickHandle(x,y); + if (handlePtr) + { + handlePtr->setSelection(); + for (auto lh : _internal->_lineHandle) + lh->setSelection( false ); + } + else + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + bool selectRotationHandle = _internal->_rotateHandle.valid() ? _internal->_rotateHandle->isPositionInside(scenePos) : false; + + if (!selectRotationHandle) + { + for (auto lh : _internal->_lineHandle) + { + if (lh != nullptr) + { + bool selectHandle = lh->isPositionInside(scenePos); + lh->setSelection( selectHandle ); + } + } + } + else _internal->_rotateHandle->setSelection(true); + } + } + } + return false; + } /* RubberbandRectangle::moveAction */ + + + bool RubberbandRectangle::checkMinMaxConstrains(const QPointF & vertexScene, QPointF & deltaMovePtr ) + { + bool ok_x = true; + bool ok_y = true; + // check min max constrains + RubberbandHandle::RubberbandCoordinate vertex( Rubberband::_internal->_mapView, vertexScene ); + + qreal mapRectWidth = 2.0 * fabs(vertex._mapCoordinate.x() - _internal->_center.x()); + qreal mapRectHeight = 2.0 * fabs(vertex._mapCoordinate.y() - _internal->_center.y()); + + if ((_internal->_minWidth > 0) && (mapRectWidth < _internal->_minWidth )) { ok_x = false; deltaMovePtr.setX(0); } + if ((_internal->_maxWidth > 0) && (mapRectWidth > _internal->_maxWidth )) { ok_x = false; deltaMovePtr.setX(0); } + if ((_internal->_minHeight > 0) && (mapRectHeight < _internal->_minHeight)) { ok_y = false; deltaMovePtr.setY(0); } + if ((_internal->_maxHeight > 0) && (mapRectHeight > _internal->_maxHeight)) { ok_y = false; deltaMovePtr.setY(0); } + + return (ok_x || ok_y); + + } /* RubberbandRectangle::checkMinMaxConstrains */ + + + bool RubberbandRectangle::checkMinMaxConstrains(const QPointF & vertexScene, bool isVertical) + { + bool ok = true; + + if (_internal->_constrainQuadratic) + { + _internal->verifyConstrains(); + /* + if (_internal->_minHeight > 0) _internal->_minWidth = _internal->_minHeight; + else if (_internal->_minWidth > 0) _internal->_minHeight = _internal->_minWidth; + if (_internal->_maxHeight > 0) _internal->_maxWidth = _internal->_maxHeight; + else if (_internal->_maxWidth > 0) _internal->_maxHeight = _internal->_maxWidth; + */ + } + + // check min max constrains + RubberbandHandle::RubberbandCoordinate vertex( Rubberband::_internal->_mapView, vertexScene ); + + if (isVertical) + { + qreal mapRectWidth = 2.0 * fabs(vertex._mapCoordinate.x() - _internal->_center.x()); + if ((_internal->_minWidth > 0) && (mapRectWidth < _internal->_minWidth )) { ok = false; } + if ((_internal->_maxWidth > 0) && (mapRectWidth > _internal->_maxWidth )) { ok = false; } + return ok; + } + else + { + qreal mapRectHeight = 2.0 * fabs(vertex._mapCoordinate.y() - _internal->_center.y()); + if ((_internal->_minHeight > 0) && (mapRectHeight < _internal->_minHeight)) { ok = false; } + if ((_internal->_maxHeight > 0) && (mapRectHeight > _internal->_maxHeight)) { ok = false; } + return ok; + } + } /* RubberbandRectangle::checkMinMaxConstrains */ + + + bool RubberbandRectangle::constrainsSatisfied( RubberbandPointHandle * handlePointPtr, unsigned int handleIdx, QPointF & deltaMove, bool & constrainsComplied ) + { + constrainsComplied = true; + + qreal sinMap = _internal->_mapOVec.y(); // calculate rotation + qreal cosMap = _internal->_mapOVec.x(); + qreal sinRec = _internal->_oVec.y(); + qreal cosRec = _internal->_oVec.x(); + + qreal sinPhi = sinRec * cosMap - cosRec * sinMap; // Addition-Theorem + qreal cosPhi = cosRec * cosMap + sinRec * sinMap; + + QPointF rotatedDeltaMove = MathUtils::rotate( deltaMove, QPointF( 0.0, 0.0 ), -sinPhi, cosPhi ); // rotate to orthogonal position relative to rectangle + + if (_internal->_constrainQuadratic) + { + qreal d = floor(sqrt(0.5*(rotatedDeltaMove.x()*rotatedDeltaMove.x() + rotatedDeltaMove.y()*rotatedDeltaMove.y()))); + + switch (handleIdx) + { + case 0 : rotatedDeltaMove = (rotatedDeltaMove.x() < 0) ? QPointF(-d, d) : QPointF( d,-d); break; + case 1 : rotatedDeltaMove = (rotatedDeltaMove.x() > 0) ? QPointF( d, d) : QPointF(-d,-d); break; + case 2 : rotatedDeltaMove = (rotatedDeltaMove.x() > 0) ? QPointF( d,-d) : QPointF(-d, d); break; + case 3 : rotatedDeltaMove = (rotatedDeltaMove.x() < 0) ? QPointF(-d,-d) : QPointF( d, d); break; + default: break; + } + } + + int dx = int((fabs(rotatedDeltaMove.x()) > 0) ? rotatedDeltaMove.x()/fabs(rotatedDeltaMove.x()) : 0 ); + int dy = int((fabs(rotatedDeltaMove.y()) > 0) ? rotatedDeltaMove.y()/fabs(rotatedDeltaMove.y()) : 0 ); + + QPointF moveStepByStep(0,0); + QPointF resultDeltaMove(0,0); + + while (constrainsComplied) + { + moveStepByStep.setX( moveStepByStep.x() + dx ); + moveStepByStep.setY( moveStepByStep.y() + dy ); + + int endCondition = 2; + + if (fabs(moveStepByStep.x()) >= fabs(rotatedDeltaMove.x())) { moveStepByStep.setX(rotatedDeltaMove.x()); --endCondition; } + if (fabs(moveStepByStep.y()) >= fabs(rotatedDeltaMove.y())) { moveStepByStep.setY(rotatedDeltaMove.y()); --endCondition; } + + QPointF loc = MathUtils::rotate( handlePointPtr-> getCoordinate()._sceneCoordinate, _internal->_centerScene, -sinPhi, cosPhi ); // rotate back to orthogonal position + + loc += moveStepByStep; + + constrainsComplied = _internal->minimumSizeReached( handleIdx, -1, loc ); + + if (constrainsComplied) + { + QPointF tmpDeltaMove(moveStepByStep); + + constrainsComplied = checkMinMaxConstrains( loc, tmpDeltaMove ); + + if (constrainsComplied) + { + if (fabs(tmpDeltaMove.x()) > fabs(resultDeltaMove.x())) resultDeltaMove.setX( tmpDeltaMove.x() ); + if (fabs(tmpDeltaMove.y()) > fabs(resultDeltaMove.y())) resultDeltaMove.setY( tmpDeltaMove.y() ); + } + } + + if (0 == endCondition) break; + + } // while + + deltaMove = MathUtils::rotate( resultDeltaMove, QPointF( 0.0, 0.0 ), sinPhi, cosPhi ); + + DEBUG_LOG(_internal->_logger) << LOGFUNCTION << " " << handleIdx << " deltaMove " << deltaMove.x() << " ; " << deltaMove.y() << " centerScene " + << _internal->_centerScene.x() << " ; " << _internal->_centerScene.y() << " --> " << constrainsComplied; + + return (constrainsComplied || !deltaMove.isNull()); + + } /* RubberbandRectangle::constrainsSatisfied */ + + + void RubberbandRectangle::adjustNeighbouringPoints( RubberbandPointHandle * handlePointPtr, unsigned int handleIdx, QPointF deltaMove ) + { + // adjust neighbouring point handles + + unsigned int beforeIdx = (handleIdx > 0) ? handleIdx-1 : 3; + unsigned int nextIdx = (handleIdx < 3) ? handleIdx+1 : 0; + + RubberbandPointHandle * beforePoint = dynamic_cast(Rubberband::_internal->_handleList[beforeIdx].get()); + RubberbandPointHandle * nextPoint = dynamic_cast(Rubberband::_internal->_handleList[nextIdx ].get()); + + qreal sinMap = _internal->_mapOVec.y(); + qreal cosMap = _internal->_mapOVec.x(); + qreal sinRec = _internal->_oVec.y(); + qreal cosRec = _internal->_oVec.x(); + + qreal sinPhi = sinRec * cosMap - cosRec * sinMap; // Addition-Theorem + qreal cosPhi = cosRec * cosMap + sinRec * sinMap; + + QPointF loc = handlePointPtr -> getCoordinate()._sceneCoordinate; + QPointF beforeLoc = beforePoint -> getCoordinate()._sceneCoordinate; + QPointF nextLoc = nextPoint -> getCoordinate()._sceneCoordinate; + + loc = MathUtils::rotate( loc , _internal->_center, -sinPhi, cosPhi ); + beforeLoc = MathUtils::rotate( beforeLoc, _internal->_center, -sinPhi, cosPhi ); + nextLoc = MathUtils::rotate( nextLoc , _internal->_center, -sinPhi, cosPhi ); + deltaMove = MathUtils::rotate( deltaMove, QPointF(0.0, 0.0) , -sinPhi, cosPhi ); + + bool beforeIsHorizontal( fabs(loc.x() - beforeLoc.x()) > fabs(loc.y() - beforeLoc.y()) ); + bool nextIsHorizontal ( fabs(loc.x() - nextLoc.x()) > fabs(loc.y() - nextLoc.y()) ); + +#ifdef NEVER + qDebug() << "RubberbandRectangle::adjustNeibouringPoints " << deltaMove.x() << " , " << deltaMove.y(); + qDebug() << " before is horizontal " << beforeIsHorizontal; + qDebug() << " next is horizontal " << nextIsHorizontal; + qDebug() << " point " << loc; + qDebug() << " point before " << beforeLoc; + qDebug() << " point next " << nextLoc; +#endif + + if (beforeIsHorizontal) beforeLoc.setY( beforeLoc.y() + deltaMove.y() ); + else beforeLoc.setX( beforeLoc.x() + deltaMove.x() ); + + if (nextIsHorizontal) nextLoc.setY( nextLoc.y() + deltaMove.y() ); + else nextLoc.setX( nextLoc.x() + deltaMove.x() ); + +#ifdef NEVER + qDebug() << " point before " << beforeLoc; + qDebug() << " point next " << nextLoc; +#endif + + // rotate back + beforeLoc = MathUtils::rotate( beforeLoc, _internal->_center, sinPhi, cosPhi ); + nextLoc = MathUtils::rotate( nextLoc , _internal->_center, sinPhi, cosPhi ); + + // store coordinates + beforePoint -> updateCoordinate(beforeLoc); + nextPoint -> updateCoordinate(nextLoc ); + + } /* RubberbandRectangle::adjustNeighbouringPoints */ + + + bool RubberbandRectangle::adjustLine( QPointF & deltaMove, bool & constrainsComplied ) + { + constrainsComplied = true; + for (unsigned i = 0; i < 4; ++i) + { + if (_internal->_lineHandle[i]->isSelected()) + { + int idx0 = i; + int idx1 = (i+1) % 4; + + QPointF loc0 = dynamic_cast(Rubberband::_internal->_handleList[idx0].get())->getCoordinate()._sceneCoordinate; + QPointF loc1 = dynamic_cast(Rubberband::_internal->_handleList[idx1].get())->getCoordinate()._sceneCoordinate; + + qreal sinMap = _internal->_mapOVec.y(); + qreal cosMap = _internal->_mapOVec.x(); + qreal sinRec = _internal->_oVec.y(); + qreal cosRec = _internal->_oVec.x(); + + qreal sinPhi = sinRec * cosMap - cosRec * sinMap; // Addition-Theorem + qreal cosPhi = cosRec * cosMap + sinRec * sinMap; + + loc0 = MathUtils::rotate( loc0 , _internal->_centerScene, -sinPhi, cosPhi ); + loc1 = MathUtils::rotate( loc1 , _internal->_centerScene, -sinPhi, cosPhi ); + deltaMove = MathUtils::rotate( deltaMove, QPointF( 0.0, 0.0 ) , -sinPhi, cosPhi ); + + bool isVertical((i%2) == 1); + + int dx = int( ( isVertical && (fabs(deltaMove.x()) > 0)) ? deltaMove.x()/fabs(deltaMove.x()) : 0 ); + int dy = int( (!isVertical && (fabs(deltaMove.y()) > 0)) ? deltaMove.y()/fabs(deltaMove.y()) : 0 ); + bool doStepByStep = ((0 != dx) || (0 != dy)); + + + QPointF moveStepByStep(0,0); + QPointF resultDeltaMove(0,0); + + while (doStepByStep) + { + QPointF stepLoc0( loc0 ); + QPointF stepLoc1( loc1 ); + + if (0 != dx) + { + moveStepByStep.setX( moveStepByStep.x() + dx ); + if (fabs(moveStepByStep.x()) >= fabs(deltaMove.x())) { moveStepByStep.setX(deltaMove.x()); doStepByStep = false; } + stepLoc0.setX( stepLoc0.x() + moveStepByStep.x() ); stepLoc1.setX( stepLoc1.x() + moveStepByStep.x() ); + } + + if (0 != dy) + { + moveStepByStep.setY( moveStepByStep.y() + dy ); + if (fabs(moveStepByStep.y()) >= fabs(deltaMove.y())) { moveStepByStep.setY(deltaMove.y()); doStepByStep = false; } + stepLoc0.setY( stepLoc0.y() + moveStepByStep.y() ); stepLoc1.setY( stepLoc1.y() + moveStepByStep.y() ); + } + + constrainsComplied = _internal->minimumSizeReached(-1,i,stepLoc0); + + if (!constrainsComplied) continue; + + constrainsComplied = checkMinMaxConstrains( stepLoc0, isVertical ); + + if (!constrainsComplied) continue; + + resultDeltaMove = moveStepByStep; + + if (_internal->_constrainQuadratic) + { + qreal h = stepLoc0.y() - _internal->_centerScene.y(); + qreal w = stepLoc0.x() - _internal->_centerScene.x(); + + qreal d = fabs( isVertical ? w : h ); + + stepLoc0 = MathUtils::rotate( QPointF(_internal->_centerScene.x() - d, _internal->_centerScene.y() + d ), _internal->_centerScene , sinPhi, cosPhi ); + dynamic_cast(Rubberband::_internal->_handleList[0].get())->updateCoordinate( stepLoc0 ); + + stepLoc0 = MathUtils::rotate( QPointF(_internal->_centerScene.x() + d, _internal->_centerScene.y() + d ), _internal->_centerScene , sinPhi, cosPhi ); + dynamic_cast(Rubberband::_internal->_handleList[1].get())->updateCoordinate( stepLoc0 ); + + stepLoc0 = MathUtils::rotate( QPointF(_internal->_centerScene.x() + d, _internal->_centerScene.y() - d ), _internal->_centerScene , sinPhi, cosPhi ); + dynamic_cast(Rubberband::_internal->_handleList[2].get())->updateCoordinate( stepLoc0 ); + + stepLoc0 = MathUtils::rotate( QPointF(_internal->_centerScene.x() - d, _internal->_centerScene.y() - d ), _internal->_centerScene , sinPhi, cosPhi ); + dynamic_cast(Rubberband::_internal->_handleList[3].get())->updateCoordinate( stepLoc0 ); + } + else + { + stepLoc0 = MathUtils::rotate( stepLoc0, _internal->_centerScene , sinPhi, cosPhi ); + stepLoc1 = MathUtils::rotate( stepLoc1, _internal->_centerScene , sinPhi, cosPhi ); + + // store coordinates + dynamic_cast(Rubberband::_internal->_handleList[idx0].get())->updateCoordinate(stepLoc0); + dynamic_cast(Rubberband::_internal->_handleList[idx1].get())->updateCoordinate(stepLoc1); + } + } // while (doStepByStep) + + deltaMove = MathUtils::rotate( resultDeltaMove, QPointF( 0.0, 0.0 ), sinPhi, cosPhi ); + + return (constrainsComplied || !deltaMove.isNull()); + } // if handle is selected + } // for handle-index + return true; + } /* RubberbandRectangle::adjustLine */ + + + void RubberbandRectangle::updateHandles(const QPointF & scenePos, QPointF deltaMove) + { + bool constrainsComplied = true; + + if ((_internal->_rotateHandle.valid()) && _internal->_rotateHandle->isSelected()) + { + qreal sinA = scenePos.x() - _internal->_centerScene.x(); + qreal cosA = - scenePos.y() + _internal->_centerScene.y(); + qreal len = sqrt(cosA*cosA+sinA*sinA); + + cosA /= len; + sinA /= len; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + + if (handlePointPtr == nullptr) continue; + + QPointF p = MathUtils::rotate( handlePointPtr->getCoordinate()._sceneCoordinate, _internal->_centerScene, -_internal->_oVec.y(), _internal->_oVec.x() ); + p = MathUtils::rotate( p, _internal->_centerScene, sinA, cosA ); + handlePointPtr->updateCoordinate( p ); + + p.setX( handlePointPtr->getCoordinate()._mapCoordinate.x() ); + p.setY( handlePointPtr->getCoordinate()._mapCoordinate.y() ); + } + + qreal dSin = sinA*_internal->_oVec.x() - cosA*_internal->_oVec.y(); // addition theorem + qreal dCos = cosA*_internal->_oVec.x() + sinA*_internal->_oVec.y(); + + _lastCoordinateChange.setToIdentity(); // clear matrix + _lastCoordinateChange.putTranslation( -_internal->_center ); // move center to (0,0) + _lastCoordinateChange.putRotate( QPointF(dCos,dSin) ); // do rotation + _lastCoordinateChange.putTranslation( _internal->_center ); // move center back + + _internal->_oVec.setX(cosA); + _internal->_oVec.setY(sinA); + + _baseInternal->_lastClickPosition = scenePos; + } + else + { + bool frameIsSelected( ((_internal->_frameHandle.valid()) && (_internal->_frameHandle->isSelected())) ); + bool pointsAdjusted( false ); + + unsigned int handleIdx = 0; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + + if ((handlePointPtr) && ((handlePointPtr->isSelected()) || (frameIsSelected))) + { + pointsAdjusted = true; + + if (!frameIsSelected) + { + if (constrainsSatisfied( handlePointPtr, handleIdx, deltaMove, constrainsComplied )) + { + adjustNeighbouringPoints( handlePointPtr, handleIdx, deltaMove ); + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Move selected handle after moving the neighbouring points because + * for larger deltaMove the decision which values have to be modified + * will fail if the handle has already moved to new position. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + handlePointPtr->moveCoordinate( deltaMove ); + + if (_internal->_constrainQuadratic) _baseInternal->_lastClickPosition = scenePos; + else _baseInternal->_lastClickPosition += deltaMove; + + break; // only one handle can be selected at time + } + } + else handlePointPtr->moveCoordinate( deltaMove ); // frameIsSelected == true + } + ++handleIdx; + } + + + if (frameIsSelected) + { + _internal->_centerScene += deltaMove; + _baseInternal->_lastClickPosition = scenePos; + + if (Rubberband::_internal->_handleList.isEmpty()) + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Rubberband has only frame handle, we have to do some calculations to set the new center point + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + QPointF lastCenter = _internal->_center; + + RubberbandHandle::RubberbandCoordinate movedCenter(Rubberband::_internal->_mapView, _internal->_centerScene ); + _internal->_center.setX( movedCenter._mapCoordinate.x() ); + _internal->_center.setY( movedCenter._mapCoordinate.y() ); + + _lastCoordinateChange.setToIdentity(); // clear matrix + _lastCoordinateChange.putTranslation( _internal->_center - lastCenter ); // set translation + } + } + else if ((!pointsAdjusted) && (adjustLine(deltaMove,constrainsComplied))) _baseInternal->_lastClickPosition += deltaMove; + } + + markFailedConstrains( scenePos, !constrainsComplied ); + + update(); + + } /* RubberbandRectangle::updateHandles */ + + + void RubberbandRectangle::buildTmpConstructionPath( const QPointF & start + , const QPointF & end + , QVector & path + , QPointF & sceneCenter + , qreal & sceneWidth + , qreal & sceneHeight ) + { + _internal->setScreenCenter( Rubberband::_internal->_mapView ); + + auto length = [ ]( qreal x, qreal y ) -> qreal { return sqrt(x*x+y*y); }; + + auto rotate = [this]( qreal x, qreal y, bool back ) -> QPointF + { + qreal sinPhi = (back) ? -_internal->_mapOVec.y() : _internal->_mapOVec.y(); + qreal cosPhi = _internal->_mapOVec.x(); + + qreal px = x*cosPhi + y*sinPhi; + qreal py = y*cosPhi - x*sinPhi; + return QPointF(px,py); + }; // lambda rotate + + + RubberbandHandle::RubberbandCoordinate mapPointStart( Rubberband::_internal->_mapView, start ); + + const Esri::ArcGISRuntime::Point & mapStart = mapPointStart._mapCoordinate; + + const Esri::ArcGISRuntime::Point mapMaxW( mapStart.x() + _internal->_maxWidth + , mapStart.y() + , Rubberband::_internal->_mapView->map()->spatialReference() ); + + const Esri::ArcGISRuntime::Point mapMinW( mapStart.x() + _internal->_minWidth + , mapStart.y() + , Rubberband::_internal->_mapView->map()->spatialReference() ); + + const Esri::ArcGISRuntime::Point mapMaxH( mapStart.x() + , mapStart.y() + _internal->_maxHeight + , Rubberband::_internal->_mapView->map()->spatialReference() ); + + const Esri::ArcGISRuntime::Point mapMinH( mapStart.x() + , mapStart.y() + _internal->_minHeight + , Rubberband::_internal->_mapView->map()->spatialReference() ); + + RubberbandHandle::RubberbandCoordinate mapPointMaxW( Rubberband::_internal->_mapView, mapMaxW ); + RubberbandHandle::RubberbandCoordinate mapPointMinW( Rubberband::_internal->_mapView, mapMinW ); + RubberbandHandle::RubberbandCoordinate mapPointMaxH( Rubberband::_internal->_mapView, mapMaxH ); + RubberbandHandle::RubberbandCoordinate mapPointMinH( Rubberband::_internal->_mapView, mapMinH ); + + qreal maxSceneWidth = length( mapPointMaxW._sceneCoordinate.x() - mapPointStart._sceneCoordinate.x(), mapPointMaxW._sceneCoordinate.y() - mapPointStart._sceneCoordinate.y() ); + qreal minSceneWidth = length( mapPointMinW._sceneCoordinate.x() - mapPointStart._sceneCoordinate.x(), mapPointMinW._sceneCoordinate.y() - mapPointStart._sceneCoordinate.y() ); + qreal maxSceneHeight = length( mapPointMaxH._sceneCoordinate.x() - mapPointStart._sceneCoordinate.x(), mapPointMaxH._sceneCoordinate.y() - mapPointStart._sceneCoordinate.y() ); + qreal minSceneHeight = length( mapPointMinH._sceneCoordinate.x() - mapPointStart._sceneCoordinate.x(), mapPointMinH._sceneCoordinate.y() - mapPointStart._sceneCoordinate.y() ); + + const QPointF & pe = rotate( end.x() - start.x(), end.y() - start.y(), true ); + + qreal w = pe.x(); + qreal h = pe.y(); + + if ((_internal->_minWidth > 0) && (minSceneWidth > fabs(w))) w = (w>=0) ? minSceneWidth : -minSceneWidth; + if ((_internal->_maxWidth > 0) && (maxSceneWidth < fabs(w))) w = (w>=0) ? maxSceneWidth : -maxSceneWidth; + if ((_internal->_minHeight > 0) && (minSceneHeight > fabs(h))) h = (h>=0) ? minSceneHeight : -minSceneHeight; + if ((_internal->_maxHeight > 0) && (maxSceneHeight < fabs(h))) h = (h>=0) ? maxSceneHeight : -maxSceneHeight; + + if (_internal->_constrainQuadratic) + { + if (fabs(w) > fabs(h)) h = (h >= 0) ? fabs(w) : -fabs(w); + else if (fabs(w) < fabs(h)) w = (w >= 0) ? fabs(h) : -fabs(h); + } + + path << start; + path << start + rotate( w, 0, false ); + path << start + rotate( w, h, false ); + path << start + rotate( 0, h, false ); + + sceneWidth = fabs(w); + sceneHeight = fabs(h); + sceneCenter = start + rotate(w*0.5,h*0.5,false); + + } /* RubberbandRectangle::buildTmpConstructionPath */ + + + void RubberbandRectangle::buildTmpConstruction( SituationMap::RefPointer & tmpGraphic, QPointF & start, QPointF & end ) + { + if (!tmpGraphic.valid()) + { + tmpGraphic = new DrawablePainterPath( Rubberband::_internal->_mapView ); + tmpGraphic -> setDrawColor( _drawColor ); + tmpGraphic -> setFillColor( _fillColor ); + tmpGraphic -> setLineWidth( 1 ); + tmpGraphic -> zBufferPlane( RubberbandDrawable::ZBufferPlane::Drawables ); + } + + QVector path; + QPointF sceneCenter; + qreal sceneWidth; + qreal sceneHeight; + + buildTmpConstructionPath( start, end, path, sceneCenter, sceneWidth, sceneHeight ); + + DrawablePainterPath * tmpPath = static_cast(tmpGraphic.get()); + + tmpPath -> setPath( path ); + tmpPath -> enableClosedLine(true); + + } /* RubberbandRectangle::buildTmpConstruction */ + + + void RubberbandRectangle::getTmpConstructionParameter( const QPointF & start, const QPointF & end, QPointF & sceneCenter, qreal & sceneWidth, qreal & sceneHeight ) + { + QVector path; + + buildTmpConstructionPath( start, end, path, sceneCenter, sceneWidth, sceneHeight ); + + } /* RubberbandRectangle::getTmpConstructionParameter */ + + + bool RubberbandRectangle::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) + { + markFailedConstrains( QPointF(), false ); + + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + + Rubberband::_internal->_lastClickPosition = scenePos; + + if (isEnabledConstructionState()) + { + if (_internal->_tmpConstruction.valid()) + { + qreal sceneWidth; + qreal sceneHeight; + + getTmpConstructionParameter( _internal->_constructionStart, scenePos, _internal->_centerScene, sceneWidth, sceneHeight ); + _internal->setInitialRectSize( this, sceneWidth, sceneHeight ); + _internal->_tmpConstruction = nullptr; + + if (_finishAfterConstruction) + { + disenableConstructionState(); + constructHandles(); + update(); + applyAndEndAction(causer,x,y); + return true; + } + } + else + { + _internal->_centerScene = scenePos; + + _internal->setInitialRectSize( this, scenePos, x, y ); + + // now set values in map coordinates + } + + _internal->setRotatedFrame( Rubberband::_internal->_mapView ); + + disenableSelectedState(); + disenableConstructionState(); + constructHandles(); + update(); + activateButtons(); + return true; + } + else // isEnabledConstructionState() + { + if (isEnabledSelectedState()) + { + updateHandles( scenePos, scenePos-_baseInternal->_lastClickPosition ); + + markFailedConstrains( QPointF(), false ); + + for (auto lh : _internal->_lineHandle) + { + if (lh.valid()) lh->setSelection(false); + } + if (_internal->_rotateHandle.valid()) _internal->_rotateHandle->setSelection(false); + if (_internal->_frameHandle.valid()) _internal->_frameHandle->setSelection(false); + + disenableSelectedState(); + disenableMultiSelectedState(); + updateButtonPosition(); + update(); + activateButtons(); + return true; + + } + else if (_baseInternal->buttonProxyActive( scenePos )) return false; + } + + updateButtonPosition(); + if (isEnabledModificationState()) activateButtons(); + } + return false; + } /* RubberbandRectangle::setCoordinateAction */ + + + bool RubberbandRectangle::movePickedAction( AbstractEventDispatcher*, int x, int y ) + { + if ((rubberbandIsAlive()) && (Rubberband::_internal->_mapView) && (Rubberband::_internal->_mapScene)) + { + if (isEnabledConstructionState()) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + if ((_internal->_tmpConstruction.valid()) || ((_internal->_constructionStart - scenePos).manhattanLength() > 5)) + { + buildTmpConstruction( _internal->_tmpConstruction, _internal->_constructionStart, scenePos ); + } + return true; + } + else + { + if (isEnabledSelectedState()) + { + QPointF scenePos(pixel2ScenePosition(x,y)); + updateHandles( scenePos, scenePos-_baseInternal->_lastClickPosition ); + return true; + } + } + } + return false; + } /* RubberbandRectangle::movePickedAction */ + + + bool RubberbandRectangle::contextMenuAction( AbstractEventDispatcher * , int , int ) + { + return rubberbandIsAlive(); + } /* RubberbandRectangle::contextMenuAction */ + + + bool RubberbandRectangle::completeStateAction( AbstractEventDispatcher * causer, int x, int y ) + { + if (isEnabledConstructionState()) + { + // Position of rectangle is still unknown, there are no data to apply + return abortAction(causer,x,y); + } + return applyAndEndAction(causer,x,y); + } /* RubberbandRectangle::completeStateAction */ + + + bool RubberbandRectangle::undoAction( AbstractEventDispatcher * , int , int ) + { + return rubberbandIsAlive(); + } /* RubberbandRectangle::undoAction */ + + + bool RubberbandRectangle::deleteAction(AbstractEventDispatcher * , int , int ) + { + return rubberbandIsAlive(); + } /* RubberbandRectangle::deleteAction */ + + + + void RubberbandRectangle::update() + { + Rubberband::update(); + + QVector coordinateList; + + coordinateList.reserve(4); + + if (Rubberband::_internal->_handleList.count() == 4) + { + for (unsigned int i = 0; i < 4; ++i) + { + auto handlePoint = Rubberband::_internal->_handleList[i]; + RubberbandPointHandle * handlePointPtr = dynamic_cast(handlePoint.get()); + + if (handlePointPtr) + { + const RubberbandHandle::RubberbandCoordinate & pos = handlePointPtr->getCoordinate(); + + _internal->_lineHandle[i] -> updateCoordinate( 0, pos._sceneCoordinate ); + _internal->_lineHandle[i] -> setDrawColor(_handleDrawColor); + _internal->_lineHandle[i] -> setFillColor(_handleFillColor); + _internal->_lineHandle[i] -> setSelectionColor(_handleSelectionColor); + _internal->_lineHandle[i] -> setPickingRatio( _pickingRatio ); + + if (i == 0) _internal->_lineHandle[ 3 ] -> updateCoordinate( 1, pos._sceneCoordinate ); + else _internal->_lineHandle[i-1] -> updateCoordinate( 1, pos._sceneCoordinate ); + + coordinateList.append( handlePointPtr->getCoordinate() ); + } + } /* for handlePoint */ + } + if (_internal->_frameHandle.valid()) + { + if (coordinateList.isEmpty()) + { + const QPointF & center = _internal->_center; + qreal w = _internal->_rectWidth*0.5; + qreal h = _internal->_rectHeight*0.5; + qreal sinPhi = -_internal->_oVec.y(); + qreal cosPhi = _internal->_oVec.x(); + QPointF p; + + p = _internal->rotateVertex( center, -w, -h, sinPhi, cosPhi ); + coordinateList += RubberbandHandle::RubberbandCoordinate( Rubberband::_internal->_mapView, convert(p,Rubberband::_internal->_mapView->map()->spatialReference()) ); + + p = _internal->rotateVertex( center, w, -h, sinPhi, cosPhi ); + coordinateList += RubberbandHandle::RubberbandCoordinate( Rubberband::_internal->_mapView, convert(p,Rubberband::_internal->_mapView->map()->spatialReference()) ); + + p = _internal->rotateVertex( center, w, h, sinPhi, cosPhi ); + coordinateList += RubberbandHandle::RubberbandCoordinate( Rubberband::_internal->_mapView, convert(p,Rubberband::_internal->_mapView->map()->spatialReference()) ); + + p = _internal->rotateVertex( center, -w, h, sinPhi, cosPhi ); + coordinateList += RubberbandHandle::RubberbandCoordinate( Rubberband::_internal->_mapView, convert(p,Rubberband::_internal->_mapView->map()->spatialReference()) ); + } + + _internal->_frameHandle -> setDrawColor(_frameColor); + _internal->_frameHandle -> setFillColor(_frameFillColor); + _internal->_frameHandle -> setSelectionColor(_frameSelectionColor); + _internal->_frameHandle -> updateFrameSceneMargin( _frameHandleMargin ); + _internal->_frameHandle -> updateFrameShape( coordinateList, _internal->_oVec.y(), _internal->_oVec.x() ); + _internal->_frameHandle -> drawDisabled( (_frameHandleMargin < 0) ); + } + + _internal -> updateFrame( Rubberband::_internal->_mapView, coordinateList ); + + if (_internal->_rotateHandle.valid()) + { + _internal -> _rotateHandle -> setSceneOffset( _internal->getSceneOffsetRotationHandle( (_frameHandleMargin > 0) ? _frameHandleMargin : 0 ) ); + _internal -> _rotateHandle -> updateCoordinate( _internal->getScenePositionRotationHandle() ); + } + + } /* RubberbandRectangle::update */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.h new file mode 100755 index 000000000..1e293b46a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_rectangle.h @@ -0,0 +1,274 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define point feature geoemtry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband.h" + +#include + + +namespace SituationMap { namespace RB +{ + + class RubberbandPointHandle; + + /** + @class RubberbandRectangle rubberband_rectangle.h + @brief Implementation of a rectangle rubberband + @see Rubberband + */ + class SITUATIONMAP_EXPORT RubberbandRectangle : public Rubberband + { + Q_OBJECT + + /** + @name Construction & Destruction + @} + */ + + friend class RubberbandFactory; + friend class RubberbandEllipse; + friend class RubberbandTransform; + + explicit RubberbandRectangle( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + + ~RubberbandRectangle(); + /** @} */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionPress ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionRelease( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override; + + + /** + @see rectangleVerticies + */ + bool coordinatesAreValid() const override { return rectangleIsValid(); } + /** + * @brief Get 4 verticies of rectangle + */ + QVector coordinateVector( CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + /** + * @brief Get center point of rectangle, see methode centerPoint + */ + QPointF singleCoordinate( unsigned int idx = 0, CoordinateSystem cs = CoordinateSystem::Geo ) const override; + + /** + * @brief Set 4 verticies of rectangle + */ + void setCoordinateVector( const QVector &, CoordinateSystem cs = CoordinateSystem::Geo ) override; + + + /** + * @brief Returns center point of rectangle + */ + QPointF centerPoint( CoordinateSystem cs = CoordinateSystem::Geo ) const; + + /** + * @brief Sets center point of rectangle + */ + void centerPoint(const QPointF ¢erPoint, CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + * @brief Get 4 verticies of rectangle + */ + QVector rectangleVerticies( CoordinateSystem cs = CoordinateSystem::Geo ) const; + + /** + * @brief Set 4 verticies of rectangle + */ + void rectangleVerticies( const QVector &, CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + * @brief Returns true if a valid rectangle has been set with method \c rectangleVerticies + * The rectangle rubberband is expecting input coordinates defining a rectangle in map coordinate system. Otherwise + * a similar as possible rectangle is used for rubberbanding and the coordinates returned by \c coordinateVector() + * will be different from input coordinates. + */ + bool rectangleIsValid() const; + + /** + * @brief Pass back the rectangle parameter + * For convenience some times it would be an asset to get the parameter like center, width, height and rotation. This + * function pass back these parameter. A coordinate transformation or projection will \b not be done. All values are related to + * the coordinate system of the map. + * Warning, if the map coordinate system doesn't use the same geographical projection as the simulation coordinate system + * the returned parameter may be all wrong. + * + * @param center Center point of the rectangle in map coordinates. + * @param width Width of the rectangle. + * @param height Height of the rectangle. + * @param direction Normalized direction vector related to width axis. + */ + void rectangleParameter( QPointF & center, qreal & width, qreal & height, QPointF & direction, CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + * @brief Build rectangle from rectangle parameter + * For convenience some times it would be an asset to initialize a rectangle rubberband from rectangle the parameter like width, height and rotation. + * A coordinate transformation or projection will \b not be done. All values are related to the coordinate system of the map. + * Warning, if the map coordinate system doesn't use the same geographical projection as the simulation coordinate system all parameter may be wrong. + * + * @param center Center of the rectangle in \b cs coordinate. + * @param width Width of the rectangle. + * @param height Height of the rectangle. + * @param direction Normalized direction vector related to width axis. + * @param cs Coordinate System. + */ + void buildFromRectangleParamter( const QPointF & center, qreal width, qreal height, const QPointF & direction, CoordinateSystem cs = CoordinateSystem::Geo ); + + /** + * @brief Build rectangle relative to the bottom left corner + * For convenience some times it would be an asset to initialize a rectangle rubberband from rectangle the parameter like width, height and rotation. + * A coordinate transformation or projection will \b not be done. All values are related to the coordinate system of the map. + * Warning, if the map coordinate system doesn't use the same geographical projection as the simulation coordinate system all parameter may be wrong. + * + * @param bottomLeft Bottom left corner of the rectangle in \b cs coordinates. + * @param width Width of the rectangle. + * @param height Height of the rectangle. + * @param direction Normalized direction vector related to width axis. + * @param cs Coordinate System. + */ + void buildRectangleRelativeToBottomLeftCorner( const QPointF & bottomLeft, qreal width, qreal height, const QPointF & direction, CoordinateSystem cs = CoordinateSystem::Geo ); + + + /** + * @brief If set to true only quadratic rectangles can be created. + */ + void constrainQuadratic( bool on ); + + /** + * @brief Returns the setting for quadratic constrain. + */ + bool constrainQuadratic() const; + + /** + * @brief If set to true the rectangle can not be rotated. + */ + void constrainOrthogonal( bool on ); + + /** + * @brief Returns the setting fpr orthogonsl constrain. + */ + bool constrainOrthogonal() const; + + + /** + @name Setting Constrains for Width and Height + To define a constrain value a postive values has to be assigned. Negative values will reset the constrain setting to undefined state. + @{ + */ + void minWidth ( qreal x ); + void maxWidth ( qreal x ); + void minHeight( qreal x ); + void maxHeight( qreal x ); + /** @} */ + + + protected: + + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + bool _rectangleIsValid; + + /* + Coordinate matrix to describe the change of coordinates of the last user input + */ + MathUtils::Matrix2D _lastCoordinateChange; + + + /* + ModificationMode is used internally to restrict input for special purposes. It will be defined + by derived class + */ + enum class ModificationMode + { + NoneRestriction, + NoneRotation, + TranslationOnly, + ScalingOnly, + RotationOnly + }; + + void setModificationMode( ModificationMode ); + + + void activateButtons() override; + void constructHandles(); + void updateHandles( const QPointF & scenePos, QPointF deltaMove ); + + bool adjustLine( QPointF & deltaMove, bool & constrainsComplied ); + void adjustNeighbouringPoints( RubberbandPointHandle * handlePointPtr, unsigned int handleIdx, QPointF deltaMove ); + + bool checkMinMaxConstrains(const QPointF & vertexScene, QPointF & deltaMovePtr ); + bool checkMinMaxConstrains(const QPointF & vertexScene, bool isVertical ); + + bool constrainsSatisfied( RubberbandPointHandle * handlePointPtr, unsigned int handleIdx, QPointF & deltaMove, bool & constrainsComplied ); + + void useDottedLineHandles( bool on = true ); + + void update() override; + + QPointF getSceneCenter() const; + qreal getSceneWidth() const; + qreal getSceneHeight() const; + + QPointF getCenter() const; + qreal getWidth() const; + qreal getHeight() const; + + void getSceneRotation( qreal & sinA, qreal & cosA ) const; + void getMapRotation ( qreal & sinA, qreal & cosA ) const; + + void buildTmpConstructionPath(const QPointF & start, const QPointF & end, QVector & path, QPointF & sceneCenter, qreal & sceneWidth, qreal & sceneHeight ); + + virtual void buildTmpConstruction( SituationMap::RefPointer & tmpGraphic, QPointF & start, QPointF & end ); + virtual void getTmpConstructionParameter( const QPointF & start, const QPointF & end, QPointF & sceneCenter, qreal & sceneWidth, qreal & sceneHeight ); + + private: + + void setButtonPosition(); + + }; /* RubberbandRectangle */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.cpp new file mode 100755 index 000000000..fa4da4ce9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.cpp @@ -0,0 +1,93 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multi point feature geometry +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_smooth_line.h" +#include "rubberband_point_handle.h" + +#include "drawable_smooth_path.h" + +#include "rubberband_internal.h" + + +namespace SituationMap { namespace RB +{ + + struct RubberbandSmoothLine::InternalData + { + SituationMap::RefPointer _smoothLineItem; + }; /* RubberbandSmoothLine::InternalData*/ + + + RubberbandSmoothLine::RubberbandSmoothLine( SituationMap::Map * map, QObject * parent ) + : RubberbandMultiPointFeature(map,true,parent), _internal( new InternalData() ) + { + } /* RubberbandSmoothLine::RubberbandSmoothLine */ + + RubberbandSmoothLine::~RubberbandSmoothLine() + { + delete _internal; + } /* RubberbandSmoothLine::~RubberbandSmoothLine */ + + + void RubberbandSmoothLine::update() + { + QVector vertexVector; + + vertexVector.reserve( Rubberband::_internal->_handleList.size() ); + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * pointHandlePtr = dynamic_cast(handle.get()); + + if (pointHandlePtr) + { + vertexVector.append( pointHandlePtr -> getCoordinate() ); + } + } + if (vertexVector.size() > 2) + { + if (!_internal->_smoothLineItem) + { + _internal->_smoothLineItem = new DrawableSmoothPath( Rubberband::_internal->_mapView ); + } + + _internal->_smoothLineItem -> enableClosedLine( isClosedEnabled() ); + _internal->_smoothLineItem -> setDrawColor( _drawColor ); + _internal->_smoothLineItem -> setFillColor( _fillColor ); + _internal->_smoothLineItem -> setVisibility( true ); + _internal->_smoothLineItem -> setPath( vertexVector ); + } + else if (_internal->_smoothLineItem) _internal->_smoothLineItem->setVisibility( false ); + + RubberbandMultiPointFeature::update(); + + } /* RubberbandSmoothLine::update */ + + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.h new file mode 100755 index 000000000..ce667aa73 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_smooth_line.h @@ -0,0 +1,72 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define multi point feature geoemtry with rounded edges +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_multi_point_feature.h" + + +namespace SituationMap { namespace RB +{ + /** + @class RubberbandSmoothLine rubberband_smooth_line.h + @brief Implementation of a multi point feature rubberband with rounded edges. + @see RubberbandFactory, Rubberband, RubberbandMultiPointFeature + + */ + class SITUATIONMAP_EXPORT RubberbandSmoothLine : public RubberbandMultiPointFeature + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + + explicit RubberbandSmoothLine( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + + ~RubberbandSmoothLine(); + /** @} */ + + protected: + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + + void update() override; + + }; /* RubberbandSmoothLine */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.cpp new file mode 100755 index 000000000..dcc13802b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.cpp @@ -0,0 +1,572 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to edit states of point objects +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 19.10.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_state_select.h" +#include "rubberband_point_handle.h" +#include "rubberband_drawable.h" +#include "rubberband_handle_proxy.h" +#include "rubberband_factory.h" + +#include "rubberband_internal.h" +#include "rubberband_base_internal.h" + + +#include +#include + +#include +#include +#include +#include + +#include "polygon.xpm" + + +namespace SituationMap { namespace RB +{ + + struct RubberbandStateSelect::InternalData + { + Q_DISABLE_COPY(InternalData) + + InternalData() : _logger(getLogger("RubberbandStateSelect")) + {} + + ~InternalData() + { + delete _stateMenu; + } + + QVector _states; + QVector _items; + QList> _stateMenuSelectedHandleList; + LOGGER_T _logger; + + const State * getStateFromValue( int stateValue ) const + { + for (const State &state : _states) + { + if (state.value() == stateValue) return &state; + } + return nullptr; + } /* getStateFromValue */ + + const State * getStateFromItem( const StateItem & item ) const { return getStateFromValue(item.stateValue()); } + + static QPixmap * _polygonIcon; + + QPushButton * _polygonButton = nullptr; + QMenu * _stateMenu = nullptr; + bool _stateMenuIsActive = false; + + }; /* RubberbandMultiPointFeature::InternalData */ + + + QPixmap * RubberbandStateSelect::InternalData::_polygonIcon = nullptr; + + void RubberbandStateSelect::setIconPolygon(const QPixmap & icon) + { + if (InternalData::_polygonIcon == nullptr) InternalData::_polygonIcon = new QPixmap(icon); + else *InternalData::_polygonIcon = icon; + } /* RubberbandStateSelect::setIconPolygon */ + + + RubberbandStateSelect::RubberbandStateSelect( SituationMap::Map * map, QObject * parent ) + : Rubberband(map,parent), _internal(new InternalData()) + { + delete RubberbandBase::_baseInternal->_undoButton; + RubberbandBase::_baseInternal->_undoButton = nullptr; + + _internal->_polygonButton = new QPushButton( (InternalData::_polygonIcon) ? *InternalData::_polygonIcon : QPixmap(polygon_xpm), QString(), RubberbandBase::_baseInternal->_buttonBox ); + + RubberbandBase::_baseInternal->_buttonLayout->insertWidget( 0, _internal->_polygonButton ); + + connect( _internal->_polygonButton, &QPushButton::clicked, this, &RubberbandStateSelect::onDo_areaSelectionBegin ); + + // Check Quartz + connect( Rubberband::_internal->_mapView.data(), &Esri::ArcGISRuntime::MapGraphicsView::viewpointChanged, + this, &RubberbandStateSelect::onDo_updateButtonFixCoordinates ); + connect( Rubberband::_internal->_mapView.data(), &Esri::ArcGISRuntime::MapGraphicsView::rectChanged , + this, &RubberbandStateSelect::onDo_updateButtonFixCoordinates ); + + + setFixedButtonBoxPosition(QPoint(5,5)); + + } /* RubberbandStateSelect::RubberbandStateSelect */ + + + bool RubberbandStateSelect::setStateItems( const QVector & stateVector + , const QVector & itemVector + , CoordinateSystem cs ) + { + if (stateVector.size() < 2) return false; + if (itemVector.isEmpty()) return false; + + // check for all states are valid + for (const State & state : std::as_const(_internal->_states)) + { + if (!state.isValid()) + return false; + } + + bool ok = true; + + _internal -> _states = stateVector; + _internal -> _items = itemVector; + + for (QVector::iterator it = _internal->_items.begin(); it != _internal->_items.end(); ++it) + { + if (it->isValid()) + { + const State * statePtr = _internal->getStateFromItem( *it ); + + if (statePtr == nullptr) + { + ok = false; + statePtr = & _internal->_states.first(); + } + + const QPointF &mapPos = projectCoordinateToMap( cs, it->coordinate() ); + + Esri::ArcGISRuntime::Point mapCoordinate( mapPos.x(), mapPos.y(), Rubberband::_internal->_mapView->map()->spatialReference() ); + RubberbandPointHandle * handlePtr = new RubberbandPointHandle( Rubberband::_internal->_mapView + , Rubberband::_internal->_mapScene + , mapCoordinate + , statePtr->icon() ); + if (handlePtr) + { + handlePtr->setSelection(false); + handlePtr->setUserData( &(*it) ); + Rubberband::_internal->_handleList.append( SituationMap::RefPointer(handlePtr) ); + } + } + else ok = false; + } + update(); + activateButtons(); + buildStateMenu(); + return ok; + } /* RubberbandStateSelect::setStateItems */ + + + const QVector & RubberbandStateSelect::stateItems() const + { + return _internal->_items; + } /* RubberbandStateSelect::stateItems */ + + + void RubberbandStateSelect::activateButtons() + { + _baseInternal->_hasUndoButton = false; + _baseInternal->_applyIsPossible = (!Rubberband::_internal->_handleList.isEmpty()); + _baseInternal->_undoIsPossible = false; + Rubberband::activateButtons(); + } /* RubberbandStateSelect::activateButton */ + + + void RubberbandStateSelect::toggleState( RubberbandPointHandle * handlePtr ) + { + if (_internal->_states.size() < 2) return; + if (handlePtr == nullptr) return; + + StateItem * itemPtr = static_cast(handlePtr->userData()); + + if (itemPtr == nullptr) return; + + if (_internal->_states.size() == 2) + { + const State * statePtr = _internal -> getStateFromItem( *itemPtr ); + + if (statePtr != nullptr) + { + if (statePtr == &_internal->_states.first()) statePtr = &_internal->_states.last(); + else statePtr = &_internal->_states.first(); + } + else statePtr = &_internal->_states.first(); + + itemPtr->_stateValue = statePtr->value(); + handlePtr->setIcon( statePtr->icon() ); + } + + } /* RubberbandStateSelect::toggleState */ + + + void RubberbandStateSelect::setState( RubberbandPointHandle * handlePtr, int stateValue ) + { + if (handlePtr == nullptr) return; + + const State * statePtr = _internal -> getStateFromValue(stateValue); + StateItem * itemPtr = static_cast(handlePtr->userData()); + + if (itemPtr == nullptr) return; + if (statePtr == nullptr) return; + + itemPtr->_stateValue = stateValue; + handlePtr->setIcon( statePtr->icon() ); + + } /* RubberbandStateSelect::setState */ + + + void RubberbandStateSelect::buildStateMenu() + { + if (_internal->_states.isEmpty()) return; + + _internal->_stateMenu = new QMenu(); + + for (const State & state : std::as_const(_internal->_states)) + { + QAction * action = _internal->_stateMenu->addAction( state.icon(), state.description() ); + action -> setData( state.value() ); + + connect( action, &QAction::triggered, this, &RubberbandStateSelect::onDo_setState ); + } + + connect( _internal->_stateMenu, &QMenu::aboutToShow, this, [this]() + { + this->_internal->_stateMenuSelectedHandleList.clear(); + + _baseInternal->_buttonBox->setDisabled(true); + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + if (handle->isSelected()) + { + this->_internal->_stateMenuSelectedHandleList << handle; + } + } + } ); + + connect( _internal->_stateMenu, &QMenu::aboutToHide, this, [this]() + { + _baseInternal->_buttonBox->setEnabled(true); + deSelectHandles(); + } ); + + } /* RubberbandStateSelect::buildStateMenu */ + + + void RubberbandStateSelect::onDo_setState(bool) + { + QAction * action = (QAction*) sender(); + + if (action == nullptr) return; + + int stateValue = action->data().toInt(); + + for (auto handle : std::as_const(_internal->_stateMenuSelectedHandleList)) + setState( dynamic_cast(handle.get()), stateValue ); + + _internal -> _stateMenuSelectedHandleList.clear(); + _internal -> _stateMenuIsActive = false; + + update(); + + } /* RubberbandStateSelect::onDo_setState */ + + + void RubberbandStateSelect::chooseStateMenu() + { + // popup a window with states the user can choose + _internal->_stateMenu->popup( QCursor::pos() ); + _internal -> _stateMenuIsActive = true; + } /* RubberbandStateSelect::chooseStateMenu */ + + + bool RubberbandStateSelect::selectAction( AbstractEventDispatcher*, int x, int y ) + { + DEBUG_LOG(_internal->_logger) << LOGFUNCTION << x << " ; " << y; + + _internal->_stateMenuSelectedHandleList.clear(); + + if (rubberbandIsAlive()) + { + if (_internal->_stateMenuIsActive) return true; + + QPointF scenePos(pixel2ScenePosition(x,y)); + + if (_baseInternal->buttonProxyActive(scenePos)) return false; + + RubberbandHandle * handlePtr = pickHandle(x,y); + + if (handlePtr) + { + handlePtr->setSelection(); + enableSelectedState(); + + update(); + deactivateButtons(); + return true; + } + } + return false; + } /* RubberbandStateSelect::selectAction */ + + + bool RubberbandStateSelect::multiSelectActionPress(AbstractEventDispatcher*, int x, int y) + { + DEBUG_LOG(_internal->_logger) << LOGFUNCTION << x << " ; " << y; + + if (rubberbandIsAlive()) + { + if (_internal -> _stateMenuIsActive) return true; + + RubberbandHandle * handlePtr = pickHandle(x,y); + + if (handlePtr) + { + handlePtr->setSelection(); + enableSelectedState(); + enableMultiSelectedState(); + + update(); + deactivateButtons(); + return true; + } + } + return false; + } /* RubberbandStateSelect::multiSelectActionPress */ + + + bool RubberbandStateSelect::multiSelectActionRelease( AbstractEventDispatcher*, int, int ) + { + return false; + } /* RubberbandStateSelect::multiSelectActionRelease */ + + + bool RubberbandStateSelect::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) + { + DEBUG_LOG(_internal->_logger) << LOGFUNCTION << x << " ; " << y; + + if (rubberbandIsAlive()) + { + Rubberband::_internal->_lastClickPosition = pixel2ScenePosition(x,y); + + if (_internal->_stateMenuIsActive) + { + _internal -> _stateMenuIsActive = false; + disenableSelectedState(); + disenableMultiSelectedState(); + activateButtons(); + return true; + } + + if (isEnabledSelectedState()) + { + bool handlePicked = (pickHandle(x,y) != nullptr); + bool toggleMode = (_internal->_states.size() == 2); + bool eventHandled = false; + + if (!toggleMode) + { + disenableSelectedState(); + disenableMultiSelectedState(); + + chooseStateMenu(); + return true; + } + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if ((handlePointPtr) && (handlePointPtr->isSelected())) + { + handlePointPtr->setSelection(false); + if ((handlePicked) && (toggleMode)) toggleState( handlePointPtr ); + eventHandled = true; + } + } + + disenableSelectedState(); + disenableMultiSelectedState(); + activateButtons(); + return eventHandled; + } + else Rubberband::setCoordinateAction(causer,x,y); + } + return false; + } /* RubberbandStateSelect::setCoordinateAction */ + + + bool RubberbandStateSelect::moveAction(AbstractEventDispatcher*, int x, int y) + { + DEBUG_LOG(_internal->_logger) << LOGFUNCTION << x << " ; " << y; + + if (rubberbandIsAlive()) + { + if (_internal -> _stateMenuIsActive) return false; + + bool eventHandled = false; + + QPointF scenePosition(pixel2ScenePosition(x,y)); + + if (_baseInternal->buttonProxyActive(scenePosition)) return false; + + if (Rubberband::_internal->_mapView != nullptr) + { + if (!isEnabledMultiSelectedState()) deSelectHandles(); + + RubberbandHandle * handlePtr = pickHandle(x,y); + if (handlePtr != nullptr) + { + handlePtr->setSelection(); + eventHandled = true; + } + } + + if (eventHandled) update(); + } + return false; + } /* RubberbandStateSelect::moveAction */ + + + bool RubberbandStateSelect::movePickedAction(AbstractEventDispatcher * causer, int x, int y ) + { + DEBUG_LOG(_internal->_logger) << LOGFUNCTION << x << " ; " << y; + + Rubberband::movePickedAction(causer,x,y); + return false; // isEnabledSelectedState() || _internal->_stateMenuIsActive; + } /* RubberbandStateSelect::movePickedAction */ + + + bool RubberbandStateSelect::contextMenuAction( AbstractEventDispatcher*, int, int ) + { + if (!_internal -> _stateMenuIsActive) chooseStateMenu(); + return true; + } /* RubberbandStateSelect::contextMenuAction */ + + + void RubberbandStateSelect::onDo_areaSelectionBegin() + { + bool mRbands = _baseInternal->_map->rubberbandFactory().allowMultipleRubberbands(); + _baseInternal->_map->rubberbandFactory().allowMultipleRubberbands(true); + { + RB::RubberbandMultiPointFeature * selectionRB = _baseInternal->_map->rubberbandFactory().create(this); + + if (selectionRB == nullptr) + { + qWarning() << LOGFUNCTION << "Creating rubberband multi points feature failed"; + } + else + { + selectionRB->setAutoDelete(true); // when finished rubberband will commit suicide + selectionRB->setDrawColor( QColor(255,0,0,255) ); + selectionRB->setFillColor( QColor( 0,0,0,100) ); + + selectionRB -> enableClosed( true ); + selectionRB -> enableFinishAfterConstruction( true ); + + selectionRB -> setFixedButtonBoxPosition(QPoint(15,15)); + + QObject::connect( selectionRB, &RB::Rubberband::apply, this, &RubberbandStateSelect::onDo_areaSelectionApply ); + QObject::connect( selectionRB, &RB::Rubberband::abort, this, &RubberbandStateSelect::onDo_areaSelectionAbort ); + + RubberbandBase::_baseInternal->_buttonBox->setDisabled(true); + } + } + _baseInternal->_map->rubberbandFactory().allowMultipleRubberbands( mRbands ); + } /* RubberbandStateSelect::onDo_areaSelectionBegin */ + + + void RubberbandStateSelect::onDo_areaSelectionApply( RubberbandBase * selectionRB ) + { + RubberbandBase::_baseInternal->_buttonBox->setEnabled(true); + if (selectionRB) + { + QVector polygonVertexList = selectionRB->coordinateVector( CoordinateSystem::Map ); + + // Looking for all handle which are inside polygon and select + + unsigned int hitCounter = 0; + + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + RubberbandPointHandle * handlePointPtr = dynamic_cast(handle.get()); + if (handlePointPtr) + { + const Esri::ArcGISRuntime::Point &v = handlePointPtr->getCoordinate()._mapCoordinate; + bool inside = false; + int nvert = polygonVertexList.size(); + + for (int i = 0, j = nvert-1; i < nvert; j = i++) + { + const QPointF & vertex_i = polygonVertexList[i]; + const QPointF & vertex_j = polygonVertexList[j]; + + if ((vertex_i.y()>v.y()) != (vertex_j.y()>v.y())) + { + if (v.x() < (vertex_j.x() - vertex_i.x()) * (v.y() - vertex_i.y()) / (vertex_j.y() - vertex_i.y()) + vertex_i.x()) + { + inside = !inside; + } + } + } + if (inside) + { + hitCounter++; + handlePointPtr->setSelection(); + } + } + } + if (hitCounter > 0) + { + if (_internal->_states.size() == 2) + { + for (auto handle : std::as_const(Rubberband::_internal->_handleList)) + { + if (!handle->isSelected()) continue; + handle->setSelection(false); + toggleState( dynamic_cast(handle.get()) ); + } + } + else + { + chooseStateMenu(); + // ????? enableMultiSelectedState(); + } + } + } + } /* RubberbandStateSelect::onDo_areaSelectionApply */ + + + void RubberbandStateSelect::onDo_areaSelectionAbort( RubberbandBase * ) + { + _baseInternal->_buttonBox->setEnabled(true); + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Really nothing else to do here? + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + } /* RubberbandStateSelect::onDo_areaSelectionAbort */ + + + void RubberbandStateSelect::update() + { + Rubberband::update(); + + onDo_updateButtonFixCoordinates(); + + } /* RubberbandPointCluster::update */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.h new file mode 100755 index 000000000..018708c8a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_state_select.h @@ -0,0 +1,180 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to edit states of point objects +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 18.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband.h" + + +namespace SituationMap { namespace RB +{ + class RubberbandPointHandle; + + + /** + @class RubberbandPointCluster rubberband_point_cluster.h + @brief Implementation of a rubberband to input single coodinates for building point feature clusters + @see Rubberband, RubberbandPointFeature + */ + class SITUATIONMAP_EXPORT RubberbandStateSelect : public Rubberband + { + Q_OBJECT + /** + @name Construction & Destruction + @{ + */ + + friend class RubberbandFactory; + + explicit RubberbandStateSelect( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + + class State + { + public: + State() = default; + State(int value, const QString & description, const QPixmap & icon) + : _description(description), _icon(icon), _value(value), _valid(true) + { + } + + inline bool isValid() const { return _valid; } + inline int value() const { return _value; } + inline const QString & description() const { return _description; } + inline const QPixmap & icon() const { return _icon; } + + protected: + + QString _description; + QPixmap _icon; + int _value = 0; + bool _valid = false; + + }; /* State */ + + + class StateItem + { + friend class RubberbandStateSelect; + + public: + + StateItem() : _valid(false), _id(0), _description(), _coordinate(), _stateValue(0) {;} + + StateItem( quint32 id, const QString &description, const QPointF & coordinate, int stateValue ) + : _valid(true), _id(id), _description(description), _coordinate(coordinate), _stateValue(stateValue) + { + } /* StateItem */ + + ~StateItem() = default; + + StateItem(const StateItem &) = default; + StateItem & operator = (const StateItem &) = default; + + inline bool isValid() const { return _valid; } + inline quint32 id() const { return _id; } + inline const QPointF & coordinate() const { return _coordinate; } + inline int stateValue() const { return _stateValue; } + + protected: + + bool _valid; + quint32 _id; + QString _description; + QPointF _coordinate; + int _stateValue; + + }; /* StateItem */ + + + virtual ~RubberbandStateSelect() = default; + + RubberbandStateSelect(const RubberbandStateSelect &) = delete; + RubberbandStateSelect & operator = (const RubberbandStateSelect &) = delete; + /** @} */ + + bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionPress ( AbstractEventDispatcher * causer, int x, int y ) override; + bool multiSelectActionRelease( AbstractEventDispatcher * causer, int x, int y ) override; + bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) override; + bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) override { return contextMenuAction(causer,x,y); } + + bool setStateItems( const QVector & stateVector, + const QVector & itemVector, + CoordinateSystem cs = CoordinateSystem::Geo ); + + const QVector & stateItems() const; + + static void setIconPolygon( const QPixmap & ); + + protected: + + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + + void activateButtons() override; + + void update() override; + + void applyButtonClicked() override { applyAndEndAction( _windowEventDispatcher, 0.0, 0.0 ); } + + void buildStateMenu(); + void chooseStateMenu(); + + void toggleState( RubberbandPointHandle * handlePtr ); + void setState( RubberbandPointHandle * handlePtr, int stateValue ); + + + + protected slots: + + void onDo_areaSelectionBegin(); + void onDo_areaSelectionApply( RubberbandBase* ); + void onDo_areaSelectionAbort( RubberbandBase* ); + + void onDo_setState( bool ); + + private: + + void setCoordinateVector(const QVector &, CoordinateSystem ) override {} + + QVector coordinateVector( CoordinateSystem ) const override { return QVector(); } + QPointF singleCoordinate( unsigned int, CoordinateSystem ) const override { return QPointF(); } + + }; /* RubberbandStateSelect */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.cpp new file mode 100755 index 000000000..3d8cddd74 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.cpp @@ -0,0 +1,92 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define moving transforms +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.04.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_transform.h" + +#include "rubberband_internal.h" + +#include + + +namespace SituationMap { namespace RB +{ + + struct RubberbandTransform::InternalData + { + Q_DISABLE_COPY(InternalData) + MathUtils::Matrix2D _transformCoordinates; + LOGGER_T _logger; + + InternalData() + : _transformCoordinates() + , _logger(getLogger("RubberbandTransform")) + { + } /* InternalData */ + }; /* RubberbandTransform::InternalData */ + + + + + RubberbandTransform::RubberbandTransform( SituationMap::Map * map, QObject * parent ) + : RubberbandRectangle(map,parent) + , _internal(new InternalData()) + { + } /* RubberbandTransform::RubberbandTransform */ + + + RubberbandTransform::~RubberbandTransform() + { + delete _internal; + _internal = nullptr; + } /* RubberbandTransform::~RubberbandTransform */ + + + const MathUtils::Matrix2D & RubberbandTransform::transformMatrix() const { return _internal->_transformCoordinates; } + + + void RubberbandTransform::update() + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * It is importent here to call RubberbandRectangle::update(); first to do an update, because + * in other case the RubberbandRectangle::_lastCoordinateChange variable will have wrong values. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + RubberbandRectangle::update(); + + if (!_lastCoordinateChange.isIdentity()) + { + _internal->_transformCoordinates = _lastCoordinateChange * _internal->_transformCoordinates; + } + + emit updatePreview( this, _lastCoordinateChange ); + + _lastCoordinateChange.setToIdentity(); // reset matrix + + } /* RubberbandTransform::update */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.h new file mode 100755 index 000000000..dbadbbd2a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform.h @@ -0,0 +1,99 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define moving, scale and rotating transforms +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 22.06.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_rectangle.h" + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandTransform rubberband_transform.h + @brief Implementation of a rubberband to move, scale and rotate features + @see Rubberband + */ + class SITUATIONMAP_EXPORT RubberbandTransform : public RubberbandRectangle + { + Q_OBJECT + + friend class RubberbandFactory; + + friend class RubberbandTransformMove; + friend class RubberbandTransformRotate; + friend class RubberbandTransformScale; + + + /** + @name Construction & Destruction + @} + */ + + explicit RubberbandTransform( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + + ~RubberbandTransform(); + /** @} */ + + /** + Provides the transformation matrix + */ + const MathUtils::Matrix2D & transformMatrix() const; + + signals: + + /** + The updatePreview signal is emitted before the rubbberband itself will be redrawn. Connecting this signal can be + used to draw an preview e.g in case of transform rubberbands. + + @param rbnd pointer to this Rubberband + @param mat transformation matrix to transform features for preview + @see Rubberband + */ + void updatePreview( Rubberband * rbnd, const MathUtils::Matrix2D & mat ); + + protected: + + /* + Forward declaration of InternalData to hold private member variables. Becareful the base class Rubberband + has similary a class for internal data member with name "InternalData". Don't be confused it is not the same. + */ + struct InternalData; + + InternalData * _internal; + + void update() override; + + private: + + void setButtonPosition(); + + }; /* RubberbandTransform */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.cpp new file mode 100755 index 000000000..b2c810a38 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.cpp @@ -0,0 +1,48 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define moving transforms +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.04.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_transform_move.h" + +namespace SituationMap { namespace RB +{ + + RubberbandTransformMove::RubberbandTransformMove( SituationMap::Map * map, QObject * parent ) + : RubberbandTransform(map,parent) + { + setModificationMode( ModificationMode::TranslationOnly ); + } /* RubberbandTransformMove::RubberbandTransformMove */ + + + QPointF RubberbandTransformMove::translationVector() const + { + const MathUtils::Matrix2D & M = transformMatrix(); + return QPointF(M(0,2),M(1,2)); + } /* RubberbandTransformMove::translationVector() const */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.h new file mode 100755 index 000000000..492448105 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_move.h @@ -0,0 +1,67 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define moving transforms +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 17.06.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_transform.h" + + +namespace SituationMap { namespace RB +{ + + class RubberbandPointHandle; + + /** + @class RubberbandTransformMove rubberband_transform_move.h + @brief Implementation of a rubberband to move features + @see Rubberband, RubberbandRectangle, RubberbandTransform + */ + class SITUATIONMAP_EXPORT RubberbandTransformMove : public RubberbandTransform + { + Q_OBJECT + + friend class RubberbandFactory; + + /** + @name Construction & Destruction + @} + */ + + explicit RubberbandTransformMove( SituationMap::Map * map, QObject * parent = nullptr ); + + /** + Returns vector of translation + */ + QPointF translationVector() const; + + public: + /** @} */ + + }; /* RubberbandTransformMove */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.cpp new file mode 100755 index 000000000..ee1295e76 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.cpp @@ -0,0 +1,62 @@ +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define rotating transforms +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.04.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_transform_rotate.h" + +#include + + +namespace SituationMap { namespace RB +{ + + + RubberbandTransformRotate::RubberbandTransformRotate( SituationMap::Map * map, QObject * parent ) + : RubberbandTransform(map,parent) + { + setModificationMode( ModificationMode::RotationOnly ); + } /* RubberbandTransformRotate::RubberbandTransformRotate */ + + qreal RubberbandTransformRotate::rotationSin() const + { + return transformMatrix()(0,1); + } // RubberbandTransformRotate::rotationSin + + + qreal RubberbandTransformRotate::rotationCos() const + { + return transformMatrix()(0,0); + } // RubberbandTransformRotate::rotationCos + + + qreal RubberbandTransformRotate::rotationAngle() const + { + return MathUtils::getMathAngle(rotationSin(),rotationCos()); + } // RubberbandTransformRotate::rotationAngle + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.h new file mode 100755 index 000000000..ecf1c86b2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_rotate.h @@ -0,0 +1,77 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define moving transformation +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.04.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_transform.h" + + +namespace SituationMap { namespace RB +{ + + class RubberbandPointHandle; + + /** + @class RubberbandTransformRotate rubberband_transform_rotate.h + @brief Implementation of a rubberband to rotate features + @see Rubberband + */ + class SITUATIONMAP_EXPORT RubberbandTransformRotate : public RubberbandTransform + { + Q_OBJECT + + friend class RubberbandFactory; + + /** + @name Construction & Destruction + @} + */ + + explicit RubberbandTransformRotate( SituationMap::Map * map, QObject * parent ); + + public: + /** @} */ + + /** + Returns sinus of rotation angle + */ + qreal rotationSin() const; + + /** + Return cosinus of roation angle + */ + qreal rotationCos() const; + + /** + Return rotation angle (math.) in radians + */ + qreal rotationAngle() const; + + }; /* RubberbandTransformRotate */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.cpp new file mode 100755 index 000000000..6d0d4370d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.cpp @@ -0,0 +1,47 @@ +/** +** +** @RDE_HEADER@ +** Implementation of rubberband class to define scaling transforms +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.04.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "rubberband_transform_scale.h" + + +namespace SituationMap { namespace RB +{ + + + + RubberbandTransformScale::RubberbandTransformScale( SituationMap::Map * map, QObject * parent ) + : RubberbandTransform(map,parent) + { + setModificationMode( ModificationMode::ScalingOnly ); + } /* RubberbandTransformScale::RubberbandTransformScale */ + + + qreal RubberbandTransformScale::scaleX() const { return transformMatrix()(0,0); } + qreal RubberbandTransformScale::scaleY() const { return transformMatrix()(1,1); } + +} } /* namespace SituationMap::RB */ \ No newline at end of file diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.h new file mode 100755 index 000000000..190541b33 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/rubberband_transform_scale.h @@ -0,0 +1,64 @@ +/** +** +** @RDE_HEADER@ +** Definition of rubberband class to define scaling transformation +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 07.04.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_transform.h" + + +namespace SituationMap { namespace RB +{ + + /** + @class RubberbandTransformScale rubberband_transform_scale.h + @brief Implementation of a rubberband to scale features + @see Rubberband + */ + class SITUATIONMAP_EXPORT RubberbandTransformScale : public RubberbandTransform + { + Q_OBJECT + + friend class RubberbandFactory; + + /** + @name Construction & Destruction + @} + */ + + explicit RubberbandTransformScale( SituationMap::Map * map, QObject * parent = nullptr ); + + public: + /** @} */ + + + qreal scaleX() const; /**< Returns scaling factor in x direction */ + qreal scaleY() const; /**< Returns scaling factor in y direction */ + + }; /* RubberbandTransformRotate */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/sphere_pattern.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/sphere_pattern.xpm new file mode 100755 index 000000000..b04c1c9b1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/sphere_pattern.xpm @@ -0,0 +1,32 @@ +/* XPM */ +static const char *sphere_pattern_xpm[] = { +"16 16 13 1", +" c None", +". c #FFFFFF", +"+ c #F8F8F8", +"@ c #DCDCDC", +"# c #BFBFBF", +"$ c #E3E3E3", +"% c #929292", +"& c #6C6C6C", +"* c #484848", +"= c #393939", +"- c #F3F3F3", +"; c #B5B5B5", +"> c #0A0A0A", +" ...... ", +" .....+.... ", +" ..@++++++... ", +" ..#$++....++.. ", +" .%#$+.......+. ", +"..%#$........+..", +".&%#@........+..", +".*%%#$........+.", +".=&%##@-.....+-.", +".=*&%##@$--++-@.", +".;=*&%##@@$$@@#.", +" .>==&%%######. ", +" ..>>==&&%%%%.. ", +" ..>>>>==*=.. ", +" ..;>>>>;.. ", +" ...... "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.cpp new file mode 100755 index 000000000..c0d9dbdad --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.cpp @@ -0,0 +1,88 @@ + +/** +** +** @RDE_HEADER@ +** Implementation of preview for tactical symbols +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 27.03.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "transient_tactical_symbol.h" + +#include +#include +#include + + +namespace SituationMap { namespace RB +{ + + TransientTacticalSymbol::TransientTacticalSymbol( MapImpl * mapImpl, const QString & sidc ) + { + QString layerIdent; + if (mapImpl) + { + layerIdent = mapImpl->layerFactoryImpl().rubberbandLayer(); + } + + if (layerIdent.isEmpty()) + { + qWarning() << LOGFUNCTION << "not layername found"; + } + + _tacticalRubberbandObj = new SidcSymbolImpl( sidc, QStringLiteral("TransientTacticalSymbol:sidc=%1").arg(sidc) ); + _tacticalRubberbandObj -> init( mapImpl, layerIdent ); + //_tacticalRubberbandObj -> setDrawOrder( 2000 ); + + } /* PreviewTaticalSymbol::PreviewTaticalSymbol */ + + TransientTacticalSymbol::~TransientTacticalSymbol() + { + delete _tacticalRubberbandObj; + } /* TransientTacticalSymbol::~TransientTacticalSymbol */ + + + void TransientTacticalSymbol::update(const QVector & coordinateVector) + { + PositionCont controlPoints; + controlPoints.reserve(coordinateVector.size()); + + for (const RubberbandDrawable::RubberbandCoordinate &pos : coordinateVector) + { + const Esri::ArcGISRuntime::Point & p = pos._mapCoordinate; + + controlPoints.append(Point3D(p.x(), p.y(), p.z())); + } + _tacticalRubberbandObj -> setPositionsTranslated( controlPoints ); + try + { + _tacticalRubberbandObj -> updateView(); + } + catch (...) + { + qWarning() << LOGFUNCTION << "Exception from TacticalAppearance::updateView() catched!!!!"; + } + } /* TransientTacticalSymbol::update */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.h new file mode 100755 index 000000000..5d2144ed7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/transient_tactical_symbol.h @@ -0,0 +1,79 @@ +/** +** +** @RDE_HEADER@ +** Definition of tansient tactical symbols while rubberband input is done. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 01.09.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include "rubberband_drawable.h" + +#include +#include + +#include + +namespace SituationMap { class SidcSymbolImpl; } + +namespace SituationMap { namespace RB +{ + + /** + @class TransientTacticalSymbol transient_tactical_symbol.h + The class \c TransientTacticalSymbol a map object of type RubberbandObjectImpl to display a tactical symbol + transiently while rubberband input is done. The tactical symbol is defined by a \a sidc code which is passed + to the ctor as second parameter. + + @see RubberbandObjectImpl + */ + class TransientTacticalSymbol : public QObject, public SituationMap::Referenced + { + Q_OBJECT + public: + + + /** + @name Construction & Destruction + @{ + */ + explicit TransientTacticalSymbol( MapImpl *, const QString & sidc ); + virtual ~TransientTacticalSymbol(); + + TransientTacticalSymbol(const TransientTacticalSymbol &) = delete; + TransientTacticalSymbol & operator = (const TransientTacticalSymbol &) = delete; + /** @} */ + + virtual void update( const QVector & coordinateVector ); + + protected: + + SidcSymbolImpl * _tacticalRubberbandObj; + + protected slots: + + + }; /* TransientTacticalSymbol */ + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/undo.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/undo.xpm new file mode 100755 index 000000000..4193abac9 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/undo.xpm @@ -0,0 +1,134 @@ +/* XPM */ +static const char * undo_xpm[] = { +"24 24 107 2", +" c None", +". c #BEAD52", +"+ c #D4C15C", +"@ c #CCBA59", +"# c #A49546", +"$ c #F7E16A", +"% c #FEE65E", +"& c #FEE350", +"* c #FEE248", +"= c #FEE34C", +"- c #FEE662", +"; c #DAC75E", +"> c #CBBA57", +", c #FDE456", +"' c #FCDE32", +") c #FCDD2D", +"! c #FCE143", +"~ c #E5D15F", +"{ c #C1B152", +"] c #FBE047", +"^ c #FADB2B", +"/ c #FADE3C", +"( c #BBAB4F", +"_ c #94873F", +": c #F9DF49", +"< c #F8D929", +"[ c #F5DC4B", +"} c #EFD751", +"| c #F6D627", +"1 c #EACC25", +"2 c #E3C524", +"3 c #F1D226", +"4 c #F6D72A", +"5 c #AA9A46", +"6 c #BAAA4B", +"7 c #F3D52B", +"8 c #F3D424", +"9 c #E1C421", +"0 c #C0A81C", +"a c #D3BD43", +"b c #EBD242", +"c c #F1D122", +"d c #D3B71E", +"e c #B29B19", +"f c #E7CB37", +"g c #97893C", +"h c #EECF25", +"i c #EECE1F", +"j c #E7C81E", +"k c #D8BB1C", +"l c #ECCE28", +"m c #CDB741", +"n c #EBCC1C", +"o c #EBCD1F", +"p c #CBB84D", +"q c #DCC857", +"r c #B5A447", +"s c #E6CA31", +"t c #E8C919", +"u c #D0B416", +"v c #DFC118", +"w c #E8CA26", +"x c #EACF3B", +"y c #ECD552", +"z c #E3CE59", +"A c #CAB74F", +"B c #E6C71C", +"C c #E6C617", +"D c #BEA51A", +"E c #D3B513", +"F c #E3C314", +"G c #E4C51D", +"H c #E6CA2E", +"I c #DBBC13", +"J c #917F20", +"K c #917E15", +"L c #E0C011", +"M c #DDBF17", +"N c #C0AE49", +"O c #D8BA0E", +"P c #DDBE0E", +"Q c #DEBF12", +"R c #E1C62C", +"S c #E5CE4A", +"T c #D3BF4F", +"U c #928436", +"V c #AC930E", +"W c #DBBB0C", +"X c #DEC01D", +"Y c #E2C93D", +"Z c #CCB84B", +"` c #D7B809", +" . c #D8B909", +".. c #CCAF09", +"+. c #BDA107", +"@. c #D6B607", +"#. c #CFB007", +"$. c #897504", +"%. c #D4B405", +"&. c #D0B105", +"*. c #978004", +"=. c #C9AA03", +"-. c #D2B203", +";. c #D0B003", +">. c #A48B03", +",. c #968109", +"'. c #AD9301", +" ", +" ", +" . + @ ", +" # $ % & * = - ; ", +" > , ' ) ) ) ) ) ! ~ ", +" { ] ^ ^ ^ ^ ^ ^ ^ ^ / ( ", +" _ : < < < < < < < < < < [ ", +" } | | | | | | 1 2 3 | | 4 5 ", +" 6 7 8 8 8 8 9 0 8 8 a ", +" b c c c c d e c f ", +" g h i i i j k l ", +" m n n n n o ", +" p q r s t t t u v ", +" w x y z A B C C C D ", +" E F F G H F F F I J ", +" K L L L L L L L M N ", +" O P P P P P P Q R S T U ", +" V W W W W W W W W W X Y Z ", +" ` . . . . . . . ... ", +" +.@.@.@.@.@.@.#.$. ", +" %.%.%.%.&.*. ", +" =.-.;.>. ", +" ,.'. ", +" "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/winkel_icon.xpm b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/winkel_icon.xpm new file mode 100755 index 000000000..65ed9a370 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Rubberbands/winkel_icon.xpm @@ -0,0 +1,84 @@ +/* XPM */ +static const char * winkelIcon_xpm[] = { +"40 40 41 1", +" c None", +". c #250075", +"+ c #250074", +"@ c #260074", +"# c #250073", +"$ c #240072", +"% c #250071", +"& c #22006F", +"* c #24006E", +"= c #280078", +"- c #290076", +"; c #270076", +"> c #240073", +", c #250072", +"' c #240071", +") c #240070", +"! c #23006E", +"~ c #23006D", +"{ c #22006D", +"] c #23006C", +"^ c #21006D", +"/ c #270075", +"( c #230073", +"_ c #24006F", +": c #22006E", +"< c #22006C", +"[ c #240075", +"} c #260075", +"| c #230070", +"1 c #24006C", +"2 c #230072", +"3 c #260073", +"4 c #23006F", +"5 c #230071", +"6 c #24006D", +"7 c #210064", +"8 c #210066", +"9 c #200065", +"0 c #210065", +"a c #200063", +"b c #1D0062", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" .++ ", +" @+#+$%&* ", +" =-;#>,')!~{]^ ", +" .;/>(>)_:~~~<~< ", +" [}###'|&:~<<~~1!* ", +" .2$3'')4::{~~~~~^ ", +" $%'5' 4{6<~~~~_ ", +" 4)| <~~~~! ", +" :~~~!~ ", +" 4{~~~< ", +" ~~~~{_ ", +" ~~~6~ ", +" ~~~~: ", +" ]~~{~! ", +" ~~~{~ ", +" ~~~]! ", +" ~~~~:| ", +" {<~~~! ", +" ~~~~!~ ", +" <~~~~! ", +" :{{<~~~*] ", +" !::]1!{{{{<~{~~~~]! ", +" ^64~~]!~~~<~~~~~*6{ ", +" {!&!]~~~~~~~<]~*~ ", +" :*]~~~~~~~<_< ", +" 7890009ab ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/CMakeLists.txt new file mode 100755 index 000000000..285567da1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/CMakeLists.txt @@ -0,0 +1,44 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# LIBRARY +SET(SideBar_SRCS + side_bar.cpp + side_bar.h + side_bar_export.h + side_bar_item.cpp + side_bar_item.h + side_bar_model.cpp + side_bar_model.h +) + +SET(SidBar_ICONS +# state_icon_b.xpm +# state_icon_a.xpm +# state_icon_c.xpm +) + +SOURCE_GROUP(SideBar FILES ${SideBar_SRCS}) +NGL_ADD_LIBRARY(WS situationMapSideBar SHARED ${SideBar_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(situationMapSideBar + PUBLIC +# ${CMAKE_CURRENT_SOURCE_DIR} +# ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + + +NGL_TARGET_LINK_LIBRARIES(situationMapSideBar + PRIVATE + NGLLogger + PUBLIC + situationMapCommon + Qt6::Core + Qt6::Widgets +) + +NGL_INSTALL_TARGET(situationMapSideBar) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.cpp new file mode 100755 index 000000000..44245dd6c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.cpp @@ -0,0 +1,416 @@ +/** + * @RDE_HEADER@ + * SideBar + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2015-01-19 + */ + + +// Own +#include "side_bar.h" +#include "side_bar_model.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SituationMap +{ + +class SideBar::Private +{ +public: + Private() = default; + ~Private() = default; + + + SideBarModel *m_model = nullptr; + QTreeView *m_view = nullptr; + bool m_suppressClick = false; + QTimer *m_suppressTimer = nullptr; + + QSharedPointer m_sideBarAniOn; + QSharedPointer m_sideBarAniOff; + + +}; // class SideBar::Private +//----------------------------------------------------------------------------- + + + + +//----------------------------------------------------------------------------- +QImage SideBar::createImage(const QColor &fgColor, + const QColor &bgColor, + int size) +{ + QImage burgerMenueImage(size, size, QImage::Format_ARGB32); + if (bgColor.isValid()) + burgerMenueImage.fill(bgColor); + else + burgerMenueImage.fill(Qt::transparent); + + QPainter p(&burgerMenueImage); + + { + QPen pen(Qt::white); + pen.setWidth(size * 0.15); + pen.setCapStyle(Qt::PenCapStyle::RoundCap); + p.setPen(pen); + + double yFactor = 0.1; + p.drawLine(QPointF(size * yFactor, size * 0.25), QPointF(size - size * yFactor, size * 0.25)); + p.drawLine(QPointF(size * yFactor, size * 0.50), QPointF(size - size * yFactor, size * 0.50)); + p.drawLine(QPointF(size * yFactor, size * 0.75), QPointF(size - size * yFactor, size * 0.75)); + } + + { + QPen pen(fgColor); + pen.setWidth(size * 0.1); + pen.setCapStyle(Qt::PenCapStyle::RoundCap); + p.setPen(pen); + + double yFactor = 0.1; + p.drawLine(QPointF(size * yFactor, size * 0.25), QPointF(size - size * yFactor, size * 0.25)); + p.drawLine(QPointF(size * yFactor, size * 0.50), QPointF(size - size * yFactor, size * 0.50)); + p.drawLine(QPointF(size * yFactor, size * 0.75), QPointF(size - size * yFactor, size * 0.75)); + } + + return burgerMenueImage; +} + +QIcon SideBar::createIcon(const QColor &fgColor, + const QColor &bgColor, + int size) +{ + return QIcon(QPixmap::fromImage(SideBar::createImage(fgColor, bgColor, size))); +} + + +SideBar::SideBar(QWidget *parent) + : BaseClass(parent) + , d(new Private) + , m_logger(getLogger("SideBar")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // Prepare animations: + d->m_sideBarAniOn = QSharedPointer(new QPropertyAnimation(this, "pos")); + connect(d->m_sideBarAniOn.data(), &QPropertyAnimation::finished, this, &SideBar::onAnimationOnFinished); + + d->m_sideBarAniOff = QSharedPointer(new QPropertyAnimation(this, "pos")); + connect(d->m_sideBarAniOff.data(), &QPropertyAnimation::finished, this, &SideBar::onAnimationOffFinished); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setObjectName(QLatin1String("SideBarLayout")); + this->setLayout(layout); + + // 1st row + QHBoxLayout *layoutTopRow = new QHBoxLayout(); + layoutTopRow->setObjectName(QLatin1String("SideBarTopRowLayout")); + layout->addLayout(layoutTopRow); + layoutTopRow->addStretch(2); + + QPushButton *close = new QPushButton(); + close->setObjectName(QLatin1String("SideBarCloseButton")); + close->setMinimumSize(QSize(0, 0)); + close->setStyleSheet(QLatin1String("QPushButton {background-color: white; border: 1px solid transparent;}")); + close->setIcon(QIcon(QLatin1String("icon:dialog-close.svg"))); +// close->setIcon(QIcon(QLatin1String("qrc:///icons/svg/dialog-close.svg"))); + close->setIconSize(QSize(20, 20)); +// close->setStyleSheet(QLatin1String("QPushButton { image: url(\"icon:dialog-close.svg\"); }")); + + + + // lambda :-) + connect(close, &QPushButton::clicked, this, [this]() + { + emit closeRequest(); + this->hideAnimated(); + }); + + + layoutTopRow->addWidget(close); + + // 2nd Row + d->m_view = new QTreeView(this); + if (d->m_view) + { + d->m_view->setObjectName(QLatin1String("SideBarTreeView")); + d->m_view->setMinimumSize(QSize(0, 0)); + d->m_view->setAlternatingRowColors(true); + connect(d->m_view, &QTreeView::clicked, this, &SideBar::onClicked); + + layout->addWidget(d->m_view); + + initModel(); + d->m_view->setModel(d->m_model); + } + + initAppearanceToSideBar(true); + //initAppearanceToSideBar(false); + + + d->m_suppressTimer = new QTimer(this); + d->m_suppressTimer->setSingleShot(true); + d->m_suppressTimer->setInterval(20); + connect(d->m_suppressTimer, &QTimer::timeout, this, [this]() { + d->m_suppressClick = false; + }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +SideBar::~SideBar() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +bool SideBar::initAppearanceToSideBar(bool toSideBar) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, toSideBar = " << toSideBar; + + if (toSideBar) + { + setWindowFlag(Qt::FramelessWindowHint); + if (d->m_view) + d->m_view->setHeaderHidden(true); // only TreeView + + this->layout()->setSpacing(0); + this->layout()->setContentsMargins(0, 0, 0, 0); + +// d->m_view->setStyleSheet(QLatin1String("QLabel {border: none;}")); + + } + else + { + if (d->m_view) + d->m_view->setHeaderHidden(false); + + this->layout()->setSpacing(2); + this->layout()->setContentsMargins(-1, -1, -1, -1); // -1 -> invalid (!?) + + } + + setAutoFillBackground(true); + // // this->setStyleSheet(QLatin1String("QWidget {background-color: white;}")); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void SideBar::setAnimationDuration(int msecs) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + d->m_sideBarAniOn->setDuration(msecs); + d->m_sideBarAniOff->setDuration(msecs); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBar::setInvisiblePosition(const QPoint &invisiblePos) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " invisiblePos=" << invisiblePos; + + // x---> + d->m_sideBarAniOn->setStartValue(invisiblePos); + + // x<--- + d->m_sideBarAniOff->setEndValue(invisiblePos); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " invisiblePos=" << invisiblePos; +} + +void SideBar::setVisiblePosition(const QPoint &visiblePos) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " visiblePos=" << visiblePos; + + // --->x + d->m_sideBarAniOn->setEndValue(visiblePos); + + // <---x + d->m_sideBarAniOff->setStartValue(visiblePos); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " visiblePos=" << visiblePos; +} + +void SideBar::showAnimated() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + this->show(); + this->raise(); + + onAnimationStarted(); + d->m_sideBarAniOn->start(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool SideBar::isCurrentlyShowing() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_sideBarAniOn->state() == QAbstractAnimation::Running; +} + +void SideBar::hideAnimated() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // use current position (maybe showAnimation is interrupted) + d->m_sideBarAniOff->setStartValue(this->pos()); + + onAnimationStarted(); + d->m_sideBarAniOff->start(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool SideBar::isCurrentlyHiding() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return d->m_sideBarAniOff->state() == QAbstractAnimation::Running; +} + + +bool SideBar::initModel() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + delete d->m_model; + d->m_model = new SideBarModel(this); + d->m_model->setObjectName(QLatin1String("SideBarModel")); + + connect(d->m_model, &SideBarModel::itemChanged, this, &SideBar::onItemChanged); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +bool SideBar::addCheckableItem(SideBarItem *item) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + item->setCheckable(true); + item->setEditable(false); + + d->m_model->addItem(item); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + + +bool SideBar::addWidget(QWidget *widget) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->layout()->addWidget(widget); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void SideBar::onItemChanged(QStandardItem *item) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QString itemText(QLatin1String("-")); + if (item) + itemText = item->text(); + INFO_LOG(m_logger) << LOGFUNCTION << " itemChanged, itemText='" << itemText << "'"; + + // hide if empty: + if (d->m_view) + d->m_view->setEnabled(d->m_model->rowCount() > 0); + + emit dataChanged(); + + // If user clicks checkbox, 'clicked' is also emitted afterwards... + // In this case, avoid evaluating that signal for a small amount on time + d->m_suppressClick = true; + d->m_suppressTimer->start(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBar::onAnimationOnFinished() +{ + onAnimationFinished(); +} + +void SideBar::onAnimationOffFinished() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->hide(); + onAnimationFinished(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBar::onClicked(const QModelIndex &index) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_model) + return; + + if (d->m_suppressClick) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_suppressClick"; + return; + } + + QStandardItem *item = d->m_model->itemFromIndex(index); + if (!item) + return; + if (!item->isCheckable()) + return; + if (!item->isEnabled()) + return; + + if (item->checkState() == Qt::CheckState::Checked) + item->setCheckState(Qt::CheckState::Unchecked); + else + item->setCheckState(Qt::CheckState::Checked); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBar::onAnimationStarted() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + int h = this->parentWidget()->height(); + this->setFixedHeight(h); // SetFixedSize/setFixedHeight to have smooth animations, especially in case of some JumpPoints + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBar::onAnimationFinished() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->setFixedHeight(QWIDGETSIZE_MAX); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.h new file mode 100755 index 000000000..1f22e96e1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar.h @@ -0,0 +1,102 @@ +/** + * @RDE_HEADER@ + * SideBar + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2015-01-19 + */ + +#pragma once + +// Own +#include "side_bar_export.h" +#include "side_bar_item.h" +#include + +// Qt +#include + +class QModelIndex; +class QStandardItem; + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +// forward: +class Map; + + +// using baseclass QDialog -> with background / no transparency +// using baseclass QWidget -> no background / will be transparent +class SIDEBAR_EXPORT SideBar : public QDialog +{ + using BaseClass = QDialog; + + Q_OBJECT + +public: + static QImage createImage(const QColor &fgColor + , const QColor &bgColor + , int size); + + static QIcon createIcon(const QColor &fgColor + , const QColor &bgColor + , int size); + + + explicit SideBar(QWidget *parent = nullptr); + virtual ~SideBar(); + + // Animation regarding position: + virtual void setAnimationDuration(int msecs); + virtual void setInvisiblePosition(const QPoint &invisiblePos); + virtual void setVisiblePosition(const QPoint &visiblePos); + + virtual void showAnimated(); + virtual bool isCurrentlyShowing(); + + virtual void hideAnimated(); + virtual bool isCurrentlyHiding(); + + virtual bool addCheckableItem(SideBarItem *item); + + virtual bool addWidget(QWidget *widget); + +signals: + void dataChanged(); + void closeRequest(); + + void positionString(bool onOff); + + +protected Q_SLOTS: + void onItemChanged(QStandardItem *item); + void onAnimationOnFinished(); + void onAnimationOffFinished(); + void onClicked(const QModelIndex &index); + + void onAnimationStarted(); + void onAnimationFinished(); + + +private: + bool initAppearanceToSideBar(bool toSideBar = true); + bool initModel(); + +private: + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; + + LOGGER_T m_logger; ///< Interface of the Logging-Facility + +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_export.h new file mode 100755 index 000000000..ccbd301ad --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_export.h @@ -0,0 +1,21 @@ +/** + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef SIDEBAR_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define SIDEBAR_EXPORT +# else +# if defined(MAKE_SITUATIONMAPSIDEBAR_LIB) +# define SIDEBAR_EXPORT Q_DECL_EXPORT +# else +# define SIDEBAR_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.cpp new file mode 100755 index 000000000..3894fb2c7 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.cpp @@ -0,0 +1,42 @@ +/** + * @RDE_HEADER@ + * SideBarItem + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2015-01-19 + */ + + +// Own +#include "side_bar_item.h" + + +namespace SituationMap +{ + +SideBarItem::SideBarItem(const QString &text) + : QObject() + , QStandardItem(text) + , m_logger(getLogger("SideBarItem")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SideBarItem::~SideBarItem() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBarItem::doEmitChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + emit changed(this->checkState() == Qt::CheckState::Checked); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.h new file mode 100755 index 000000000..11614fc0d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_item.h @@ -0,0 +1,51 @@ +/** + * @RDE_HEADER@ + * SideBarItem + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2015-01-19 + */ + +#pragma once + +// Own +#include +#include "side_bar_export.h" + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + + +class SIDEBAR_EXPORT SideBarItem : public QObject, public QStandardItem +{ + Q_OBJECT + +public: + explicit SideBarItem(const QString &text); + virtual ~SideBarItem(); + + virtual int type() const + { + return QStandardItem::UserType + 1; + } + + void doEmitChanged(); + +signals: + bool changed(bool); + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + +}; + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.cpp new file mode 100755 index 000000000..61fce1555 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.cpp @@ -0,0 +1,123 @@ +/** + * @RDE_HEADER@ + * SideBarModel + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2015-01-19 + */ + + +// Own +#include "side_bar_model.h" +#include + + +namespace SituationMap +{ + +SideBarModel::SideBarModel(QObject *parent) + : BaseClass(0, 1, parent) + , m_logger(getLogger("SideBarModel")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + +// fillDummyData(); + + connect(this, &SideBarModel::itemChanged, this, &SideBarModel::onItemChanged); +// connect(this, &SideBarModel::dataChanged, this, &SideBarModel::onDataChanged); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SideBarModel::~SideBarModel() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBarModel::fillDummyData() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + // remove old entries: + removeRows(0, rowCount()); + + // setHeader + setHeaderData(0, Qt::Horizontal, QStringLiteral("HeaderH")); + + // fill data: + QStandardItem *rootItem = invisibleRootItem(); + + // be prepared for a tree: + { + QList itemRow; + QStandardItem *item = new QStandardItem(QLatin1String("Entry01")); + item->setCheckable(true); + itemRow.append(item); + rootItem->appendRow(itemRow); + } + + { + QList itemRow; + QStandardItem *item = new QStandardItem(QLatin1String("Entry02")); + item->setCheckable(true); + itemRow.append(item); + rootItem->appendRow(itemRow); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//void SideBarModel::addItem(QStandardItem *item, QStandardItem *parentItem) +//{ +// TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + +// QList itemRow; +// itemRow.append(item); + +// if (parentItem) +// parentItem->appendRow(itemRow); +// else +// invisibleRootItem()->appendRow(itemRow); + +// TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +//} + +void SideBarModel::addItem(SideBarItem *item, SideBarItem *parentItem) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QList itemRow; + itemRow.append(item); + + if (parentItem) + parentItem->appendRow(itemRow); + else + invisibleRootItem()->appendRow(itemRow); + + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SideBarModel::onItemChanged(QStandardItem *item) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QString itemText(QLatin1String("-")); + if (item) + itemText = item->text(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " itemChanged, itemText='" << itemText << "'"; + + SideBarItem *sideBarItem = dynamic_cast(item); + if (sideBarItem) + sideBarItem->doEmitChanged(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.h new file mode 100755 index 000000000..8bf171c9e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SideBar/side_bar_model.h @@ -0,0 +1,67 @@ +/** + * @RDE_HEADER@ + * SideBarModel + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, SLSE, 2015-01-19 + */ + +#pragma once + +// Own +#include +#include "side_bar_item.h" +#include "side_bar_export.h" + + +// Qt +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SIDEBAR_EXPORT SideBarModel : public QStandardItemModel +{ + using BaseClass = QStandardItemModel; + + Q_OBJECT + +public: + explicit SideBarModel(QObject *parent); + virtual ~SideBarModel(); + + void fillDummyData(); + +// void addItem(QStandardItem *item, QStandardItem *parentItem = nullptr); + void addItem(SideBarItem *item, SideBarItem *parentItem = nullptr); + + + +// // QAbstractItemModel: reimplemented : +// QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; +// QModelIndex parent(const QModelIndex &child) const; + +// virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0; +// virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0; + +// virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0; +// // : reimplemented + +signals: + +protected Q_SLOTS: + void onItemChanged(QStandardItem *item); + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + +}; + + + +} // namespace SituationMap + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/CMakeLists.txt new file mode 100755 index 000000000..4313a334b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/CMakeLists.txt @@ -0,0 +1,46 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# LIBRARY + +SET(SymbologyGui_SRCS + base_symbol_lookup_dialog.cpp + base_symbol_lookup_dialog.h + base_symbol_lookup_widget.cpp + base_symbol_lookup_widget.h + field_table_model.cpp + field_table_model.h + symbol_modification_widget.h + symbol_modification_widget.cpp + symbol_modification_dialog.h + symbol_modification_dialog.cpp +) + +SOURCE_GROUP(SymbologyGui FILES ${SymbologyGui_SRCS}) + +NGL_WRAP_UI(SymbologyGui_SRCS + base_symbol_lookup_widget.ui +) + +NGL_ADD_LIBRARY2(WS advancedSymbologyGui SHARED ${SymbologyGui_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(advancedSymbologyGui + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Common +) + +NGL_TARGET_LINK_LIBRARIES(advancedSymbologyGui + PRIVATE + Qt6::Gui + Qt6::Network + PUBLIC + LtsWidgets + LtsUtilsSettings + advancedSymbology + situationMap + Qt6::Core +) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.cpp new file mode 100755 index 000000000..5911fd6e2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.cpp @@ -0,0 +1,339 @@ +/** +** +** @RDE_HEADER@ +** PlanEditor. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 13.06.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + + +#include "base_symbol_filter_proxy.h" + +#include +#include +#include + +#include +#include +#include + + +namespace AdvancedSymbology +{ +namespace Gui +{ + +struct BaseSymbolProxyFilter::InternalData +{ + QStringList m_sidcList; + QStringList m_keyList; + // no need for QHash/QSet - element count is very small + QVector m_geometrySet = {GeometryType::Point, GeometryType::Polyline, GeometryType::Polygon}; + bool m_filterParicipantSymbols = false; + bool m_etsSymbols = true; + bool m_xmlSymbols = true; + bool m_esriDictSymbols = true; + +}; // class BaseSymbolProxyFilter::InternalData + + +BaseSymbolProxyFilter::BaseSymbolProxyFilter(QObject *parent) + : QSortFilterProxyModel(parent) + , m_internal(new InternalData()) +{} + +BaseSymbolProxyFilter::~BaseSymbolProxyFilter() +{ + delete m_internal; +} // BaseSymbolProxyFilter::~BaseSymbolProxyFilter() + +QStringList BaseSymbolProxyFilter::mimeTypes() const +{ + QStringList mineTypeList = QSortFilterProxyModel::mimeTypes(); + mineTypeList << QStringLiteral("Participant"); + return mineTypeList; +} // QStringList BaseSymbolProxyFilter::mimeTypes() const + + +QMimeData *BaseSymbolProxyFilter::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *mimeData = QSortFilterProxyModel::mimeData(indexes); + + if (mimeData) + { + const QByteArray &data = QByteArray::number(int(filterParticipants())); + mimeData->setData(QStringLiteral("Participant"), data); + } + + return mimeData; +} // QMimeData * BaseSymbolProxyFilter::mimeData(const QModelIndexList &indexes) const + + +void BaseSymbolProxyFilter::updateModel(const QStringList &keyList) +{ + m_internal->m_keyList = keyList; + + if (keyList.isEmpty()) + { + m_internal->m_sidcList.clear(); + beginFilterChange(); + endFilterChange(); + return; + } + + const AdvancedSymbology::SymbolDictionary &dict = symbolDictionary(); + + AdvancedSymbology::SearchParameters lookupDefs; + + lookupDefs.tags = keyList; + lookupDefs.symbolClasses = QStringList({QStringLiteral("3"), QStringLiteral("4"), QStringLiteral("5")}); + + AdvancedSymbology::PropertyResultWatcher *symbolGuard = new AdvancedSymbology::PropertyResultWatcher(this); + + connect(symbolGuard, &AdvancedSymbology::SymbolResultWatcher::resultReady, this, [this, symbolGuard]() + { + BaseSymbolModel *sidcLookupModel = static_cast(sourceModel()); + if (!sidcLookupModel) + return; + + QSharedPointer lookupResult = symbolGuard->propertyResult(); + const SymbolPropertyList &symbolPropertyList = lookupResult->properties(); + if (symbolPropertyList.isEmpty()) + return; + + { + m_internal->m_sidcList.clear(); + for (const AdvancedSymbology::SymbolProperties &prop : symbolPropertyList) + { + const QString &baseSymbolCode = SidcCodec::encodeBaseSymbolCode(symbolDictionary().symbologyType(), prop.key()); + QString sidc = AdvancedSymbology::SidcBuilderBase::baseSymbolCodeToSidc(baseSymbolCode); + + if (sidc.isEmpty()) sidc = prop.sidc(); // case of ETS and XML symbol the props.key() is the filename and sidc will be empty + + if (sidcLookupModel->isInModel(sidc)) + { + m_internal->m_sidcList += sidc; + } + } + beginFilterChange(); + endFilterChange(); + } + symbolGuard->deleteLater(); + }); + + symbolGuard->setPropertyResult(dict.searchSymbols(lookupDefs)); + +} // void BaseSymbolProxyFilter::updateModel( const QStringList & keyList ) + + +bool BaseSymbolProxyFilter::filterAcceptsRow(int sourceRow, const QModelIndex &) const +{ + BaseSymbolModel *sidcLookupModel = static_cast(sourceModel()); + const QString &sidc = sidcLookupModel->getSidc(sourceRow); + + bool accept = (m_internal->m_sidcList.isEmpty()) ? true : m_internal->m_sidcList.contains(sidc); + + SidcCodec::SidcType sidcType = SidcCodec::decodeSidcType(sidc); + + accept = (accept) ? ( (!SidcCodec::isCivil(sidc) && m_internal->m_esriDictSymbols) + || (m_internal->m_etsSymbols && (SidcCodec::SidcType::CivilEts == sidcType)) + || (m_internal->m_xmlSymbols && (SidcCodec::SidcType::CivilXml == sidcType)) ) : false; + + if (accept) + { + if (m_internal->m_filterParicipantSymbols) + accept = m_internal->m_geometrySet.contains(GeometryType::Point) && sidcLookupModel->isParticipant(sourceRow); + else + accept = m_internal->m_geometrySet.contains(sidcLookupModel->getSymbolGeometry(sourceRow)); + } + return accept; +} // bool BaseSymbolProxyFilter::filterAcceptsRow( int sourceRow, const QModelIndex & sourceParent ) + + +const AdvancedSymbology::SymbolDictionary &BaseSymbolProxyFilter::symbolDictionary() const +{ + BaseSymbolModel *sidcLookupModel = static_cast(sourceModel()); + return sidcLookupModel->symbolDictionary(); +} // const AdvancedSymbology::SymbolDictionary & BaseSymbolProxyFilter::symbolDictionary() + + +void BaseSymbolProxyFilter::setSourceModel(BaseSymbolModel *sourceModel) +{ + setSourceModel(static_cast(sourceModel)); +} // void BaseSymbolProxyFilter::setSourceModel( AdvancedSymbologyGui::BaseSymbolModel * sourceModel ) + + +bool BaseSymbolProxyFilter::isEmpty() const +{ + BaseSymbolModel *sidcLookupModel = static_cast(sourceModel()); + return sidcLookupModel->isEmpty(); +} // bool BaseSymbolProxyFilter::isEmpty() const + + +QString BaseSymbolProxyFilter::getSidc(int idx) const +{ + const QModelIndex &sourceIndex = mapToSource(index(idx, 0)); + BaseSymbolModel *sidcLookupModel = static_cast(sourceModel()); + return sidcLookupModel->getSidc(sourceIndex.row()); +} // QString BaseSymbolProxyFilter::getSidc( int idx ) const + + +int BaseSymbolProxyFilter::size() const +{ + BaseSymbolModel *sidcLookupModel = static_cast(sourceModel()); + return sidcLookupModel->size(); +} // int BaseSymbolProxyFilter::size() const + + +const QStringList &BaseSymbolProxyFilter::filterSymbolTags() const +{ + return m_internal->m_keyList; +} // const QStringList & BaseSymbolProxyFilter::filterSymbolTags() const + + +bool BaseSymbolProxyFilter::filterPoints() const +{ + return m_internal->m_geometrySet.contains(AdvancedSymbology::GeometryType::Point); +} // bool BaseSymbolProxyFilter::filterPoints() const + + +bool BaseSymbolProxyFilter::filterLines() const +{ + return m_internal->m_geometrySet.contains(AdvancedSymbology::GeometryType::Polyline); +} // bool BaseSymbolProxyFilter::filterLines() const + + +bool BaseSymbolProxyFilter::filterAreas() const +{ + return m_internal->m_geometrySet.contains(AdvancedSymbology::GeometryType::Polygon); +} // bool BaseSymbolProxyFilter::filterAreas() const + + +bool BaseSymbolProxyFilter::filterParticipants() const +{ + return m_internal->m_filterParicipantSymbols; +} // bool BaseSymbolProxyFilter::filterParticipants() const + + +bool BaseSymbolProxyFilter::filterExtraSymbols() const +{ + return m_internal->m_etsSymbols && m_internal->m_xmlSymbols; +} // bool BaseSymbolProxyFilter::filterExtraSymbols() const + +bool BaseSymbolProxyFilter::filterEtsSymbols() const +{ + return m_internal->m_etsSymbols; +} // bool BaseSymbolProxyFilter::filterEtsSymbols() const + +bool BaseSymbolProxyFilter::filterXmlSymbols() const +{ + return m_internal->m_xmlSymbols; +} // bool BaseSymbolProxyFilter::filterXmlSymbols() const + +bool BaseSymbolProxyFilter::filterEsriDictSymbols() const +{ + return m_internal->m_esriDictSymbols; +} // bool BaseSymbolProxyFilter::filterXmlSymbols() const + + +void BaseSymbolProxyFilter::onFilterSymbolTags(const QStringList &keyList) +{ + updateModel(keyList); +} // void BaseSymbolProxyFilter::onFilterSymbolTags(const QStringList &keyList) + +void BaseSymbolProxyFilter::onFilterExtraSymbols(bool on) +{ + if (m_internal->m_etsSymbols == on && m_internal->m_xmlSymbols == on) + return; + m_internal->m_etsSymbols = on; + m_internal->m_xmlSymbols = on; + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterExtraSymbols(bool on) + + +void BaseSymbolProxyFilter::onFilterEtsSymbols(bool on) +{ + if (m_internal->m_etsSymbols == on) + return; + m_internal->m_etsSymbols = on; + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterEtsSymbols(bool on) + +void BaseSymbolProxyFilter::onFilterXmlSymbols(bool on) +{ + if (m_internal->m_xmlSymbols == on) + return; + m_internal->m_xmlSymbols = on; + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterXmlSymbols(bool on) + + +void BaseSymbolProxyFilter::onFilterEsriDictSymbols(bool on) +{ + if (m_internal->m_esriDictSymbols == on) + return; + m_internal->m_esriDictSymbols = on; + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterXmlSymbols(bool on) + +void BaseSymbolProxyFilter::onFilterParticipants(bool on) +{ + if (m_internal->m_filterParicipantSymbols == on) + return; + m_internal->m_filterParicipantSymbols = on; + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterParticipants(bool on) + +void BaseSymbolProxyFilter::onFilterPoints(bool on) +{ + if (on) m_internal->m_geometrySet.push_back(AdvancedSymbology::GeometryType::Point); + else m_internal->m_geometrySet.removeAll(AdvancedSymbology::GeometryType::Point); + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterPoints(bool on) + + +void BaseSymbolProxyFilter::onFilterLines(bool on) +{ + if (on) m_internal->m_geometrySet.push_back(AdvancedSymbology::GeometryType::Polyline); + else m_internal->m_geometrySet.removeAll(AdvancedSymbology::GeometryType::Polyline); + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterLines(bool on) + + +void BaseSymbolProxyFilter::onFilterAreas(bool on) +{ + if (on) m_internal->m_geometrySet.push_back(AdvancedSymbology::GeometryType::Polygon); + else m_internal->m_geometrySet.removeAll(AdvancedSymbology::GeometryType::Polygon); + beginFilterChange(); + endFilterChange(); +} // void BaseSymbolProxyFilter::onFilterAreas(bool on) + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.h new file mode 100755 index 000000000..7fc220d08 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_filter_proxy.h @@ -0,0 +1,142 @@ +/** +** +** @RDE_HEADER@ +** PlanEditor. +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 13.06.2016, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + + +#pragma once + +#include "base_symbol_model.h" + +#include +#include +#include + + +namespace AdvancedSymbology +{ +namespace Gui +{ +/** + @brief Filter proxy to BaseSymbolModel to retain list of symbols + + To retain the full list of symbols to a smaller list which can be easier handled + by the user a QSortFilterProxyModel is used. + + @see BaseSymbolModel, BaseSymbolLookupWidget + + */ + +class ADVANCED_SYMBOLOGY_GUI_EXPORT BaseSymbolProxyFilter : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + + explicit BaseSymbolProxyFilter(QObject *parent = nullptr); + + ~BaseSymbolProxyFilter(); + + const AdvancedSymbology::SymbolDictionary &symbolDictionary() const; + + void setSourceModel(AdvancedSymbology::Gui::BaseSymbolModel *sourceModel); + + int size() const; + + bool isEmpty() const; + + /** + Provides the code of the symbol as string. + @param idx row number of the symbol + @return full concret sidc of symbol + */ + QString getSidc(int idx) const; + + /** + Provides current constraints to filter full symbol list of BaseSymbolModel + @{ + */ + const QStringList &filterSymbolTags() const; + + bool filterExtraSymbols() const; + bool filterEtsSymbols() const; + bool filterXmlSymbols() const; + bool filterEsriDictSymbols() const; + + bool filterParticipants() const; + + bool filterPoints() const; + bool filterLines() const; + bool filterAreas() const; + /* @} */ + + QStringList mimeTypes() const override; + QMimeData * mimeData(const QModelIndexList &indexes) const override; + +protected: + + struct InternalData; + + InternalData *m_internal; + + /** + Internal update of filtering tags by using the dictionary (is called from \ c onFilterSymbolTags). + */ + void updateModel(const QStringList &keyList); + + /** + Overriden from QSortFilterProxyModel to associate source model with filter model. + */ + void setSourceModel(QAbstractItemModel *sourceModel) override { QSortFilterProxyModel::setSourceModel(sourceModel); } + + /** + Overriden from QSortFilterProxyModel to do the filtering. + */ + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + +public slots: + + /** + Define constraints to filter full symbol list of BaseSymbolModel + @{ + */ + void onFilterSymbolTags(const QStringList &keyList); + + void onFilterExtraSymbols(bool on); + void onFilterEtsSymbols(bool on); + void onFilterXmlSymbols(bool on); + void onFilterEsriDictSymbols(bool on); + + void onFilterParticipants(bool on); + + void onFilterPoints(bool on); + void onFilterLines(bool on); + void onFilterAreas(bool on); + /* @} */ +}; // BaseSymbolProxyFilter + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.cpp new file mode 100755 index 000000000..2aa107603 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.cpp @@ -0,0 +1,210 @@ +/** + * @RDE_HEADER@ + * Dialog for selecting (base) symbols + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, NLE, 12.12.2019 + */ + +#include "base_symbol_lookup_dialog.h" +#include "base_symbol_lookup_widget.h" +#include +#include + +#include +#include +#include + +#include // for baseSymbolCodeToSidc + +#include +#include +#include + + +namespace AdvancedSymbology +{ +namespace Gui +{ + +//------------------------------------------------------------------------------ +// BaseSymbolLookupDialog::Private +//------------------------------------------------------------------------------ +struct BaseSymbolLookupDialog::Private +{ +public: + Private() = default; + + ~Private() + { + delete m_baseSymbolWidget; + delete m_buttonBox; + } + BaseSymbolLookupWidget *m_baseSymbolWidget = nullptr; + LtsWidgets::LtsDialogButtonBox *m_buttonBox = nullptr; +}; + + +//------------------------------------------------------------------------------ +// BaseSymbolLookupDialog +//------------------------------------------------------------------------------ +BaseSymbolLookupDialog::BaseSymbolLookupDialog(QWidget *parent) + : BaseClass(parent, LtsWidgets::Behaviour::Modal) + , m_logger(getLogger("BaseSymbolLookupDialog")) + , d(new Private()) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool BaseSymbolLookupDialog::participantMode() const +{ + if (!d->m_baseSymbolWidget) + return false; + return d->m_baseSymbolWidget->participantMode(); +} +void BaseSymbolLookupDialog::setParticipantMode(bool onOff) +{ + if (!d->m_baseSymbolWidget) + return; + d->m_baseSymbolWidget->setParticipantMode(onOff); +} + +void BaseSymbolLookupDialog::setValidBaseSymbolCodes(const QSet &validBaseSymbolCodes) +{ + if (!d->m_baseSymbolWidget) + return; + d->m_baseSymbolWidget->setValidBaseSymbolCodes(validBaseSymbolCodes); +} + +bool BaseSymbolLookupDialog::init(AdvancedSymbology::SymbolDictionary &symbolDictionary) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + LtsUtils::ProjectSettings *ps = LtsUtils::ProjectSettings::getInstance(); + + bool enableEtsSymbols = ps->value(QStringLiteral("SituationMap"), QStringLiteral("enableEtsSymbols"), true).toBool(); + + // setup: + delete d->m_baseSymbolWidget; + delete d->m_buttonBox; + + AdvancedSymbology::SymbolDictionaryImpl &sdi = static_cast(symbolDictionary); + AdvancedSymbology::BaseSymbolModel *bsmEsri = sdi.getBaseSymbolModelEsri(); + AdvancedSymbology::BaseSymbolModel *bsmXml = sdi.getBaseSymbolModelXML(); + AdvancedSymbology::BaseSymbolModel *bsmEts = sdi.getBaseSymbolModelEts(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " bsmEsri rowCount" << bsmEsri->rowCount(); + const SymbologyType symbologyType = sdi.symbologyType(); + AdvancedSymbology::Gui::MODEL_CONT modelCont; + modelCont.append(AdvancedSymbology::Gui::ModelItem(AdvancedSymbology::Gui::SYMBOL_TYPE::ESRI, toString(symbologyType), bsmEsri)); + modelCont.append(AdvancedSymbology::Gui::ModelItem(AdvancedSymbology::Gui::SYMBOL_TYPE::XML, QLatin1String("User-defined Symbols"), bsmXml)); + if (enableEtsSymbols ) + modelCont.append(AdvancedSymbology::Gui::ModelItem(AdvancedSymbology::Gui::SYMBOL_TYPE::ETS, QLatin1String("Ets"), bsmEts)); + + d->m_baseSymbolWidget = new AdvancedSymbology::Gui::BaseSymbolLookupWidget(modelCont, this); + + connect(d->m_baseSymbolWidget, &AdvancedSymbology::Gui::BaseSymbolLookupWidget::symbolClicked, this, &BaseSymbolLookupDialog::onSymbolClicked); + connect(d->m_baseSymbolWidget, &AdvancedSymbology::Gui::BaseSymbolLookupWidget::symbolDoubleClicked, this, &BaseSymbolLookupDialog::onSymbolDoubleClicked); + + + d->m_buttonBox = new LtsWidgets::LtsDialogButtonBox(this); + + connect(d->m_buttonBox->getButton(LtsWidgets::DialogButton::Ok), &QPushButton::clicked, this, &BaseSymbolLookupDialog::apply); + connect(d->m_buttonBox->getButton(LtsWidgets::DialogButton::Close), &QPushButton::clicked, this, &BaseSymbolLookupDialog::close); + + d->m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setEnabled(false); // until an item is selected + + + // setup layout: + QVBoxLayout *layout = new QVBoxLayout(); + + layout->addWidget(d->m_baseSymbolWidget, 2); + layout->addWidget(d->m_buttonBox); + setLayout(layout); + + setMinimumWidth(800); + resize(sizeHint().height(), minimumWidth()); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +BaseSymbolLookupDialog::~BaseSymbolLookupDialog() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QString BaseSymbolLookupDialog::getCurrentBaseSymbolCode() const +{ + if (!d->m_baseSymbolWidget) + return QString(); + return d->m_baseSymbolWidget->getCurrentBaseSymbolCode(); +} + +bool BaseSymbolLookupDialog::setCurrentSidc(const QString &sidc) +{ + if (!d->m_baseSymbolWidget) + return false; + bool success = d->m_baseSymbolWidget->setCurrentSidc(sidc); +// d->m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setEnabled(success); + return success; +} + +void BaseSymbolLookupDialog::onSymbolClicked(const QString &baseSymbolCode, const QString &sidc) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, " + << " baseSymbolCode='" << baseSymbolCode << "'" + << " sidc='" << sidc << "'"; + + d->m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setEnabled(!sidc.isEmpty()); + +// slotChangesMade(); +} + +void BaseSymbolLookupDialog::onSymbolDoubleClicked(const QString &baseSymbolCode, const QString &sidc) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, " + << " baseSymbolCode='" << baseSymbolCode << "'" + << " sidc='" << sidc << "'"; + apply(); +} + +void BaseSymbolLookupDialog::apply() +{ + if (!d->m_baseSymbolWidget) + return ; + QSharedPointer bsi = d->m_baseSymbolWidget->getCurrentBaseSymbolItem(); + if (!bsi) + return; + + const QString &curBsc = bsi->baseSymbolCode(); + const QString &curSidc = bsi->sidc(); + + emit lookupAppliedBaseSymbolCode(curBsc); + emit lookupAppliedCompleteSidc(curSidc); + accept(); +} + +BaseSymbolLookupDialog *BaseSymbolLookupDialog::showInstance() +{ + static QPointer s_BaseSymbolLookupDialog; + + if (!s_BaseSymbolLookupDialog) + s_BaseSymbolLookupDialog = createDlg(); + + return s_BaseSymbolLookupDialog; +} + +BaseSymbolLookupDialog *BaseSymbolLookupDialog::createDlg(QWidget *parent) +{ + AdvancedSymbology::Gui::BaseSymbolLookupDialog *dlg = new AdvancedSymbology::Gui::BaseSymbolLookupDialog(parent); + dlg->init(AdvancedSymbology::SymbolDictionary::dictionary(AdvancedSymbology::SymbolDictionary::defaultSymbologyType())); + return dlg; +} + + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.h new file mode 100755 index 000000000..9fead78c2 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_dialog.h @@ -0,0 +1,76 @@ +/** + * @RDE_HEADER@ + * Dialog for selecting (base) symbols + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, NLE, 12.12.2019 + */ + +#pragma once + +#include +#include +#include +#include + + +namespace AdvancedSymbology +{ +class SymbolDictionary; + + +namespace Gui +{ + +//------------------------------------------------------------------------------ +// BaseSymbolLookupDialog +//------------------------------------------------------------------------------ +class ADVANCEDSYMBOLOGYGUI_EXPORT BaseSymbolLookupDialog : public LtsWidgets::LtsFramelessDialog +{ + Q_OBJECT + using BaseClass = LtsWidgets::LtsFramelessDialog; + +public: + static BaseSymbolLookupDialog *showInstance(); + + static BaseSymbolLookupDialog *createDlg(QWidget *parent = nullptr); + + + explicit BaseSymbolLookupDialog(QWidget *parent = nullptr); + ~BaseSymbolLookupDialog(); + + bool init(SymbolDictionary &symbolDictionary); + + bool participantMode() const; + void setParticipantMode(bool onOff); + + void setValidBaseSymbolCodes(const QSet &validBaseSymbolCodes); + + QString getCurrentBaseSymbolCode() const; + bool setCurrentSidc(const QString &sidc); + +Q_SIGNALS: + void lookupAppliedBaseSymbolCode(const QString &sidc); + void lookupAppliedCompleteSidc(const QString &sidc); + +protected: + void apply(); + +protected Q_SLOTS: + void onSymbolDoubleClicked(const QString &baseSymbolCode, const QString &sidc); + void onSymbolClicked(const QString &baseSymbolCode, const QString &sidc); + +private: + SituationMap::LOGGER_T m_logger; + + struct Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; // BaseSymbolLookupDialog + + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.cpp new file mode 100755 index 000000000..ad13d5289 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.cpp @@ -0,0 +1,481 @@ +/** + * @RDE_HEADER@ + * Widget for selecting (base) symbols + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, NLE, 12.12.2019 + */ + +#include "base_symbol_lookup_widget.h" +#include "ui_base_symbol_lookup_widget.h" + +#include // for concreteSidcToBaseSymbolCode +#include + +// Lts +//#include +#include "LtsDataView/lts_data_view.h" + +// Qt +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace AdvancedSymbology +{ +namespace Gui +{ + +//------------------------------------------------------------------------------ +// BaseSymbolLookupWidget::Private +//------------------------------------------------------------------------------ +struct BaseSymbolLookupWidget::Private +{ + MODEL_CONT modelContainer; + + QMap m_symbologyTypeToIdx; + QMap m_idxToSymbologyType; + QMap m_idxToDataViewCont; // tabIdx -> view + QMap m_idxToModel; // tabIdx -> view + + bool m_participantMode = false; + QCheckBox *m_cbPoint = nullptr; + QCheckBox *m_cbLine = nullptr; + QCheckBox *m_cbArea = nullptr; + QSet m_validBaseSymbolCodes; +}; + + +//------------------------------------------------------------------------------ +// BaseSymbolLookupWidget +//------------------------------------------------------------------------------ +BaseSymbolLookupWidget::BaseSymbolLookupWidget(const MODEL_CONT &modelCont, QWidget *parent) + : QWidget(parent) + , m_logger(getLogger("BaseSymbolLookupWidget")) + , m_ui(new Ui::BaseSymbolLookupWidget) + , d(new Private()) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_ui->setupUi(this); + + // clear, we use our own widgets: + m_ui->tabWidget->removeTab(0); + + d->modelContainer = modelCont; + + for (const auto &modelData : std::as_const(d->modelContainer)) + { + if (!modelData.m_baseSymbolModel) + continue; + + LtsDataView::LtsDataView *dv = new LtsDataView::LtsDataView(this); + + BaseSymbolProxyModel *proxy = new BaseSymbolProxyModel(this); + proxy->setSourceModel(modelData.m_baseSymbolModel); + proxy->setDynamicSortFilter(false); + dv->setModel(proxy); + + // connect: + connect(dv, &LtsDataView::LtsDataView::clicked, this, &BaseSymbolLookupWidget::onClicked); + connect(dv, &LtsDataView::LtsDataView::doubleClicked, this, &BaseSymbolLookupWidget::onDoubleClicked); + + if (modelData.m_symbolType == SYMBOL_TYPE::ESRI) + { + // ESRI -> add some cbs for Point, Line, Area: + // HBox with 3 cbs: + QHBoxLayout *layoutGeomSelect = new QHBoxLayout(); + d->m_cbPoint = new QCheckBox(tr("Point"), this); + d->m_cbLine = new QCheckBox(tr("Line"), this); + d->m_cbArea = new QCheckBox(tr("Area"), this); + layoutGeomSelect->addWidget(d->m_cbPoint, 0); + layoutGeomSelect->addWidget(d->m_cbLine, 0); + layoutGeomSelect->addWidget(d->m_cbArea, 0); + layoutGeomSelect->addStretch(9); + + d->m_cbPoint->setChecked(proxy->filterPoints()); + d->m_cbLine->setChecked(proxy->filterLines()); + d->m_cbArea->setChecked(proxy->filterAreas()); + connect(d->m_cbPoint, &QCheckBox::toggled, proxy, &BaseSymbolProxyModel::onFilterPoints); + connect(d->m_cbLine, &QCheckBox::toggled, proxy, &BaseSymbolProxyModel::onFilterLines); + connect(d->m_cbArea, &QCheckBox::toggled, proxy, &BaseSymbolProxyModel::onFilterAreas); + + // VBox with layoutGeomSelect and LtsDataView + QVBoxLayout *layout = new QVBoxLayout(); + layout->addLayout(layoutGeomSelect); + layout->addWidget(dv); + + // wrap within a widget: + QWidget *w = new QWidget(); + w->setLayout(layout); + + int idx = m_ui->tabWidget->addTab(w, modelData.m_name); + d->m_symbologyTypeToIdx[modelData.m_symbolType] = idx; + d->m_idxToSymbologyType[idx] = modelData.m_symbolType; + d->m_idxToDataViewCont[idx] = dv; + d->m_idxToModel[idx] = modelData.m_baseSymbolModel; + } + else + { + const int idx = m_ui->tabWidget->addTab(dv, modelData.m_name); + d->m_symbologyTypeToIdx[modelData.m_symbolType] = idx; + d->m_idxToSymbologyType[idx] = modelData.m_symbolType; + d->m_idxToDataViewCont[idx] = dv; + d->m_idxToModel[idx] = modelData.m_baseSymbolModel; + } + + showHideColumns(modelData.m_symbolType); + + } // for + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +BaseSymbolLookupWidget::~BaseSymbolLookupWidget() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + delete m_ui; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void BaseSymbolLookupWidget::setValidBaseSymbolCodes(const QSet &validBaseSymbolCodes) +{ + d->m_validBaseSymbolCodes = validBaseSymbolCodes; + + const int tabCount = m_ui->tabWidget->count(); + for (int tabIdx = 0; tabIdx < tabCount; ++tabIdx) + { + LtsDataView::LtsDataView *dv = d->m_idxToDataViewCont[tabIdx]; + if (!dv) + continue; + + BaseSymbolProxyModel *proxy = qobject_cast(dv->proxyModel()); + if (proxy) + proxy->setValidBaseSymbolCodes(d->m_validBaseSymbolCodes); + } // for +} + +bool BaseSymbolLookupWidget::participantMode() const +{ + return d->m_participantMode; +} + +void BaseSymbolLookupWidget::setParticipantMode(bool onOff) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, participantMode =" << onOff; + d->m_participantMode = onOff; + if (d->m_participantMode) + { + d->m_cbPoint->setChecked(true); + d->m_cbLine->setChecked(false); + d->m_cbArea->setChecked(false); + } + else + { + d->m_cbPoint->setChecked(true); + d->m_cbLine->setChecked(true); + d->m_cbArea->setChecked(true); + } + + d->m_cbPoint->setVisible(!d->m_participantMode); + d->m_cbLine->setVisible(!d->m_participantMode); + d->m_cbArea->setVisible(!d->m_participantMode); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void BaseSymbolLookupWidget::showHideColumns(SYMBOL_TYPE symType) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, symType=" << static_cast(symType); + if (!d->m_symbologyTypeToIdx.contains(symType)) + return; + + const int idx = d->m_symbologyTypeToIdx[symType]; + LtsDataView::LtsDataView *dv = d->m_idxToDataViewCont[idx]; + if (!dv) + return; + +#ifdef LTS_DEBUG + const bool showAll = QGuiApplication::keyboardModifiers().testFlag(Qt::KeyboardModifier::ControlModifier); +#else + const bool showAll = false; +#endif // LTS_DEBUG + + switch (symType) + { + case SYMBOL_TYPE::ESRI: +// dv->setColumnHidden(BaseSymbolModel::Columns::ID, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::ORIGINAL_KEY, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::KEY, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::OPERATOR_NAME, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::LAST_MODIFIED, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::SIDC, !showAll); + + + dv->setColumnHidden(BaseSymbolModel::Columns::SEARCH_ICON, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::FETCH_ICON, !showAll); +// dv->setColumnHidden(BaseSymbolModel::Columns::FIND_BASE_SYMBOL, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::FIND_PREVIEW, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::FIND_IMAGE, !showAll); + + dv->setColumnHidden(BaseSymbolModel::Columns::XML_SYMBOL_SIZE, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::CATEGORY, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::SYMBOLCLASS, !showAll); +// dv->setColumnHidden(BaseSymbolModel::Columns::TAGS, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::GCTSTRING, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::GEOMETRYTYPE, !showAll); + break; + + case SYMBOL_TYPE::XML: +// dv->setColumnHidden(BaseSymbolModel::Columns::ID, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::ORIGINAL_KEY, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::KEY, !showAll); +// dv->setColumnHidden(BaseSymbolModel::Columns::OPERATOR_NAME, !showAll); +// dv->setColumnHidden(BaseSymbolModel::Columns::LAST_MODIFIED, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::SIDC, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::SYMBOL_SET, true); + + dv->setColumnHidden(BaseSymbolModel::Columns::SEARCH_ICON, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::FETCH_ICON, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::FIND_BASE_SYMBOL, !showAll); +// dv->setColumnHidden(BaseSymbolModel::Columns::FIND_PREVIEW, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::FIND_IMAGE, !showAll); + +// dv->setColumnHidden(BaseSymbolModel::Columns::XML_SYMBOL_SIZE, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::CATEGORY, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::SYMBOLCLASS, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::TAGS, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::GCTSTRING, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::GEOMETRYTYPE, !showAll); + break; + + case SYMBOL_TYPE::ETS: +// dv->setColumnHidden(BaseSymbolModel::Columns::ID, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::ORIGINAL_KEY, true); + dv->setColumnHidden(BaseSymbolModel::Columns::KEY, true); + dv->setColumnHidden(BaseSymbolModel::Columns::OPERATOR_NAME, true); + dv->setColumnHidden(BaseSymbolModel::Columns::LAST_MODIFIED, true); + dv->setColumnHidden(BaseSymbolModel::Columns::SIDC, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::SYMBOL_SET, true); + + dv->setColumnHidden(BaseSymbolModel::Columns::SEARCH_ICON, true); + dv->setColumnHidden(BaseSymbolModel::Columns::FETCH_ICON, true); +// dv->setColumnHidden(BaseSymbolModel::Columns::FIND_BASE_SYMBOL, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::FIND_PREVIEW, true); + dv->setColumnHidden(BaseSymbolModel::Columns::FIND_IMAGE, true); + + dv->setColumnHidden(BaseSymbolModel::Columns::XML_SYMBOL_SIZE, true); +// dv->setColumnHidden(BaseSymbolModel::Columns::CATEGORY, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::SYMBOLCLASS, true); +// dv->setColumnHidden(BaseSymbolModel::Columns::TAGS, !showAll); + dv->setColumnHidden(BaseSymbolModel::Columns::GCTSTRING, true); + dv->setColumnHidden(BaseSymbolModel::Columns::GEOMETRYTYPE, true); + break; + } // switch + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool BaseSymbolLookupWidget::setCurrentTab(SYMBOL_TYPE symType) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START, symType=" << static_cast(symType); + if (!d->m_symbologyTypeToIdx.contains(symType)) + return false; + const int idx = d->m_symbologyTypeToIdx[symType]; + m_ui->tabWidget->setCurrentIndex(idx); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + +void BaseSymbolLookupWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); + showHideColumns(SYMBOL_TYPE::ESRI); + showHideColumns(SYMBOL_TYPE::XML); + showHideColumns(SYMBOL_TYPE::ETS); +} + + +QSharedPointer BaseSymbolLookupWidget::getCurrentBaseSymbolItem() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + const int tabIdx = m_ui->tabWidget->currentIndex(); + LtsDataView::LtsDataView *dv = d->m_idxToDataViewCont[tabIdx]; + BaseSymbolModel *bsm = d->m_idxToModel[tabIdx]; + + if (!dv || !bsm) + return nullptr; + + // found :-) + QItemSelectionModel *selectModel = dv->selectionModel(); + + if (!selectModel || !selectModel->hasSelection()) + return nullptr; + + const QItemSelection &selectionSource = dv->mapSelectionToSource(selectModel->selection()); + const int row = selectionSource.indexes().first().row(); // assume singleSelection + + return bsm->getRowData(row); +} + +QString BaseSymbolLookupWidget::getCurrentBaseSymbolCode() const +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + QSharedPointer bsi = getCurrentBaseSymbolItem(); + if (bsi) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " m_id" << bsi->m_id + << ", baseSymbolCode" << bsi->baseSymbolCode(); + return bsi->baseSymbolCode(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return QString(); +} + +QString BaseSymbolLookupWidget::getCurrentSidc() const +{ + QSharedPointer bsi = getCurrentBaseSymbolItem(); + if (bsi) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " m_id" << bsi->m_id + << ", sidc" << bsi->sidc(); + return bsi->sidc(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return QString(); +} + +bool BaseSymbolLookupWidget::setCurrentSidc(const QString &sidc) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " sidc='" << sidc << "'"; + + if (sidc.isEmpty()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " sidc is empty, maybe not set..."; + return false; + } + + if (!SidcCodec::isEncoded(sidc)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " sidc isn't encoded: '" << sidc << "'"; + return false; + } + + SYMBOL_TYPE symType = SYMBOL_TYPE::ESRI; + QString baseSymbolCode = sidc; + + const SidcCodec::SidcType sidcType = SidcCodec::decodeSidcType(sidc); + switch (sidcType) + { + case SidcCodec::SidcType::None: + { + ERROR_LOG(m_logger) << LOGFUNCTION << " SidcType::None isn't supported. Sidc= '" << sidc << "'"; + return false; + } + case SidcCodec::SidcType::Complete: + baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(sidc); + case SidcCodec::SidcType::Base: + break; + case SidcCodec::SidcType::CivilEts: + symType = SYMBOL_TYPE::ETS; + break; + case SidcCodec::SidcType::CivilXml: + symType = SYMBOL_TYPE::XML; + break; + default : + { + ERROR_LOG(m_logger) << LOGFUNCTION << " SidcType is unknown. Sidc= '" << sidc << "'"; + return false; + } + } // switch + + + // switch to model/view/tabIdx + + if (!d->m_symbologyTypeToIdx.contains(symType)) + return false; + + const int tabIdx = d->m_symbologyTypeToIdx[symType]; + BaseSymbolModel *bsm = d->m_idxToModel[tabIdx]; + LtsDataView::LtsDataView *dv = d->m_idxToDataViewCont[tabIdx]; + + if (!bsm || !dv) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " switch to model/view/tabIndex failed, sidc '" << sidc << "'"; + return false; + } + + // switch to tabIdx + m_ui->tabWidget->setCurrentIndex(tabIdx); + + // switch to model/view + const int row = bsm->findRowForBaseSymbolCode(baseSymbolCode); + if (row >= 0) + { + const QModelIndex sourceIdx = bsm->index(row, 0); + dv->selectRow(dv->proxyModel()->mapFromSource(sourceIdx)); + dv->scrollTo(dv->proxyModel()->mapFromSource(sourceIdx)); + } + + INFO_LOG(m_logger) << LOGFUNCTION << " sidc='" << sidc << "'" + << " tabIdx = " << tabIdx + << " findRowForBaseSymbolCode -> row = " << row; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return (row >= 0); +} + +void BaseSymbolLookupWidget::onClicked(const QModelIndex &index) +{ + Q_UNUSED(index); + + QSharedPointer bsi = getCurrentBaseSymbolItem(); + if (!bsi) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " item not found"; + return; + } + const QString &curBsc = bsi->baseSymbolCode(); + const QString &curSidc = bsi->sidc(); + emit symbolClicked(curBsc, curSidc); +} + +void BaseSymbolLookupWidget::onDoubleClicked(const QModelIndex &index) +{ + Q_UNUSED(index); + + QSharedPointer bsi = getCurrentBaseSymbolItem(); + if (!bsi) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " item not found"; + return; + } + const QString &curBsc = bsi->baseSymbolCode(); + const QString &curSidc = bsi->sidc(); + emit symbolDoubleClicked(curBsc, curSidc); +} + + +} // namespace Gui +} // namespace AdvancedSymbology + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.h new file mode 100755 index 000000000..782f376bc --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.h @@ -0,0 +1,104 @@ +/** + * @RDE_HEADER@ + * Widget for selecting (base) symbols + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Norbert Thoden, NLE, 12.12.2019 + */ + +#pragma once + +#include +#include + +#include +#include + + +namespace Ui +{ +class BaseSymbolLookupWidget; +} + + +namespace AdvancedSymbology +{ +class BaseSymbolModel; +class BaseSymbolItem; +namespace Gui +{ + +enum class SYMBOL_TYPE +{ + ESRI = 0, + XML, + ETS +}; + +struct ModelItem +{ + ModelItem(SYMBOL_TYPE symbolType = SYMBOL_TYPE::ESRI, + const QString &name = QString(), + BaseSymbolModel *baseSymbolModel = nullptr) + : m_symbolType(symbolType), + m_name(name), + m_baseSymbolModel(baseSymbolModel) + {} + SYMBOL_TYPE m_symbolType; + QString m_name; + BaseSymbolModel *m_baseSymbolModel = nullptr; +}; + +using MODEL_CONT = QVector; + + +//------------------------------------------------------------------------------ +// BaseSymbolLookupWidget +//------------------------------------------------------------------------------ +class ADVANCEDSYMBOLOGYGUI_EXPORT BaseSymbolLookupWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BaseSymbolLookupWidget(const MODEL_CONT &modelCont, QWidget *parent); + ~BaseSymbolLookupWidget(); + + bool participantMode() const; + void setParticipantMode(bool onOff); + + void setValidBaseSymbolCodes(const QSet &validBaseSymbolCodes); + + QSharedPointer getCurrentBaseSymbolItem() const; + QString getCurrentBaseSymbolCode() const; // encoded, baseSymbolCode + QString getCurrentSidc() const; // encoded, complete + bool setCurrentSidc(const QString &sidc); // encoded, baseSymbolCode or complete Sidc + +Q_SIGNALS: + void symbolClicked(const QString &baseSymbolCode, const QString &sidc); + void symbolDoubleClicked(const QString &baseSymbolCode, const QString &sidc); + +protected: + bool setCurrentTab(SYMBOL_TYPE symType); + void showHideColumns(SYMBOL_TYPE symType); + +protected Q_SLOTS: + void showEvent(QShowEvent *event) override; + + void onClicked(const QModelIndex &index); + void onDoubleClicked(const QModelIndex &index); + +private: + SituationMap::LOGGER_T m_logger; + + Ui::BaseSymbolLookupWidget *m_ui; + + struct Private; + /** + * The private d->pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.ui b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.ui new file mode 100755 index 000000000..ec83a4540 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/base_symbol_lookup_widget.ui @@ -0,0 +1,33 @@ + + + BaseSymbolLookupWidget + + + + 0 + 0 + 840 + 463 + + + + Form + + + + + + 0 + + + + Tab 1 + + + + + + + + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.cpp new file mode 100755 index 000000000..21e05ba86 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.cpp @@ -0,0 +1,126 @@ +/** + * + * @RDE_HEADER@ + * Helper model for SymbolModificationWidget + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "field_table_model.h" + +namespace AdvancedSymbology +{ +namespace Gui +{ + +int FieldTableModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 2; +} + +int FieldTableModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_fieldKeys.size(); +} + +QVariant FieldTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) return QVariant(); + if (Qt::Orientation::Horizontal == orientation) + { + switch (section) + { + case 0 : return tr("Field Title"); + case 1 : return tr("Value"); + default : break; + } + } + return QVariant(); +} // QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override + + +QVariant FieldTableModel::data(const QModelIndex &index, int role) const +{ + if (index.row() >= 0 && index.row() < m_fieldKeys.size() && + role == Qt::DisplayRole) + { + const QString &key = m_fieldKeys.at(index.row()); + switch (index.column()) + { + case 0 : return key; + case 1 : return m_fieldValueMap[key]; + default : QVariant(); + } + } + return QVariant(); +} // QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override + +bool FieldTableModel::setFieldAttrMap(const QMap &fieldAttrMap) +{ + beginResetModel(); + { + m_fieldKeys = fieldAttrMap.keys(); + m_fieldKeys.sort(); + m_fieldValueMap = fieldAttrMap; + } + endResetModel(); + return true; +} // bool setFieldAttrMap( const QMap & fieldAttrMap ) + + +void FieldTableModel::setFieldAttribute(const QString &key, const QString &value) +{ + beginResetModel(); + { + m_fieldValueMap[key] = value; + if (!m_fieldKeys.contains(key)) + { + m_fieldKeys += key; + m_fieldKeys.sort(); + } + } + endResetModel(); +} // void setFieldAttribute( const QString & key, const QString & value ) + + +void FieldTableModel::setFieldAttribute(SituationMap::Participant::TextModifier textMod, const QString &value) +{ + const QString &key = participantAttributeKey(textMod); + if (!key.isEmpty()) setFieldAttribute(key, value); +} // void setFieldAttribute(SituationMap::TextModifier textMod, const QString & value) + + +void FieldTableModel::removeFieldAttrMap(const QString &key) +{ + if (m_fieldKeys.contains(key)) + { + beginResetModel(); + { + m_fieldKeys.removeAll(key); + m_fieldValueMap.remove(key); + } + endResetModel(); + } +} // void removeFieldAttrMap( const QString & key ) + + +QString FieldTableModel::fieldAttribute(const QString &key) const +{ + return m_fieldValueMap.value(key).toString(); +} // void fieldAttribute( const QString & key ) + +QString FieldTableModel::fieldAttribute(SituationMap::Participant::TextModifier textMod) +{ + const QString &key = participantAttributeKey(textMod); + if (key.isEmpty()) return QString(); + return fieldAttribute(key); +} // QString fieldAttribute( SituationMap::TextModifier textMod ) + +QString FieldTableModel::participantAttributeKey(SituationMap::Participant::TextModifier textMod) +{ + return SituationMap::Participant::toString(textMod); +} // QString participantAttributeKey(SituationMap::TextModifier textMod) + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.h new file mode 100755 index 000000000..805a6bd78 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/field_table_model.h @@ -0,0 +1,47 @@ +/** + * + * @RDE_HEADER@ + * Helper model for SymbolModificationWidget + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include +#include + +namespace AdvancedSymbology +{ +namespace Gui +{ + +class FieldTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + using QAbstractTableModel::QAbstractTableModel; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + const QMap &fieldAttrMap() const { return m_fieldValueMap; } + bool setFieldAttrMap(const QMap &fieldAttrMap); + void setFieldAttribute(const QString &key, const QString &value); + void setFieldAttribute(SituationMap::Participant::TextModifier textMod, const QString &value); + void removeFieldAttrMap(const QString &key); + QString fieldAttribute(const QString &key) const; + QString fieldAttribute(SituationMap::Participant::TextModifier textMod); + +private: + static QString participantAttributeKey(SituationMap::Participant::TextModifier textMod); +private: + QStringList m_fieldKeys; + QMap m_fieldValueMap; + +}; // FieldTableModel + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.cpp new file mode 100755 index 000000000..c9499d411 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.cpp @@ -0,0 +1,137 @@ +/** +* @RDE_HEADER@ +* symbol_modification_dialog for sidc configuration +* $Id$ +* $HeadURL$ +* $Author$ +* Created by Karwat, SLSE, 2017-09-07 +*/ + +#include "symbol_modification_dialog.h" +#include "symbol_modification_widget.h" +#include + + +namespace AdvancedSymbology +{ +namespace Gui +{ + +//Dialog + +SymbolModificationDialog::SymbolModificationDialog(SituationMap::Map *map, bool isParticipant, QWidget *parent) + : LtsWidgets::LtsDialog(parent, LtsWidgets::ButtonCombination::OkCancel) +{ + initLayout(map, isParticipant); +} + +/* + * TODO: FIXME: Warum ist das ein OkCancel Dialog aber Ok wird gehided und Cancel dann wieder zu close umbenannt? + * Warum ist das nicht gleich ein CloseONly Dialog? + */ +void SymbolModificationDialog::removeOkAndApplyButtons() +{ + m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setVisible(false); + m_buttonBox->getButton(LtsWidgets::DialogButton::Apply)->setVisible(false); + m_buttonBox->getButton(LtsWidgets::DialogButton::Close)->setText(tr("Close")); +} + +void SymbolModificationDialog::initLayout(SituationMap::Map *map, bool isParticipant) +{ + + QVBoxLayout *layout = new QVBoxLayout(this); + m_symbolModificationWidget = new SymbolModificationWidget(map, isParticipant, map->symbolDictionary(), this); + connect(m_symbolModificationWidget, &SymbolModificationWidget::changed, this, &SymbolModificationDialog::changed); + connect(m_symbolModificationWidget, &SymbolModificationWidget::changed, this, &SymbolModificationDialog::slotChangesMade); + m_buttonBox->connectButton(LtsWidgets::DialogButton::No, m_symbolModificationWidget, &SymbolModificationWidget::onRevert); + m_buttonBox->setText(LtsWidgets::DialogButton::No, tr("Revert")); + layout->addWidget(m_symbolModificationWidget, 2); +} + + + +void SymbolModificationDialog::aboutToClose() { emit signalAboutToClose(); } +bool SymbolModificationDialog::apply() +{ + //const QString & sidc = getCurrentSidc(); + + //if (sidc.isEmpty()) return false; + + //emit lookupApplied( sidc ); + return true; +} // SymbolModificationDialog::apply() + + +void SymbolModificationDialog::retranslate() +{ + // lupdate doesn't like translate(tr("foo")) ... + const QString txt(tr("Symbol Modification")); + translate(txt); + +} // SymbolModificationDialog::retranslate() + + + +//Frameless +SymbolModificationDialogFrameless::SymbolModificationDialogFrameless(SituationMap::Map *map, bool isParticipant, QWidget *parent) + : LtsWidgets::LtsFramelessDialog(parent) + , m_buttonBox(nullptr) +{ + initLayout(map, isParticipant, map->symbolDictionary()); +} + +SymbolModificationDialogFrameless::SymbolModificationDialogFrameless(SymbolDictionary &symbolDictionary, bool isParticipant, QWidget *parent) + : LtsWidgets::LtsFramelessDialog(parent) + , m_buttonBox(nullptr) +{ + initLayout(nullptr, isParticipant, symbolDictionary); +} + + +void SymbolModificationDialogFrameless::initLayout(SituationMap::Map *map, bool isParticipant, SymbolDictionary &symbolDictionary) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + m_buttonBox = new LtsWidgets::LtsDialogButtonBox(this); + m_symbolModificationWidget = new SymbolModificationWidget(map, isParticipant, symbolDictionary, this); + connect(m_symbolModificationWidget, &SymbolModificationWidget::changed, this, &SymbolModificationDialogFrameless::sidcChanged); + + layout->addWidget(m_symbolModificationWidget, 2); + layout->addWidget(m_buttonBox); + + connect(m_buttonBox->getButton(LtsWidgets::DialogButton::Ok), &QPushButton::clicked, this, &SymbolModificationDialogFrameless::apply); + connect(m_buttonBox->getButton(LtsWidgets::DialogButton::Close), &QPushButton::clicked, this, &SymbolModificationDialogFrameless::onClose); + m_buttonBox->connectButton(LtsWidgets::DialogButton::No, m_symbolModificationWidget, &SymbolModificationWidget::onRevert); + m_buttonBox->setText(LtsWidgets::DialogButton::No, tr("Revert")); + setMinimumWidth(800); + setMinimumHeight(300); + resize(minimumHeight(), minimumWidth()); +} + + +void SymbolModificationDialogFrameless::setSidc(const QString &sidc) +{ + m_symbolModificationWidget->setSidc(sidc); + m_oldSidc = sidc; +} + +void SymbolModificationDialogFrameless::apply() +{ + emit sidcOnClose(m_newSidc); + close(); +} + +void SymbolModificationDialogFrameless::onClose() +{ + emit sidcOnClose(m_oldSidc); + close(); +} + +void SymbolModificationDialogFrameless::sidcChanged() +{ + const QString &newSidc = m_symbolModificationWidget->modifiedSidc(); + m_newSidc = newSidc; + emit changed(); +} + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.h new file mode 100755 index 000000000..a856ea879 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_dialog.h @@ -0,0 +1,86 @@ +/** +* +* @RDE_HEADER@ +* symbol_modification_dialog for sidc configuration +* $Id$ +* $HeadURL$ +* $Author$ +* Created by Karwat, SLSE, 2017-09-07 +*/ + +#pragma once + +#include + +#include + +#include "symbol_modification_widget.h" + + + +namespace AdvancedSymbology +{ +class SymbolDictionary; + +namespace Gui +{ + +class ADVANCEDSYMBOLOGYGUI_EXPORT SymbolModificationDialog : public LtsWidgets::LtsDialog +{ + Q_OBJECT + +public: + + explicit SymbolModificationDialog(SituationMap::Map *map, bool isParticipant, QWidget *parent = nullptr); + + SymbolModificationWidget *getWidget() { return m_symbolModificationWidget; } + void removeOkAndApplyButtons(); + +signals: + void changed(); + void signalAboutToClose(); + +protected: + + bool apply() override; + + void aboutToClose() override; + +private: + void retranslate() override; + void initLayout(SituationMap::Map *map, bool isParticipant); + + SymbolModificationWidget *m_symbolModificationWidget; +}; // SymbolModificationDialog + +class ADVANCEDSYMBOLOGYGUI_EXPORT SymbolModificationDialogFrameless : public LtsWidgets::LtsFramelessDialog +{ + Q_OBJECT + +public: + explicit SymbolModificationDialogFrameless(SituationMap::Map *map, bool isParticipant, QWidget *parent = nullptr); + explicit SymbolModificationDialogFrameless(SymbolDictionary &symbolDictionary, bool isParticipant, QWidget *parent = nullptr); + + void setSidc(const QString &sidc); + + +signals: + void changed(); + void sidcOnClose(const QString &); + +protected slots: + void apply(); //next override + void onClose(); + void sidcChanged(); + +private: + void initLayout(SituationMap::Map *map, bool isParticipant, SymbolDictionary &symbolDictionary); + SymbolModificationWidget *m_symbolModificationWidget; + LtsWidgets::LtsDialogButtonBox *m_buttonBox; + QString m_newSidc; + QString m_oldSidc; + +}; // SymbolModificationDialogFrameless + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.cpp new file mode 100755 index 000000000..9f7608eb0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.cpp @@ -0,0 +1,1298 @@ +/** +* +* @RDE_HEADER@ +* symbol_modification_wdiget for sidc configuration +* $Id$ +* $HeadURL$ +* $Author$ +* Created by Karwat, SLSE, 2017-09-07 +*/ + + +#include "symbol_modification_widget.h" + +#include "field_table_model.h" + +#include + +#include +#include +#include + +// Lts +#include + +// Qt +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace AdvancedSymbology +{ +namespace Gui +{ + +class SidcWithIcon : public QWidget +{ + +public: + + explicit SidcWithIcon(QWidget *parent = nullptr) : QWidget(parent) + { + QHBoxLayout *layout = new QHBoxLayout(this); + // layout->setContentsMargins(0,0,0,0); + // layout->setSpacing(0); + + + m_lbImage = new QLabel(this); + // m_lbImage->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + // m_lbImage->setMaximumSize(QSize(100,100)); + layout->addWidget(m_lbImage); + + m_lbText = new QLabel(this); + // m_lbText->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + // m_lbImage->setMaximumSize(QSize(100,100)); + layout->addWidget(m_lbText); + + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + // setMaximumHeight(100); + // setMaximumWidth(300); + } // SidcWithIcon(QWidget* parent = nullptr) + + void setImage(const QImage &img) { m_lbImage->setPixmap(QPixmap::fromImage(img).scaledToHeight(100)); } + + void setText(const QString &text) { m_lbText->setText(text); } + +private: + + QLabel *m_lbImage; + QLabel *m_lbText; + +}; // SidcWithIcon + + + + +//--------------------------------- +// EnumComboBox +//--------------------------------- + +using MyComboBox = LtsWidgets::LtsComboBox; + + +template class EnumComboBox : public MyComboBox +{ + +public: + + explicit EnumComboBox(QWidget *parent = nullptr) : MyComboBox(parent) {;} + + void insertNumberedItem(int idx, T value) + { + insertNumberedItem(idx, AdvancedSymbology::SidcDefinitions::toPixmap(value), AdvancedSymbology::SidcDefinitions::toString(value), static_cast(value)); + } // void insertNumberedItem( int idx, T value ) + + + void insertNumberedItem(int idx, const QString &text, int value) + { + MyComboBox::insertItem(idx, QStringLiteral("%1 %2").arg(value).arg(text), value); + } // void insertNumberedItem( int idx, const QString & text, int value ) + + + void insertNumberedItem(int idx, const QPixmap & pixmap, const QString &text, int value) + { + MyComboBox::insertItem(idx, pixmap, QStringLiteral("%1 %2").arg(value).arg(text), value); + } // void insertNumberedItem( int idx, const QString & text, int value ) + + + void insertItems(const QVector &items) + { + MyComboBox::clear(); + + int idx = 0; + for (const auto &item : items) + insertNumberedItem(idx++, item); + if (MyComboBox::count() > 0) + setCurrentIndex(0); + } // void insertItems( const QVector & items ) + + + T currentValue() const { return static_cast(currentData().toInt()); } + int currentValueAsInt() const { return currentData().toInt(); } + + + bool setCurrent(const T &newCurrentValue) + { + QVariant newCurVar(static_cast(newCurrentValue)); + MyComboBox::setCurrentIndex(findData(newCurVar)); + return true; + } // bool setCurrent( const T & newCurrentValue ) + +signals: + + void changed(T newValue); + +protected Q_SLOTS: + + void onCurrentIndexChanged(int) { emit changed(currentValue()); } + +}; // template class EnumComboBox + + + + + +class SymbolModificationWidget::Internal +{ +public: + + QPointer m_map; + + AdvancedSymbology::SidcDefinitions m_sidcDefinitions; + AdvancedSymbology::SidcBuilder m_sidcBuilder; + AdvancedSymbology::SymbolDictionary &m_dict; + AdvancedSymbology::SymbologyType m_symbologyType; + + bool m_isParticipant; + + QString m_originalSidc; + QString m_lastModifiedSidc; + QString m_baseSymbolCode; + QString m_modifiedSidc; + + QTabWidget *m_tabWidget; + QStackedWidget *m_tabSidcEdit; + QWidget *m_tabFieldEdit; + QWidget *m_tabPreview; + + EnumComboBox *m_cbContext_MIL2525D; + EnumComboBox *m_cbIdentity_MIL2525D; + EnumComboBox *m_cbSymbolSet_MIL2525D; + EnumComboBox *m_cbStatus_MIL2525D; + EnumComboBox *m_cbHQTFDummy_MIL2525D; + + EnumComboBox *m_cbAmplifierGroup_MIL2525D; + EnumComboBox *m_cbAmplifierDescriptor_MIL2525D; + EnumComboBox *m_cbSector1Modifier_MIL2525D; + EnumComboBox *m_cbSector2Modifier_MIL2525D; + + EnumComboBox *m_cbIdentity_APP6; + MyComboBox *m_cbSize_APP6; + MyComboBox *m_cbSizeHQ_APP6; + MyComboBox *m_cbSizeTF_APP6; + MyComboBox *m_cbSizeFDHQ_APP6; + MyComboBox *m_cbSizeFDTF_APP6; + MyComboBox *m_cbSizeFD_APP6; + MyComboBox *m_cbMobility_APP6; + MyComboBox *m_cbStatus_APP6; + MyComboBox *m_cbOrderOfBattle_APP6; + + MyComboBox *m_cbFieldTitle; + QLineEdit *m_leFieldValue; + QTableView *m_fieldTable; + FieldTableModel *m_fieldTableModel; + + + explicit Internal(SituationMap::Map *map, SymbolDictionary &symbolDictionary) + : m_map(map) + , m_sidcDefinitions((map) ? & map->symbolDictionary() : & symbolDictionary) + , m_sidcBuilder((map) ? & map->symbolDictionary() : & symbolDictionary) + , m_dict(symbolDictionary) + , m_symbologyType(AdvancedSymbology::SymbologyType::None) + , m_isParticipant(false) + , m_originalSidc() + , m_lastModifiedSidc() + , m_baseSymbolCode() + , m_modifiedSidc() + , m_tabWidget(nullptr) + , m_tabSidcEdit(nullptr) + , m_tabFieldEdit(nullptr) + , m_tabPreview(nullptr) + , m_cbContext_MIL2525D(nullptr) + , m_cbIdentity_MIL2525D(nullptr) + , m_cbSymbolSet_MIL2525D(nullptr) + , m_cbStatus_MIL2525D(nullptr) + , m_cbHQTFDummy_MIL2525D(nullptr) + , m_cbAmplifierGroup_MIL2525D(nullptr) + , m_cbAmplifierDescriptor_MIL2525D(nullptr) + , m_cbSector1Modifier_MIL2525D(nullptr) + , m_cbSector2Modifier_MIL2525D(nullptr) + , m_cbIdentity_APP6(nullptr) + , m_cbSize_APP6(nullptr) + , m_cbSizeHQ_APP6(nullptr) + , m_cbSizeTF_APP6(nullptr) + , m_cbSizeFDHQ_APP6(nullptr) + , m_cbSizeFDTF_APP6(nullptr) + , m_cbSizeFD_APP6(nullptr) + , m_cbMobility_APP6(nullptr) + , m_cbStatus_APP6(nullptr) + , m_cbOrderOfBattle_APP6(nullptr) + , m_cbFieldTitle(nullptr) + , m_leFieldValue(nullptr) + , m_fieldTable(nullptr) + , m_fieldTableModel(nullptr) + { + } // explicit Internal() + + ~Internal() = default; + + +}; // SymbolModificationWidget::Internal + + +enum class SymbologyTypeStackIndex +{ + APP6 = 0, + MIL2525D = 1 +}; // SymbologyTypeStackIndex + + +//--------------------------------- +// SymbolModificationWidget +//--------------------------------- +SymbolModificationWidget::SymbolModificationWidget(SituationMap::Map *map, bool isParticipant, SymbolDictionary &symbolDictionary, QWidget *parent) + : QWidget(parent) + , m_internal(new Internal(map, symbolDictionary)) +{ + m_internal->m_isParticipant = isParticipant; + + QVBoxLayout *layout = new QVBoxLayout(this); + QHBoxLayout *layoutCtrl = new QHBoxLayout(); + + m_internal->m_tabWidget = new QTabWidget(this); + + m_internal->m_tabSidcEdit = new QStackedWidget(m_internal->m_tabWidget); + m_internal->m_tabFieldEdit = new QWidget(m_internal->m_tabWidget); + m_internal->m_tabPreview = new QWidget(m_internal->m_tabWidget); + + m_internal->m_tabWidget->setTabsClosable(false); + m_internal->m_tabWidget->setMovable(false); + m_internal->m_tabWidget->setTabBarAutoHide(true); + + m_internal->m_tabSidcEdit -> setObjectName(QStringLiteral("SymbolModificationWidgetSymbolSidcEdit")); + m_internal->m_tabFieldEdit-> setObjectName(QStringLiteral("SymbolModificationWidgetSymbolFieldEdit")); + m_internal->m_tabPreview -> setObjectName(QStringLiteral("SymbolModificationWidgetSymbolPreview")); + + m_internal->m_tabSidcEdit -> setEnabled(false); + m_internal->m_tabFieldEdit -> setEnabled(false); + m_internal->m_tabPreview -> setEnabled(false); + + m_internal->m_tabSidcEdit -> hide(); + m_internal->m_tabFieldEdit -> hide(); + m_internal->m_tabPreview -> hide(); + + layout->setContentsMargins(10, 10, 10, 10); + layout->addSpacing(10); + layout->addWidget(m_internal->m_tabWidget); + layout->addSpacing(10); + layout->addLayout(layoutCtrl); + + // d->m_preview = new QVBoxLayout(); + // d->m_preview->setObjectName(QLatin1String("previewCol")); + // layout->addLayout(d->m_preview, 0, 0); + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + QWidget * sidcEditAPP6 = new QWidget(m_internal->m_tabSidcEdit); + QWidget * sidcEditMIL2525D = new QWidget(m_internal->m_tabSidcEdit); + + + m_internal->m_tabSidcEdit->addWidget(sidcEditAPP6); // SymbologyTypeStackIndex::APP6 + m_internal->m_tabSidcEdit->addWidget(sidcEditMIL2525D); // SymbologyTypeStackIndex::MIL2525D + + buildSidcModifierTab_APP6(sidcEditAPP6); + buildSidcModifierTab_MIL2525D( sidcEditMIL2525D ); + + + buildFieldModifierTab(m_internal->m_tabFieldEdit); + + + buildPreview(m_internal->m_tabPreview); + + +} // SymbolModificationWidget::SymbolModificationWidget + +void SymbolModificationWidget::retranslate() +{ + // FIXME: UI Retranslation does not work with the entire Plan Editor, as there are no UIs ... + //translate(m_ui, tr("??")); +} + +void SymbolModificationWidget::buildSidcModifierTab_MIL2525D(QWidget *widget) +{ + QHBoxLayout *layout = new QHBoxLayout(widget); + QFormLayout *layoutEditLeft = new QFormLayout(); + QFormLayout *layoutEditRight = new QFormLayout(); + + layout->addStretch(2); + layout->addLayout(layoutEditLeft); + layout->addStretch(2); + QFrame *separator = new QFrame(widget); + separator->setFrameStyle(QFrame::HLine | QFrame::Plain); + layout->addWidget(separator); + layout->addStretch(2); + layout->addLayout(layoutEditRight); + layout->addStretch(2); + + // Context + m_internal->m_cbContext_MIL2525D = new EnumComboBox(widget); + m_internal->m_cbContext_MIL2525D->insertItems(AdvancedSymbology::SidcDefinitions::contexts()); + + connect(m_internal->m_cbContext_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onContextChanged); + + // Identity + m_internal->m_cbIdentity_MIL2525D = new EnumComboBox(widget); + m_internal->m_cbIdentity_MIL2525D->insertItems(AdvancedSymbology::SidcDefinitions::identities()); + + connect(m_internal->m_cbIdentity_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onIdentityChanged); + + // Status + m_internal->m_cbStatus_MIL2525D = new EnumComboBox(widget); + m_internal->m_cbStatus_MIL2525D->insertItems(AdvancedSymbology::SidcDefinitions::statuss()); + + connect(m_internal->m_cbStatus_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onStatusChanged); + + // HQTFDummy + m_internal->m_cbHQTFDummy_MIL2525D = new EnumComboBox(widget); + m_internal->m_cbHQTFDummy_MIL2525D->insertItems(AdvancedSymbology::SidcDefinitions::hQTFDummy()); + + connect(m_internal->m_cbHQTFDummy_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onHQTFDummyChanged); + + layoutEditLeft->setHorizontalSpacing(10); + layoutEditLeft->setLabelAlignment(Qt::AlignRight); + + layoutEditLeft->addRow(tr("Context"), m_internal->m_cbContext_MIL2525D); + layoutEditLeft->addRow(tr("Identity"), m_internal->m_cbIdentity_MIL2525D); + layoutEditLeft->addRow(tr("Status"), m_internal->m_cbStatus_MIL2525D); + layoutEditLeft->addRow(tr("HqTfDummy"), m_internal->m_cbHQTFDummy_MIL2525D); + + // AmplifierGroup + m_internal->m_cbAmplifierGroup_MIL2525D = new EnumComboBox(widget); + m_internal->m_cbAmplifierGroup_MIL2525D->insertItems(AdvancedSymbology::SidcDefinitions::amplifierGroups()); + + connect(m_internal->m_cbAmplifierGroup_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onAmplifierGroupChanged, Qt::DirectConnection); // as soon as possible!!! + + // AmplifierDescriptor + m_internal->m_cbAmplifierDescriptor_MIL2525D = new EnumComboBox(widget); + // to fill->use slot of primary ComboBox: + onAmplifierGroupChanged(); + + connect(m_internal->m_cbAmplifierDescriptor_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onAmplifierDesriptorChanged); + + // Sector1Modifier + m_internal->m_cbSector1Modifier_MIL2525D = new EnumComboBox(widget); + connect(m_internal->m_cbSector1Modifier_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onSector1ModifierChanged); + + // Sector2Modifier + m_internal->m_cbSector2Modifier_MIL2525D = new EnumComboBox(widget); + connect(m_internal->m_cbSector2Modifier_MIL2525D + , static_cast(&QComboBox::currentIndexChanged) + , this + , &SymbolModificationWidget::onSector2ModifierChanged); + + + layoutEditRight->setHorizontalSpacing(10); + layoutEditRight->setLabelAlignment(Qt::AlignRight); + + layoutEditRight->addRow(tr("AmplifierGroup"), m_internal->m_cbAmplifierGroup_MIL2525D); + layoutEditRight->addRow(tr("AmplifierDescriptor"), m_internal->m_cbAmplifierDescriptor_MIL2525D); + separator = new QFrame(widget); + separator->setFrameStyle(QFrame::HLine | QFrame::Plain); + layoutEditRight->addRow(separator); + layoutEditRight->addRow( tr("Sector 1 Modifier"), m_internal->m_cbSector1Modifier_MIL2525D ); + layoutEditRight->addRow( tr("Sector 2 Modifier"), m_internal->m_cbSector2Modifier_MIL2525D ); + +} // void SymbolModificationWidget::buildSidcModifierTab_MIL2525D( QWidget * widget ) + + +void SymbolModificationWidget::buildSidcModifierTab_APP6(QWidget * widget) +{ + QFormLayout *layout = new QFormLayout(widget); + + // Identity + m_internal->m_cbIdentity_APP6 = new EnumComboBox(widget); + m_internal->m_cbIdentity_APP6->insertItems(AdvancedSymbology::SidcDefinitions::identities()); + + m_internal->m_cbStatus_APP6 = new MyComboBox(widget); + m_internal->m_cbOrderOfBattle_APP6 = new MyComboBox(widget); + m_internal->m_cbSize_APP6 = new MyComboBox(widget); + m_internal->m_cbSizeHQ_APP6 = new MyComboBox(widget); + m_internal->m_cbSizeTF_APP6 = new MyComboBox(widget); + m_internal->m_cbSizeFDHQ_APP6 = new MyComboBox(widget); + m_internal->m_cbSizeFDTF_APP6 = new MyComboBox(widget); + m_internal->m_cbSizeFD_APP6 = new MyComboBox(widget); + m_internal->m_cbMobility_APP6 = new MyComboBox(widget); + + // Size/Mobility + // Zwei abhaengige ComboBoxen + m_internal->m_cbStatus_APP6->addItem(tr("Anticipated/Planned"), QVariant(QLatin1Char('A'))); + m_internal->m_cbStatus_APP6->addItem(tr("Present") , QVariant(QLatin1Char('P'))); + + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("None") , QVariant(QLatin1Char('*'))); + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("Air OB") , QVariant(QLatin1Char('A'))); + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("Electronic OB") , QVariant(QLatin1Char('E'))); + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("Civilian OB") , QVariant(QLatin1Char('C'))); + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("Ground OB") , QVariant(QLatin1Char('G'))); + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("Maritime OB") , QVariant(QLatin1Char('M'))); + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("Strategic Force Related"), QVariant(QLatin1Char('S'))); + m_internal->m_cbOrderOfBattle_APP6->addItem(tr("Control Markings") , QVariant(QLatin1Char('X'))); + + m_internal->m_cbSize_APP6->addItem(tr("None") , QStringLiteral("--")); + m_internal->m_cbSize_APP6->addItem(tr("Team/Crew") , QStringLiteral("_A")); + m_internal->m_cbSize_APP6->addItem(tr("Squad") , QStringLiteral("_B")); + m_internal->m_cbSize_APP6->addItem(tr("Platoon") , QStringLiteral("_C")); + m_internal->m_cbSize_APP6->addItem(tr("Company") , QStringLiteral("_D")); + m_internal->m_cbSize_APP6->addItem(tr("Battalion") , QStringLiteral("_E")); + m_internal->m_cbSize_APP6->addItem(tr("Regiment/Group") , QStringLiteral("_F")); + m_internal->m_cbSize_APP6->addItem(tr("Brigade") , QStringLiteral("_H")); + m_internal->m_cbSize_APP6->addItem(tr("Division") , QStringLiteral("_I")); + m_internal->m_cbSize_APP6->addItem(tr("Corps/Mef") , QStringLiteral("_J")); + m_internal->m_cbSize_APP6->addItem(tr("Army") , QStringLiteral("_K")); + m_internal->m_cbSize_APP6->addItem(tr("Army Group/Front"), QStringLiteral("_L")); + m_internal->m_cbSize_APP6->addItem(tr("Region") , QStringLiteral("-M")); + + m_internal->m_cbSizeHQ_APP6->addItem(tr("None") , QStringLiteral("--")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("Headquarters (HQ)"), QStringLiteral("A-")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Team/Crew") , QStringLiteral("AA")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Squad") , QStringLiteral("AB")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Section") , QStringLiteral("AC")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Platoon") , QStringLiteral("AD")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Company") , QStringLiteral("AE")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Battalion") , QStringLiteral("AF")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Regiment/Group"), QStringLiteral("AG")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Brigade") , QStringLiteral("AH")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Division") , QStringLiteral("AI")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Corp/MEF") , QStringLiteral("AJ")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Army") , QStringLiteral("AK")); + m_internal->m_cbSizeHQ_APP6->addItem(tr("HQ Region") , QStringLiteral("AM")); + + m_internal->m_cbSizeTF_APP6->addItem(tr("None") , QStringLiteral("--")); + m_internal->m_cbSizeTF_APP6->addItem(tr("Task Force (TF) HQ"), QStringLiteral("B-")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Team/Crew") , QStringLiteral("BA")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Squad") , QStringLiteral("BB")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Section") , QStringLiteral("BC")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Platoon") , QStringLiteral("BD")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Company") , QStringLiteral("BE")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Battalion") , QStringLiteral("BF")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Regiment/Group") , QStringLiteral("BG")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Brigade") , QStringLiteral("BH")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Division") , QStringLiteral("BI")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Corp/MEF") , QStringLiteral("BJ")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Army") , QStringLiteral("BK")); + m_internal->m_cbSizeTF_APP6->addItem(tr("TF Region") , QStringLiteral("BM")); + + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("None") , QStringLiteral("--")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("Feint Dummy (FD) HQ") , QStringLiteral("C-")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Team/Crew") , QStringLiteral("CA")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Squad") , QStringLiteral("CB")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Section") , QStringLiteral("CC")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Platoon)") , QStringLiteral("CD")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Company") , QStringLiteral("CE")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Battalion") , QStringLiteral("CF")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Regiment/Group"), QStringLiteral("CG")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Brigade") , QStringLiteral("CH")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Division") , QStringLiteral("CI")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Corp/MEF") , QStringLiteral("CJ")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Army") , QStringLiteral("CK")); + m_internal->m_cbSizeFDHQ_APP6->addItem(tr("FD HQ Region") , QStringLiteral("CM")); + + m_internal->m_cbSizeFDTF_APP6->addItem(tr("None") , QStringLiteral("--")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("Feint Dummy (FD/TF) HQ") , QStringLiteral("D-")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Team/Crew") , QStringLiteral("DA")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Squad") , QStringLiteral("DB")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Section") , QStringLiteral("DC")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Platoon)") , QStringLiteral("DD")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Company") , QStringLiteral("DE")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Battalion") , QStringLiteral("DF")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Regiment/Group"), QStringLiteral("DG")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Brigade") , QStringLiteral("DH")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Division") , QStringLiteral("DI")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Corp/MEF") , QStringLiteral("DJ")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Army") , QStringLiteral("DK")); + m_internal->m_cbSizeFDTF_APP6->addItem(tr("FD/TF HQ Region") , QStringLiteral("DM")); + + + m_internal->m_cbSizeFD_APP6->addItem(tr("None") , QStringLiteral("--")); + m_internal->m_cbSizeFD_APP6->addItem(tr("Feint Dummy (FD)") , QStringLiteral("F-")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Team/Crew") , QStringLiteral("FA")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Squad") , QStringLiteral("FB")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Section") , QStringLiteral("FC")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Platoon)") , QStringLiteral("FD")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Company") , QStringLiteral("FE")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Battalion") , QStringLiteral("FF")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Regiment/Group"), QStringLiteral("FG")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Brigade") , QStringLiteral("FH")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Division") , QStringLiteral("FI")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Corp/MEF") , QStringLiteral("FJ")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Army") , QStringLiteral("FK")); + m_internal->m_cbSizeFD_APP6->addItem(tr("FD Region") , QStringLiteral("FM")); + + + m_internal->m_cbMobility_APP6->addItem(tr("None") , QStringLiteral("--")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Equipment") , QStringLiteral("M-")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Fixed Wing") , QStringLiteral("MF")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Helicopter") , QStringLiteral("MH")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Missile") , QStringLiteral("MM")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Wheeled / Limited Cross Country") , QStringLiteral("MO")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Cross Country") , QStringLiteral("MP")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Wheeled and Tracked Combination") , QStringLiteral("MR")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Towed") , QStringLiteral("MS")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Rail") , QStringLiteral("MT")); + m_internal->m_cbMobility_APP6->addItem(tr("Mobility Space") , QStringLiteral("S-")); + + layout->addRow(tr("APP6 Affiliation") , m_internal->m_cbIdentity_APP6); + layout->addRow(tr("APP6 Size") , m_internal->m_cbSize_APP6); + layout->addRow(tr("APP6 Headquarters (HQ)") , m_internal->m_cbSizeHQ_APP6); + layout->addRow(tr("APP6 Task Force (TF) HQ") , m_internal->m_cbSizeTF_APP6); + layout->addRow(tr("APP6 Feint Dummy (FD) HQ") , m_internal->m_cbSizeFDHQ_APP6); + layout->addRow(tr("APP6 Feint Dummy (FD/TF) HQ"), m_internal->m_cbSizeFDTF_APP6); + layout->addRow(tr("APP6 Feint Dummy (FD)") , m_internal->m_cbSizeFD_APP6); + layout->addRow(tr("Mobility") , m_internal->m_cbMobility_APP6); + layout->addRow(tr("APP6 Status") , m_internal->m_cbStatus_APP6); + layout->addRow(tr("APP6 Order Of Battle") , m_internal->m_cbOrderOfBattle_APP6); + + connect( m_internal->m_cbIdentity_APP6, static_cast(&QComboBox::activated), this, [this](int) + { + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + QChar code = QLatin1Char('*'); + + switch (m_internal->m_cbIdentity_APP6->currentValue()) + { + case SidcDefinitions::Identity::Pending : code = QLatin1Char('P'); break; + case SidcDefinitions::Identity::Unknown : code = QLatin1Char('U'); break; + case SidcDefinitions::Identity::AssumedFriend : code = QLatin1Char('A'); break; + case SidcDefinitions::Identity::Friend : code = QLatin1Char('F'); break; + case SidcDefinitions::Identity::Neutral : code = QLatin1Char('N'); break; + case SidcDefinitions::Identity::SuspectJoker : code = QLatin1Char('S'); break; + case SidcDefinitions::Identity::HostileFaker : code = QLatin1Char('H'); break; + } + + QString rawSidc = SidcCodec::decodeRawSidc(modifiedSidc()); + rawSidc[1] = code; + m_internal->m_modifiedSidc = SidcCodec::encodeComplete(SymbologyType::APP6B, rawSidc); + + emitChanged(); + }); + + + connect(m_internal->m_cbSize_APP6 , static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onChangeApp6SizeMobility); + connect(m_internal->m_cbSizeHQ_APP6 , static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onChangeApp6SizeMobility); + connect(m_internal->m_cbSizeTF_APP6 , static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onChangeApp6SizeMobility); + connect(m_internal->m_cbSizeFDHQ_APP6, static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onChangeApp6SizeMobility); + connect(m_internal->m_cbSizeFDTF_APP6, static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onChangeApp6SizeMobility); + connect(m_internal->m_cbSizeFD_APP6 , static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onChangeApp6SizeMobility); + connect(m_internal->m_cbMobility_APP6, static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onChangeApp6SizeMobility); + + + connect(m_internal->m_cbStatus_APP6, static_cast(&QComboBox::activated), this, [this]() + { + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + QChar code = m_internal->m_cbStatus_APP6->currentData().toChar(); + + QString rawSidc = SidcCodec::decodeRawSidc(modifiedSidc()); + rawSidc[3] = code; + m_internal->m_modifiedSidc = SidcCodec::encodeComplete(SymbologyType::APP6B, rawSidc); + + emitChanged(); + }); + + connect(m_internal->m_cbOrderOfBattle_APP6, static_cast(&QComboBox::activated), this, [this]() + { + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + QChar code = m_internal->m_cbOrderOfBattle_APP6->currentData().toChar(); + + QString rawSidc = SidcCodec::decodeRawSidc(modifiedSidc()); + rawSidc[14] = code; + m_internal->m_modifiedSidc = SidcCodec::encodeComplete(SymbologyType::APP6B, rawSidc); + + emitChanged(); + }); +} // void SymbolModificationWidget::buildSidcModifierTab_APP6(QWidget * widget) + + +void SymbolModificationWidget::buildFieldModifierTab(QWidget *widget) +{ + QVBoxLayout *layout = new QVBoxLayout(widget); + QHBoxLayout *layoutFieldEdit = new QHBoxLayout(); + + m_internal->m_cbFieldTitle = new MyComboBox(widget); + m_internal->m_leFieldValue = new QLineEdit(widget); + QPushButton *pbAdd = new QPushButton( QIcon(QStringLiteral("icon:add.svg")), QString(), widget ); + QPushButton *pbDel = new QPushButton( QIcon(QStringLiteral("icon:remove.svg")), QString(), widget ); + + layoutFieldEdit->addWidget(new QLabel(tr("Field"), widget)); + layoutFieldEdit->addWidget(m_internal->m_cbFieldTitle, 2); + layoutFieldEdit->addWidget(new QLabel(tr("Value"), widget)); + layoutFieldEdit->addWidget(m_internal->m_leFieldValue, 3); + layoutFieldEdit->addWidget(pbAdd); + layoutFieldEdit->addWidget(pbDel); + + m_internal->m_fieldTableModel = new FieldTableModel(widget); + + m_internal->m_fieldTable = new QTableView(widget); + m_internal->m_fieldTable->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows); + m_internal->m_fieldTable->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_internal->m_fieldTable->setModel(m_internal->m_fieldTableModel); + m_internal->m_fieldTable->setAlternatingRowColors(true); + m_internal->m_fieldTable->setShowGrid(false); + + QHeaderView *header = m_internal->m_fieldTable->horizontalHeader(); + + header->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch); + header->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch); + + layout->addLayout(layoutFieldEdit); + layout->addWidget(m_internal->m_fieldTable, 2); + + connect(pbAdd, &QPushButton::clicked, this, &SymbolModificationWidget::onFieldAttributeAdd); + connect(pbDel, &QPushButton::clicked, this, &SymbolModificationWidget::onFieldAttributeRemove); + + connect(m_internal->m_leFieldValue, &QLineEdit::returnPressed, this, [this]() { onFieldAttributeAdd(true); }); + connect(m_internal->m_fieldTable, &QTableView::clicked, this, &SymbolModificationWidget::onFieldItemActived); + connect(m_internal->m_fieldTable, &QTableView::activated, this, &SymbolModificationWidget::onFieldItemActived); + + connect(m_internal->m_cbFieldTitle, static_cast(&QComboBox::activated), this, &SymbolModificationWidget::onSetFieldTitle); + +} // void SymbolModificationWidget::buildFieldModifierTab( QWidget * widget ) + + +void SymbolModificationWidget::emitChanged() +{ + emit changed(); +} // void SymbolModificationWidget::emitChanged() + + +void SymbolModificationWidget::buildPreview(QWidget *widget) +{ + delete m_previewTab; + + m_previewTab = new PreviewTab(widget); + + m_previewTab->lbBaseSymbolName->setAlignment(Qt::AlignCenter); + + m_previewTab->lbSidcOutImg->setAlignment(Qt::AlignCenter); + m_previewTab->lbSidcInImg ->setAlignment(Qt::AlignCenter); + + m_previewTab->layout->addWidget(m_previewTab->lbBaseSymbolName); + m_previewTab->layout->addLayout(m_previewTab->layoutPreview, 2); + m_previewTab->layout->addLayout(m_previewTab->layoutPreview); + + m_previewTab->layoutPreview->setHorizontalSpacing(10); + m_previewTab->layoutPreview->setVerticalSpacing(10); + m_previewTab->layoutPreview->setLabelAlignment(Qt::AlignRight); + + m_previewTab->layoutPreview->addRow(tr("Base Symbol Code"), m_previewTab->lbBaseSymbolId); + + + QFrame *separator = new QFrame(widget); + separator->setFrameStyle(QFrame::HLine | QFrame::Plain); + m_previewTab->layoutPreview->addRow(separator); + m_previewTab->layoutPreview->addRow(tr("Modified SIDC"), m_previewTab->lbSidcOutTxt); + m_previewTab->layoutPreview->addWidget(m_previewTab->lbSidcOutImg); + + separator = new QFrame(widget); + separator->setFrameStyle(QFrame::HLine | QFrame::Plain); + m_previewTab->layoutPreview->addRow(separator); + m_previewTab->layoutPreview->addRow(tr("Original SIDC"), m_previewTab->lbSidcInTxt); + m_previewTab->layoutPreview->addWidget(m_previewTab->lbSidcInImg); + + connect(this, &SymbolModificationWidget::changed, + this, &SymbolModificationWidget::updatePreviewTab); + +} // void SymbolModificationWidget::buildPreview(QWidget * widget) + +void SymbolModificationWidget::updatePreviewTab() +{ + if (!m_previewTab) + return; + + const QString &originalSidc = this->originalSidc(); + const QString &modifiedSidc = this->modifiedSidc(); + + if (originalSidc.isEmpty()) + return; + + m_previewTab->lbBaseSymbolId ->setText(SidcCodec::decodeRawSidc(m_internal->m_baseSymbolCode)); + m_previewTab->lbBaseSymbolName->setText(this->symbolName()); + + m_previewTab->lbSidcInTxt ->setText(SidcCodec::decodeRawSidc(originalSidc)); + m_previewTab->lbSidcOutTxt->setText(SidcCodec::decodeRawSidc(modifiedSidc)); + + setSymbolPixmap(m_previewTab->lbSidcInImg, originalSidc); + if (originalSidc == modifiedSidc) + m_previewTab->lbSidcOutImg->setPixmap(QPixmap()); + else + setSymbolPixmap(m_previewTab->lbSidcOutImg, modifiedSidc); +} + +SymbolModificationWidget::~SymbolModificationWidget() +{ + delete m_internal; + m_internal = nullptr; +} + + +static void fillUpComboBox(const AdvancedSymbology::SymbolDictionary &dict, const QString &baseSymbolCode, unsigned int sector, QComboBox *cb) +{ + cb->clear(); + + cb->addItem(QIcon(), QStringLiteral("0 Not Applicable"), 0); + const QList &modifierList = dict.symbolModifierList(baseSymbolCode, sector); + for (const auto &modifier : modifierList) + { + const QString &modName = QString::number(modifier.m_modifierValue) + QLatin1Char(' ') + modifier.m_modifierName; + cb->addItem(QIcon(modifier.m_modifierIcon), modName, modifier.m_modifierValue); + } +} // static void fillUpComboBox(const QString & baseSymbolCode, unsigned int sector, QComboBox * cb) + + +SymbologyType SymbolModificationWidget::symbologyType() const +{ + return m_internal->m_symbologyType; +} // SymbologyType SymbolModificationWidget::symbologyType() const + + +bool SymbolModificationWidget::setSidc(const QString &sidc) +{ + if (sidc.isEmpty()) + return false; + + const AdvancedSymbology::SymbolDictionary &dict = m_internal->m_dict; + + if (!m_internal->m_sidcBuilder.setSidc(sidc)) + { + m_internal->m_tabWidget->setEnabled(false); + return false; + } + + m_internal->m_symbologyType = SidcCodec::decodeSymbologyType(sidc); + + switch (m_internal->m_symbologyType) + { + case SymbologyType::None : return false; + + case SymbologyType::APP6B : m_internal->m_tabSidcEdit->setCurrentIndex(static_cast(SymbologyTypeStackIndex::APP6)); break; + + case SymbologyType::APP6D : + case SymbologyType::MIL2525C : + case SymbologyType::MIL2525D : m_internal->m_tabSidcEdit->setCurrentIndex(static_cast(SymbologyTypeStackIndex::MIL2525D)); break; + } + + + m_internal->m_tabWidget->setEnabled(true); + + m_internal->m_originalSidc = m_internal->m_sidcBuilder.toSidc(); + m_internal->m_lastModifiedSidc = m_internal->m_sidcBuilder.toSidc(); + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + + if (SidcCodec::isCivil(sidc)) + { + m_internal->m_tabSidcEdit ->setEnabled(false); + m_internal->m_tabFieldEdit->setEnabled(false); + m_internal->m_tabPreview ->setEnabled(false); + + m_internal->m_tabSidcEdit ->hide(); + m_internal->m_tabFieldEdit->hide(); + m_internal->m_tabPreview ->hide(); + + m_internal->m_tabWidget->removeTab(m_internal->m_tabWidget->indexOf(m_internal->m_tabSidcEdit)); + m_internal->m_tabWidget->removeTab(m_internal->m_tabWidget->indexOf(m_internal->m_tabFieldEdit)); + m_internal->m_tabWidget->removeTab(m_internal->m_tabWidget->indexOf(m_internal->m_tabPreview)); + + return false; // ToDo: implement buildParticipantTab and return true here + } + + m_internal->m_tabWidget->addTab(m_internal->m_tabSidcEdit, tr("Sector A and B Attributes")); + + m_internal->m_tabSidcEdit ->setEnabled(true); + m_internal->m_tabFieldEdit->setEnabled(true); + + if (m_internal->m_isParticipant) + { + m_internal->m_tabWidget->removeTab(m_internal->m_tabWidget->indexOf(m_internal->m_tabFieldEdit)); + + m_internal->m_tabFieldEdit->setEnabled(false); + m_internal->m_tabFieldEdit->hide(); + } + else + { + m_internal->m_tabWidget->addTab(m_internal->m_tabFieldEdit, tr("Field Modifier")); + m_internal->m_tabFieldEdit->setEnabled(true); + } + + #ifdef LTS_DEBUG + m_internal->m_tabWidget->addTab(m_internal->m_tabPreview, tr("Preview")); + m_internal->m_tabPreview->setEnabled(true); + #endif + + const QString &baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(sidc); + const QString &baseSymbolKey = SidcCodec::decodeRawSidc(baseSymbolCode); + + + fillUpComboBox(dict, baseSymbolKey, 1, m_internal->m_cbSector1Modifier_MIL2525D); + fillUpComboBox(dict, baseSymbolKey, 2, m_internal->m_cbSector2Modifier_MIL2525D); + + m_internal->m_cbSector1Modifier_MIL2525D->setEnabled((m_internal->m_cbSector1Modifier_MIL2525D->count() > 0)); + m_internal->m_cbSector2Modifier_MIL2525D->setEnabled((m_internal->m_cbSector2Modifier_MIL2525D->count() > 0)); + + m_internal->m_baseSymbolCode = baseSymbolCode; + + if (!analyze(m_internal->m_symbologyType,sidc)) + return false; + + const AdvancedSymbology::SymbolProperties &symbolProps = dict.symbolProperties(baseSymbolCode); + + if (symbolProps.geometryType() != AdvancedSymbology::GeometryType::Point) + { + const QString &widgetName = m_internal->m_tabPreview->objectName(); + m_internal->m_tabPreview->setStyleSheet(QLatin1String("QWidget#") + widgetName + QLatin1String(" { background-color: lightgrey }")); + } + else + m_internal->m_tabPreview->setStyleSheet(QString()); + + QStringList fieldKeys = dict.symbologyAttributeKeyList(); + + fieldKeys.sort(); + + m_internal->m_cbFieldTitle->clear(); + for (const auto &fieldKey : std::as_const(fieldKeys)) + m_internal->m_cbFieldTitle->addItem(fieldKey); + + if (m_internal->m_tabWidget->isEnabled()) + emitChanged(); + + return true; +} // bool SymbolModificationWidget::setSidc( const QString & sidc ) + + +const QMap SymbolModificationWidget::fieldAttributes() +{ + return m_internal->m_fieldTableModel->fieldAttrMap(); +} // const QMap SymbolModificationWidget::fieldAttributes() + + +bool SymbolModificationWidget::setFieldAttributes(const QMap &fieldAttrMap) +{ + return m_internal->m_fieldTableModel->setFieldAttrMap(fieldAttrMap); +} // bool SymbolModificationWidget::setFieldAttributes(const QString & symbolFieldAttr) + + +bool SymbolModificationWidget::analyze(SymbologyType symbologyType, const QString &sidc) +{ + if (!m_internal->m_tabWidget->isEnabled()) return false; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + m_internal->m_sidcBuilder.setSidc(sidc); + + switch (symbologyType) + { + case SymbologyType::None : break; + case SymbologyType::APP6B : break; + case SymbologyType::MIL2525C: break; + + case SymbologyType::APP6D : + case SymbologyType::MIL2525D: + { + m_internal->m_cbContext_MIL2525D->setCurrent(m_internal->m_sidcBuilder.standardIdentity().context); + m_internal->m_cbIdentity_MIL2525D->setCurrent(m_internal->m_sidcBuilder.standardIdentity().identity); + + m_internal->m_cbStatus_MIL2525D->setCurrent(m_internal->m_sidcBuilder.status()); + + m_internal->m_cbHQTFDummy_MIL2525D->setCurrent(m_internal->m_sidcBuilder.hqTfFd()); + + m_internal->m_cbAmplifierGroup_MIL2525D->setCurrent(m_internal->m_sidcBuilder.amplifierGroup()); + + int idxAmpDescr = m_internal->m_cbAmplifierDescriptor_MIL2525D->findData(m_internal->m_sidcBuilder.amplifier2ndDigit()); + + m_internal->m_cbAmplifierDescriptor_MIL2525D->setCurrentIndex(idxAmpDescr); + + int idxMod1 = m_internal->m_cbSector1Modifier_MIL2525D->findData(m_internal->m_sidcBuilder.sectorModifier1Digit()); + int idxMod2 = m_internal->m_cbSector2Modifier_MIL2525D->findData(m_internal->m_sidcBuilder.sectorModifier2Digit()); + + m_internal->m_cbSector1Modifier_MIL2525D->setCurrentIndex(idxMod1); + m_internal->m_cbSector2Modifier_MIL2525D->setCurrentIndex(idxMod2); + } break; + } + + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + + emitChanged(); // to update previewIcon (sidcNew) + + return true; // (m_internal->m_sidcDelta.toSidc() == sidc); +} // bool SymbolModificationWidget::setSidc( const QString & sidc ) + + +QString SymbolModificationWidget::originalSidc() const { return m_internal->m_originalSidc; } +QString SymbolModificationWidget::modifiedSidc() const { return m_internal->m_modifiedSidc; } +QString SymbolModificationWidget::lastModifiedSidc() const { return m_internal->m_lastModifiedSidc; } + + +QString SymbolModificationWidget::symbolName() const +{ + const QString &baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(originalSidc()); + const AdvancedSymbology::SymbolDictionary &dict = m_internal->m_dict; + const AdvancedSymbology::SymbolProperties &symbolProps = dict.symbolProperties(baseSymbolCode); + return symbolProps.name(); +} // QString SymbolModificationWidget::symbolName() const + + +void SymbolModificationWidget::setSymbolPixmap(QLabel *label, const QString &sidc) +{ + label->setPixmap(QPixmap()); // first clear pixmap on label + + AdvancedSymbology::SymbolResultWatcher *symbolGuard = new AdvancedSymbology::SymbolResultWatcher(this); + + QObject::connect(symbolGuard, &AdvancedSymbology::SymbolResultWatcher::resultReady, this, [label, symbolGuard]() + { + QSharedPointer symbolResult = symbolGuard->symbolResult(); + if (symbolResult) + { + QPixmap symbolPixmap = QPixmap::fromImage(symbolResult->symbolImage().image()); + + if (symbolPixmap.width() > 200) symbolPixmap = symbolPixmap.scaledToWidth(200, Qt::TransformationMode::SmoothTransformation); + if (symbolPixmap.height() > 100) symbolPixmap = symbolPixmap.scaledToHeight(100, Qt::TransformationMode::SmoothTransformation); + + label->setPixmap(symbolPixmap); + } + }); + + const AdvancedSymbology::SymbolDataToRender symDataToRender(sidc, 200); + + symbolGuard->setSymbolResult(m_internal->m_dict.findImage(symDataToRender)); + +} // void SymbolModificationWidget::setSymbolPixmap( QLabel * label, const QString & sidc ) + + +void SymbolModificationWidget::onRevert() +{ + const QString &restoreSidc = originalSidc(); + analyze(SidcCodec::decodeSymbologyType(restoreSidc),restoreSidc); +} // void SymbolModificationWidget::onRevert() + + +void SymbolModificationWidget::onContextChanged() +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + AdvancedSymbology::SidcDefinitions::StandardIdentity si = m_internal->m_sidcBuilder.standardIdentity(); + si.context = m_internal->m_cbContext_MIL2525D->currentValue(); + m_internal->m_sidcBuilder.setStandardIdentity(si); + + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + + emitChanged(); +} // void SymbolModificationWidget::onContextChanged() + + +void SymbolModificationWidget::onIdentityChanged() +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + AdvancedSymbology::SidcDefinitions::StandardIdentity si = m_internal->m_sidcBuilder.standardIdentity(); + si.identity = m_internal->m_cbIdentity_MIL2525D->currentValue(); + m_internal->m_sidcBuilder.setStandardIdentity(si); + + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + + emitChanged(); +} // void SymbolModificationWidget::onIdentityChanged() + + +void SymbolModificationWidget::onSymbolSetChanged() +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + m_internal->m_sidcBuilder.setSymbolSet(m_internal->m_cbSymbolSet_MIL2525D->currentValue()); + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + emitChanged(); +} // void SymbolModificationWidget::onSymbolSetChanged() + + +void SymbolModificationWidget::onStatusChanged() +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + m_internal->m_sidcBuilder.setStatus(m_internal->m_cbStatus_MIL2525D->currentValue()); + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + emitChanged(); +} // void SymbolModificationWidget::onStatusChanged() + + +void SymbolModificationWidget::onHQTFDummyChanged() +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + m_internal->m_sidcBuilder.setHqTfFd(m_internal->m_cbHQTFDummy_MIL2525D->currentValue()); + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + emitChanged(); +} // void SymbolModificationWidget::onHQTFDummyChanged() + + +void SymbolModificationWidget::onAmplifierGroupChanged() +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + const AdvancedSymbology::SidcDefinitions::AmplifierGroup &lifierGroup = m_internal->m_cbAmplifierGroup_MIL2525D->currentValue(); + + // First, renew the secondary comboBox!!! + // Remember, this is of type QComboBox + m_internal->m_cbAmplifierDescriptor_MIL2525D->clear(); + + + m_internal->m_cbAmplifierDescriptor_MIL2525D->blockSignals(true); + { + int idx = 0; + + switch (amplifierGroup) + { + case AdvancedSymbology::SidcDefinitions::AmplifierGroup::Unknown: break; + case AdvancedSymbology::SidcDefinitions::AmplifierGroup::Echelon_BrigadeAndBelow: + { + const QVector &items = AdvancedSymbology::SidcDefinitions::echelonBrigadeAndBelows(); + for (const auto &item : items) + m_internal->m_cbAmplifierDescriptor_MIL2525D->insertNumberedItem(idx++, QString(AdvancedSymbology::SidcDefinitions::toString(item)), static_cast(item)); + } + break; + + case AdvancedSymbology::SidcDefinitions::AmplifierGroup::Echelon_DivisionAndAbove: + { + const QVector &items = AdvancedSymbology::SidcDefinitions::echelonDivisionAndAboves(); + for (const auto &item : items) + m_internal->m_cbAmplifierDescriptor_MIL2525D->insertNumberedItem(idx++, AdvancedSymbology::SidcDefinitions::toString(item), static_cast(item)); + } + break; + + case AdvancedSymbology::SidcDefinitions::AmplifierGroup::Mobility_OnLand: + { + const QVector &items = AdvancedSymbology::SidcDefinitions::mobilityOnLands(); + for (const auto &item : items) + m_internal->m_cbAmplifierDescriptor_MIL2525D->insertNumberedItem(idx++, AdvancedSymbology::SidcDefinitions::toString(item), static_cast(item)); + } + break; + + case AdvancedSymbology::SidcDefinitions::AmplifierGroup::Mobility_OnWater: + { + const QVector &items = AdvancedSymbology::SidcDefinitions::mobilityOnWaters(); + for (const auto &item : items) + m_internal->m_cbAmplifierDescriptor_MIL2525D->insertNumberedItem(idx++, AdvancedSymbology::SidcDefinitions::toString(item), static_cast(item)); + } + break; + + case AdvancedSymbology::SidcDefinitions::AmplifierGroup::Mobility_OnSnow: + { + const QVector &items = AdvancedSymbology::SidcDefinitions::mobilityOnSnows(); + for (const auto &item : items) + m_internal->m_cbAmplifierDescriptor_MIL2525D->insertNumberedItem(idx++, AdvancedSymbology::SidcDefinitions::toString(item), static_cast(item)); + } + break; + + case AdvancedSymbology::SidcDefinitions::AmplifierGroup::Naval_TowedArry: + { + const QVector &items = AdvancedSymbology::SidcDefinitions::navalTowedArrays(); + for (const auto &item : items) + m_internal->m_cbAmplifierDescriptor_MIL2525D->insertNumberedItem(idx++, AdvancedSymbology::SidcDefinitions::toString(item), static_cast(item)); + } + break; + + } // switch + } + m_internal->m_cbAmplifierDescriptor_MIL2525D->blockSignals(false); + + + m_internal->m_sidcBuilder.setAmplifierGroup(amplifierGroup); + + if (m_internal->m_cbAmplifierDescriptor_MIL2525D->count() > 0) + { + m_internal->m_cbAmplifierDescriptor_MIL2525D->setEnabled(true); + + unsigned int ampDescr = m_internal->m_sidcBuilder.amplifier2ndDigit(); + int index = m_internal->m_cbAmplifierDescriptor_MIL2525D->findData(ampDescr); + + m_internal->m_cbAmplifierDescriptor_MIL2525D->setCurrentIndex(index); + } + else m_internal->m_cbAmplifierDescriptor_MIL2525D->setEnabled(false); + + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + + emitChanged(); + +} // void SymbolModificationWidget::onAmplifierGroupChanged() + + +void SymbolModificationWidget::onAmplifierDesriptorChanged(int index) +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + unsigned int ampDescr = m_internal->m_cbAmplifierDescriptor_MIL2525D->itemData(index).toUInt(); + m_internal->m_sidcBuilder.setAmplifier2ndDigit(ampDescr); + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + emitChanged(); +} // void SymbolModificationWidget::onAmplifierDesriptorChanged( int index ) + + +void SymbolModificationWidget::onSector1ModifierChanged(int index) +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + unsigned int sectorModifier = m_internal->m_cbSector1Modifier_MIL2525D->itemData(index).toUInt(); + m_internal->m_sidcBuilder.setSectorModifier1Digit(sectorModifier); + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + emitChanged(); +} // void SymbolModificationWidget::onSector1ModifierChanged( int ) + + +void SymbolModificationWidget::onSector2ModifierChanged(int index) +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + unsigned int sectorModifier = m_internal->m_cbSector2Modifier_MIL2525D->itemData(index).toUInt(); + m_internal->m_sidcBuilder.setSectorModifier2Digit(sectorModifier); + m_internal->m_modifiedSidc = m_internal->m_sidcBuilder.toSidc(); + emitChanged(); +} // void SymbolModificationWidget::onSector2ModifierChanged( int ) + + +void SymbolModificationWidget::onFieldAttributeAdd(bool) +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + const QString &value = m_internal->m_leFieldValue->text(); + + if (!value.isEmpty()) + { + const QString &key = m_internal->m_cbFieldTitle->currentText(); + m_internal->m_fieldTableModel->setFieldAttribute(key, value); + emitChanged(); + } + else onFieldAttributeRemove(true); +} // void SymbolModificationWidget::onFieldAttributeAdd() + + +void SymbolModificationWidget::onFieldAttributeRemove(bool) +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + const AdvancedSymbology::SymbolDictionary &dict = m_internal->m_dict; + const QString &key = m_internal->m_cbFieldTitle->currentText(); + + if (dict.symbologyAttributeKeyList().contains(key)) + { + m_internal->m_fieldTableModel->removeFieldAttrMap(key); + emitChanged(); + } +} // void PlanEditor::SymbolModificationWidget::onFieldAttributeRemove() + + +void SymbolModificationWidget::onSetFieldTitle(const QString &fieldTitel) +{ + const AdvancedSymbology::SymbolDictionary &dict = m_internal->m_dict; + + if (dict.symbologyAttributeKeyList().contains(fieldTitel)) + m_internal->m_leFieldValue->setText(m_internal->m_fieldTableModel->fieldAttribute(fieldTitel)); +}// void SymbolModificationWidget::onSetFieldTitle(const QString &) + + +void SymbolModificationWidget::onFieldItemActived(const QModelIndex &index) +{ + const AdvancedSymbology::SymbolDictionary &dict = m_internal->m_dict; + const QString key = m_internal->m_fieldTableModel->data(m_internal->m_fieldTableModel->index(index.row(), 0)).toString(); + const QString val = m_internal->m_fieldTableModel->data(m_internal->m_fieldTableModel->index(index.row(), 1)).toString(); + + if (dict.symbologyAttributeKeyList().contains(key)) + { + m_internal->m_leFieldValue->setText(val); + m_internal->m_cbFieldTitle->setCurrentText(key); + } +} // void SymbolModificationWidget::onFieldItemActived( const QModelIndex & index ) + + +void SymbolModificationWidget::onChangeApp6SizeMobility() +{ + if (!m_internal->m_tabWidget->isEnabled()) return; + + m_internal->m_lastModifiedSidc = modifiedSidc(); + + MyComboBox * cb = qobject_cast(sender()); + if (cb) + { + QString rawSidc = SidcCodec::decodeRawSidc(modifiedSidc()); + QString code = cb->currentData().toString(); + + rawSidc[10] = code[0]; + rawSidc[11] = code[1]; + + m_internal->m_modifiedSidc = SidcCodec::encodeComplete(SymbologyType::APP6B, rawSidc); + + if (cb != m_internal->m_cbSize_APP6) m_internal->m_cbSize_APP6->setCurrentIndex(0); + if (cb != m_internal->m_cbSizeHQ_APP6) m_internal->m_cbSizeHQ_APP6->setCurrentIndex(0); + if (cb != m_internal->m_cbSizeTF_APP6) m_internal->m_cbSizeTF_APP6->setCurrentIndex(0); + if (cb != m_internal->m_cbSizeFDHQ_APP6) m_internal->m_cbSizeFDHQ_APP6->setCurrentIndex(0); + if (cb != m_internal->m_cbSizeFDTF_APP6) m_internal->m_cbSizeFDTF_APP6->setCurrentIndex(0); + if (cb != m_internal->m_cbSizeFD_APP6) m_internal->m_cbSizeFD_APP6->setCurrentIndex(0); + if (cb != m_internal->m_cbMobility_APP6) m_internal->m_cbMobility_APP6->setCurrentIndex(0); + + emitChanged(); + } +} // void SymbolModificationWidget::onChangeApp6SizeMobility(int) + + +} // namespace Gui +} // namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.h new file mode 100755 index 000000000..9d3f86f00 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/SymbologyGui/symbol_modification_widget.h @@ -0,0 +1,135 @@ +/** +* @RDE_HEADER@ +* symbol_modification_wdiget for sidc configuration +* $Id$ +* $HeadURL$ +* $Author$ +* Created by Karwat, SLSE, 2017-09-07 +*/ + +#pragma once + +#include + + +#include +#include +#include +#include + +#include +#include +#include +#include + +class QLabel; +class QBoxLayout; + +namespace AdvancedSymbology +{ + +namespace Gui +{ + +class ADVANCEDSYMBOLOGYGUI_EXPORT SymbolModificationWidget : public QWidget +{ + Q_OBJECT + +public: + + explicit SymbolModificationWidget(SituationMap::Map *map, bool isParticipant, SymbolDictionary &symbolDictionary, QWidget *parent = nullptr); + ~SymbolModificationWidget(); + + bool setSidc(const QString &sidc); + bool setFieldAttributes(const QMap &fieldAttrMap); + + const QMap fieldAttributes(); + bool analyze(SymbologyType symbologyType, const QString &sidc); + QString originalSidc() const; + QString modifiedSidc() const; + QString lastModifiedSidc() const; + QString symbolName() const; + + SymbologyType symbologyType() const; + +protected: + + void retranslate(); + + void setSymbolPixmap(QLabel *, const QString &sidc); + + void buildSidcModifierTab_MIL2525D(QWidget *widget); + void buildSidcModifierTab_APP6(QWidget *widget); + + void buildFieldModifierTab(QWidget *widget); + void buildParticipantTab(QWidget *widget); + void buildPreview(QWidget *widget); + + void emitChanged(); + + class Internal; + + Internal *m_internal; + + struct PreviewTab + { + explicit PreviewTab(QWidget *widget) + : layout(new QVBoxLayout(widget)) + , layoutPreview(new QFormLayout()) + , lbBaseSymbolId(new QLabel(widget)) + , lbBaseSymbolName(new QLabel(widget)) + , lbSidcOutTxt(new QLabel(widget)) + , lbSidcInTxt(new QLabel(widget)) + , lbSidcOutImg(new QLabel(widget)) + , lbSidcInImg(new QLabel(widget)) + {} + QVBoxLayout *layout; + QFormLayout *layoutPreview; + + QLabel *lbBaseSymbolId; + QLabel *lbBaseSymbolName; + QLabel *lbSidcOutTxt; + QLabel *lbSidcInTxt; + QLabel *lbSidcOutImg; + QLabel *lbSidcInImg; + }; + PreviewTab *m_previewTab = nullptr; + + +signals: + + void changed(); + + +public Q_SLOTS: + + void updatePreviewTab(); + void onRevert(); + + +private Q_SLOTS: + + void onContextChanged(); + void onIdentityChanged(); + void onSymbolSetChanged(); + void onStatusChanged(); + + void onHQTFDummyChanged(); + + void onAmplifierGroupChanged(); + void onAmplifierDesriptorChanged(int); + + void onSector1ModifierChanged(int); + void onSector2ModifierChanged(int); + + void onFieldAttributeAdd(bool); + void onFieldAttributeRemove(bool); + + void onSetFieldTitle(const QString &); + void onFieldItemActived(const QModelIndex &); + + void onChangeApp6SizeMobility(); +}; +} //namespace Gui + +} //namespace AdvancedSymbology diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CMakeLists.txt new file mode 100755 index 000000000..f60146849 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +ADD_SUBDIRECTORY(SituationMap) + +IF(BUILD_SITMAP_UNIT_TESTS) +# ADD_SUBDIRECTORY(SitMapTestApp) +ADD_SUBDIRECTORY(exampleSvgRenderer) +ADD_SUBDIRECTORY(LineOfSightEvaluation) +ENDIF() + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.cpp new file mode 100755 index 000000000..a45a5f61b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.cpp @@ -0,0 +1,391 @@ +/* +** +** @RDE_HEADER@ +** Shows a crash when using QGraphicsItems and ESRI-Graphics in same scene +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Linux, Qt 5.7. ArcGISRuntimeQt 100 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 16.01.2017, JT : Initial +** +** ----------------------------------------------------------------------------------------------- +*/ +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * The class MapWindow creates a tiled base map from an UTM based TPK from Bremen. Loading the map will be done + * by clicking the "Load Map" button. When the map is loaded, you can click button "Symbol Failure" to create a + * MIL2525D symbol on map. With the combobox left of this button you can setup the geometry that will be use to + * build the symbol. The symbol with sidc="10012500002713000000" will cause always a crash. Other symbols can + * cause a crash some times if not choosing the fitting geometry. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +class MapWindow : public QWidget +{ + Esri::ArcGISRuntime::ArcGISTiledLayer * m_tiledLayer; + Esri::ArcGISRuntime::Basemap * m_basemap; + Esri::ArcGISRuntime::Map * m_map; + Esri::ArcGISRuntime::MapGraphicsView * m_mapView; + + QComboBox * m_cbGeometry; + QPushButton * m_pbSymbolFailure; + + Esri::ArcGISRuntime::DictionarySymbolStyle * m_dictionarySymbolStyle; + + public: + + MapWindow() : QWidget(), m_tiledLayer(nullptr), m_dictionarySymbolStyle(nullptr) + { + m_basemap = new Esri::ArcGISRuntime::Basemap(this); + m_map = new Esri::ArcGISRuntime::Map(this); + + m_map->setAutoFetchLegendInfos(true); + + m_mapView = new Esri::ArcGISRuntime::MapGraphicsView(this); + + m_mapView->setObjectName(QLatin1String("EsriMapView")); + m_mapView->setFrameShape(QFrame::NoFrame); + m_mapView->setWrapAroundMode(Esri::ArcGISRuntime::WrapAroundMode::Disabled); + m_mapView->setFixedSize(900,900); + + m_cbGeometry = new QComboBox( this ); + m_cbGeometry->addItem( "Point" ); + m_cbGeometry->addItem( "Linear" ); + m_cbGeometry->addItem( "Areal" ); + m_cbGeometry->setCurrentIndex(0); + m_cbGeometry->setEditable(false); + + QPushButton * pbLoadMap = new QPushButton(QStringLiteral("Load Map"), this); + + m_pbSymbolFailure = new QPushButton(QStringLiteral("Symbol Failure"), this); + m_pbSymbolFailure -> setEnabled(false); + + connect( pbLoadMap , &QPushButton::clicked, this, [this](bool) { initMap(); }); + connect( m_pbSymbolFailure, &QPushButton::clicked, this, [this](bool) { buildDictSymbol(); }); + + QVBoxLayout * layout = new QVBoxLayout(this); + QHBoxLayout * hLayout = new QHBoxLayout(); + + hLayout->addStretch(2); + hLayout->addWidget(m_cbGeometry); + hLayout->addSpacing(10); + hLayout->addWidget(m_pbSymbolFailure); + hLayout->addStretch(2); + + layout->addWidget(pbLoadMap); + layout->addLayout(hLayout); + layout->addWidget(m_mapView,2); + + + connect(m_map, &Esri::ArcGISRuntime::Map::doneLoading, this, [this](Esri::ArcGISRuntime::Error loadError) + { + if (!loadError.isEmpty()) + { + qCritical() << loadError.message(); + qCritical() << loadError.additionalMessage(); + + Esri::ArcGISRuntime::ErrorDomainType errorDomain = loadError.domain(); + Esri::ArcGISRuntime::ExtendedErrorType extendedErr = loadError.extendedErrorType(); + + Q_UNUSED(errorDomain); + Q_UNUSED(extendedErr); + } + if (Esri::ArcGISRuntime::LoadStatus::Loaded == m_map->loadStatus()) + { + if (m_dictionarySymbolStyle && (Esri::ArcGISRuntime::LoadStatus::Loaded == m_dictionarySymbolStyle->loadStatus())) + { + m_pbSymbolFailure->setEnabled(true); + } + } + }); + + } // MapWindow() + + + void initMap() + { + #define BremenMap + #ifdef BremenMap + // m_tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(QUrl("http://192.168.133.90:6080/arcgis/rest/services/BremenGeoTiffUTM/MapServer")); + Esri::ArcGISRuntime::TileCache * tpkCache = new Esri::ArcGISRuntime::TileCache("Topographic.tpk", this); + m_tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(tpkCache); + #else + Esri::ArcGISRuntime::TileCache * tpkCache = new Esri::ArcGISRuntime::TileCache("/usr/share/arcgis/resources/Topographic.tpk", this); + m_tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(tpkCache); + #endif + + m_basemap->baseLayers()->append(m_tiledLayer); + + m_map->setBasemap(m_basemap); + + Esri::ArcGISRuntime::GraphicsOverlayListModel * graphicsOverlayListModel = m_mapView->graphicsOverlays(); + Esri::ArcGISRuntime::GraphicsOverlay * graphicalOverlay = new Esri::ArcGISRuntime::GraphicsOverlay(); + + graphicalOverlay->setSelectionColor(Qt::magenta); + graphicsOverlayListModel->append(graphicalOverlay); + + m_mapView->setMap(m_map); + + loadDictionary(); + + } // void initMap() + + + void loadDictionary() + { + + #ifdef BremenMap + QString stylxPath = "C:/Program Files (x86)/ArcGIS SDKs/Qt100.0/resources/symbols/mil2525d/mil2525d.stylx"; // warning must be stylx file path + #else // if path is wrong --> crash (only Windows) + QString stylxPath = "/usr/share/arcgis/resources/symbols/mil2525d/mil2525d.stylx"; + #endif + + m_dictionarySymbolStyle = new Esri::ArcGISRuntime::DictionarySymbolStyle("mil2525d", stylxPath, this); + + connect(m_dictionarySymbolStyle, &Esri::ArcGISRuntime::DictionarySymbolStyle::loadStatusChanged, this, [this]( Esri::ArcGISRuntime::LoadStatus loadStatus ) + { + if ((Esri::ArcGISRuntime::LoadStatus::Loaded == loadStatus) && (Esri::ArcGISRuntime::LoadStatus::Loaded == m_map->loadStatus())) + { + m_pbSymbolFailure->setEnabled(true); + } + } ); + + m_dictionarySymbolStyle->load(); + + } // void loadDictionary() + + + ~MapWindow() = default; + + + void buildDictSymbol() + { + Esri::ArcGISRuntime::GraphicsOverlay * layer = new Esri::ArcGISRuntime::GraphicsOverlay(this); + Esri::ArcGISRuntime::DictionaryRenderer * dictRenderer = new Esri::ArcGISRuntime::DictionaryRenderer( m_dictionarySymbolStyle, this); + + connect( dictRenderer, &Esri::ArcGISRuntime::DictionaryRenderer::errorOccurred, this, [this]( const Esri::ArcGISRuntime::Error & error ) + { + qCritical() << "Error loading dictionary: " << error.message() << endl; + } ); + + //layer->setRenderingMode(Esri::ArcGISRuntime::GraphicsRenderingMode::Static); + layer->setRenderingMode(Esri::ArcGISRuntime::GraphicsRenderingMode::Dynamic); + layer->setRenderer( dictRenderer ); + + + QVariantMap rawAttributes; + + rawAttributes["sidc"] = "10012500002713000000"; // --> always crash + + // rawAttributes["sidc"] = "10012500001521000000"; // as polygon geometry --> failed to generate sequences for graphics + // or crash if swap coordinate 3 and 4 (see #define CRASH_25152100) + // as multipoint --> ok, with strange behaviour under zoom + + // rawAttributes["sidc"] = "10012500003419000000"; // --> failed to generate sequences for graphics + + setWindowTitle( "Crash Dictionary Symbol - Symbol Code " + rawAttributes["sidc"].toString() ); + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * see mil2525d.pdf, page 600 (25 271300) + * , page 459 (25 152100) + * coordinate sequenze as follows: + * + * (3) ------------------------------------------->(4) + * green orange + * + * + * + * (1) ------------------------------------------->(2) + * red blue + * + * or page 649 (25 341900): + * + * (4)<------------------------------------------- (3) + * orange green + * ^ + * | + * | + * (1) ------------------------------------------->(2) + * red blue + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + /* for Bremen map: + QPointF p1 = QPointF( 493200,5878900 ); // UTM Bremen 493200,5878900 + QPointF p2 = QPointF( 495200,5878900 ); // UTM Bremen 495200,5878900 + QPointF p3 = QPointF( 493200,5880900 ); // UTM Bremen 493200,5880900 + QPointF p4 = QPointF( 495200,5880900 ); // UTM Bremen 495200,5880900 + */ + + QPointF p1 = QPointF( 0, 0 ); + QPointF p2 = QPointF( 3000000, 0 ); + QPointF p3 = QPointF( 0, 5890000 ); + QPointF p4 = QPointF( 3000000, 5800000 ); + + // first show coordinate in map + auto drawPictureMarker = [this](const QPointF & p, const QColor & c) -> Esri::ArcGISRuntime::Graphic * + { + Esri::ArcGISRuntime::Graphic * graphic = new Esri::ArcGISRuntime::Graphic(); + Esri::ArcGISRuntime::SimpleMarkerSymbol * marker = new Esri::ArcGISRuntime::SimpleMarkerSymbol(); + + marker->setSize(8); + marker->setStyle( Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle ); + marker->setColor(c); + + graphic->setGeometry( Esri::ArcGISRuntime::Point( p.x(), p.y(), m_map->spatialReference() ) ); + graphic->setSymbol( marker ); + + return graphic; + }; // drawPictureMarker + + if (rawAttributes["sidc"] == "10012500003419000000") + { + // swap coordinate 3 and 4 in case of Relief in Place symbol (25 341900) stay compatible with MIL2525D + QPointF swap = p3; p3 = p4; p4 = swap; + } + + #define CRASH_25152100 + #ifdef CRASH_25152100 + if ((m_cbGeometry->currentIndex() == 2) && (rawAttributes["sidc"] == "10012500001521000000")) + { + // swap coordinate 3 and 4 in case of Suppoprt by Fire Position symbol show crash + QPointF swap = p3; p3 = p4; p4 = swap; + } + #endif + + layer->graphics()->append( drawPictureMarker(p1,QColor("red" )) ); + layer->graphics()->append( drawPictureMarker(p2,QColor("blue" )) ); + layer->graphics()->append( drawPictureMarker(p3,QColor("green" )) ); + layer->graphics()->append( drawPictureMarker(p4,QColor("orange")) ); + + Esri::ArcGISRuntime::Graphic * graphic = new Esri::ArcGISRuntime::Graphic(); + + switch(m_cbGeometry->currentIndex()) + { + case 0: + case 1: + { + qWarning() << " use 4 coordinates - MultipointBuilder"; + Esri::ArcGISRuntime::MultipointBuilder pointsBuilder( m_map->spatialReference() ); + Esri::ArcGISRuntime::PointCollection points( m_map->spatialReference() ); + + points.addPoint( p1.x(),p1.y() ); // 1 + points.addPoint( p2.x(),p2.y() ); // 2 + points.addPoint( p3.x(),p3.y() ); // 3 + points.addPoint( p4.x(),p4.y() ); // 4 + + pointsBuilder.setPoints( &points ); + + graphic->setGeometry( pointsBuilder.toGeometry() ); + } break; + + case 2: + { + qWarning() << " use 4 coordinates - PolygonBuilder"; + Esri::ArcGISRuntime::PolygonBuilder polygonBuilder( m_map->spatialReference() ); + + polygonBuilder.addPoint( Esri::ArcGISRuntime::Point(p1.x(),p1.y(),m_map->spatialReference()) ); // 1 + polygonBuilder.addPoint( Esri::ArcGISRuntime::Point(p2.x(),p2.y(),m_map->spatialReference()) ); // 2 + polygonBuilder.addPoint( Esri::ArcGISRuntime::Point(p3.x(),p3.y(),m_map->spatialReference()) ); // 3 + polygonBuilder.addPoint( Esri::ArcGISRuntime::Point(p4.x(),p4.y(),m_map->spatialReference()) ); // 4 + + graphic->setGeometry( polygonBuilder.toPolygon() ); + } + } + + qWarning() << " rawAttributes[sidc] = '" << rawAttributes[QLatin1String("sidc")] << "'"; + + graphic->attributes()->setAttributesMap(rawAttributes); + + //add the graphic to the graphics overlay + layer->graphics()->append(graphic); + + m_mapView->graphicsOverlays()->append(layer); + + qWarning() << " END of buildDictSymbol"; + } // void buildDictSymbol() + + +}; // MapWindow + + +int main(int argc, char* argv[]) +{ + QApplication app(argc,argv); + + // ESRI : +#ifdef Q_OS_WIN + // Force usage of OpenGL ES through ANGLE on Windows + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); +#endif + // : ESRI + + MapWindow * mapWin = new MapWindow(); + + int width = mapWin->frameGeometry().width(); + int height = mapWin->frameGeometry().height(); + + QScreen *screen = QGuiApplication::primaryScreen(); + int screenWidth = screen ? screen->size().width() : 1920; + int screenHeight = screen ? screen->size().height() : 1080; + + mapWin->setGeometry((screenWidth / 2) - (width / 2), (screenHeight / 2) - (height / 2), width, height); + + mapWin->setWindowTitle( "Crash Dictionary Symbol" ); + mapWin->show(); + + app.setQuitOnLastWindowClosed(true); + + int retval = app.exec(); + + delete mapWin; + + return retval; + +} // main diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.pro b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.pro new file mode 100755 index 000000000..b38064fea --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/CrashDictSymbol/CrashDictSymbol.pro @@ -0,0 +1,47 @@ +#------------------------------------------------- +# Copyright 2015 Esri. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#------------------------------------------------- + +mac { + cache() +} + + +CONFIG += c++11 esri_runtime_qt100_0_0 + +QT += core gui opengl xml network positioning sensors + +QMAKE_CXXFLAGS += -std=c++0x + + +# $$(ENVVAR) +# $(ENVVAR) +INCLUDEPATH += /XXX +LIBPATH += /XXX +LIBS += -lEsriCommonQt +LIBS += -lEsriRuntimeQt +LIBS += -lc++ + + + +win32:CONFIG += \ + embed_manifest_exe + +TARGET = CrashDictSymbol +TEMPLATE = app + +SOURCES += \ + CrashDictSymbol.cpp + +HEADERS += diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/CMakeLists.txt new file mode 100755 index 000000000..fa0c99714 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/CMakeLists.txt @@ -0,0 +1,23 @@ + +cmake_minimum_required(VERSION 3.16) + +SET(QT_MIN_VERSION 6.0.0) +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +FILE ( GLOB sourceFiles *.h *.cpp ) + +FIND_PACKAGE(OpenGL) +FIND_PACKAGE(Qt6 COMPONENTS Core Gui Location OpenGL Positioning Sensors Widgets REQUIRED) + +INCLUDE_DIRECTORIES ( ${Qt6Widgets_INCLUDE_DIRS}, "C:/Program Files (x86)/ArcGIS SDKs/Qt10.2.6/sdk/windows/include" ) + +add_definitions(${Qt6Widgets_DEFINITIONS}) + +SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + +ADD_EXECUTABLE( EsriMapTouchTest WIN32 ${sourceFiles} ) + +TARGET_LINK_LIBRARIES( EsriMapTouchTest Qt6::Core Qt6::Gui Qt6::Location Qt6::OpenGL Qt6::Sensors Qt6::Widgets ) +TARGET_LINK_LIBRARIES( EsriMapTouchTest debug "C:/Program Files (x86)/ArcGIS SDKs/Qt10.2.6/sdk/windows/x86/lib/EsriRuntimeQtd.lib" ) +TARGET_LINK_LIBRARIES( EsriMapTouchTest optimized "C:/Program Files (x86)/ArcGIS SDKs/Qt10.2.6/sdk/windows/x86/lib/EsriRuntimeQt.lib" ) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.cpp new file mode 100755 index 000000000..0715c5489 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.cpp @@ -0,0 +1,311 @@ +/** +** +** @RDE_HEADER@ +** Abstract Event Dispatcher for Qt5 +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 28.04.2003, JT : First edition for use with Qt4 in START Feature Editor fee +** 25.03.2015, JT : Refactoring for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "abstract_event_dispatcher.h" +#include "gestures.h" + +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include // for debugging only + + +namespace SituationMap { namespace RB +{ + + + QGestureRecognizer * AbstractEventDispatcher::_applyGestureDetection = nullptr; + unsigned int AbstractEventDispatcher::_applyGestureID = 0; + + + AbstractEventDispatcher::AbstractEventDispatcher( QWidget * getEventsFrom ) + : QObject(getEventsFrom) + , _getEventsFrom(getEventsFrom) + , _cursorIsInWidget(false) + , _ignoreMouseMoveTimestamp() + , _synthesisTouchFromMouseEvents(false) + , _deleteRequested(false) + , _hasGrab(false) + , _finishFlag(false) + , _activeEvent(false) + , _dispatcherCursor() + , _whatsThisBackup() + , _eventActions(nullptr) + { + initDispatcher(); + } /* AbstractEventDispatcher::AbstractEventDispatcher */ + + + AbstractEventDispatcher::AbstractEventDispatcher( QWidget * getEventsFrom, EventActionInterface * eventActions ) + : QObject(getEventsFrom) + , _getEventsFrom(getEventsFrom) + , _cursorIsInWidget(false) + , _ignoreMouseMoveTimestamp() + , _synthesisTouchFromMouseEvents(false) + , _deleteRequested(false) + , _hasGrab(false) + , _finishFlag(false) + , _activeEvent(false) + , _dispatcherCursor() + , _whatsThisBackup() + , _eventActions(eventActions) + + { + initDispatcher(); + } /* AbstractEventDispatcher::AbstractEventDispatcher */ + + + void AbstractEventDispatcher::initDispatcher() + { + _restoreCursor = true; + + /* The methode QWidget::ownCursor doesn't exist any more. + * Try to find out if the inherited from parent + */ + + // _restoreCursor = getEventsFrom->ownCursor(); + + if (_restoreCursor) + { + _cursorToRestore = getCursorFromWidget(); + } + _restoreMouseTracking = _getEventsFrom->hasMouseTracking(); + + // just a test exe, but remove process Events + // QCoreApplication::process Events ( ); /// removePostedEvents( _getEventsFrom ); + + _getEventsFrom->setMouseTracking(true); + _getEventsFrom->raise(); + _getEventsFrom->installEventFilter(this); + + _synthesisTouchFromMouseEvents = QCoreApplication::testAttribute( Qt::AA_SynthesizeTouchForUnhandledMouseEvents ); + QCoreApplication::setAttribute( Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false ); + QCoreApplication::setAttribute( Qt::AA_SynthesizeMouseForUnhandledTouchEvents, true ); +#ifdef NEVER + static const Qt::GestureType gestures[] = { Qt::GestureType::TapGesture + , Qt::GestureType::TapAndHoldGesture + , Qt::GestureType::PanGesture + , Qt::GestureType::PinchGesture + , Qt::GestureType::SwipeGesture }; + + for (auto gestureType : gestures) _getEventsFrom->grabGesture(gestureType); + + if (_applyGestureDetection == nullptr) + { + _applyGestureDetection = new ApplyGestureRecognizer(); + _applyGestureID = QGestureRecognizer::registerRecognizer( _applyGestureDetection ); + } + + _getEventsFrom->grabGesture( (Qt::GestureType)_applyGestureID ); + _getEventsFrom->setAttribute( Qt::WA_AcceptTouchEvents, true ); +#endif + } /* AbstractEventDispatcher::initDispatcher */ + + + AbstractEventDispatcher::~AbstractEventDispatcher() + { + + _finishFlag = true; + + if (_getEventsFrom) + { + _getEventsFrom->removeEventFilter(this); + _getEventsFrom->setMouseTracking( _restoreMouseTracking ); + + ungrab(); + + if (!_whatsThisBackup.isNull()) + { + _getEventsFrom->setWhatsThis(_whatsThisBackup); + } + } + QCoreApplication::setAttribute( Qt::AA_SynthesizeTouchForUnhandledMouseEvents, _synthesisTouchFromMouseEvents ); + + } /* AbstractEventDispatcher::~AbstractEventDispatcher */ + + + void AbstractEventDispatcher::onDo_delete() + { + if (_deleteRequested) return; + _deleteRequested = true; + QObject::deleteLater(); + } /* AbstractEventDispatcher::onDo_delete */ + + + void AbstractEventDispatcher::KickTheBucket() + { + if (_getEventsFrom == nullptr) return; + + _finishFlag = true; + + unlinkEventDispatcher(); + + _getEventsFrom->removeEventFilter(this); + _getEventsFrom->setMouseTracking( _restoreMouseTracking ); + + ungrab(); + + if (!_whatsThisBackup.isNull()) + { + _getEventsFrom->setWhatsThis(_whatsThisBackup); + } + + _getEventsFrom = nullptr; + + emit signalKickTheBucket(); + + QCoreApplication::flush(); + // just a test exe, but remove process Events + // QCoreApplication::process Events(); // dispatch window system events + + onDo_delete(); + + } /* AbstractEventDispatcher::KickTheBucket */ + + + void AbstractEventDispatcher::onDo_kickTheBucket() { KickTheBucket(); } + + + void AbstractEventDispatcher::setWhatsThis(const QString & whatsThis) + { + if (_whatsThisBackup.isNull()) + { + _whatsThisBackup = _getEventsFrom->whatsThis(); + } + _getEventsFrom->setWhatsThis(whatsThis); + } /* AbstractEventDispatcher::setWhatsThis */ + + + + void AbstractEventDispatcher::grab() + { + if (_hasGrab) return; + _hasGrab = true; + + setCursorToWidget( _dispatcherCursor ); + _getEventsFrom -> setFocus(); + } /* AbstractEventDispatcher::Grab */ + + + void AbstractEventDispatcher::ungrab() + { + if (!_hasGrab) return; + + _hasGrab = false; + if (_restoreCursor) + { + setCursorToWidget( _cursorToRestore ); + } + // else _getEventsFrom -> unsetCursor(); + + _getEventsFrom -> clearFocus(); + + } /* AbstractEventDispatcher::Ungrab */ + + + bool AbstractEventDispatcher::eventFilter( QObject * widget, QEvent * e ) + { + if (widget != ((QObject*)_getEventsFrom)) return false; + + + return false; + } /* AbstractEventDispatcher::eventFilter */ + + + void AbstractEventDispatcher::setCursorToWidget( const QCursor & cursor ) + { + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * The cursor handling of QGraphicView are not free of bugs. If it tries to restore the default cursor after setting a special cursor for one item + * it can happend that the restored cursor is not the last cursor that was set with setCursor function because the internal stored restore cursor is + * not updated with setCursor function. As work around a mouse move event is send to the widget to reset cursor handling. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + const QPoint & currentCursorPosition = _getEventsFrom->mapFromGlobal( QCursor::pos() ); + +#ifdef ShowBug + QMouseEvent && mouseEvent = QMouseEvent( QEvent::Type::MouseMove, QPointF(-1,-1), Qt::MouseButton::NoButton, Qt::MouseButtons(), Qt::KeyboardModifier::NoModifier ); + + _ignoreMouseMoveTimestamp = mouseEvent.timestamp(); + + QCoreApplication::sendEvent(_getEventsFrom, &mouseEvent ); + QCoreApplication::process Events(); + + _getEventsFrom -> setCursor( cursor ); + + if (_cursorIsInWidget) + { + mouseEvent = QMouseEvent( QEvent::Type::MouseMove, currentCursorPosition, Qt::MouseButton::NoButton, Qt::MouseButtons(), Qt::KeyboardModifier::NoModifier ); + QCoreApplication::sendEvent(_getEventsFrom, &mouseEvent ); + } + QCoreApplication::process Events(); + _getEventsFrom -> update(); +#else + _getEventsFrom -> setCursor( cursor ); +#endif + } /* AbstractEventDispatcher::setCursorToWidget */ + + + QCursor AbstractEventDispatcher::getCursorFromWidget() + { + const QPoint & currentCursorPosition = _getEventsFrom->mapFromGlobal( QCursor::pos() ); + +#ifdef ShowBug + QMouseEvent && mouseEvent = QMouseEvent( QEvent::Type::MouseMove, QPointF(-1,-1), Qt::MouseButton::NoButton, Qt::MouseButtons(), Qt::KeyboardModifier::NoModifier ); + + _ignoreMouseMoveTimestamp = mouseEvent.timestamp(); + + QCoreApplication::sendEvent(_getEventsFrom, &mouseEvent ); + QCoreApplication::process Events(); + + const QCursor & currentCursor = _getEventsFrom->cursor(); + + if (_cursorIsInWidget) + { + mouseEvent = QMouseEvent( QEvent::Type::MouseMove, currentCursorPosition, Qt::MouseButton::NoButton, Qt::MouseButtons(), Qt::KeyboardModifier::NoModifier ); + QCoreApplication::sendEvent(_getEventsFrom, &mouseEvent ); + } + QCoreApplication::process Events(); + _getEventsFrom -> update(); +#else + const QCursor & currentCursor = _getEventsFrom->cursor(); +#endif + return currentCursor; + } /* AbstractEventDispatcher::getCursorFromWidget */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.h new file mode 100755 index 000000000..00eb9b392 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/abstract_event_dispatcher.h @@ -0,0 +1,232 @@ +/** +** +** @RDE_HEADER@ +** Abstract Event Dispatcher for Qt5 +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 28.04.2003, JT : First edition for use with Qt4 in START Feature Editor fee +** 25.03.2015, JT : Refactoring for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include + + +class QWidget; +class QWheelEvent; +class QGestureRecognizer; + + +namespace SituationMap { namespace RB +{ + + /** + @class AbstractEventDispatcher abstract_event_dispatcher.h + @brief Base class of Qt event filter implementations to handle user interactions of map window + + An EventDispatcher installs an event filter on a QWidget to receive all events that are send to + the widget. Depending on the type of event and the state of the event dispatcher the event will + be processed by the dispatcher and stopped or forwarded to the QWidget. In case of processing + the event methodes of the EventActionInterface are called to controll a rubberband. + + @see EventDelegater, EventDispatcher, EventMouseNavigator + */ + class AbstractEventDispatcher : public QObject + { + friend class EventDelegater; + + Q_OBJECT + + public: + + /** + @brief Logical interface between EventDispatcher and Rubberband + Defines interface functions the EventDispatcher will call to control a rubberband. The rubberband class + is derived from EventActionInterface and overrides the interface functions to implement them. The mapping + user interaction to interface action is defined the derived classes of AbstractEventDispatcher. + */ + class EventActionInterface + { + public: + + EventActionInterface() = default; + virtual ~EventActionInterface() = default; + + virtual void unlinkEventDispatcher( AbstractEventDispatcher * causer ) = 0; + + virtual bool selectAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool multiSelectAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool setCoordinateAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool moveAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool movePickedAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual void resetAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool contextMenuAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool completeStateAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool deleteAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool undoAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool applyAndEndAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + virtual bool abortAction ( AbstractEventDispatcher * causer, int x, int y ) = 0; + + }; /* EventActionInterface */ + + /** + @name Construction & Destruction + @{ + */ + AbstractEventDispatcher( QWidget * getEventsFrom ); + AbstractEventDispatcher( QWidget * getEventsFrom, EventActionInterface* ); + + virtual ~AbstractEventDispatcher(); + + AbstractEventDispatcher(const AbstractEventDispatcher &) = delete; + AbstractEventDispatcher & operator = (const AbstractEventDispatcher &) = delete; + /** @} */ + + /** + Conceal deleteLater slot. Use onDo_delete() instead to avoid "QCoreApplication::postEvent: Unexpected null receiver" warning. + */ + void deleteLater() = delete; + + + inline void setEventActions(EventActionInterface * eventActions) { _eventActions = eventActions; } + + inline bool hasGrab() const { return _hasGrab; } + + inline void setCursor(const QCursor & cursor) { _dispatcherCursor = cursor; } + + void setWhatsThis(const QString & whatsThis); + + virtual void grab(); + virtual void ungrab(); + + + public slots: + + /** + @brief Slot calling internal \c KickTheBucket methode + + Calls internal methode \c KickTheBucket. Be careful using this function. Should only be used + from implementations of \c EventActionInterface methodes or \c EventDelegater class. + */ + void onDo_kickTheBucket(); + + /** + @brief Slot calling \c deleteLater function of QObject + + Never use \c delete directly for EventDispatcher class. Instead of use \c onDo_delete() function. + */ + void onDo_delete(); + + protected: + + QWidget * _getEventsFrom; + bool _cursorIsInWidget; + + inline bool hasFinished() { return _finishFlag; } + inline bool hasActiveEvent() { return _activeEvent; } + + inline void setActiveEvent(bool val) { _activeEvent = val; } + + /** + @brief Destroying EventDispatcher as result of an incoming event. + + As result of an incomming event (e.g. Keypress ESC) the EventDispatcher will stop, emiting signal \c signalKickTheBucket() + and calls methode \c EventActionInterface::unlinkEventDispatcher() before setup \c deleteLater() call to destroy it self. + */ + virtual void KickTheBucket(); + + + inline void unlinkEventDispatcher() + { + if (_eventActions) + { + _eventActions -> unlinkEventDispatcher( this ); + _eventActions = nullptr; + } + } /* unlinkEventDispatcher */ + + + inline bool callEventAction( bool( EventActionInterface::*actionMethodePtr)( AbstractEventDispatcher*,int,int), int x, int y ) + { + if (_eventActions == nullptr) return false; + return (_eventActions->*actionMethodePtr)( this, x, y ); + } /* callEventAction */ + + + /** + Event filter function to override to implement event handling. To workaround a cursor handling bug in QGraphicsView it is + necessary to call the eventFilter function of this base class, which will monitor enter and leave events. + */ + virtual bool eventFilter( QObject * widget, QEvent * e ); + + inline EventActionInterface * getEventActions() const { return _eventActions; } + + void setCursorToWidget( const QCursor & cursor ); + QCursor getCursorFromWidget(); + + ulong _ignoreMouseMoveTimestamp; + + bool _synthesisTouchFromMouseEvents; + + static unsigned int _applyGestureID; + + private: + + void initDispatcher(); + + bool _deleteRequested; + + bool _hasGrab; + bool _finishFlag; + + bool _restoreCursor; + bool _restoreMouseTracking; + QCursor _cursorToRestore; + + bool _activeEvent; + + QCursor _dispatcherCursor; + QString _whatsThisBackup; + + EventActionInterface * _eventActions; + + + + static QGestureRecognizer * _applyGestureDetection; + + signals: + + void signalKickTheBucket(); + + }; /* EventDispatcher */ + +} } /* namespace SituationMap::RB */ + \ No newline at end of file diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.cpp new file mode 100755 index 000000000..e49c5678f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.cpp @@ -0,0 +1,495 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to delegate mouse and keyboard event to another event dispatcher +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 15.04.2015, JT : First edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include "event_mouse_navigator.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include // for debugging only +#include + +#include +#include +#include +#include +#include +#include + + +namespace SituationMap { namespace RB +{ + + int EventMouseNavigator::_pickTolerance = 20; + + EventMouseNavigator::EventMouseNavigator(QWidget * getEventsFrom, EsriRuntimeQt::Map * esriMap ) + : AbstractEventDispatcher(getEventsFrom) + , _esriMap(esriMap) + , _selectionMode() + , _selectedIdList() + , _idConnectionCount(0) + , _mousePanningActive(false) + { + } /* EventMouseNavigator::EventMouseNavigator */ + + + bool EventMouseNavigator::eventFilter(QObject * watched, QEvent * e) + { + Q_UNUSED(watched); + + QString debugString(QLatin1String("unhandled event")); + + if (hasFinished()) return true; // don't accept new events if dispatcher is going to finish + if (hasActiveEvent()) return true; // don't accept new events before last event has been handled completly + + if (e) + { + bool handled( false ); + + setActiveEvent( true ); + + switch( e->type() ) + { + + case QEvent::MouseButtonPress : + { + debugString = QLatin1String("mouse button press event"); + + QMouseEvent * mouseEvent = static_cast(e); + + _mousePanningActive = false; + + _mouseMoveRefPos = mouseEvent->pos(); + _restoreMouseCursor = getCursorFromWidget(); + + if (Qt::LeftButton == mouseEvent->button()) + { + handled = false; + + if (mouseEvent->modifiers() == Qt::KeyboardModifier::ControlModifier) + { + _selectionMode = SelectionMode::MultiSelection; + setCursorToWidget( QCursor(Qt::CursorShape::PointingHandCursor) ); + } + else if (mouseEvent->modifiers().testFlag( Qt::KeyboardModifier::ShiftModifier )) + { + _selectionMode = SelectionMode::FrameSelection; + if (mouseEvent->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier)) + { + _selectionMode &= SelectionMode::MultiSelection; + } + else + { + _selectionMode &= SelectionMode::SingleSelection; + } + setCursorToWidget( QCursor(Qt::CursorShape::SizeAllCursor) ); + } + else + { + setCursorToWidget( QCursor(Qt::CursorShape::ArrowCursor) ); + _selectionMode = SelectionMode::SingleSelection; + } + } + + if (Qt::MidButton == mouseEvent->button()) + { + handled = (Qt::MidButton == mouseEvent->button()); + + _mousePanningActive = true; + setCursorToWidget( QCursor(Qt::CursorShape::ClosedHandCursor) ); + } + } break; + + case QEvent::MouseButtonRelease : + { + debugString = QLatin1String("mouse button release event"); + + QMouseEvent * mouseEvent = static_cast(e); + + if (Qt::LeftButton == mouseEvent->button()) + { + handled = false; + + if (!_mousePanningActive) + { + setCursorToWidget( _restoreMouseCursor ); + + selectionRequested( mouseEvent->pos() ); + + _selectionMode = SelectionMode::NoSelection; + // do not handled = true; because some buttons in GraphicsView needs to get the event later. + } + } + + if (Qt::RightButton == mouseEvent->button()) + { + handled = true; + pickRequested( mouseEvent->pos() ); + } + + if ((_mousePanningActive) || (Qt::MidButton == mouseEvent->button())) + { + handled = (Qt::MidButton == mouseEvent->button()); + setCursorToWidget( _restoreMouseCursor ); + } + + _mousePanningActive = false; + } break; + + case QEvent::MouseMove : + { + debugString = QLatin1String("mouse button move event"); + QMouseEvent *mouseEvent = static_cast(e); + + if (mouseEvent->timestamp() != _ignoreMouseMoveTimestamp) + { + if (isSignalConnected(QMetaMethod::fromSignal(&EventMouseNavigator::mouseMovedEvent))) + { + mouseMovedEvent( mouseEvent->pos().x(), mouseEvent->pos().y() ); + } + + if ((!_mousePanningActive) && mouseEvent->buttons().testFlag(Qt::LeftButton)) + { + QPoint deltaMove = mouseEvent->pos()-_mouseMoveRefPos; + if (deltaMove.manhattanLength() > 3) + { + _mousePanningActive = true; + setCursorToWidget( QCursor(Qt::CursorShape::ClosedHandCursor) ); + } + } + + if ((_mousePanningActive) || mouseEvent->buttons().testFlag(Qt::MidButton)) + { + handled = true; + EsriRuntimeQt::AnimationMode mode = _esriMap->animationMode(); + + QPoint deltaMove = mouseEvent->pos()-_mouseMoveRefPos; + QPoint winCenter = QPoint( _getEventsFrom->width() / 2, _getEventsFrom->height() / 2 ); + QPoint newCenter = winCenter-deltaMove; + + const EsriRuntimeQt::Point & mapCenter = _esriMap->toMapPoint(newCenter.x(), newCenter.y()); + + _esriMap -> setAnimationMode( EsriRuntimeQt::AnimationMode::NoAnimation ); + _esriMap -> panTo( mapCenter ); + _esriMap -> setAnimationMode(mode); + + _getEventsFrom -> update(); + + _mouseMoveRefPos = mouseEvent->pos(); + } + } + } break; + + case QEvent::Wheel : + { + debugString = QLatin1String("mouse wheel event"); + // QWheelEvent * wheelEvent = static_cast(e); + // handled = true; + handled = false; + } break; + + case QEvent::Gesture: + { + QGestureEvent * gestureEvent = static_cast(e); + + if (gestureEvent) + { + const QList & gestureList = gestureEvent->gestures(); + for (auto gesture : gestureList) + { + Qt::GestureType gestureType = gesture->gestureType(); + Qt::GestureState gestureState = gesture->state(); + + QTapGesture * tapGesture = nullptr; + QTapAndHoldGesture * tapAndHoldGesture = nullptr; + QPanGesture * panGesture = nullptr; + QPinchGesture * pinchGesture = nullptr; + QSwipeGesture * swipeGesture = nullptr; + QString typeName; + QString stateName; + + switch (gestureType) + { + case Qt::TapGesture : tapGesture = (QTapGesture *)gesture; typeName = QLatin1String("TapGesture"); break; + case Qt::TapAndHoldGesture : tapAndHoldGesture = (QTapAndHoldGesture*)gesture; typeName = QLatin1String("TapAndHoldGesture"); break; + case Qt::PanGesture : panGesture = (QPanGesture *)gesture; typeName = QLatin1String("PanGesture"); break; + case Qt::PinchGesture : pinchGesture = (QPinchGesture *)gesture; typeName = QLatin1String("PinchGesture"); break; + case Qt::SwipeGesture : swipeGesture = (QSwipeGesture *)gesture; typeName = QLatin1String("SwipeGesture"); break; + + default: break; + } + switch (gestureState) + { + case Qt::NoGesture : stateName = QLatin1String("NoGesture"); break; + case Qt::GestureStarted : stateName = QLatin1String("GestureStarted"); break; + case Qt::GestureUpdated : stateName = QLatin1String("GestureUpdated"); break; + case Qt::GestureFinished : stateName = QLatin1String("GestureFinished"); break; + case Qt::GestureCanceled : stateName = QLatin1String("GestureCanceled"); break; + } + + // tapGesture = nullptr; + panGesture = nullptr; + + if (panGesture) + { + EsriRuntimeQt::AnimationMode mode = _esriMap->animationMode(); + + QPointF deltaMove = panGesture->delta(); + QPoint winCenter = QPoint( _getEventsFrom->width() / 2, _getEventsFrom->height() / 2 ); + QPoint newCenter = winCenter-QPoint((int)deltaMove.x(),(int)deltaMove.y()); + + const EsriRuntimeQt::Point & mapCenter = _esriMap->toMapPoint(newCenter.x(), newCenter.y()); + + _esriMap -> setAnimationMode( EsriRuntimeQt::AnimationMode::NoAnimation ); + _esriMap -> panTo( mapCenter ); + _esriMap -> setAnimationMode(mode); + + _getEventsFrom -> update(); + + if (gestureState == Qt::GestureStarted) gestureEvent -> setAccepted(panGesture, true); + + } + + if (pinchGesture) + { + EsriRuntimeQt::AnimationMode mode = _esriMap->animationMode(); + QPinchGesture::ChangeFlags changeFlags = pinchGesture->changeFlags(); + + _esriMap -> setAnimationMode( EsriRuntimeQt::AnimationMode::NoAnimation ); + + if (changeFlags & QPinchGesture::RotationAngleChanged) + { + qreal deltaPhi = pinchGesture->rotationAngle() - pinchGesture->lastRotationAngle(); + _esriMap -> setRotation( _esriMap->rotation()-deltaPhi ); + } + if (changeFlags & QPinchGesture::ScaleFactorChanged) _esriMap -> zoom( 1.0/pinchGesture->scaleFactor() ); + + _esriMap -> setAnimationMode(mode); + + _getEventsFrom -> update(); + + if (gestureState == Qt::GestureStarted) gestureEvent -> setAccepted(pinchGesture, true); + } + } + } + } break; + + default: break; + + } /* switch */ + + if (!hasFinished()) setActiveEvent( false ); + + if (handled) e->accept(); + + if (handled) return true; + } + return AbstractEventDispatcher::eventFilter(watched,e); + + } /* EventMouseNavigator::eventFilter */ + + + void EventMouseNavigator::selectionRequested( const QPoint & pos ) + { + const bool appendSelection = _selectionMode.testFlag( SelectionMode::MultiSelection ); + + if (isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::selectionRequest) )) + { + // do the work only in case of slot connections are installed + + if (0 != _idConnectionCount) + { + qWarning() << LOGFUNCTION << " graphic layer id list request is still pending!"; + return; + } + + pickObjects( pos, _selectedIdList, appendSelection, &EventMouseNavigator::selectionRequest ); + + } + } /* EventMouseNavigator::selectionRequested */ + + + void EventMouseNavigator::pickRequested( const QPoint & pos ) + { + if (isSignalConnected( QMetaMethod::fromSignal(&EventMouseNavigator::contextMenuRequest) )) + { + // do the work only in case of slot connections are installed + + if (0 != _idConnectionCount) + { + qWarning() << LOGFUNCTION << " graphic layer id list request is still pending!"; + return; + } + + pickObjects( pos, _selectedIdList, false, &EventMouseNavigator::contextMenuRequest ); + + } + } /* EventMouseNavigator::pickRequested */ + + + void EventMouseNavigator::pickObjects( const QPoint & pos + , QList & pickIdList + , bool append + , void (EventMouseNavigator::*pickSignal)(const QList< uint>&,QPoint) ) + { +#ifdef NEVER + if (!append) _selectedIdList.clear(); + + QList layerList = _esriMap->layers(); + QList selectLayerList; + + /* ~~~~~~~~~~~~~~~~~~~ first look for all layers in map ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + for (auto layer : layerList) + { + EsriRuntimeQt::MessageGroupLayer* msgGrpLayer = qobject_cast(layer); + if (msgGrpLayer) + { + selectLayerList.append( msgGrpLayer->getLayers() ); + } + else selectLayerList.append(layer); + } + + /* ~~~~~~~~~~~~~~~~~~~ loop over all layers in layer list ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + for (auto layer : selectLayerList) + { + EsriRuntimeQt::FeatureLayer* featureLayer = qobject_cast(layer); + if (featureLayer) + { + /*~~~~~~~~~~~~~~~~~~~~~~~ get selected features by direct function call ~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + QList fidList = featureLayer -> featureIds( pos.x(), pos.y(), _pickTolerance ); + featureLayer->selectFeatures( fidList, append ); + + EsriRuntimeQt::FeatureTable* featureTable = featureLayer->featureTable(); + + for (auto featureId : fidList) + { + if (featureTable) + { + EsriRuntimeQt::Feature* feature = featureTable->feature( featureId ); + if (feature) + { + bool ok; + SituationMap::UniqueMapId id; + + id = feature->property( SituationMap::UniqueMapIdProperty ).toUInt( &ok ); + if (ok) + { + if (!pickIdList.contains(id)) + { + pickIdList += id; + } + } + } + } + } // for + } // if (featureLayer) + + EsriRuntimeQt::GraphicsLayer * graphicsLayer = qobject_cast(layer); + if (graphicsLayer) + { + + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * In case of message symbols, in a graphic layer a slot method has to be used to get + * the ids of selected symbols. The slot installed (connected) each time + * EventMouseNavigator::selectionRequested() has been called and will be disconnect itself at + * beginning of slot function. Because the slot methode will be called later, we have to store + * some information like the _selectedIdList as member variable. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * connect here, because the layer in MessageGroupLayer can change: + * do not use QMetaObject:.Connection directly because disconnect will fail. + * See also http://stackoverflow.com/questions/13847507/qt5-new-signal-to-lambda-connections-memory-leak + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + _idConnectionCount++; + + auto conn = std::make_shared(); + *conn = connect( graphicsLayer, &EsriRuntimeQt::GraphicsLayer::graphicIdsComplete, + this , [conn,pos,&pickIdList,graphicsLayer,pickSignal,this]( const QList& gIdList ) + { + disconnect( *conn ); + for (auto gId : gIdList) + { + QString objectName(QLatin1String(" - no graphic - ")); + + EsriRuntimeQt::Graphic * graphic = graphicsLayer->graphic(gId); + if (graphic) + objectName = graphic->objectName(); + + qDebug() << LOGFUNCTION + << " gId=" << gId + << " name= " << objectName; + + if (graphic) + { + bool ok; + SituationMap::UniqueMapId id; + + id = graphic->property( SituationMap::UniqueMapIdProperty ).toUInt( &ok ); + if (ok) + { + if (!pickIdList.contains(id)) + { + pickIdList += id; + } + } + } + } + + // Just emit selectionRequest only once + if (_idConnectionCount <= 1) + { + (this->*pickSignal)( pickIdList, pos ); + _idConnectionCount = 0; + } + else _idConnectionCount--; + + } ); // end of lambda methode + + graphicsLayer->graphicIds( pos.x(), pos.y(), _pickTolerance, 100 ); + + } + } // for layer + if (_idConnectionCount == 0) + { + (this->*pickSignal)( pickIdList, pos ); + } +#endif + } // EventMouseNavigator::pickObjects + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.h new file mode 100755 index 000000000..42bbb84f0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/event_mouse_navigator.h @@ -0,0 +1,154 @@ +/** +** +** @RDE_HEADER@ +** Event Dispatcher for Qt5 to handle mouse events navigate on map +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 24.04.2015, JT : first edition +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include + +#include "abstract_event_dispatcher.h" + + +namespace EsriRuntimeQt +{ + class Map; + +} /* namespace EsriRuntimeQt */ + + +namespace SituationMap { namespace RB +{ + + /** + @class EventMouseNavigator event_mouse_navigator.h + @brief Event filter that handles mouse events for general map operation like selecting object, zooming and paning the map window + + Implements class \c AbstractEventDispatcher to manage event handling for general map operation like selecting object, zooming and + panning the map window. Monitoring the mouse position can be realized by connecting the \c mouseMovedEvent signal. If the user has + clicked to map objects the signal \c selectionRequest is emitted. + + Following events are handled: + + | Input-Event | Interface Function | Description | + | :--------------------------------------------- | : ----------------- | : --------------------------------------------------------------------------- | + | Mouse move without any modifier | | Move Cursor. | + | Mouse move with pressed left button | | Panning of map if more then 3 pixels moved. | + | Mouse move with pressed middle button | | Panning of map. | + | Mouse wheel | | Redirect to \c Esri map for zooming. | + | Left mouse button release | | Emits signal \c selectionRequest with list of map objects at cursor position. | + + @see AbstractEventDispatcher, EventDelegater, EventDispatcher + */ + + class EventMouseNavigator : public AbstractEventDispatcher + { + + Q_OBJECT + + public: + + /** + @name Construction & Destruction + @{ + */ + EventMouseNavigator(const EventMouseNavigator &) = delete; + EventMouseNavigator & operator = (const EventMouseNavigator &) = delete; + + EventMouseNavigator( QWidget * getEventsFrom, EsriRuntimeQt::Map * esriMap ); + + virtual ~EventMouseNavigator() {} + /** @} */ + + inline static void setPickTolerance( int value = 20 ) { _pickTolerance = value; } + + signals: + + /** + Will be emitted if the user clicks to map objects with the left mouse button. + */ + void selectionRequest( const QList< uint > & idList, QPoint viewPortPos ); + + /** + Will be emitted if the user clicks to the map with the rigth mouse button. + */ + void contextMenuRequest( const QList< uint > & idList, QPoint viewPortPos ); + + /** + Signal will be emitted if a mouse move is done. + */ + void mouseMovedEvent( int x, int y ); + + protected: + + virtual bool eventFilter(QObject*, QEvent*); + + enum SelectionMode + { + NoSelection = 0x00, + SingleSelection = 0x01, + MultiSelection = 0x02, + FrameSelection = 0x04 + }; // SelectionMode + + Q_DECLARE_FLAGS(SelectionModes,SelectionMode) + + EsriRuntimeQt::Map * _esriMap; + QPoint _mouseMoveRefPos; + QCursor _restoreMouseCursor; + + static int _pickTolerance; + SelectionModes _selectionMode; + QList _selectedIdList; // List of selected object-ids. Is completly handled in selectionRequested() + unsigned int _idConnectionCount; // counter to avoid multiple signals selectionRequest, see implementation of selectionRequested() + bool _mousePanningActive; // is panning with mouse active + + /** + Find out which map objects are at given mouse position and emits the signal selectionRequest + */ + void selectionRequested( const QPoint & ); + + /** + Find out which map objects are at given mouse position and emits the signal contextMenuRequest + */ + void pickRequested( const QPoint & ); + + /** + Helper function to get all map objets at a given position + @param pos position in window coordinates + @param pickIdList List to store uid's of picked map objects. Don't use a local variable here because the picking is a asynchrone function + @param append If true the uid's are appended at end of list. Otherwise the list will be cleared first. + @param pickSignal Function pointer to signal to be emitted at end. + */ + void pickObjects( const QPoint & pos + , QList & pickIdList + , bool append + , void (EventMouseNavigator::*pickSignal)(const QList< uint>&,QPoint) ); + + }; /* EventMouseNavigator */ + + +} } /* namespace SituationMap::RB */ diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.cpp new file mode 100755 index 000000000..cd9147589 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.cpp @@ -0,0 +1,102 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +#include "gestures.h" + +#include +#include + +#include + +namespace SituationMap { namespace RB { + + // TabGestureRecognizer + + ApplyGestureRecognizer::ApplyGestureRecognizer() : QGestureRecognizer() + { + } + + ApplyGestureRecognizer::~ApplyGestureRecognizer() + {} + + + QGesture * ApplyGestureRecognizer::create( QObject * target ) + { + qDebug() << "ApplyGesture create"; + + if (target && target->isWidgetType()) + { + ((QWidget*)target)->setAttribute(Qt::WA_AcceptTouchEvents); + } + return new ApplyGesture(); + } // TabGestureRecognizer::create + + + QGestureRecognizer::Result ApplyGestureRecognizer::recognize( QGesture * state, QObject* , QEvent * event) + { + ApplyGesture * q = static_cast(state); + const QTouchEvent * ev = static_cast(event); + + QGestureRecognizer::Result result = QGestureRecognizer::CancelGesture; + + switch (event->type()) + { + case QEvent::TouchBegin: + { + qDebug() << "ApplyGesture begin"; + q->_position = ev->touchPoints().at(0).pos(); + q->setHotSpot(ev->touchPoints().at(0).screenPos()); + q->_tripleTouch = (ev->touchPoints().size() == 3); + result = QGestureRecognizer::TriggerGesture; + } break; + + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + if (q->state() != Qt::NoGesture && ev->touchPoints().size() >= 1 && ev->touchPoints().size() <= 3) + { + if (ev->touchPoints().size() == 3) q->_tripleTouch = true; + qDebug() << "ApplyGesture update _tripleTouch=" << q->_tripleTouch; + QTouchEvent::TouchPoint p = ev->touchPoints().at(0); + QPoint delta = p.pos().toPoint() - p.startPos().toPoint(); + enum { TapRadius = 40 }; + if (delta.manhattanLength() <= TapRadius) + { + if (event->type() == QEvent::TouchEnd) + { + result = (q->_tripleTouch) ? QGestureRecognizer::FinishGesture : QGestureRecognizer::CancelGesture; + } + else result = QGestureRecognizer::TriggerGesture; + } + } + } break; + + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + result = QGestureRecognizer::Ignore; + break; + default: + result = QGestureRecognizer::Ignore; + break; + } + + return result; + } // TabGestureRecognizer::recognize( QGesture * state, QObject * watched, QEvent * event) + + + void ApplyGestureRecognizer::reset( QGesture * state ) + { + ApplyGesture * q = static_cast(state); + q->_tripleTouch = false; + } // TabGestureRecognizer::reset( QGesture * gesture ) + + + + }} // namspaces diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.h new file mode 100755 index 000000000..4b367870a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/gestures.h @@ -0,0 +1,71 @@ +/** +** +** @RDE_HEADER@ +** Gesture recognition +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 09.12.2015, JT : First edition for use with Qt5 +** +** ----------------------------------------------------------------------------------------------- +*/ + +#pragma once + +#include +#include +#include + + +namespace SituationMap { namespace RB +{ + class ApplyGestureRecognizer : public QGestureRecognizer + { + public: + ApplyGestureRecognizer(); + virtual ~ApplyGestureRecognizer(); + + QGesture * create( QObject * target ) override; + QGestureRecognizer::Result recognize( QGesture * gesture, QObject * watched, QEvent * event) override; + void reset( QGesture * gesture ) override; + + }; // ApplyGestureRecognizer + + + class ApplyGesture : public QGesture + { + friend class ApplyGestureRecognizer; + + public: + + ApplyGesture() = default; + ~ApplyGesture() = default; + + ApplyGesture( const ApplyGesture & ) = delete; + ApplyGesture & operator = ( const ApplyGesture & ) = delete; + + QPointF position() const { return _position; } + + private: + + QPointF _position; + bool _tripleTouch; + }; // ApplyGesture + +}} // namespaces diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/main.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/main.cpp new file mode 100755 index 000000000..190e66d17 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/EsriMapTouchTest/main.cpp @@ -0,0 +1,83 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "Map.h" +#include "MapGraphicsView.h" +#include "ArcGISLocalTiledLayer.h" + +#include "event_mouse_navigator.h" + + +int main (int argc, char* argv[]) +{ + QApplication app ( argc, argv ); + + // under windows for ArcGIS 10.2.6 and higher + #ifdef Q_OS_WIN + QCoreApplication::setAttribute( Qt::AA_UseOpenGLES ); + #endif + + QApplication::setApplicationName(QString("Esri - Map Touch Test")); + + QWidget top; + QHBoxLayout * layout = new QHBoxLayout( &top ); + QTextEdit * console = new QTextEdit( &top ); + EsriRuntimeQt::Map * map = new EsriRuntimeQt::Map(); + EsriRuntimeQt::MapGraphicsView * mapGraphicsView = EsriRuntimeQt::MapGraphicsView::create(map); + + QString samplePath = EsriRuntimeQt::ArcGISRuntime::installDirectory(); + + samplePath.append("/sdk/samples/data/tpks/Topographic.tpk"); + + samplePath = QDir::toNativeSeparators(samplePath); + + if (!QFileInfo::exists(samplePath)) + { + qDebug() << "Load map from not existing file" << samplePath; + } + + EsriRuntimeQt::ArcGISLocalTiledLayer * geoMapBaseLayer = new EsriRuntimeQt::ArcGISLocalTiledLayer( samplePath, &top ); + geoMapBaseLayer -> setName( "Base Map Layer" ); + + QObject::connect( geoMapBaseLayer, &EsriRuntimeQt::ArcGISLocalTiledLayer::layerCreateError , []() { qDebug() << "Loading map failed!"; } ); + QObject::connect( geoMapBaseLayer, &EsriRuntimeQt::ArcGISLocalTiledLayer::layerCreateComplete, []() { qDebug() << "Loading map finished."; } ); + + map->setAnimationMode( EsriRuntimeQt::AnimationMode::NoAnimation ); + map->addLayer(geoMapBaseLayer); + map->setWrapAroundEnabled(true); + + map->grid()->setType( EsriRuntimeQt::GridType::Mgrs ); + map->grid()->setVisible(false); + + mapGraphicsView -> setFixedSize( 600, 400 ); + + SituationMap::RB::EventMouseNavigator * eventDispatcher = new SituationMap::RB::EventMouseNavigator( mapGraphicsView->viewport(), map ); + layout->addWidget( mapGraphicsView ); + layout->addWidget( console ); + + top.show(); + + QPushButton * button = new QPushButton( QString("Touch or Click me"), nullptr ); // button must be toplevel widget + QGraphicsProxyWidget * widgetProxy = mapGraphicsView->scene()->addWidget( button ); + + widgetProxy -> setPos( mapGraphicsView->mapToScene(250,200) ); + + QObject::connect( button, &QPushButton::clicked, [console]() { console->append(QString("Button has been clicked.\n")); }); + QObject::connect( button, &QPushButton::pressed, [console]() { console->append(QString("Button has been pressed.\n")); }); + + return app.exec(); +} // main (int argc, char* argv[]) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/CMakeLists.txt new file mode 100755 index 000000000..bbcc6de44 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/CMakeLists.txt @@ -0,0 +1,20 @@ + +cmake_minimum_required(VERSION 3.16) + +SET(QT_MIN_VERSION 6.0.0) +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +SET(CMAKE_CXX_STANDARD 11) + +FILE ( GLOB sourceFiles *.h *.cpp ) + +FIND_PACKAGE(OpenGL) +FIND_PACKAGE(Qt6 COMPONENTS Core Gui OpenGL Widgets REQUIRED) + +INCLUDE_DIRECTORIES ( ${QT_INCLUDE_DIR} ) + +SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + +ADD_EXECUTABLE( GraphicsViewTouchTest WIN32 ${sourceFiles} ) + +target_link_libraries( GraphicsViewTouchTest Qt6::Core Qt6::Gui Qt6::OpenGL Qt6::Widgets ) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/main.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/main.cpp new file mode 100755 index 000000000..c2b029a7f --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/GraphicsViewTouchTest/main.cpp @@ -0,0 +1,86 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +class View : public QGraphicsView +{ + private: + + QPushButton * m_button; + QPushButton * m_reset; + QTextEdit * m_console; + + public: + + View(QGraphicsScene * scene, QTextEdit * console, QWidget * parent = nullptr) : QGraphicsView(scene, parent), m_console(console) + { + setFixedSize( 600, 400 ); + + QGraphicsProxyWidget * widgetProxy; + + m_reset = new QPushButton( "Reset", nullptr ); // button must be toplevel widget + widgetProxy = scene->addWidget( m_reset ); + widgetProxy -> setPos( this->mapToScene(150,100) ); + + connect(m_reset, &QPushButton::clicked, this, [this]() + { + this->m_console->append("Reset cursor.\n"); + this->viewport()->setCursor( Qt::CursorShape::CrossCursor ); + }); + + m_button = new QPushButton( "Touch or Click me \n to change cursor.", nullptr ); // button must be toplevel widget + widgetProxy = scene->addWidget( m_button ); + widgetProxy -> setPos( this->mapToScene(250,200) ); + + connect(m_button, &QPushButton::clicked, this, [this]() + { + this->m_console->append("Button has been clicked --> cursor shape changed to cross cursor.\n"); + this->viewport()->setCursor( Qt::CursorShape::CrossCursor ); + update(); + }); + + connect(m_button, &QPushButton::pressed, this, [this]() + { + this->m_console->append("Button has been pressed.\n"); + }); + + this->viewport()->setCursor( Qt::CursorShape::PointingHandCursor ); + + } // View( QGraphicsScene * scene, QWidget * parent = nullptr ) + +}; // class View + + +int main (int argc, char* argv[]) +{ + QApplication app ( argc, argv ); + + QApplication::setApplicationName("QGraphicsView Touch Test"); + + QGraphicsScene scene; + QWidget top; + QHBoxLayout * layout = new QHBoxLayout( &top ); + QTextEdit * console = new QTextEdit( &top ); + View * view = new View( &scene, console, &top ); + + layout->addWidget( view ); + layout->addWidget( console ); + + top.show(); + + return app.exec(); +} // main (int argc, char* argv[]) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/CMakeLists.txt new file mode 100755 index 000000000..390444c8b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# EXAMPLE +SET(EXAMPLE_SRCS + lineOfSightEvaluation.cpp +) + +SET(EXAMPLE_HDRS +) + +IF(MSVC) + + # Configure the template file + SET(VS_USER_FILE exampleSvgRenderer.vcxproj.user) + configure_file( ../../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) + +ENDIF(MSVC) + +NGL_ADD_EXECUTABLE(WS NO_TRANSLATION lineOfSightEvaluation ${EXAMPLE_SRCS} ${EXAMPLE_HDRS}) +NGL_TARGET_LINK_LIBRARIES(lineOfSightEvaluation + NGLLogger + nglapplication + Qt6::Widgets + Qt6::Core + Qt6::Svg + Qt6::Gui + + ArcGIS::ArcGIS + ArcGISQt5Hack + + situationMapCommon + situationMapElevationMap +) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/lineOfSightEvaluation.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/lineOfSightEvaluation.cpp new file mode 100755 index 000000000..01e327c74 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/LineOfSightEvaluation/lineOfSightEvaluation.cpp @@ -0,0 +1,330 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include +#include +#include +#include +#include + + +#include +#include + +#include + +#include + +#include +using namespace SituationMap; + + +// some helper functions +inline Q_CONSTEXPR double pi() { return M_PI; } +inline Q_CONSTEXPR double deg2rad() { return pi() / 180.; } +inline Q_CONSTEXPR double rad2deg() { return 1. / deg2rad(); } + + +//---------------------------------------------------------------------------- +/*! + \brief Define visibility. + */ +enum class LineOfSightVisibility +{ + Undefined, /*!< May be no data? */ + Visible, + Obstructed +}; +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +class ObserverTargetData +{ +public: + explicit ObserverTargetData(const Point3D &oberserver = Point3D(), // z-value: elevation@terrain + observerHeight + const Point3D &target = Point3D()) // z-value: elevation@terrain + targetHeight + : m_oberserver(oberserver), + m_target(target) + {} + + const Point3D &observer() const { return m_oberserver; } + const Point3D &target() const { return m_target; } + + const Point3D &obstacle() const { return m_obstacle; } + void setObstacle(const Point3D &obstacle) { m_obstacle = obstacle; } + + LineOfSightVisibility lineOfSightVisibility() const { return m_visible; } + void setLineOfSightVisibility(const LineOfSightVisibility &visible) + { + m_visible = visible; + } + + bool isObserverAndTargetValid() + { + return (!m_oberserver.isNull()) && (!m_target.isNull()); + } + +private: + Point3D m_oberserver; + Point3D m_target; + + Point3D m_obstacle; + LineOfSightVisibility m_visible = LineOfSightVisibility::Undefined; + +}; +//---------------------------------------------------------------------------- + + + +class LineOfSightCalculation +{ +public: + explicit LineOfSightCalculation(ElevationMap *elevationMap) + : m_logger(getLogger("LineOfSightCalculation")) + , m_elevationMap(elevationMap) + { + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + } + + bool calculate(ObserverTargetData &observerTarget, unsigned int sampleIntervallMeter = 5) + { + // same checks: + if (!m_elevationMap || !m_elevationMap->isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no elevationMap configured or not valid"; + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + return false; + } + + if (!observerTarget.isObserverAndTargetValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " observer and/or target not valid"; + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + return false; + } + + if (observerTarget.observer().toPointF() == observerTarget.target().toPointF()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " observer and target are identical regard x and y"; + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + return false; + } + + + bool success = true; + + QElapsedTimer timer; + if (m_logger.isErrorEnabled()) + timer.start(); + + // + // + // consider using ElevationMap::LOSProfile + // + // + + // determine how many iterations we need: + double distanceInMeter = distanceMeter(observerTarget.observer(), + observerTarget.target()); + ERROR_LOG(m_logger) << LOGFUNCTION << " distanceMeter = " << distanceInMeter; + + int maxNumberOfIterations = distanceInMeter / sampleIntervallMeter; // check visibility every ~5m + // maxNumberOfIterations = 1000000; + + if (maxNumberOfIterations<=0) + maxNumberOfIterations = 5; + + + double distanceFromObserverToTargetAsFraction = 0; + int i = 0; + while (i<=maxNumberOfIterations) + { + /* + + z-axis (Elevation) pCandidate + |---------- / + | ---------- + | ---------- + | ---------- + | ---------- + |------------------------------------------------------------ xy-axis + ^ ^ ^ ^ ^ ^ ^ ^ samples (every ~5meters) + ^ ^ + ObserverPosition TargetPosition + + On every sample position, check if directLineElevation (maximum) is + obstructed by elevationMapElevation (real value) + if (elevationMapElevation>directLineElevation) -> obstructed/break + */ + + distanceFromObserverToTargetAsFraction = (double)i/maxNumberOfIterations; + + + const Point3D &pCandidate = movePoint(observerTarget.observer(), + observerTarget.target(), + distanceFromObserverToTargetAsFraction); + +// { +// static Point3D oldCandidate; +// double distanceInMeter = distanceMeter(pCandidate, oldCandidate); +// ERROR_LOG(m_logger) << LOGFUNCTION << " distanceMeter = " << distanceInMeter; +// oldCandidate = pCandidate; +// } + + + // z is the elevation of a *direct* line: + const double directLineElevation = pCandidate.z(); + const double elevationMapElevation = m_elevationMap->elevation(pCandidate.x(), pCandidate.y()); + + // Coord covered by elevationMap? + if (elevationMapElevation <= std::numeric_limits::min()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, coordinate not covered be elevationData. Finish this LineOfSightCalculation." + << " i=" << i + << ", pCandidate = " << pCandidate; + + // write back data + observerTarget.setObstacle(pCandidate); + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined); + + // finish the loop: + success = false; + break; + } + + // analyze / check visibility: + if (elevationMapElevation>directLineElevation) + { + INFO_LOG(m_logger) << LOGFUNCTION << " i=" << i + << " break - pCandidate = " << pCandidate + << " -> directLineElevation = " << directLineElevation + << " -> elevationMapElevation = " << elevationMapElevation; + + // write result + observerTarget.setObstacle(pCandidate); + observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Obstructed); + + // finish the loop: + break; + } + + + ++i; + } // while + + + if (m_logger.isErrorEnabled()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " END" + << ", success = " << success + << ", " << i << " of "<< maxNumberOfIterations << " iterations" + << ", distanceFromObserverToTargetAsFraction = " << distanceFromObserverToTargetAsFraction + << ", pCandidate=" << observerTarget.obstacle() + << ", lineOfSightVisibility=" << static_cast(observerTarget.lineOfSightVisibility()) + << ", took [ms] " << timer.elapsed(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", success = " << success; + return success; + } + +protected: + static Point3D movePoint(const Point3D &oberserver, + const Point3D &target, + double distanceFromObserverToTargetAsFraction) // 0.0 ... 1.0 + { + // positions are lon/lat @ rad. + // for evaluation we do that very simple: + + // strange, but: using Point3D seems to be faster then using QPointF + const Point3D &dist = target - oberserver; + const Point3D &point = oberserver + (dist * distanceFromObserverToTargetAsFraction); + +// FATAL_LOG(m_logger) << LOGFUNCTION << " obs = " << oberserver; +// FATAL_LOG(m_logger) << LOGFUNCTION << " tgt = " << target; +// FATAL_LOG(m_logger) << LOGFUNCTION << " fct = " << distanceFromObserverToTargetInPercent; +// FATAL_LOG(m_logger) << LOGFUNCTION << " mvd = " << point; + + return point; + } + + static double distanceMeter(const Point3D &oberserver, + const Point3D &target) + { + Esri::ArcGISRuntime::Point geom1(oberserver.x()*rad2deg(), oberserver.y()*rad2deg(), oberserver.z(), Esri::ArcGISRuntime::SpatialReference::wgs84()); + Esri::ArcGISRuntime::Point geom2(target.x() *rad2deg(), target.y() *rad2deg(), target.z() , Esri::ArcGISRuntime::SpatialReference::wgs84()); + + Esri::ArcGISRuntime::GeometryEngine *gEngine = Esri::ArcGISRuntime::GeometryEngine::instance(); + Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistanceResult = gEngine->distanceGeodetic(geom1, geom2, + Esri::ArcGISRuntime::LinearUnitId::Meters, + Esri::ArcGISRuntime::AngularUnitId::Degrees, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + double distance = geodeticDistanceResult.distance(); + + return distance; + } + + + + +private: + LOGGER_T m_logger; ///< Interface of the Logging-Facility + ElevationMap *m_elevationMap = nullptr; + + ObserverTargetData m_observerTarget; +}; + + + +#include + + +int main (int argc, char* argv[]) +{ + NGLApplication app ( argc, argv ); + + app.createLogger("lineOfSightEvaluation", "log4cxx_sitmapMain.props"); + + LOGGER_T m_logger=app.baseLogger(); + + STARTUP_LOG(m_logger); + + + QString elevationFileAbs(QLatin1String("/usr/share/arcgis/resources/elevation/srtm_38_02.tif")); // srtm_38_02.tif -> Bremen + QFileInfo fi (elevationFileAbs); + + if (!fi.exists(elevationFileAbs)) + { + ERROR_LOG(m_logger) << LOGFUNCTION << ", elevationFile '" << elevationFileAbs << "' doesn't exists"; + return -1; + } + + ElevationMap *elevationMap = new ElevationMap(fi); + + elevationMap->waitForInitialization(); + ERROR_LOG(m_logger) << LOGFUNCTION << " elevation.isValid() =" << elevationMap->isValid(); + + //qCritical() << LOGFUNCTION << " elevation.isValid() = " << elevationMap->isValid() + // << " elevation =" << elevationMap->elevation(0.155, 0.926); + + LineOfSightCalculation losCalc(elevationMap); + + ObserverTargetData obsTgt(Point3D(0.1504, 0.926, 20), + Point3D(0.1530, 0.926, 5)); + + losCalc.calculate(obsTgt); + + int iRet = app.exec(); + + delete elevationMap; + SHUTDOWN_LOG(m_logger); + + return iRet; +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.cpp new file mode 100755 index 000000000..94e9b8419 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.cpp @@ -0,0 +1,267 @@ +/* +** +** @RDE_HEADER@ +** Shows a crash when using QGraphicsItems and ESRI-Graphics in same scene +** $Id$ +** $HeadURL$ +** $Author$ +** +** ----------------------------------------------------------------------------------------------- +** +** Hardware-Environment: Intel-PC +** Software-Environment: Windows 7, Qt 5.7. ArcGISRuntimeQt 100 +** +** Language ........: C++ 11 +** +** ----------------------------------------------------------------------------------------------- +** +** Author ...........: Jörg Turla, SPD G, Tel. 3132 +** +** ----------------------------------------------------------------------------------------------- +** +** Notes .........: +** +** 16.01.2017, JT : Initial +** +** ----------------------------------------------------------------------------------------------- +*/ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * The class MapWindow creates a tiled base map from an UTM based TPK from Bremen. Loading the map will be done + * by clicking the "Load Map" button. The next 2 buttons creates graphics on top of the map, as follows: + * + * (1) Draw QGraphicsPathItem + * The "Draw QGraphicsPathItem" is creating an QGraphicsItemPath. The QGraphicsItemPath has a blue border line and + * is filled with a tranparent green color. See drawQGraphicPathItem(). + * + * (2) Draw Overlay + * The "Draw Overlay" is creating GraphicsOverlay with one simple polygon. See buildSymbol(). + * + * All is working as expected when using only one of each button, restart the program and click the other one. + * Each of both drawings are ok. In one case you will see a red filled polygon and in other case a green polygon + * with a border is drawn on the map. + * + * But now restart the program click "Draw QGraphicsPathItem" the green area with + * a blue border is visible now. After that click "Draw Overlay". The red area of GraphicOverlay polygon appears + * on the map. But the green filling of the QGraphicsItemPath has been vanished. + * + * Restart: Next click the buttons in reversed order ( "Draw Overlay" first and next "Draw QGraphicsPathItem"). On Windows + * the behaviour is the same as before - the green polygon filling is not drawn. On Linux a crash in the OpenGl + * Driver appears. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +class MapWindow : public QWidget +{ + Esri::ArcGISRuntime::ArcGISTiledLayer * m_tiledLayer; + Esri::ArcGISRuntime::Basemap * m_basemap; + Esri::ArcGISRuntime::Map * m_map; + Esri::ArcGISRuntime::MapGraphicsView * m_mapView; + + private: + + + public: + + MapWindow() : QWidget(), m_tiledLayer(nullptr) + { + m_basemap = new Esri::ArcGISRuntime::Basemap(this); + m_map = new Esri::ArcGISRuntime::Map(this); + + m_map->setAutoFetchLegendInfos(true); + + m_mapView = new Esri::ArcGISRuntime::MapGraphicsView(this); + + m_mapView->setObjectName(QLatin1String("EsriMapView")); + m_mapView->setFrameShape(QFrame::NoFrame); + m_mapView->setWrapAroundMode(Esri::ArcGISRuntime::WrapAroundMode::Disabled); + m_mapView->setFixedSize(900,900); + + + QPushButton * btn1 = new QPushButton(QStringLiteral("Load Map"), this); + QPushButton * btn2 = new QPushButton(QStringLiteral("Draw QGraphicsPathItem"), this); + QPushButton * btn3 = new QPushButton(QStringLiteral("Draw Overlay (Esri primitive)"), this); + + btn2->setEnabled(false); + btn3->setEnabled(false); + + connect( btn1, &QPushButton::clicked, this, [this](bool) { initMap(); }); + connect( btn2, &QPushButton::clicked, this, [this](bool) { drawQGraphicPathItem(); }); + connect( btn3, &QPushButton::clicked, this, [this](bool) { buildSymbol(); }); + + QVBoxLayout * layout = new QVBoxLayout(this); + + layout->addWidget(btn1); + layout->addWidget(btn2); + layout->addWidget(btn3); + layout->addWidget(m_mapView,2); + + + connect(m_map, &Esri::ArcGISRuntime::Map::doneLoading, this, [this,btn2,btn3](Esri::ArcGISRuntime::Error loadError) + { + if (loadError.isEmpty()) + { + btn2->setEnabled(true); + btn3->setEnabled(true); + } else { + qCritical() << loadError.message(); + qCritical() << loadError.additionalMessage(); + + // Esri::ArcGISRuntime::ErrorDomainType errorDomain = loadError.domain(); + // Esri::ArcGISRuntime::ExtendedErrorType extendedErr = loadError.extendedErrorType(); + } + + }); + } // MapWindow() + + + void initMap() + { + //m_tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(QUrl("http://192.168.133.90:6080/arcgis/rest/services/BremenGeoTiffUTM/MapServer")); + Esri::ArcGISRuntime::TileCache * tpkCache = new Esri::ArcGISRuntime::TileCache("/usr/share/arcgis/resources/Topographic.tpk", this); + + m_tiledLayer = new Esri::ArcGISRuntime::ArcGISTiledLayer(tpkCache); + + m_basemap->baseLayers()->append(m_tiledLayer); + + m_map->setBasemap(m_basemap); + + Esri::ArcGISRuntime::GraphicsOverlayListModel * graphicsOverlayListModel = m_mapView->graphicsOverlays(); + Esri::ArcGISRuntime::GraphicsOverlay * graphicalOverlay = new Esri::ArcGISRuntime::GraphicsOverlay(); + + graphicalOverlay->setSelectionColor(Qt::magenta); + graphicsOverlayListModel->append(graphicalOverlay); + + m_mapView->setMap(m_map); + + + } // void initMap() + + + ~MapWindow() = default; + + void buildSymbol() + { + Esri::ArcGISRuntime::GraphicsOverlay * layer = new Esri::ArcGISRuntime::GraphicsOverlay(this); + m_mapView->graphicsOverlays()->append(layer); + + //layer->setRenderingMode(Esri::ArcGISRuntime::GraphicsRenderingMode::Static); + layer->setRenderingMode(Esri::ArcGISRuntime::GraphicsRenderingMode::Dynamic); + + Esri::ArcGISRuntime::PolygonBuilder builder( m_map->spatialReference() ); + + builder.addPoint( Esri::ArcGISRuntime::Point( 0, 0,m_map->spatialReference()) ); + builder.addPoint( Esri::ArcGISRuntime::Point( 0,5878900,m_map->spatialReference()) ); + builder.addPoint( Esri::ArcGISRuntime::Point(493200,5880900,m_map->spatialReference()) ); + builder.addPoint( Esri::ArcGISRuntime::Point(495200, 0,m_map->spatialReference()) ); + + Esri::ArcGISRuntime::Graphic * graphic = new Esri::ArcGISRuntime::Graphic(this); + Esri::ArcGISRuntime::SimpleFillSymbol * simpleFillSym = new Esri::ArcGISRuntime::SimpleFillSymbol(this); + + simpleFillSym->setColor( QColor("red") ); + simpleFillSym->setStyle( Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid ); + + graphic->setGeometry( builder.toPolygon() ); + + graphic->setSymbol(simpleFillSym); + + //add the graphic to the graphics overlay + layer->graphics()->append(graphic); + + } // void buildSymbol() + + + + void drawQGraphicPathItem() + { + QGraphicsScene * scene = m_mapView->scene(); + QGraphicsPathItem * pathItem = scene->addPath( QPainterPath() ); + + QColor drawColor("darkblue"); + + QColor fillColor("green"); + fillColor.setAlphaF( 0.5 ); + + pathItem -> setFlags( QGraphicsItem::ItemIgnoresTransformations ); + + pathItem -> setPen ( QPen(drawColor) ); + pathItem -> setBrush ( QBrush(fillColor) ); + pathItem -> setZValue( 100 ); + + QPainterPath painterPath; + + painterPath.moveTo(QPointF(200,200)); + painterPath.lineTo(QPointF(700,200)); + painterPath.lineTo(QPointF(450,700)); + painterPath.lineTo(QPointF(200,700)); + painterPath.closeSubpath(); + + pathItem -> setPath( painterPath ); + } // void drawQGraphicPathItem() + +}; // MapWindow + + +int main(int argc, char* argv[]) +{ + QApplication app(argc,argv); + + // ESRI : +#ifdef Q_OS_WIN + // Force usage of OpenGL ES through ANGLE on Windows + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); +#endif + // : ESRI + + MapWindow * mapWin = new MapWindow(); + + int width = mapWin->frameGeometry().width(); + int height = mapWin->frameGeometry().height(); + + QScreen *screen = QGuiApplication::primaryScreen(); + int screenWidth = screen ? screen->size().width() : 1920; + int screenHeight = screen ? screen->size().height() : 1080; + + mapWin->setGeometry((screenWidth / 2) - (width / 2), (screenHeight / 2) - (height / 2), width, height); + + mapWin->show(); + + app.setQuitOnLastWindowClosed(true); + + int retval = app.exec(); + + delete mapWin; + + return retval; + +} // main diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.pro b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.pro new file mode 100755 index 000000000..414350b86 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/QGraphicsItem_Fill/QGraphicsItem_FillBug.pro @@ -0,0 +1,47 @@ +#------------------------------------------------- +# Copyright 2015 Esri. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#------------------------------------------------- + +mac { + cache() +} + + +CONFIG += c++11 esri_runtime_qt100_0_0 + +QT += core gui opengl xml network positioning sensors + +QMAKE_CXXFLAGS += -std=c++0x + + +# $$(ENVVAR) +# $(ENVVAR) +INCLUDEPATH += /XXX +LIBPATH += /XXX +LIBS += -lEsriCommonQt +LIBS += -lEsriRuntimeQt +LIBS += -lc++ + + + +win32:CONFIG += \ + embed_manifest_exe + +TARGET = QGraphicsItem_FillBug +TEMPLATE = app + +SOURCES += \ + QGraphicsItem_FillBug.cpp + +HEADERS += diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/CMakeLists.txt new file mode 100755 index 000000000..3a345c0ba --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/CMakeLists.txt @@ -0,0 +1,32 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# EXAMPLE +SET(EXAMPLE_SRCS + main.cpp + Viewshed.cpp +) + +SET(EXAMPLE_HDRS + Viewshed.h +) + + +IF(MSVC) + + # Configure the template file + SET(VS_USER_FILE rdeexample.vcxproj.user) + configure_file( ../../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) + +ENDIF(MSVC) + +NGL_ADD_EXECUTABLE(WS rdeexample_viewshed ${EXAMPLE_SRCS} ${EXAMPLE_HDRS}) +NGL_TARGET_LINK_LIBRARIES(rdeexample_viewshed + NGLLogger + situationMap + ArcGIS::ArcGIS +) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.cpp new file mode 100755 index 000000000..fc1350aaa --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.cpp @@ -0,0 +1,556 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "Viewshed.h" + + +// Qt +#include +#include +#include +#include +#include +#include + + +// Esri +#include +#include +#include +#include +#include +#include + + + +// Uncomment if needed +//#include +//#include +//#include +//#include +//#include +//#include + + +//#define USE_SAMPLE_SERVER + + +EsriRuntimeQt::SpatialReference sr(102100); + +mainwindow::mainwindow(QWidget *parent) +: QMainWindow(parent) +{ + m_map = new EsriRuntimeQt::Map(this); + + connect(m_map, SIGNAL(mapReady()), this, SLOT(onMapReady())); + + m_mapGraphicsView = EsriRuntimeQt::MapGraphicsView::create(m_map, this); + setCentralWidget(m_mapGraphicsView); + m_map->setWrapAroundEnabled(false); + + // create and add the tiled layer + EsriRuntimeQt::ArcGISTiledMapServiceLayer* mapServiceLayer = + new EsriRuntimeQt::ArcGISTiledMapServiceLayer(QLatin1String("http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer"), this); + m_map->addLayer(mapServiceLayer); + + QString path = EsriRuntimeQt::ArcGISRuntime::installDirectory(); + path.append(QLatin1String("/sdk/samples/data")); + QDir dataDir(path); // using QDir to convert to correct file separator + QString pathSampleData = dataDir.path() + QDir::separator(); + qDebug() << " installDirectory='" << path << "'"; + + QString tiledBaseMapLayer = pathSampleData + QLatin1String("tpks/Topographic.tpk"); + qDebug() << " tiledBaseMapLayer='" << tiledBaseMapLayer << "'"; + +// m_tiledLayer = new EsriRuntimeQt::ArcGISLocalTiledLayer(tiledBaseMapLayer, this); +// m_tiledLayer->setName( QLatin1String("ArcGISLocalTiledLayer") ); +// m_map->addLayer(m_tiledLayer); + + + // Create Graphics Layer + m_graphicsLayer = new EsriRuntimeQt::GraphicsLayer(this); + m_graphicsLayer -> setName(QLatin1String("GraphicsLayer")); + + m_map->addLayer(m_graphicsLayer); + +// { +// // +// // Create red circle at maps origin +// // + +// EsriRuntimeQt::Point nullPoint(0, 0, m_map->spatialReference()); +// EsriRuntimeQt::SimpleMarkerSymbol redCircle(Qt::red, 20, EsriRuntimeQt::SimpleMarkerSymbolStyle::Circle); +// EsriRuntimeQt::Graphic* graphic1 = new EsriRuntimeQt::Graphic( nullPoint, redCircle); + +// qint64 gId = m_graphicsLayer->addGraphic(graphic1); + +// // +// // Get access to graphic and hide: -> Works fine :-) +// // +// EsriRuntimeQt::Graphic* graphicRetrieved = m_graphicsLayer->graphic(gId); + +// if (graphicRetrieved) +// { +// graphicRetrieved->setVisible(false); +// } else { +// qWarning() << LOGFUNCTION << "could NOT retrieve graphic" ; +// } +// } + + + QString url; +#ifdef USE_SAMPLE_SERVER + url = QLatin1String("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/GPServer/Viewshed"); +#else + url = QLatin1String("http://elevation.arcgis.com/arcgis/rest/services/Tools/Elevation/GPServer/Viewshed"); +#endif + + qDebug() << LOGFUNCTION << " url='" << url << "'"; + + + // create a Geoprocessor that points to the remote geoprocessing service. + EsriRuntimeQt::UserCredentials *ucEsri = new EsriRuntimeQt::UserCredentials; + ucEsri->setUserAccount("norbert.thoden", "RDEESRI7"); + m_geoprocessor = new EsriRuntimeQt::Geoprocessor(url, + ucEsri, + this); + + connect(m_geoprocessor, + SIGNAL(gpError(const EsriRuntimeQt::ServiceError&)), this, + SLOT(onGpError(const EsriRuntimeQt::ServiceError&))); + +#ifdef USE_SAMPLE_SERVER + connect(m_geoprocessor, + SIGNAL(gpExecuteComplete(const QList&)), this, + SLOT(onGpExecuteComplete(const QList&))); + +#else + connect(m_geoprocessor, + SIGNAL(gpSubmitJobComplete(const EsriRuntimeQt::GPJobResource&)), this, + SLOT(onGpSubmitJobComplete(const EsriRuntimeQt::GPJobResource&))); + // used: + connect(m_geoprocessor, + SIGNAL(gpJobStatusComplete(const EsriRuntimeQt::GPJobResource&)), this, + SLOT(onGpJobStatusComplete(const EsriRuntimeQt::GPJobResource&))); + + // used: + connect(m_geoprocessor, + SIGNAL(gpResultComplete(EsriRuntimeQt::GPParameter*)), this, + SLOT(onGpResultComplete(EsriRuntimeQt::GPParameter*))); + + // unused!? + connect(m_geoprocessor, + SIGNAL(gpServiceInfoComplete(EsriRuntimeQt::GPServiceInfo)), this, + SLOT(onGpServiceInfoComplete(EsriRuntimeQt::GPServiceInfo))); + + // unused!? + connect(m_geoprocessor, + SIGNAL(gpSubmitJobAndGetResultComplete(const EsriRuntimeQt::GPJobResource&, const QList&)), this, + SLOT(onGpSubmitJobAndGetResultComplete(const EsriRuntimeQt::GPJobResource&, const QList&))); + + // unused!? + connect(m_geoprocessor, + SIGNAL(statusUpdated(const EsriRuntimeQt::GPJobResource&, const QList&)), this, + SLOT(onStatusUpdated(const EsriRuntimeQt::GPJobResource&, const QList&))); + + // unused!? + connect(m_geoprocessor, + SIGNAL(gpTaskInfoComplete(EsriRuntimeQt::GPTaskInfo)), this, + SLOT(onGpTaskInfoComplete(const EsriRuntimeQt::GPTaskInfo&))); +#endif + + // connect signals + connect(m_map, SIGNAL(mapReady()), this, SLOT(onMapReady())); + connect(m_map, SIGNAL(mousePressRelease(QMouseEvent&)), this, SLOT(onMousePressRelease(QMouseEvent&))); + + +// // set up progress bar +// m_progressDialog = new QProgressDialog(); +// m_progressDialog->setBaseSize(280, 20); +// m_progressDialog->setRange(0, 0); +// m_progressDialog->setValue(0); +// m_progressDialog->setCancelButton(0); +// connect(m_mapGraphicsView->scene(), SIGNAL(sceneRectChanged(QRectF)), this, SLOT(onResizeProgressBar())); +// m_progressDialog->setLabelText(""); +// m_progressDialog->setAttribute(Qt::WA_NoBackground); +// m_progressDialog->setAutoFillBackground(false); +// m_progressDialog->setVisible(false); + +// WidgetPanel* widget = new WidgetPanel(); +// QLabel* instructionsLabel = new QLabel( +// QString("Click on the map to set the viewshed point. The viewshed will be computed ") + +// QString("from the specified buffer area around the viewshed point. ")); +// instructionsLabel->setWordWrap(true); +// QFont font; +// font.setPixelSize(11); +// instructionsLabel->setFont(font); +// instructionsLabel->setMargin(5); +// instructionsLabel->setObjectName("instructions"); +// instructionsLabel->setStyleSheet("QLabel#instructions { background: rgba(0,0,0, 192); color: #ddd; }"); +// instructionsLabel->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); +// +// QVBoxLayout* layout = new QVBoxLayout(); +// layout->setMargin(0); +//// layout->addWidget(instructionsLabel, Qt::AlignLeft); + +// QHBoxLayout* horizontalBox = new QHBoxLayout(); +// horizontalBox->setMargin(1); + +// QFont font; +// font.setPixelSize(12); +// QLabel* distanceLabel = new QLabel("Viewshed Buffer (miles):"); +// distanceLabel->setFixedSize(180, 25); +// distanceLabel->setFont(font); +// distanceLabel->setMargin(5); +// horizontalBox->addWidget(distanceLabel, Qt::AlignRight); + +// m_textDistanceLineEdit = new QLineEdit("9"); +// m_textDistanceLineEdit->setFixedSize(60, 25); +// m_textDistanceLineEdit->setFont(font); + +// horizontalBox->addWidget(m_textDistanceLineEdit, Qt::AlignLeft); +// layout->addLayout(horizontalBox); + +//// widget->setFixedSize(210, 105); +//// widget->setLayout(layout); + +// QGraphicsProxyWidget *proxy = m_mapGraphicsView->scene()->addWidget(widget); +// proxy->setPos(10, 10); +// proxy->setAcceptedMouseButtons(Qt::LeftButton); +// proxy->setFlag(QGraphicsItem::ItemIsFocusable, true); +// proxy->setFlag(QGraphicsItem::ItemIsSelectable, true); +// proxy->setFlag(QGraphicsItem::ItemAcceptsInputMethod, true); +// proxy->setOpacity(0.85); + +// m_proxyProgressBar = m_mapGraphicsView->scene()->addWidget(m_progressDialog); +// m_proxyProgressBar->setAcceptedMouseButtons(Qt::NoButton); +// m_proxyProgressBar->setFlag(QGraphicsItem::ItemIsSelectable, false); + + + + +// m_viewShed = new +// SituationMap::ViewshedBase( +// m_geoprocessor->url() +// , *m_graphicsLayer, +// this); + +// bool init = m_viewShed->init(); +// qDebug() << LOGFUNCTION << " init=" << init; + + +} /* mainwindow::mainwindow */ + + +mainwindow::~mainwindow() +{ +} + + +void mainwindow::onMapReady() +{ + qDebug() << LOGFUNCTION << " ready"; +// m_map->setExtent(EsriRuntimeQt::Envelope(-10000, -10000, 10000, 10000, sr)); + + m_map->setExtent(EsriRuntimeQt::Envelope(-13386516, 4516694, -13236394, 4616827, m_map->spatialReference())); + m_srMap = m_map->spatialReference(); +} + + +void mainwindow::onMousePressRelease(QMouseEvent &event) +{ + qDebug() << LOGFUNCTION; + + if (event.button() != Qt::LeftButton) + return; + + if (m_inProgress) + return; + + QPoint scenePosition = event.pos(); + +// // make sure the mouse didn't move - if so it was a pan so do nothing +// QGraphicsItem * graphicsItem = nullptr; +// graphicsItem = itemFromQGraphicsScene(m_mapGraphicsView->scene(), scenePosition); +// if (graphicsItem) +// return; // Clicked item box + + // remove viewshed from previous computation + m_graphicsLayer->removeAll(); + + int inputValue = 10; +// inputValue = m_textDistanceLineEdit->text().toInt(); + if (inputValue > 12 || inputValue < 1) + { + if (m_progressDialog) + m_progressDialog->setVisible(false); + QMessageBox::warning(this, "Invalid Distance", "Please enter an integer from 1 to 12."); + return; + } + + // the click point is the viewshed point + EsriRuntimeQt::Point viewshedPoint = m_map->toMapPoint(scenePosition.x(), scenePosition.y()); + + qDebug() << LOGFUNCTION<< " esriViewshedPoint.x=" << viewshedPoint.x() + << " ;y=" << viewshedPoint.y() + << " wkid=" << viewshedPoint.spatialReference().wkid() + << " m_inProgress=" << m_inProgress + ; + + + // make sure it is a point on the map + if (!EsriRuntimeQt::GeometryEngine::within(viewshedPoint, m_map->extent())) + return; + + m_inProgress = true; + + if (m_progressDialog) + { + m_progressDialog->setLabelText("Computing the viewshed..."); + m_progressDialog->setVisible(true); + } + + EsriRuntimeQt::Graphic* viewshedPointGraphic = new EsriRuntimeQt::Graphic(viewshedPoint, EsriRuntimeQt::SimpleMarkerSymbol(QColor("Black"), 14, EsriRuntimeQt::SimpleMarkerSymbolStyle::X)); + m_graphicsLayer->addGraphic(viewshedPointGraphic); + + // initialize the input parameters. refer to help link in the geoprocessing service URL. + m_geoprocessor->setProcessSR(m_srMap); + m_geoprocessor->setOutSR(m_srMap); + + QList gpInputParams; + +#ifdef USE_SAMPLE_SERVER + EsriRuntimeQt::GPFeatureRecordSetLayer* gpInputViewshedPoint = + new EsriRuntimeQt::GPFeatureRecordSetLayer("Input_Observation_Point", this); +#else + EsriRuntimeQt::GPFeatureRecordSetLayer* gpInputViewshedPoint = + new EsriRuntimeQt::GPFeatureRecordSetLayer("InputPoints", this); +#endif + gpInputViewshedPoint->setSpatialReference(m_srMap); + gpInputViewshedPoint->addGraphic(qobject_cast(viewshedPointGraphic->clone(this))); + + + +#ifdef USE_SAMPLE_SERVER + EsriRuntimeQt::GPLinearUnit* gpInputViewshedDistance = + new EsriRuntimeQt::GPLinearUnit("Viewshed_Distance", this); + gpInputViewshedDistance->setUnits("esriMiles"); + gpInputViewshedDistance->setDistance(inputValue); + + gpInputParams.append(gpInputViewshedPoint); + gpInputParams.append(gpInputViewshedDistance); + +#else + EsriRuntimeQt::GPDouble* gpInputMaxDistance = + new EsriRuntimeQt::GPDouble("MaximumDistance", this); + gpInputMaxDistance->setValue(inputValue); + + EsriRuntimeQt::GPString* gpInputMaxDistanceUnits = + new EsriRuntimeQt::GPString("MaximumDistanceUnits", this); + gpInputMaxDistanceUnits->setValue(QLatin1String("Miles")); + + EsriRuntimeQt::GPDouble* gpInputObserverHeight = + new EsriRuntimeQt::GPDouble("ObserverHeight", this); + gpInputObserverHeight->setValue(3000); // [Meter] + + EsriRuntimeQt::GPBoolean* gpInputGeneralize = + new EsriRuntimeQt::GPBoolean("GeneralizeViewshedPolygons", this); + gpInputGeneralize->setValue(false); + + + gpInputParams.append(gpInputViewshedPoint); + gpInputParams.append(gpInputMaxDistance); + gpInputParams.append(gpInputMaxDistanceUnits); + gpInputParams.append(gpInputObserverHeight); +// gpInputParams.append(gpInputGeneralize); +#endif + + // execute the geoprocessing request + + // ------------------------------------------------------------------------------------- + // Option 1 - execute sync + // Client execution will happen in this thread. It will be blocked on server. + // Client side result collection will happen in this thread. It will be blocked on server. + // + // This is ideal for jobs that take less processing time and whose result size is small. + // This will freeze the UI. + // ------------------------------------------------------------------------------------- + /*QList result = m_geoprocessor->execute(gpInputParams); + processResult(result);*/ + + // ------------------------------------------------------------------------------------- + // Option 2 - execute async + // Client execution will happen in a new thread. It will be blocked on server. + // Client side result collection will happen on that thread. It will be blocked on server. + // + // This is ideal for jobs that take relatively more processing time than option 1 + // and whose result size is small. + // ------------------------------------------------------------------------------------- +// m_geoprocessor->execute(gpInputParams); + + m_geoprocessor->setStatusPollingInterval(10); + +#ifdef USE_SAMPLE_SERVER + m_geoprocessor->execute( gpInputParams ); +#else + m_geoprocessor->submitJob(gpInputParams); +#endif + + + +} + +void mainwindow::onGpExecuteComplete(const QList &result) +{ + qDebug() << LOGFUNCTION << " result.size()=" << result.size(); + + for ( EsriRuntimeQt::GPParameter* outputParameter, result) + { + if (!outputParameter) + continue; + + EsriRuntimeQt::GPFeatureRecordSetLayer* gpLayer = qobject_cast(outputParameter); + if (gpLayer) + { + qDebug() << LOGFUNCTION << " gpLayer->graphics().size=" << gpLayer->graphics().size(); + // get all the graphics and add them to the graphics layer. + for ( EsriRuntimeQt::Graphic* resultGraphic, gpLayer->graphics()) + { + EsriRuntimeQt::Graphic* theGraphic = new EsriRuntimeQt::Graphic(resultGraphic->geometry(), EsriRuntimeQt::SimpleFillSymbol(QColor(255, 0, 0, 80), EsriRuntimeQt::SimpleFillSymbolStyle::Solid, EsriRuntimeQt::SimpleLineSymbol(QColor("Red"), 0.5))); + // add to the graphics layer + m_graphicsLayer->addGraphic(theGraphic); + } + } + + outputParameter->deleteLater(); + } + + if (m_progressDialog) + m_progressDialog->setVisible(false); + + m_inProgress = false; + +} + +void mainwindow::onGpError(const EsriRuntimeQt::ServiceError &error) +{ + if (m_progressDialog) + m_progressDialog->setVisible(false); + qWarning() << LOGFUNCTION << QString("Service Error, code: %1, message: %2, details: %3").arg(QString::number(error.code()), error.message(), error.details()); + + m_inProgress = false; +} + +void mainwindow::onGpSubmitJobComplete(const EsriRuntimeQt::GPJobResource &jobResource) +{ + qWarning() << "\n"; + qWarning() << LOGFUNCTION << " isEmpty=" << jobResource.isEmpty(); + qWarning() << LOGFUNCTION << " jobId='" << jobResource.jobId() << "'"; + qWarning() << LOGFUNCTION << " jobStatus='" << (int)jobResource.jobStatus() << "'"; + qWarning() << LOGFUNCTION << " inputParameters().size()='" << (int)jobResource.inputParameters().size() << "'"; + qWarning() << LOGFUNCTION << " outputParameters().size()='" << (int)jobResource.outputParameters().size() << "'"; + qWarning() << LOGFUNCTION << " messages().size()='" << (int)jobResource.messages().size() << "'"; + + + delete m_timer; + m_timer = new QTimer; + connect(m_timer, &QTimer::timeout, [=]() { + qWarning() << " jobStatus() ..."; + m_geoprocessor->jobStatus(); + + } ); + + qWarning() << "\n starting timer..."; + m_timer->start(1000); +} + +void mainwindow::onGpJobStatusComplete(const EsriRuntimeQt::GPJobResource &jobResource) +{ + qWarning() << "\n"; + qWarning() << LOGFUNCTION << " isEmpty=" << jobResource.isEmpty(); + qWarning() << LOGFUNCTION << " jobId='" << jobResource.jobId() << "'"; + qWarning() << LOGFUNCTION << " jobStatus='" << (int)jobResource.jobStatus() << "'"; + qWarning() << LOGFUNCTION << " inputParameters().size()='" << (int)jobResource.inputParameters().size() << "'"; + qWarning() << LOGFUNCTION << " outputParameters().size()='" << (int)jobResource.outputParameters().size() << "'"; + qWarning() << LOGFUNCTION << " messages().size()='" << (int)jobResource.messages().size() << "'"; + + if (jobResource.jobStatus() == EsriRuntimeQt::JobStatus::Succeeded) + { + qWarning() << " Succeeded :-)"; + + if (m_timer) + m_timer->stop(); + + m_geoprocessor->result(jobResource, + QLatin1String("OutputViewshed"), + true); // GetAsImageResult + } +} + +void mainwindow::onGpResultComplete(EsriRuntimeQt::GPParameter *outputParameter) +{ + qWarning() << "\n"; + qWarning() << LOGFUNCTION << " outputParameter=" << outputParameter; + + if (outputParameter) + { + EsriRuntimeQt::GPFeatureRecordSetLayer* gpLayer = qobject_cast(outputParameter); + if (gpLayer) + { + qDebug() << LOGFUNCTION << " gpLayer->graphics().size=" << gpLayer->graphics().size(); + // get all the graphics and add them to the graphics layer. + for ( EsriRuntimeQt::Graphic* resultGraphic, gpLayer->graphics()) + { + EsriRuntimeQt::Graphic* theGraphic = + new EsriRuntimeQt::Graphic(resultGraphic->geometry(), + EsriRuntimeQt::SimpleFillSymbol(QColor(0, 255, 0, 80), EsriRuntimeQt::SimpleFillSymbolStyle::Solid, + EsriRuntimeQt::SimpleLineSymbol(QColor("Red"), 0.5))); + // add to the graphics layer + m_graphicsLayer->addGraphic(theGraphic); + } + } + outputParameter->deleteLater(); + } +} + +void mainwindow::onGpServiceInfoComplete(EsriRuntimeQt::GPServiceInfo serviceInfo) +{ + qWarning() << "\n"; + qWarning() << LOGFUNCTION ; +} + +void mainwindow::onGpSubmitJobAndGetResultComplete(const EsriRuntimeQt::GPJobResource &, const QList &results) +{ + qWarning() << "\n"; + qWarning() << LOGFUNCTION ; +} + +void mainwindow::onStatusUpdated(const EsriRuntimeQt::GPJobResource &jobResource, const QList &msgList) +{ + qWarning() << "\n"; + qWarning() << LOGFUNCTION << " isEmpty=" << jobResource.isEmpty(); + qWarning() << LOGFUNCTION << " jobId='" << jobResource.jobId() << "'"; + qWarning() << LOGFUNCTION << " jobStatus='" << (int)jobResource.jobStatus() << "'"; + qWarning() << LOGFUNCTION << " inputParameters().size()='" << (int)jobResource.inputParameters().size() << "'"; + qWarning() << LOGFUNCTION << " outputParameters().size()='" << (int)jobResource.outputParameters().size() << "'"; + qWarning() << LOGFUNCTION << " messages().size()='" << (int)jobResource.messages().size() << "'"; + + qWarning() << LOGFUNCTION << " 2 msgList().size()='" << msgList.size() << "'"; + + m_geoprocessor->jobStatus(); +} + +void mainwindow::onGpTaskInfoComplete(const EsriRuntimeQt::GPTaskInfo &) +{ + qWarning() << "\n"; + qWarning() << LOGFUNCTION ; +} + + + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.h new file mode 100755 index 000000000..f3449dd19 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/Viewshed.h @@ -0,0 +1,87 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +namespace EsriRuntimeQt +{ +class MapGraphicsView; +} + +// Esri +#include + +//Uncommented layer(s) needed +#include +#include +#include +#include + +#include +#include + + +#include + +//#include + + + +class mainwindow : public QMainWindow +{ + Q_OBJECT +public: + mainwindow (QWidget *parent = 0); + ~mainwindow (); + +public slots: + void onMapReady(); + void onMousePressRelease(QMouseEvent& event); + + void onGpExecuteComplete(const QList& result); + void onGpError(const EsriRuntimeQt::ServiceError& error); + + void onGpSubmitJobComplete(const EsriRuntimeQt::GPJobResource& jobResource); + void onGpJobStatusComplete(const EsriRuntimeQt::GPJobResource&); + + void onGpResultComplete(EsriRuntimeQt::GPParameter* outputParameter); + void onGpServiceInfoComplete(EsriRuntimeQt::GPServiceInfo serviceInfo); + + void onGpSubmitJobAndGetResultComplete(const EsriRuntimeQt::GPJobResource&, const QList& results); + + + void onStatusUpdated(const EsriRuntimeQt::GPJobResource& jobResource, const QList &msgList); + + void onGpTaskInfoComplete(const EsriRuntimeQt::GPTaskInfo&); + + +private: + EsriRuntimeQt::Map *m_map = nullptr; + EsriRuntimeQt::MapGraphicsView *m_mapGraphicsView = nullptr; + EsriRuntimeQt::ArcGISTiledMapServiceLayer* m_tiledServiceLayer; + EsriRuntimeQt::ArcGISLocalTiledLayer *m_tiledLayer = nullptr; + EsriRuntimeQt::GraphicsLayer *m_graphicsLayer = nullptr; + + + EsriRuntimeQt::Geoprocessor *m_geoprocessor = nullptr; + EsriRuntimeQt::SpatialReference m_srMap; + QTimer *m_timer = nullptr; + + QProgressDialog *m_progressDialog = nullptr; + QLineEdit *m_textDistanceLineEdit = nullptr; + QGraphicsProxyWidget *m_proxyProgressBar = nullptr; + + bool m_inProgress = false; + + +// SituationMap::ViewshedBase *m_viewShed = nullptr; + +private slots: + + +}; + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/main.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/main.cpp new file mode 100755 index 000000000..0d45a341b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/RdeExample_Viewshed/main.cpp @@ -0,0 +1,92 @@ +/** + * @RDE_HEADER@ + * Declaration/Definition + * $Id$ + * $HeadURL$ + * $Author$ + */ +#include "Viewshed.h" + +#include + + +// Esri +#include +#include + + +// Qt +#include +#include +#include +#include + + + +int main (int argc, char* argv[]) +{ + QApplication app ( argc, argv ); +#ifdef Q_OS_WIN + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); +#endif + + +#define USEPROXY +#ifdef USEPROXY + EsriRuntimeQt::ProxySetup::setupProxy("192.168.133.178" // proxy @ mike + , 8080 + , QNetworkProxy::HttpProxy); +#endif + + +#define DO_LIC +#ifdef DO_LIC + // ESRI-Account: + QLatin1String clientId("8UJNp7vsRWT4l9lc"); // 8UJNp7vsRWT4l9lc -> taken from ESRI-Account + EsriRuntimeQt::ArcGISRuntime::setClientId(clientId); + +#define OLD_LICENSE +#ifdef OLD_LICENSE + //alter methode (langer String): + QLatin1String licenseCode("runtimestandard,101,rud077222135,none,FAYRMD1AJ42HXH46C167"); +#else + // Neue Methode (kurzer String) + QLatin1String licenseCode("RUD077222135"); // RUD077222135 -> taken from mail "ESRI order 2874794" +#endif + + + EsriRuntimeQt::LicenseStatus status = + EsriRuntimeQt::ArcGISRuntime::License::setLicense(licenseCode); + + qDebug() << LOGFUNCTION + << " clientId='" << clientId << "'" + << " licenseCode='" << licenseCode << "'" + << " LicenseStatus=" << static_cast(status) + << " (LicenseStatusVALID=" << static_cast(EsriRuntimeQt::LicenseStatus::Valid) << ")" + ; + + // Use this code to check for licensing errors + if (EsriRuntimeQt::ArcGISRuntime::initialize() == false) + { + // QMessageBox msgBox; + // msgBox.setText(EsriRuntimeQt::ArcGISRuntime::error()); + // msgBox.exec(); + // app.quit(); + return 1; + } +#endif // DO_LIC + + // Init the logger + NGL::NGLLoggerBootstrap::createNGLRootLogger("log4cxxSituationMapUnitTest.props", true); + + + + mainwindow applicationWindow; + applicationWindow.setMinimumWidth(800); + applicationWindow.setMinimumHeight(600); + + applicationWindow.show(); + + return app.exec(); +} + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/CMakeLists.txt new file mode 100755 index 000000000..7d7c7cb91 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/CMakeLists.txt @@ -0,0 +1,69 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# Situation Map Test Application +SET(EXE_SRCS + main.cpp + mainwindow.cpp + nglsettings.cpp + + log4cxx_sitMapTestApp.props + mapsettings.ini +) + +SET(EXE_HDRS + mainwindow.h + nglsettings.h +) + +SET(EXE_UICS + ui/mainwindow.ui +) + +NGL_WRAP_UI(EXE_SRCS ${EXE_UICS}) + +SET(SETTINGS_FILE mapsettings.ini) +SET(LOG4CXX_PROPS_FILE log4cxx_sitMapTestApp.props) + + +NGL_ADD_EXECUTABLE(WS NO_TRANSLATION sitMapTestApp ${EXE_SRCS} ${EXE_HDRS} ${EXE_UICS}) +NGL_TARGET_LINK_LIBRARIES( + sitMapTestApp + NGLLogger + nglapplication + nglwidgets + nglsettings + situationMap + situationMapObjects + situationMapFunctionDialog + ArcGIS::ArcGIS + LtsCoordTrans +) + +IF(TARGET sitMapTestApp) + ADD_CUSTOM_COMMAND( TARGET sitMapTestApp POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${LOG4CXX_PROPS_FILE} $/${LOG4CXX_PROPS_FILE} + COMMAND ${CMAKE_COMMAND} + ARGS -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${SETTINGS_FILE} $/${SETTINGS_FILE} + ) + IF (UNIX) + ADD_CUSTOM_COMMAND( TARGET sitMapTestApp POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${SETTINGS_FILE} $ENV{HOME}/${SETTINGS_FILE} + ) + ENDIF() +ENDIF() + +IF(MSVC) + # Configure the template file + SET(VS_USER_FILE sitMapTestApp.vcxproj.user) + configure_file( ../../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) +ENDIF(MSVC) + +NGL_INSTALL_TARGET_DEVEL(sitMapTestApp) +NGL_INSTALL(FILES WS ${LOG4CXX_PROPS_FILE} DESTINATION "bin" COMPONENT Devel) +NGL_INSTALL(FILES WS ${SETTINGS_FILE} DESTINATION "." COMPONENT Devel) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/log4cxx_sitMapTestApp.props b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/log4cxx_sitMapTestApp.props new file mode 100755 index 000000000..01ba98a75 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/log4cxx_sitMapTestApp.props @@ -0,0 +1,165 @@ +log4j.reset=true +log4j.rootLogger=WARN, file + +# SituationMapTestApp +########################################################### +log4j.logger.SituationMap.SitMapTestApp=DEBUG + +# AdvancedSymbology (AdvSymbology) +########################################################### +log4j.logger.AdvSymbology=FATAL +log4j.logger.AdvSymbology.SymbolDictionary=FATAL +log4j.logger.AdvSymbology.SymbolDictionaryImpl=FATAL +log4j.logger.AdvSymbology.Sidc2525Delta=FATAL + +# SituationMap +########################################################### +log4j.logger.SituationMap=FATAL +log4j.logger.SituationMap.MapImpl=FATAL +log4j.logger.SituationMap.MapSettings=FATAL +log4j.logger.SituationMap.MapInitializer=FATAL +log4j.logger.SituationMap.LayerFactoryImpl=FATAL +log4j.logger.SituationMap.LayerControl=INFO +log4j.logger.SituationMap.CoordTranslationFunctorImpl=FATAL +log4j.logger.SituationMap.CoordTranslationThread=FATAL +log4j.logger.SituationMap.PositionsTranslated=FATAL +log4j.logger.SituationMap.GridImpl=FATAL +log4j.logger.SituationMap.ObjectFactory=FATAL +log4j.logger.SituationMap.IdGenerator=FATAL +log4j.logger.SituationMap.ObjectContainer=FATAL +log4j.logger.SituationMap.ObjectContainerCTC=FATAL +log4j.logger.SituationMap.OverlayHandlerImpl=FATAL +log4j.logger.SituationMap.ViewshedImpl=FATAL +log4j.logger.SituationMap.ViewshedBase=FATAL + +log4j.logger.SituationMap.AbstractMapObjectImpl=FATAL +log4j.logger.SituationMap.AbstractEffectSimulationImpl=FATAL +log4j.logger.SituationMap.OriginTranslated=FATAL +log4j.logger.SituationMap.PositionsTranslated=FATAL +log4j.logger.SituationMap.AbstractMultiPointMapObjectImpl=FATAL +log4j.logger.SituationMap.AbstractSinglePointMapObjectImpl=FATAL +log4j.logger.SituationMap.BasicPathObjectImpl=FATAL +log4j.logger.SituationMap.IndicationContainer=INFO + +log4j.logger.SituationMap.PolygonImpl=FATAL +log4j.logger.SituationMap.PolylineImpl=FATAL +log4j.logger.SituationMap.TextImpl=FATAL + +log4j.logger.SituationMap.AreaFireImpl=FATAL +log4j.logger.SituationMap.BiologicalAgentImpl=FATAL +log4j.logger.SituationMap.BuildingOutlineImpl=FATAL +log4j.logger.SituationMap.ChemicalAgentImpl=FATAL +log4j.logger.SituationMap.DangerAreaImpl=FATAL +log4j.logger.SituationMap.FunctionalAreaImpl=FATAL +log4j.logger.SituationMap.HitLineImpl=FATAL +log4j.logger.SituationMap.MapObjectIndicatorImpl=FATAL +log4j.logger.SituationMap.MineImpl=FATAL +log4j.logger.SituationMap.MineFieldImpl=FATAL +log4j.logger.SituationMap.NuclearAgentImpl=FATAL +log4j.logger.SituationMap.ParticipantImpl=FATAL +log4j.logger.SituationMap.SingleFireImpl=FATAL + +log4j.logger.SituationMap.AbstractGraphicalObject=FATAL +log4j.logger.SituationMap.GraphicalObjectEllipse=FATAL +log4j.logger.SituationMap.GraphicalObjectPath=FATAL +log4j.logger.SituationMap.GraphicalObjectMultiPath=FATAL +log4j.logger.SituationMap.GraphicalObjectPictureMarker=FATAL +log4j.logger.SituationMap.GraphicalObjectRange=FATAL +log4j.logger.SituationMap.GraphicalObjectText=FATAL +log4j.logger.SituationMap.TacticalAppearance=FATAL +log4j.logger.SituationMap.MessageBase=FATAL + +log4j.logger.SituationMap.EventDispatcher=FATAL +log4j.logger.SituationMap.Rubberband=FATAL +log4j.logger.SituationMap.RubberbandRectangle=FATAL +log4j.logger.SituationMap.RubberbandDrawablesAndHandles=FATAL +log4j.logger.SituationMap.RubberbandFrameHandle=FATAL +log4j.logger.SituationMap.RubberbandPointHandle=FATAL +log4j.logger.SituationMap.RubberbandStateSelect=FATAL + +# SituationMap-SideBar +########################################################### +log4j.logger.SituationMap.SideBar=FATAL +log4j.logger.SituationMap.SideBarItem=FATAL +log4j.logger.SituationMap.SideBarModel=FATAL + + +# PlanEditor +########################################################### +log4j.logger.SituationMap.PlanEditor=FATAL +log4j.logger.SituationMap.PlanRenderer=FATAL + + + +# Appnd_File +########################################################### +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_SituationMapTestApp.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %-5p %c{1} |#| %m (%-.30F:%L) %n + +#log4j.appender.stdout.layout.ConversionPattern= %d %-5p %-30c |#| %m (%-.30F:%L) %n + + +# Appnd_Syslog +########################################################### +log4j.appender.syslog=org.apache.log4j.SyslogAppender +log4j.appender.syslog.Threshold=ERROR +log4j.appender.syslog.SysLogHost=localhost +log4j.appender.syslog.Facility=LOCAL7 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m +log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true +log4j.appender.syslog.LevelRangeFilter.LevelMin=FATAL +log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL + + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/main.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/main.cpp new file mode 100755 index 000000000..e3a6930f6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/main.cpp @@ -0,0 +1,67 @@ +/** + * @RDE_HEADER@ + * SquishTest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include +#include + +#include "mainwindow.h" + +#include + +// ------------------------------------------------------------------- +int main (int argc, char* argv[]) +{ + NGLApplication app ( argc, argv ); + + // under windows for ArcGIS 10.2.6 and higher +#ifdef Q_OS_WIN + QCoreApplication::setAttribute( Qt::AA_UseOpenGLES ); +#endif + + app.createLogger("sitMapTestApp", "log4cxx_sitMapTestApp.props"); + TRACE_LOG(app.baseLogger()) << LOGFUNCTION << " START"; + + STARTUP_LOG(app.baseLogger()); + + app.setOrganizationName(QLatin1String("Rheinmetall Defence Electronics")); + app.setApplicationName(QLatin1String("Situation Map Test Application")); + + MainWindow *mw = new MainWindow(); +/* + QDesktopWidget *desktopWidget = QApplication::desktop(); + QRect geom; + +// DEBUG_LOG(app.baseLogger()) << LOGFUNCTION << " desktopWidget->screenCount() = " << desktopWidget->screenCount(); + + if (desktopWidget->screenCount() > 1) + geom = desktopWidget->screenGeometry(1); + else + geom = desktopWidget->screenGeometry(); + + int width = mw->frameGeometry().width(); + int height = mw->frameGeometry().height(); + int screenWidth = geom.width(); + int screenHeight = geom.height(); + int screenWidth = 1024; + int screenHeight = 768; + mw->setGeometry((screenWidth/2)-(width/2),(screenHeight/2)-(height/2),width,height); +*/ + mw->setGeometry(0,0,1024,768); +// mw->setWindowState(Qt::WindowMaximized); + mw->show(); + + app.setQuitOnLastWindowClosed(true); + + int retval = app.exec(); + + delete mw; + + SHUTDOWN_LOG(app.baseLogger()); + + return retval; +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.cpp new file mode 100755 index 000000000..c48572d10 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.cpp @@ -0,0 +1,2702 @@ +/** + * @RDE_HEADER@ + * + * MainWindow for SitMapTestApp + * + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "mainwindow.h" +#include "nglsettings.h" +#include "ui_mainwindow.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//TODO: use templates + + +//static LtsCoordTrans::UtmCoordinate s_basePoint; + + +//---------------------------------------------------------------------------------------------------------- +MainWindow::MainWindow(QWidget *parent) + : NGLMainWindow(parent) + , m_ui(new Ui::widgetMain) + , m_logger(SituationMap::getLogger("SitMapTestApp")) +{ + m_ui->setupUi(this); + + connect(m_ui->actionExit, SIGNAL(triggered()), this, SLOT(onClose())); + + // map slots + connect(m_ui->actionLoadMap, SIGNAL(triggered()), this, SLOT(createMap())); + connect(m_ui->actionRemoveMap, SIGNAL(triggered()), this, SLOT(destroyMap())); + + // map objects + connect(m_ui->actionCreateBuildings, SIGNAL(triggered()), this, SLOT(createBuildings())); + connect(m_ui->actionRemoveBuildings, SIGNAL(triggered()), this, SLOT(removeBuildings())); + connect(m_ui->actionUpdateBuildings, SIGNAL(triggered()), this, SLOT(updateMapObjects())); + connect(m_ui->actionHideBuildings, SIGNAL(triggered()), this, SLOT(hideBuildings())); + + connect(m_ui->actionCreateDangerAreas, SIGNAL(triggered()), this, SLOT(createDangerAreas())); + connect(m_ui->actionRemoveDangerAreas, SIGNAL(triggered()), this, SLOT(removeDangerAreas())); + connect(m_ui->actionUpdateDangerAreas, SIGNAL(triggered()), this, SLOT(updateMapObjects())); + connect(m_ui->actionHideDangerAreas, SIGNAL(triggered()), this, SLOT(hideDangerAreas())); + + connect(m_ui->actionCreateFireAreas, SIGNAL(triggered()), this, SLOT(createFireAreas())); + connect(m_ui->actionRemoveFireAreas, SIGNAL(triggered()), this, SLOT(removeFireAreas())); + connect(m_ui->actionUpdateFireAreas, SIGNAL(triggered()), this, SLOT(updateMapObjects())); + connect(m_ui->actionHideFireAreas, SIGNAL(triggered()), this, SLOT(hideFireAreas())); + + connect(m_ui->actionCreateBAgents, SIGNAL(triggered()), this, SLOT(createBAgents())); + connect(m_ui->actionRemoveBAgents, SIGNAL(triggered()), this, SLOT(removeBAgents())); + connect(m_ui->actionUpdateBAgents, SIGNAL(triggered()), this, SLOT(updateMapObjects())); + connect(m_ui->actionHideBAgents, SIGNAL(triggered()), this, SLOT(hideBAgents())); + + connect(m_ui->actionCreateHitLines, SIGNAL(triggered()), this, SLOT(createHitLines())); + connect(m_ui->actionRemoveHitLines, SIGNAL(triggered()), this, SLOT(removeHitLines())); + connect(m_ui->actionUpdateHitLines, SIGNAL(triggered()), this, SLOT(updateMapObjects())); + connect(m_ui->actionHideHitLines, SIGNAL(triggered()), this, SLOT(hideHitLines())); + + //Draw slots + connect(m_ui->actionPointCluster, SIGNAL(triggered()), this, SLOT(rubberbandPointCluster())); + connect(m_ui->actionMultiPoint, SIGNAL(triggered()), this, SLOT(rubberbandMultiPoint())); + connect(m_ui->actionFilledMultiPoint, SIGNAL(triggered()), this, SLOT(rubberbandFilledMultiPoint())); + connect(m_ui->actionSmoothLine, SIGNAL(triggered()), this, SLOT(rubberbandSmoothLine())); + connect(m_ui->actionFilledSmoothLine, SIGNAL(triggered()), this, SLOT(rubberbandFilledSmoothLine())); + connect(m_ui->actionRectangle, SIGNAL(triggered()), this, SLOT(rubberbandRectangle())); + connect(m_ui->actionSquare, SIGNAL(triggered()), this, SLOT(rubberbandSquare())); + connect(m_ui->actionEllipse, SIGNAL(triggered()), this, SLOT(rubberbandEllipse())); + connect(m_ui->actionArrow, SIGNAL(triggered()), this, SLOT(rubberbandArrow())); + connect(m_ui->actionCorridorBoundary, SIGNAL(triggered()), this, SLOT(rubberbandCorridorBoundary())); + connect(m_ui->actionCorridorCenterline, SIGNAL(triggered()), this, SLOT(rubberbandCorridorCenterline())); + + // Create slots + connect(m_ui->actionCreatePointCluster, SIGNAL(triggered()), this, SLOT(rubberbandCreatePointClusters())); + connect(m_ui->actionCreateMultiPoint, SIGNAL(triggered()), this, SLOT(rubberbandCreateMultiPoints())); + connect(m_ui->actionCreateFilledMultiPoint, SIGNAL(triggered()), this, SLOT(rubberbandCreateFilledMultiPoints())); + connect(m_ui->actionCreateSmoothLine, SIGNAL(triggered()), this, SLOT(rubberbandCreateSmoothLines())); + connect(m_ui->actionCreateFilledSmoothLine, SIGNAL(triggered()), this, SLOT(rubberbandCreateFilledSmoothLines())); + connect(m_ui->actionCreateRectangle, SIGNAL(triggered()), this, SLOT(rubberbandCreateRectangles())); + connect(m_ui->actionCreateSquare, SIGNAL(triggered()), this, SLOT(rubberbandCreateSquares())); + connect(m_ui->actionCreateEllipse, SIGNAL(triggered()), this, SLOT(rubberbandCreateEllipses())); + connect(m_ui->actionCreateArrow, SIGNAL(triggered()), this, SLOT(rubberbandCreateArrows())); + connect(m_ui->actionCreateCorridorBoundary, SIGNAL(triggered()), this, SLOT(rubberbandCreateCorridorBoundaries())); + connect(m_ui->actionCreateCorridorCenterline, SIGNAL(triggered()), this, SLOT(rubberbandCreateCorridorCenterlines())); + + init(); + +} + +//---------------------------------------------------------------------------------------------------------- +MainWindow::~MainWindow() +{ + clearMapObjects(); + + delete m_ui; + delete m_timer; +} + +//---------------------------------------------------------------------------------------------------------- +void MainWindow::init() +{ + m_scale = MAP::Settings::mapScale(); + INFO_LOG(m_logger) << LOGFUNCTION << " MAP SCALE = " << QString::number(m_scale,'f',6); + + s_basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(MAP::Settings::basePoint()); + m_objectBasePoint = QPointF(s_basePoint.x,s_basePoint.y); + INFO_LOG(m_logger) << LOGFUNCTION << " MAP BASEPOINT = " << MAP::Settings::basePoint(); + + m_wkid = MAP::Settings::mapWkid(); + INFO_LOG(m_logger) << LOGFUNCTION << " MAP WKID = " << QString::number(m_wkid,'f',6); + + m_timer = new QTimer(this); +} + +//---------------------------------------------------------------------------------------------------------- +void MainWindow::onClose() +{ + this->close(); +} + +//---------------------------------------------------------------------------------------------------------- +void MainWindow::destroyMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " REMOVE MAP "; + + m_ui->gridLayout->removeWidget(m_map); + + clearMapObjects(); + + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//---------------------------------------------------------------------------------------------------------- +void MainWindow::clearMapObjects() +{ + m_building.reset(); + m_buildingMap.clear(); + + m_dangerArea.reset(); + m_dangerAreaMap.clear(); + + m_fireArea.reset(); + m_fireAreaMap.clear(); + + m_hitLine.reset(); + m_hitLineMap.clear(); + + m_bAgent.reset(); + m_bAgentMap.clear(); + + delete m_map; + m_map = nullptr; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE MAP "; + + // 0. License: + if (!SituationMap::Map::applyLicense()) + FATAL_LOG(m_logger) << LOGFUNCTION << " applyLicense failed"; + + // 1. Map + m_map = SituationMap::Map::create(this); + + connect(m_map, &SituationMap::Map::mapReady, this, &MainWindow::onMapReady); + connect(m_map, &SituationMap::Map::mapObjectsClicked, this, &MainWindow::onMapObjectClicked); + connect(m_map, &SituationMap::Map::extentChanged, this, &MainWindow::onExtentChanged); + connect(m_map, &SituationMap::Map::rectChanged, this, &MainWindow::onExtentChanged); + + // 2. SymbologyType + AdvancedSymbology::SymbologyType symType = AdvancedSymbology::SymbologyType::MIL2525D; //default + fromString(symType, MAP::Settings::mapSymbologyType()); + m_map->setSymbologyType(symType); + + // 3. init (choose 2d or 3D) + m_map->init(SituationMap::Mode::Mode_2D); + + // 4. Basemaps + QString mapURL = MAP::Settings::mapURL(); + m_map->layerFactory().addBasemap(QLatin1Literal("DummyLayerName"), + QUrl(mapURL), + SituationMap::BasemapType::Aerial); + DEBUG_LOG(m_logger) << LOGFUNCTION << " MAP created with SymbologyType = " << toString(symType); + DEBUG_LOG(m_logger) << LOGFUNCTION << " MAP URL = " << mapURL; + + // 5. load + m_map->load(); + + // 6. initializeOperationalLayers + m_map->layerFactory().initializeOperationalLayers(QStringList() << QLatin1String("AES") << QLatin1String("Participants")); + + // 7. Grid + m_map->grid().setGridType(MAP::Settings::getGridType(MAP::Settings::mapGridType())); + m_map->grid().setVisible(MAP::Settings::mapGridVisible()); + + // 8. Initialize objectFactory + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::MapObjectIndicator), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Cube), new SituationMap::GenericObjectFactory() ); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Polygon), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Polyline), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Text), new SituationMap::GenericObjectFactory() ); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Participant), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::AreaFire), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BiologicalAgent), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::ChemicalAgent), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BuildingOutline), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::DangerArea), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::GeoImage), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::FunctionalArea), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::HitLine), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Mine), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::MineField), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::NuclearAgent), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SingleFire), new SituationMap::GenericObjectFactory() ); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SidcSymbol), new SituationMap::GenericObjectFactory() ); + + // 9: CoordTransFunctor + m_map->initCoordTranslationFunctor(); + + // 10. extent, rotation: set later in mapReady + + // 11. Add widget to main grid layout + m_ui->gridLayout->addWidget(m_map, 1, 0); + + // 12. Create FunctionDialog: + SituationMap::FunctionDialog::addButtonToMap(m_map, MAP::Settings::mapFunctionDialogVisible()); + + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " UPDATE MAP"; + m_map->update(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onMapObjectClicked(const SituationMap::Map::RefPointerMapObjectList &mapObjList) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + for (auto mapObj : mapObjList) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " id=" << mapObj->uniqueMapId() + << " object-debugInfo=" << mapObj->debugInfo() + << " object-identifier=" << mapObj->identifier(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onMapReady() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + +// QString centerPoint = MAP::Settings::centerPointMgrs(); +// m_map->setCenterPoint(LtsCoordTrans::geographicFromMgrs(centerPoint)); // QPointF(92750, 80525) -> Rennplatz - Bremen + + m_map->setScale(m_scale); + + m_map->setRotation(MAP::Settings::mapRotationDegree()); + +// m_map->setExtent(MAP::Settings::XMin(), MAP::Settings::YMin(), MAP::Settings::XMax(), MAP::Settings::YMax()); + + m_map->update(); + + INFO_LOG(m_logger) << LOGFUNCTION << " MAP READY"; + + INFO_LOG(m_logger) << LOGFUNCTION << " MAP PIXEL SIZE = " << m_map->pixelSize(); + INFO_LOG(m_logger) << LOGFUNCTION << " MAP SCALE = " << m_map->scale(); + + INFO_LOG(m_logger) << LOGFUNCTION << " MAP Width = " << m_map->width(); + INFO_LOG(m_logger) << LOGFUNCTION << " MAP Height = " << m_map->height(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onExtentChanged() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " MAP EXTENT CHANGED "; + + TRACE_LOG(m_logger) << LOGFUNCTION << " MAP PIXEL SIZE = " << m_map->pixelSize(); + TRACE_LOG(m_logger) << LOGFUNCTION << " MAP SCALE = " << m_map->scale(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " MAP Width = " << m_map->width(); + TRACE_LOG(m_logger) << LOGFUNCTION << " MAP Height = " << m_map->height(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::setVersionView() +{ + //TODO: +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createBuildings() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->createBuilding(1); + this->createBuilding(2); + //TODO: more buildings + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createBuilding(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_building = m_buildingMap[objId]; + if (m_building) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Building already exists " << objId; + return; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Creating building " << objId; + m_building = m_map->objectFactory().create( QLatin1String("AES"), QLatin1String("BuildingOutlineId")); + + if (!m_building) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No building object "; + return; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Building Identifier: " << MAP::Settings::objectIdentifier(QLatin1String("Building"),objId); + + m_building->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("Building"),objId)); + m_building->setVisibility(MAP::Settings::getObjectBoolAttribute(QLatin1String("Building"),QLatin1String("Visibility"),objId,true)); + m_building->setVisibility(SituationMap::ObjectAppearance::Technical, MAP::Settings::getObjectBoolAttribute(QLatin1String("Building"),QLatin1String("VisibilityTech"),objId,true)); + m_building->setVisibility(SituationMap::ObjectAppearance::Tactical, MAP::Settings::getObjectBoolAttribute(QLatin1String("Building"),QLatin1String("VisibilityTact"),objId,true)); + + m_building->rTextStyle().setTextColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("Building"),QLatin1String("TextColor"),objId,QLatin1String("magenta")))); + m_building->rTextStyle().setSize(MAP::Settings::getObjectIntAttribute(QLatin1String("Building"),QLatin1String("TextSize"),objId,30)); + m_building->rTextStyle().setAngle(MAP::Settings::getObjectIntAttribute(QLatin1String("Building"),QLatin1String("TextAngle"),objId,0)); + m_building->rTextStyle().setAntiAliased(MAP::Settings::getObjectBoolAttribute(QLatin1String("Building"),QLatin1String("TextItalic"),objId,true)); + m_building->rTextStyle().setItalic(MAP::Settings::getObjectBoolAttribute(QLatin1String("Building"),QLatin1String("TextItalic"),objId,false)); + + m_building->setScale(MAP::Settings::mapObjectsTextScale()); + + // set additional values: + SituationMap::PositionCont posCont; + + posCont.reserve(4); + for (int i = 1; i <= 4; i++) + { + const QPointF &pIni = MAP::Settings::getPoint(QLatin1String("Building"),objId,i); + const QPointF &pTrans = s_basePoint.geographic(pIni); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : " + << "pIni= " << pIni << ", pTrans= " << pTrans; + posCont << SituationMap::Point3D::fromQPointF(pTrans); + } + + m_building->setPositions(posCont); + + // update view + bool success = m_building->updateView(); + + if (!success) + DEBUG_LOG(m_logger) << LOGFUNCTION << " updateView NOT OK"; + + m_buildingMap[objId] = m_building; + + connect(m_timer, &QTimer::timeout, this, [=]() + { + DEBUG_LOG(m_logger) << " Updating building " << objId; + onBuildingUpdate(objId); + }); + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onBuildingUpdate(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_building = m_buildingMap[objId]; + if (!m_building) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No building object "; + return; + } + + m_building->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("Building"),objId) + QLatin1String("Update")); + + m_building->rTextStyle().setAngle(MAP::Settings::getObjectIntAttribute(QLatin1String("Building"),QLatin1String("TextAngleUpdate"),objId,0)); + + m_building->rFillStyle().setFillColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("Building"),QLatin1String("FillColor"),objId,QLatin1String("yellow")))); + m_building->rFillStyle().setFillPattern((SituationMap::FillPattern)MAP::Settings::getObjectIntAttribute(QLatin1String("Building"),QLatin1String("FillPattern"),objId,2)); + + m_building->rLineStyle().setLineColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("Building"),QLatin1String("LineColor"),objId,QLatin1String("red")))); + m_building->rLineStyle().setLineWidth(MAP::Settings::getObjectIntAttribute(QLatin1String("Building"),QLatin1String("LineWidth"),objId,6)); + m_building->rLineStyle().setLinePattern(MAP::Settings::getLinePattern(MAP::Settings::getObjectStringAttribute(QLatin1String("Building"),QLatin1String("LinePattern"),objId,QLatin1String("DashDotDot")))); + + //TODO: move building on update ? + + bool success = m_building->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " success=" << success; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeBuildings() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QList keys = m_buildingMap.keys(); + for ( int objId, keys) + { + DEBUG_LOG(m_logger) << "Removing Building: " << objId; + removeBuilding(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeBuilding(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_building = m_buildingMap[objId]; + if (m_building) + { + m_building->removeMySelf(); + m_building.reset(); + m_buildingMap.remove(objId); + } + + if (m_map) + { + m_map->update(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideBuildings() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + for (auto it = m_buildingMap.cbegin(), end = m_buildingMap.cend(); it != end; ++it) + { + int objId = it.key(); + DEBUG_LOG(m_logger) << "Hide/Show Building: " << objId; + hideBuilding(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideBuilding(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_building = m_buildingMap[objId]; + if (m_building) + { + bool isVisible = m_building->visibility(); + m_building->setVisibility(!isVisible); + m_building->updateView(); + } + if (m_map) + m_map->update(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::updateMapObjects() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_timer->setSingleShot(true); + m_timer->start(1000); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createDangerAreas() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->createDangerArea(1); + // this->createDangerArea(2); + //TODO: more danger areas + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createDangerArea(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_dangerArea = m_dangerAreaMap[objId]; + if (m_dangerArea) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Danger Area already exists " << objId; + return; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Creating danger area " << objId; + m_dangerArea = m_map->objectFactory().create( QLatin1String("AES"), QLatin1String("DangerAreaId")); + + if (!m_dangerArea) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No danger area object "; + return; + } + + m_dangerArea->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("DangerArea"),objId)); + + m_dangerArea->setVisibility(MAP::Settings::getObjectBoolAttribute(QLatin1String("DangerArea"),QLatin1String("Visibility"),objId,true)); + m_dangerArea->setVisibility(SituationMap::ObjectAppearance::Technical, MAP::Settings::getObjectBoolAttribute(QLatin1String("DangerArea"),QLatin1String("VisibilityTech"),objId,true)); + m_dangerArea->setVisibility(SituationMap::ObjectAppearance::Tactical, MAP::Settings::getObjectBoolAttribute(QLatin1String("DangerArea"),QLatin1String("VisibilityTact"),objId,true)); + + m_dangerArea->setScale(MAP::Settings::mapObjectsTextScale()); + + m_dangerArea->rFillStyle().setFillColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("DangerArea"),QLatin1String("FillColor"),objId,QLatin1String("yellow")))); + m_dangerArea->rFillStyle().setFillPattern(MAP::Settings::getFillPattern(MAP::Settings::getObjectStringAttribute(QLatin1String("DangerArea"),QLatin1String("FillPattern"),objId,QLatin1String("DiagonalCross")))); + + m_dangerArea->rLineStyle().setLineColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("DangerArea"),QLatin1String("LineColor"),objId,QLatin1String("red")))); + m_dangerArea->rLineStyle().setLineWidth(MAP::Settings::getObjectIntAttribute(QLatin1String("DangerArea"),QLatin1String("LineWidth"),objId,6)); + m_dangerArea->rLineStyle().setLinePattern(MAP::Settings::getLinePattern(MAP::Settings::getObjectStringAttribute(QLatin1String("DangerArea"),QLatin1String("LinePattern"),objId,QLatin1String("DashDotDot")))); + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = QPointF(MAP::Settings::getObjectIntAttribute(QLatin1String("DangerArea"),QLatin1String("Offset_X"),objId,50000), MAP::Settings::getObjectIntAttribute(QLatin1String("DangerArea"),QLatin1String("Offset_Y"),objId,50000)); + + int extent = MAP::Settings::getObjectIntAttribute(QLatin1String("DangerArea"),QLatin1String("Extent"),objId,30000); + posCont << SituationMap::Point3D::fromQPointF(offset - QPointF( 0, 0)); + posCont << SituationMap::Point3D::fromQPointF(offset - QPointF(extent, 0)); + posCont << SituationMap::Point3D::fromQPointF(offset - QPointF(extent, extent)); + posCont << SituationMap::Point3D::fromQPointF(offset - QPointF( 0, extent)); + + m_dangerArea->setPositions(posCont); + + // update view + bool success = m_dangerArea->updateView(); + + if (!success) + DEBUG_LOG(m_logger) << LOGFUNCTION << " updateView NOT OK"; + + m_dangerAreaMap[objId] = m_dangerArea; + + connect(m_timer, &QTimer::timeout, this, [=]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Updating danger area " << objId; + onDangerAreaUpdate(objId); + }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeDangerAreas() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QList keys = m_dangerAreaMap.keys(); + for ( int objId, keys ) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Removing Danger Area: " << objId; + removeDangerArea(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeDangerArea(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_dangerArea = m_dangerAreaMap[objId]; + if (m_dangerArea) + { + m_dangerArea->removeMySelf(); + m_dangerArea.reset(); + m_dangerAreaMap.remove(objId); + } + + if (m_map) + { + m_map->update(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideDangerAreas() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + for (auto it = m_dangerAreaMap.cbegin(), end = m_dangerAreaMap.cend(); it != end; ++it) + { + int objId = it.key(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Hide/Show Danger Area: " << objId; + hideDangerArea(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideDangerArea(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_dangerArea = m_dangerAreaMap[objId]; + if (m_dangerArea) + { + bool isVisible = m_dangerArea->visibility(); + m_dangerArea->setVisibility(!isVisible); + m_dangerArea->updateView(); + } + + if (m_map) + m_map->update(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onDangerAreaUpdate(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_dangerArea = m_dangerAreaMap[objId]; + if (!m_dangerArea) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No danger area object "; + return; + } + + m_dangerArea->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("DangerArea"),objId) + QLatin1String("Update")); + + //TODO: change more on update ? + + bool success = m_dangerArea->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " success=" << success; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createBAgents() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->createBAgent(1); + // this->createBAgent(2); + //TODO: more B-agents + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createBAgent(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_bAgent = m_bAgentMap[objId]; + if (m_bAgent) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " BAgent already exists " << objId; + return; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Creating BAgent " << objId; + m_bAgent = m_map->objectFactory().create( QLatin1String("AES"), QLatin1String("BAgentId")); + + if (!m_bAgent) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No BAgent object "; + return; + } + + m_bAgent->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("BAgent"),objId)); + + m_bAgent->setVisibility(MAP::Settings::getObjectBoolAttribute(QLatin1String("BAgent"),QLatin1String("Visibility"),objId,true)); + m_bAgent->setVisibility(SituationMap::ObjectAppearance::Technical, MAP::Settings::getObjectBoolAttribute(QLatin1String("BAgent"),QLatin1String("VisibilityTech"),objId,true)); + m_bAgent->setVisibility(SituationMap::ObjectAppearance::Tactical, MAP::Settings::getObjectBoolAttribute(QLatin1String("BAgent"),QLatin1String("VisibilityTact"),objId,true)); + + m_bAgent->setScale(MAP::Settings::mapObjectsTextScale()); + + m_bAgent->rFillStyle().setFillColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("BAgent"),QLatin1String("FillColor"),objId,QLatin1String("yellow")))); + m_bAgent->rFillStyle().setFillPattern(MAP::Settings::getFillPattern(MAP::Settings::getObjectStringAttribute(QLatin1String("BAgent"),QLatin1String("FillPattern"),objId,QLatin1String("DiagonalCross")))); + + m_bAgent->rLineStyle().setLineColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("BAgent"),QLatin1String("LineColor"),objId,QLatin1String("red")))); + m_bAgent->rLineStyle().setLineWidth(MAP::Settings::getObjectIntAttribute(QLatin1String("BAgent"),QLatin1String("LineWidth"),objId,6)); + m_bAgent->rLineStyle().setLinePattern(MAP::Settings::getLinePattern(MAP::Settings::getObjectStringAttribute(QLatin1String("BAgent"),QLatin1String("LinePattern"),objId,QLatin1String("DashDotDot")))); + + m_bAgent->rTextStyle().setTextColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("BAgent"),QLatin1String("TextColor"),objId,QLatin1String("magenta")))); + m_bAgent->rTextStyle().setSize(MAP::Settings::getObjectIntAttribute(QLatin1String("BAgent"),QLatin1String("TextSize"),objId,30)); + m_bAgent->rTextStyle().setBold(MAP::Settings::getObjectBoolAttribute(QLatin1String("BAgent"),QLatin1String("TextBold"),objId,false)); + + // set additional values: + QPointF center = QPointF(MAP::Settings::getObjectIntAttribute(QLatin1String("BAgent"),QLatin1String("Offset_X"),objId,50000), MAP::Settings::getObjectIntAttribute(QLatin1String("BAgent"),QLatin1String("Offset_Y"),objId,50000)); + int radius = MAP::Settings::getObjectIntAttribute(QLatin1String("BAgent"),QLatin1String("Extent"),objId,30000); + + m_bAgent->setOrigin(SituationMap::Point3D::fromQPointF(center)); + m_bAgent->setRadius(radius); + + // update view + bool success = m_bAgent->updateView(); + + if (!success) + DEBUG_LOG(m_logger) << LOGFUNCTION << " updateView NOT OK"; + + m_bAgentMap[objId] = m_bAgent; + + connect(m_timer, &QTimer::timeout, this, [=]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Updating BAgent " << objId; + onBAgentUpdate(objId); + }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeBAgents() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QList keys = m_bAgentMap.keys(); + for ( int objId, keys ) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Removing Fire Area: " << objId; + removeBAgent(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeBAgent(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_bAgent = m_bAgentMap[objId]; + if (m_bAgent) + { + m_bAgent->removeMySelf(); + m_bAgent.reset(); + m_bAgentMap.remove(objId); + } + + if (m_map) + { + m_map->update(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideBAgents() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + for (auto it = m_bAgentMap.cbegin(), end = m_bAgentMap.cend(); it != end; ++it) + { + int objId = it.key(); + DEBUG_LOG(m_logger) << LOGFUNCTION << "Hide/Show Fire Area: " << objId; + hideBAgent(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideBAgent(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_bAgent = m_bAgentMap[objId]; + if (m_bAgent) + { + bool isVisible = m_bAgent->visibility(); + m_bAgent->setVisibility(!isVisible); + m_bAgent->updateView(); + } + + if (m_map) + m_map->update(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onBAgentUpdate(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_bAgent = m_bAgentMap[objId]; + if (!m_bAgent) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No Fire area object "; + return; + } + + m_bAgent->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("BAgent"),objId) + QLatin1String("Update")); + + //TODO: change more on update ? + + bool success = m_bAgent->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " success=" << success; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createFireAreas() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->createFireArea(1); + // this->createFireArea(2); + //TODO: more Fire areas + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createFireArea(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_fireArea = m_fireAreaMap[objId]; + if (m_fireArea) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Fire Area already exists " << objId; + return; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Creating Fire Area " << objId; + m_fireArea = m_map->objectFactory().create( QLatin1String("AES"), QLatin1String("AreaFireId")); + + if (!m_fireArea) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No Fire Area object "; + return; + } + + m_fireArea->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("FireArea"),objId)); + + m_fireArea->setVisibility(MAP::Settings::getObjectBoolAttribute(QLatin1String("FireArea"),QLatin1String("Visibility"),objId,true)); + m_fireArea->setVisibility(SituationMap::ObjectAppearance::Technical, MAP::Settings::getObjectBoolAttribute(QLatin1String("FireArea"),QLatin1String("VisibilityTech"),objId,true)); + m_fireArea->setVisibility(SituationMap::ObjectAppearance::Tactical, MAP::Settings::getObjectBoolAttribute(QLatin1String("FireArea"),QLatin1String("VisibilityTact"),objId,true)); + + m_fireArea->setScale(MAP::Settings::mapObjectsTextScale()); + + m_fireArea->rFillStyle().setFillColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("FireArea"),QLatin1String("FillColor"),objId,QLatin1String("blue")))); + m_fireArea->rFillStyle().setFillPattern(MAP::Settings::getFillPattern(MAP::Settings::getObjectStringAttribute(QLatin1String("FireArea"),QLatin1String("FillPattern"),objId,QLatin1String("null")))); + + m_fireArea->rLineStyle().setLineColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("FireArea"),QLatin1String("LineColor"),objId,QLatin1String("red")))); + m_fireArea->rLineStyle().setLineWidth(MAP::Settings::getObjectIntAttribute(QLatin1String("FireArea"),QLatin1String("LineWidth"),objId,6)); + m_fireArea->rLineStyle().setLinePattern(MAP::Settings::getLinePattern(MAP::Settings::getObjectStringAttribute(QLatin1String("FireArea"),QLatin1String("LinePattern"),objId,QLatin1String("Solid")))); + + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = QPointF(MAP::Settings::getObjectIntAttribute(QLatin1String("FireArea"),QLatin1String("Offset_X"),objId,170000), MAP::Settings::getObjectIntAttribute(QLatin1String("FireArea"),QLatin1String("Offset_Y"),objId,170000)); + + int extent = MAP::Settings::getObjectIntAttribute(QLatin1String("FireArea"),QLatin1String("Extent"),objId,100000); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF( 0, 0)); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF(extent, 0)); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF(extent, extent)); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF(extent/2, extent/2)); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF( 0, extent)); + + m_fireArea->setPositions(posCont); + + posCont.removeAt(3); + m_fireArea->setImpacts(posCont); + + // TODO: use colors from PartyId ? + // m_fireArea->setPartyId(3); + // m_fireArea->setSelected(true); + + // update view + bool success = m_fireArea->updateView(); + + if (!success) + DEBUG_LOG(m_logger) << LOGFUNCTION << " updateView NOT OK"; + + m_fireAreaMap[objId] = m_fireArea; + + connect(m_timer, &QTimer::timeout, this, [=]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Updating Fire Area " << objId; + onFireAreaUpdate(objId); + }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeFireAreas() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QList keys = m_fireAreaMap.keys(); + for ( int objId, keys) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Removing Fire Area: " << objId; + removeFireArea(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeFireArea(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_fireArea = m_fireAreaMap[objId]; + if (m_fireArea) + { + m_fireArea->removeMySelf(); + m_fireArea.reset(); + m_fireAreaMap.remove(objId); + } + + if (m_map) + { + m_map->update(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideFireAreas() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + for (auto it = m_fireAreaMap.cbegin(), end = m_fireAreaMap.cend(); it != end; ++it) + { + int objId = it.key(); + DEBUG_LOG(m_logger) << LOGFUNCTION << "Hide/Show Fire Area: " << objId; + hideFireArea(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideFireArea(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_fireArea = m_fireAreaMap[objId]; + if (m_fireArea) + { + bool isVisible = m_fireArea->visibility(); + m_fireArea->setVisibility(!isVisible); + m_fireArea->updateView(); + } + + if (m_map) + m_map->update(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onFireAreaUpdate(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_fireArea = m_fireAreaMap[objId]; + if (!m_fireArea) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No Fire area object "; + return; + } + + m_fireArea->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("FireArea"),objId) + QLatin1String("Update")); + m_fireArea->setPartyId(4); // Will overwrite line/fillcolor + + SituationMap::PositionCont posCont; + QPointF offset = QPointF(MAP::Settings::getObjectIntAttribute(QLatin1String("FireArea"),QLatin1String("Offset_X"),objId,170000), MAP::Settings::getObjectIntAttribute(QLatin1String("FireArea"),QLatin1String("Offset_Y"),objId,170000)); + + int extent = MAP::Settings::getObjectIntAttribute(QLatin1String("FireArea"),QLatin1String("Extent"),objId,100000); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF( 0, 0)); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF(extent, 0)); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF(extent, extent*2)); + posCont << SituationMap::Point3D::fromQPointF(offset + QPointF( 0, extent)); + + m_fireArea->setPositions(posCont); + + posCont.removeAt(1); + posCont.removeAt(3); + + m_fireArea->updateView(); + + //TODO: change more on update ? + + bool success = m_fireArea->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " success=" << success; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createHitLines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->createHitLine(1); + // this->createHitLine(2); + //TODO: more Hit Lines + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::createHitLine(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_hitLine = m_hitLineMap[objId]; + if (m_hitLine) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Fire Line already exists " << objId; + return; + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Creating Fire Line " << objId; + m_hitLine = m_map->objectFactory().create( QLatin1String("AES"), QLatin1String("HitLineId")); + + if (!m_hitLine) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No Fire Line object "; + return; + } + + m_hitLine->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("HitLine"),objId)); + + m_hitLine->setVisibility(MAP::Settings::getObjectBoolAttribute(QLatin1String("HitLine"),QLatin1String("Visibility"),objId,true)); + m_hitLine->setVisibility(SituationMap::ObjectAppearance::Technical, MAP::Settings::getObjectBoolAttribute(QLatin1String("HitLine"),QLatin1String("VisibilityTech"),objId,true)); + m_hitLine->setVisibility(SituationMap::ObjectAppearance::Tactical, MAP::Settings::getObjectBoolAttribute(QLatin1String("HitLine"),QLatin1String("VisibilityTact"),objId,true)); + + m_hitLine->setScale(MAP::Settings::mapObjectsTextScale()); + + m_hitLine->rLineStyle().setLineColor(QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("HitLine"),QLatin1String("LineColor"),objId,QLatin1String("red")))); + m_hitLine->rLineStyle().setLineWidth(MAP::Settings::getObjectIntAttribute(QLatin1String("HitLine"),QLatin1String("LineWidth"),objId,6)); + m_hitLine->rLineStyle().setLinePattern(MAP::Settings::getLinePattern(MAP::Settings::getObjectStringAttribute(QLatin1String("HitLine"),QLatin1String("LinePattern"),objId,QLatin1String("DashDotDot")))); + + // set additional values: + QPointF offset = QPointF(MAP::Settings::getObjectIntAttribute(QLatin1String("HitLine"),QLatin1String("Offset_X"),objId,50000), MAP::Settings::getObjectIntAttribute(QLatin1String("HitLine"),QLatin1String("Offset_Y"),objId,50000)); + int extent = MAP::Settings::getObjectIntAttribute(QLatin1String("HitLine"),QLatin1String("Extent"),objId,30000); + + m_hitLine->setStartAndEndPoint(SituationMap::Point3D::fromQPointF(offset), + SituationMap::Point3D::fromQPointF(offset + QPointF(0, extent))); + + // update view + bool success = m_hitLine->updateView(); + + if (!success) + DEBUG_LOG(m_logger) << LOGFUNCTION << " updateView NOT OK"; + + m_hitLineMap[objId] = m_hitLine; + + connect(m_timer, &QTimer::timeout, this, [=]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Updating Fire Line " << objId; + onHitLineUpdate(objId); + }); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeHitLines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + const QList keys = m_hitLineMap.keys(); + for ( int objId, keys) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << "Removing Hit Line: " << objId; + removeHitLine(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::removeHitLine(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_hitLine = m_hitLineMap[objId]; + if (m_hitLine) + { + m_hitLine->removeMySelf(); + m_hitLine.reset(); + m_hitLineMap.remove(objId); + } + + if (m_map) + { + m_map->update(); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideHitLines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + for (auto it = m_hitLineMap.cbegin(), end = m_hitLineMap.cend(); it != end; ++it) + { + int objId = it.key(); + DEBUG_LOG(m_logger) << LOGFUNCTION << "Hide/Show Hit Line: " << objId; + hideHitLine(objId); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::hideHitLine(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + m_hitLine = m_hitLineMap[objId]; + if (m_hitLine) + { + bool isVisible = m_hitLine->visibility(); + m_hitLine->setVisibility(!isVisible); + m_hitLine->updateView(); + } + + if (m_map) + m_map->update(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::onHitLineUpdate(int objId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + m_hitLine = m_hitLineMap[objId]; + if (!m_hitLine) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No Fire Line object "; + return; + } + + m_hitLine->setIdentifier(MAP::Settings::objectIdentifier(QLatin1String("HitLine"),objId) + QLatin1String("Update")); + + // set additional values: + SituationMap::Point3D start = m_hitLine->startPoint(); + SituationMap::Point3D end = m_hitLine->endPoint(); + + m_hitLine->setStartAndEndPoint(end, + start + SituationMap::Point3D(10000, 1000, .0)); + + /* + // set additional values: + SituationMap::PositionCont posCont; + QPointF offset = QPointF(MAP::Settings::getObjectIntAttribute(QLatin1String("HitLine"),QLatin1String("Offset_X"),objId,50000), MAP::Settings::getObjectIntAttribute(QLatin1String("HitLine"),QLatin1String("Offset_Y"),objId,50000)); + int extent = MAP::Settings::getObjectIntAttribute(QLatin1String("HitLine"),QLatin1String("Extent"),objId,30000); + + posCont << offset + QPointF(extent, 0); + posCont << offset + QPointF( 0, extent); + + m_hitLine->setPositions(posCont); +*/ + + //TODO: change more on update ? + + bool success = m_hitLine->updateView(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " success=" << success; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandPointCluster() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandPointCluster * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandPointClusterApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//------------------------------------------------------------------------------------------ +void MainWindow::onRubberbandAbort( SituationMap::RB::RubberbandBase * rubberband ) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Aborting rubberband "; + + rubberband->deleteMySelf(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandMultiPoint() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandMultiPointFeature * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << "Creating rubberband failed"; + } + else + { + rubberband->enableClosed(false); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandMultiPointApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandRectangle() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandRectangle * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandRectangleApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandSquare() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandRectangle * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + rubberband->constrainQuadratic(true); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandRectangleApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandEllipse() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandEllipse * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandEllipseApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandArrow() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandArrow * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandArrowApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCorridorCenterline() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandCorridor * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + rubberband->setInputCenterline(); + rubberband->setCorridorWidth(MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("Width"),1,8000) ); + rubberband->setCorridorFillColor((QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("RB_Corridor"),QLatin1String("FillColor"),1,QLatin1String("orange")))) ); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandCorridorApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCorridorBoundary() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandCorridor * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + rubberband->setInputBoundary(); + rubberband->setCorridorFillColor((QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("RB_Corridor"),QLatin1String("FillColor"),1,QLatin1String("orange")))) ); + rubberband->maximumLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxLength"),1,100000) ); + rubberband->maximumSegmentLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxSegmentLength"),1,10000) ); + rubberband->minimumCorridorWidth( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MinWidth"),1,2000) ); + rubberband->maximumCorridorWidth( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxWidth"),1,10000) ); + rubberband->minimumPointCount( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MinPointCount"),1,3) ); + rubberband->maximumPointCount( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxPointCount"),1,8) ); + rubberband->draw(); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandCorridorApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandFilledMultiPoint() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandMultiPointFeature * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + rubberband->enableClosed(true); + rubberband->setFillColor( (QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("RB_MultiPoint"),QLatin1String("FillColor"),1,QLatin1String("teal")))) ); + rubberband->maximumLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_MultiPoint"),QLatin1String("MaxLength"),1,1000000) ); + rubberband->maximumSegmentLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_MultiPoint"),QLatin1String("MaxSegmentLength"),1,500000) ); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandMultiPointApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandSmoothLine() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandSmoothLine * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandSmoothLineApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandFilledSmoothLine() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandSmoothLine * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + rubberband->enableClosed(true); + rubberband->setFillColor( (QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("RB_SmoothLine"),QLatin1String("FillColor"),1,QLatin1String("olive")))) ); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandSmoothLineApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateRectangle(int objId, int noPoints, bool isSquare) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandRectangle * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + QVector vertexList; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE RUBBERBAND with values from ini file"; + + vertexList.reserve(noPoints); + for (int i = 1; i <= noPoints; i++) + { + vertexList.append(MAP::Settings::getPoint(QLatin1String("RB_Rectangle"),objId,i)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << MAP::Settings::getPoint(QLatin1String("RB_Rectangle"),objId,i).x() << "," << MAP::Settings::getPoint(QLatin1String("RB_Rectangle"),objId,i).y() << ")"; + } + + rubberband->setCoordinateVector(vertexList); + rubberband->setFrameHandleMargin(MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Rectangle"),QLatin1String("HandleMargin"),objId,10)); + rubberband->constrainQuadratic(isSquare); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandRectangleApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateRectangles() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateRectangle(1, 4, false); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateSquares() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateRectangle(2, 4, true); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateEllipses() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateEllipse(1, 4); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreatePointClusters() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreatePointCluster(1, 8); + //rubberbandCreatePointCluster(2, 4); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateEllipse(int objId, int noPoints) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandEllipse * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + QVector vertexList; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE RUBBERBAND with values from ini file"; + + vertexList.reserve(noPoints); + for (int i = 1; i <= noPoints; i++) + { + vertexList.append(MAP::Settings::getPoint(QLatin1String("RB_Ellipse"),objId,i)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << MAP::Settings::getPoint(QLatin1String("RB_Ellipse"),objId,i).x() << "," << MAP::Settings::getPoint(QLatin1String("RB_Ellipse"),objId,i).y() << ")"; + } + + rubberband->setCoordinateVector(vertexList); + rubberband->setFrameHandleMargin(MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Ellipse"),QLatin1String("HandleMargin"),objId,10)); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandEllipseApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreatePointCluster(int objId, int noPoints) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandPointCluster * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE RUBBERBAND with values from ini file"; + + QVector vertexList; + + vertexList.reserve(noPoints); + for (int i = 1; i <= noPoints; i++) + { + vertexList.append(MAP::Settings::getPoint(QLatin1String("RB_PointCluster"),objId,i)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << MAP::Settings::getPoint(QLatin1String("RB_PointCluster"),objId,i).x() << "," << MAP::Settings::getPoint(QLatin1String("RB_PointCluster"),objId,i).y() << ")"; + } + + rubberband->setCoordinateVector(vertexList); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandPointClusterApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateMultiPoint(int objId, int noPoints, bool filled) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandMultiPointFeature * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE RUBBERBAND with values from ini file"; + + QVector vertexList; + + vertexList.reserve(noPoints); + for (int i = 1; i <= noPoints; i++) + { + vertexList.append(MAP::Settings::getPoint(QLatin1String("RB_MultiPoint"),objId,i)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << MAP::Settings::getPoint(QLatin1String("RB_MultiPoint"),objId,i).x() << "," << MAP::Settings::getPoint(QLatin1String("RB_MultiPoint"),objId,i).y() << ")"; + } + + rubberband->setCoordinateVector(vertexList); + rubberband->enableClosed( false ); + + if (filled) + { + rubberband->enableClosed( true ); + rubberband->setFillColor( (QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("RB_MultiPoint"),QLatin1String("FillColor"),objId,QLatin1String("teal")))) ); + rubberband->maximumLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_MultiPoint"),QLatin1String("MaxLength"), objId, 1000000) ); + rubberband->maximumSegmentLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_MultiPoint"),QLatin1String("MaxSegmentLength"), objId, 500000) ); + } + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandMultiPointApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateMultiPoints() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateMultiPoint(1, 5, false); + // TODO: more + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateFilledMultiPoints() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateMultiPoint(1, 5, true); + // TODO: more + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateSmoothLines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateSmoothLine(1, 4, false); + // TODO: more + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateFilledSmoothLines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateSmoothLine(1, 4, true); + // TODO: more + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateSmoothLine(int objId, int noPoints, bool filled) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandSmoothLine * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE RUBBERBAND with values from ini file"; + + QVector vertexList; + + vertexList.reserve(noPoints); + for (int i = 1; i <= noPoints; i++) + { + vertexList.append(MAP::Settings::getPoint(QLatin1String("RB_SmoothLine"),objId,i)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << MAP::Settings::getPoint(QLatin1String("RB_SmoothLine"),objId,i).x() << "," << MAP::Settings::getPoint(QLatin1String("RB_SmoothLine"),objId,i).y() << ")"; + } + + rubberband->setCoordinateVector(vertexList); + rubberband->enableClosed( false ); + if (filled) + { + rubberband->enableClosed( true ); + rubberband->setFillColor( (QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("RB_SmoothLine"),QLatin1String("FillColor"),objId,QLatin1String("olive")))) ); + } + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandSmoothLineApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateCorridorBoundaries() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateCorridor(1, 5, true); + // TODO: more + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateCorridorCenterlines() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateCorridor(1, 5, false); + // TODO: more + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateCorridor(int objId, int noPoints, bool bound) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandCorridor * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE RUBBERBAND with values from ini file"; + + QVector vertexList; + + vertexList.reserve(noPoints); + for (int i = 1; i <= noPoints; i++) + { + vertexList.append(MAP::Settings::getPoint(QLatin1String("RB_Corridor"),objId,i)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << MAP::Settings::getPoint(QLatin1String("RB_Corridor"),objId,i).x() << "," << MAP::Settings::getPoint(QLatin1String("RB_Corridor"),objId,i).y() << ")"; + } + + double corrWidth = (double)MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("Width"), objId, 8000); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Width: " << corrWidth; + + QColor corrFillColor = (QColor(MAP::Settings::getObjectStringAttribute(QLatin1String("RB_Corridor"),QLatin1String("FillColor"), objId, QLatin1String("orange")))); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Fill Color: " << corrFillColor.name(); + + rubberband->setCoordinateVectorWidth(vertexList, corrWidth); + rubberband->setCorridorFillColor(corrFillColor); + + if (bound) + { + rubberband->setInputBoundary(); +// rubberband->minimumPointCount( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MinPointCount"), objId, 3) ); +// rubberband->maximumPointCount( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxPointCount"), objId, 8) ); +// rubberband->minimumCorridorWidth( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MinWidth"), objId, 5000) ); +// rubberband->maximumCorridorWidth( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxWidth"), objId, 10000) ); +// rubberband->maximumLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxLength"), objId, 10000) ); +// rubberband->maximumSegmentLength( MAP::Settings::getObjectIntAttribute(QLatin1String("RB_Corridor"),QLatin1String("MaxSegmentLength"), objId, 50000) ); + } + else + { + rubberband->setInputCenterline(); + } + + rubberband->draw(); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandCorridorApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateArrows() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + rubberbandCreateArrow(1, 4); + // TODO: more + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +// ------------------------------------------------------------------------------------------------- +void MainWindow::rubberbandCreateArrow(int objId, int noPoints) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!m_map) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " No map object "; + return; + } + + SituationMap::RB::RubberbandArrow * rubberband = nullptr; + + try + { + rubberband = m_map->rubberbandFactory().create(); + } + catch (SituationMap::Exception &e) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " " << e.getMessage(); + } + + if (rubberband == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " CREATE RUBBERBAND with values from ini file"; + + QVector vertexList; + + vertexList.reserve(noPoints); + for (int i = 1; i <= noPoints; i++) + { + vertexList.append(MAP::Settings::getPoint(QLatin1String("RB_Arrow"),objId,i)); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << MAP::Settings::getPoint(QLatin1String("RB_Arrow"),objId,i).x() << "," << MAP::Settings::getPoint(QLatin1String("RB_Arrow"),objId,i).y() << ")"; + } + + rubberband->setCoordinateVector(vertexList); + + QObject::connect( rubberband, &SituationMap::RB::Rubberband::apply, this, &MainWindow::onRubberbandArrowApply); + QObject::connect( rubberband, &SituationMap::RB::Rubberband::abort, this, &MainWindow::onRubberbandAbort); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//-------------------------------------------------------------------------------------------------- +void MainWindow::onRubberbandPointClusterApply( SituationMap::RB::RubberbandBase *rubberband) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + SituationMap::RB::RubberbandPointCluster *initialRB = dynamic_cast(rubberband); + + if(initialRB != nullptr) + { + bool mRbands = m_map->rubberbandFactory().allowMultipleRubberbands(); + m_map->rubberbandFactory().allowMultipleRubberbands(true); + + SituationMap::RB::RubberbandPointCluster *finalRB = m_map->rubberbandFactory().create(); + + if (finalRB == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating final rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << initialRB->coordinateCount(); + QVector vertexList = initialRB->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " INITIAL RUBBERBAND"; + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + + finalRB->setCoordinateVector(vertexList); + + connect( finalRB, &SituationMap::RB::Rubberband::apply, this, &MainWindow::endRubberband); + connect( finalRB, &SituationMap::RB::Rubberband::abort, this, &MainWindow::endRubberband); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING INITIAL RUBBERBAND"; + initialRB->deleteMySelf(); + } + + m_map->rubberbandFactory().allowMultipleRubberbands(mRbands); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Converting initial rubberband failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//-------------------------------------------------------------------------------------------------- +void MainWindow::onRubberbandMultiPointApply( SituationMap::RB::RubberbandBase *rubberband) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + SituationMap::RB::RubberbandMultiPointFeature *initialRB = dynamic_cast(rubberband); + + if (initialRB != nullptr) + { + bool mRbands = m_map->rubberbandFactory().allowMultipleRubberbands(); + m_map->rubberbandFactory().allowMultipleRubberbands(true); + + SituationMap::RB::RubberbandMultiPointFeature *finalRB = m_map->rubberbandFactory().create(); + + if (finalRB == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating final rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << initialRB->coordinateCount(); + QVector vertexList = initialRB->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " INITIAL RUBBERBAND"; + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + + QColor rbDrawColor = initialRB->getDrawColor(); + QColor rbFillColor = initialRB->getFillColor(); + + bool isClosed = initialRB->isClosedEnabled(); + + finalRB->setCoordinateVector(vertexList); + finalRB->setDrawColor(rbDrawColor); + + if (isClosed) + { + finalRB->enableClosed(true); + finalRB->setFillColor(rbFillColor); + finalRB->maximumLength(initialRB->getMaximumLength()); + finalRB->maximumSegmentLength(initialRB->getMaximumSegmentLength()); + } + + connect( finalRB, &SituationMap::RB::Rubberband::apply, this, &MainWindow::endRubberband); + connect( finalRB, &SituationMap::RB::Rubberband::abort, this, &MainWindow::endRubberband); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING INITIAL RUBBERBAND"; + initialRB->deleteMySelf(); + } + + m_map->rubberbandFactory().allowMultipleRubberbands(mRbands); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Converting initial rubberband failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +//-------------------------------------------------------------------------------------------------- +void MainWindow::onRubberbandRectangleApply( SituationMap::RB::RubberbandBase *rubberband) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + SituationMap::RB::RubberbandRectangle *initialRB = dynamic_cast(rubberband); + + if(initialRB != nullptr) + { + bool mRbands = m_map->rubberbandFactory().allowMultipleRubberbands(); + m_map->rubberbandFactory().allowMultipleRubberbands(true); + + SituationMap::RB::RubberbandRectangle *finalRB = m_map->rubberbandFactory().create(); + + if (finalRB == nullptr) + { + INFO_LOG(m_logger) << LOGFUNCTION << " Creating final rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << initialRB->coordinateCount(); + QVector vertexList = initialRB->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " INITIAL RUBBERBAND"; + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + DEBUG_LOG(m_logger) << LOGFUNCTION << " VALID = " << initialRB->coordinatesAreValid(); + + QPointF simPoint = initialRB->singleCoordinate( 0, SituationMap::RB::CoordinateSystem::Geo ); + QPointF mapPoint = initialRB->singleCoordinate( 0, SituationMap::RB::CoordinateSystem::Map ); + TRACE_LOG(m_logger) << LOGFUNCTION << " Rectangle center : sim coordinate (" << simPoint.x() << "," << simPoint.y() + << ") map coordinate (" << mapPoint.x() << "," << mapPoint.y() << ")"; + + bool constrains = initialRB->constrainQuadratic(); + QColor rbDrawColor = initialRB->getDrawColor(); + QColor rbFillColor = initialRB->getFillColor(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " QUADRATIC = " << constrains; + + finalRB->constrainQuadratic(constrains); + finalRB->setDrawColor(rbDrawColor); + finalRB->setFillColor(rbFillColor); + finalRB->setFrameHandleMargin(initialRB->getFrameHandleMargin()); + finalRB->setCoordinateVector(vertexList); + + connect( finalRB, &SituationMap::RB::Rubberband::apply, this, &MainWindow::endRubberband); + connect( finalRB, &SituationMap::RB::Rubberband::abort, this, &MainWindow::endRubberband); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING INITIAL RUBBERBAND"; + initialRB->deleteMySelf(); + } + + m_map->rubberbandFactory().allowMultipleRubberbands(mRbands); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Converting initial rubberband failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//-------------------------------------------------------------------------------------------------- +void MainWindow::onRubberbandEllipseApply( SituationMap::RB::RubberbandBase *rubberband) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + SituationMap::RB::RubberbandEllipse *initialRB = dynamic_cast(rubberband); + + if(initialRB != nullptr) + { + bool mRbands = m_map->rubberbandFactory().allowMultipleRubberbands(); + m_map->rubberbandFactory().allowMultipleRubberbands(true); + + SituationMap::RB::RubberbandEllipse *finalRB = m_map->rubberbandFactory().create(); + + if (finalRB == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating final rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << initialRB->coordinateCount(); + QVector vertexList = initialRB->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " INITIAL RUBBERBAND"; + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + + QPointF simPoint = initialRB->singleCoordinate( 0, SituationMap::RB::CoordinateSystem::Geo ); + QPointF mapPoint = initialRB->singleCoordinate( 0, SituationMap::RB::CoordinateSystem::Map ); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Ellipse center : sim coordinate (" << simPoint.x() << "," << simPoint.y() + << ") map coordinate (" << mapPoint.x() << "," << mapPoint.y() << ")"; + + bool constrains = initialRB->constrainQuadratic(); + QColor rbDrawColor = initialRB->getDrawColor(); + QColor rbFillColor = initialRB->getFillColor(); + + finalRB->constrainQuadratic(constrains); + finalRB->setCoordinateVector(vertexList); + finalRB->setDrawColor(rbDrawColor); + finalRB->setFillColor(rbFillColor); + finalRB->setFrameHandleMargin(initialRB->getFrameHandleMargin()); + + connect( finalRB, &SituationMap::RB::Rubberband::apply, this, &MainWindow::endRubberband); + connect( finalRB, &SituationMap::RB::Rubberband::abort, this, &MainWindow::endRubberband); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING INITIAL RUBBERBAND"; + initialRB->deleteMySelf(); + } + + m_map->rubberbandFactory().allowMultipleRubberbands(mRbands); + + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Converting initial rubberband failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//-------------------------------------------------------------------------------------------------- +void MainWindow::endRubberband( SituationMap::RB::RubberbandBase *rubberband) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " FINAL RUBBERBAND"; + + QVector vertexList = rubberband->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << rubberband->coordinateCount(); + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Draw Color = " << rubberband->getDrawColor().name(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Fill Color = " << rubberband->getFillColor().name(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Frame margin = " << rubberband->getFrameHandleMargin(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING FINAL RUBBERBAND"; + rubberband->deleteMySelf(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//-------------------------------------------------------------------------------------------------- +void MainWindow::onRubberbandSmoothLineApply( SituationMap::RB::RubberbandBase *rubberband) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + SituationMap::RB::RubberbandSmoothLine *initialRB = dynamic_cast(rubberband); + + if (initialRB != nullptr) + { + + bool mRbands = m_map->rubberbandFactory().allowMultipleRubberbands(); + m_map->rubberbandFactory().allowMultipleRubberbands(true); + + SituationMap::RB::RubberbandSmoothLine *finalRB = m_map->rubberbandFactory().create(); + + if (finalRB == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating final rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << initialRB->coordinateCount(); + QVector vertexList = initialRB->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " INITIAL RUBBERBAND"; + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + + QColor rbDrawColor = initialRB->getDrawColor(); + QColor rbFillColor = initialRB->getFillColor(); + bool isClosed = initialRB->isClosedEnabled(); + + finalRB->setDrawColor(rbDrawColor); + finalRB->setCoordinateVector(vertexList); + if (isClosed) + { + finalRB->enableClosed(true); + finalRB->setFillColor(rbFillColor); + } + + connect( finalRB, &SituationMap::RB::Rubberband::apply, this, &MainWindow::endRubberband); + connect( finalRB, &SituationMap::RB::Rubberband::abort, this, &MainWindow::endRubberband); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING INITIAL RUBBERBAND"; + initialRB->deleteMySelf(); + } + + m_map->rubberbandFactory().allowMultipleRubberbands(mRbands); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Converting initial rubberband failed"; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +//-------------------------------------------------------------------------------------------------- +void MainWindow::onRubberbandArrowApply( SituationMap::RB::RubberbandBase *rubberband) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + SituationMap::RB::RubberbandArrow* initialRB = dynamic_cast(rubberband); + + if (initialRB != nullptr) + { + bool mRbands = m_map->rubberbandFactory().allowMultipleRubberbands(); + m_map->rubberbandFactory().allowMultipleRubberbands(true); + + SituationMap::RB::RubberbandArrow *finalRB = m_map->rubberbandFactory().create(); + + if (finalRB == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating final rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << initialRB->coordinateCount(); + QVector vertexList = initialRB->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " INITIAL RUBBERBAND"; + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + + finalRB->setCoordinateVector(vertexList); + finalRB->setCoordinateVectorArrow( vertexList, initialRB->arrowHalfWidth(), initialRB->arrowLength() ); + + connect( finalRB, &SituationMap::RB::Rubberband::apply, this, &MainWindow::endRubberband); + connect( finalRB, &SituationMap::RB::Rubberband::abort, this, &MainWindow::endRubberband); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING INITIAL RUBBERBAND"; + initialRB->deleteMySelf(); + + } + + m_map->rubberbandFactory().allowMultipleRubberbands(mRbands); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Converting initial rubberband failed"; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +//-------------------------------------------------------------------------------------------------- +void MainWindow::onRubberbandCorridorApply( SituationMap::RB::RubberbandBase *rubberband) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + SituationMap::RB::RubberbandCorridor *initialRB = dynamic_cast(rubberband); + + if (initialRB != nullptr) + { + bool mRbands = m_map->rubberbandFactory().allowMultipleRubberbands(); + m_map->rubberbandFactory().allowMultipleRubberbands(true); + + SituationMap::RB::RubberbandCorridor * finalRB = m_map->rubberbandFactory().create(); + + if (finalRB == nullptr) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Creating final rubberband failed"; + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " Count of Coordinates " << initialRB->coordinateCount(); + QVector vertexList = initialRB->coordinateVector(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " INITIAL RUBBERBAND"; + for (int i = 0; i < vertexList.size(); ++i) + DEBUG_LOG(m_logger) << LOGFUNCTION << " Point " << i << " : (" << vertexList.at(i).x() << "," << vertexList.at(i).y() << ")"; + DEBUG_LOG(m_logger) << LOGFUNCTION << " Corridor width = " << initialRB->corridorWidth(); + + QColor rbDrawColor = initialRB->getCorridorBorderColor(); + QColor rbFillColor = initialRB->getCorridorFillColor(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " Corridor Draw color = " << rbDrawColor.name(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Corridor Fill color = " << rbFillColor.name(); + + finalRB->setCorridorBorderColor(rbDrawColor); + finalRB->setCorridorFillColor(rbFillColor); + + bool bound = initialRB->isEnabledInputBoundary(); + if (bound) + { + finalRB->setInputBoundary(); + finalRB->setCoordinateVectorWidth(vertexList, initialRB->corridorWidth()); + } + else + { + finalRB->setInputCenterline(); + finalRB->setCoordinateVectorWidth(vertexList, initialRB->corridorWidth()); + } + + connect(finalRB, &SituationMap::RB::Rubberband::apply, this, &MainWindow::endRubberband); + connect(finalRB, &SituationMap::RB::Rubberband::abort, this, &MainWindow::endRubberband); + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " DELETING INITIAL RUBBERBAND"; + initialRB->deleteMySelf(); + + m_map->rubberbandFactory().allowMultipleRubberbands(mRbands); + } + else + ERROR_LOG(m_logger) << LOGFUNCTION << " Converting initial rubberband failed"; + + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.h new file mode 100755 index 000000000..09e98af63 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mainwindow.h @@ -0,0 +1,201 @@ +/** + * @RDE_HEADER@ + * + * MainWindow for DDS View + * + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "nglsettings.h" +#include + +namespace Ui { + class widgetMain; +} + +class MainWindow : public NGLMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + void init(); + +private: + Ui::widgetMain* m_ui; + +// map + SituationMap::Map* m_map = nullptr; + +// map settings + float m_scale = 500000.0; + int m_wkid = 32632; + QPointF m_objectBasePoint = QPointF(0, 0); +// QPointF m_objectCenterPoint = QPointF(0, 0); + LtsCoordTrans::UtmCoordinate s_basePoint; + +// map objects + +// buildings + SituationMap::RefPointer m_building; + QMap > m_buildingMap; + +//danger areas + SituationMap::RefPointer m_dangerArea; + QMap > m_dangerAreaMap; + +//fire areas + SituationMap::RefPointer m_fireArea; + QMap > m_fireAreaMap; + +// B agents + SituationMap::RefPointer m_bAgent; + QMap > m_bAgentMap; + +// Fire line + SituationMap::RefPointer m_hitLine; + QMap > m_hitLineMap; + +//settings + bool m_visibility = true; + bool m_visibilityTechn = true; + bool m_visibilityTact = true; + + void setVersionView(); + +//timer for update buildings + QTimer* m_timer; + + SituationMap::LOGGER_T m_logger; + +private slots: + void onClose(); + +// map + void createMap(); + void destroyMap(); + void onMapReady(); + void onExtentChanged(); + void onMapObjectClicked( const SituationMap::Map::RefPointerMapObjectList &mapObjList ); + void updateMapObjects(); + void clearMapObjects(); + +// buildings + void createBuildings(); + void createBuilding(int objId); + void removeBuildings(); + void removeBuilding(int objId); + void onBuildingUpdate(int objId); + void hideBuildings(); + void hideBuilding(int objId); + +//danger areas + void createDangerAreas(); + void createDangerArea(int objId); + void removeDangerAreas(); + void removeDangerArea(int objId); + void onDangerAreaUpdate(int objId); + void hideDangerAreas(); + void hideDangerArea(int objId); + +//fire areas + void createFireAreas(); + void createFireArea(int objId); + void removeFireAreas(); + void removeFireArea(int objId); + void onFireAreaUpdate(int objId); + void hideFireAreas(); + void hideFireArea(int objId); + +// b agents + void createBAgents(); + void createBAgent(int objId); + void removeBAgents(); + void removeBAgent(int objId); + void onBAgentUpdate(int objId); + void hideBAgents(); + void hideBAgent(int objId); + +// fire lines + void createHitLines(); + void createHitLine(int objId); + void removeHitLines(); + void removeHitLine(int objId); + void onHitLineUpdate(int objId); + void hideHitLines(); + void hideHitLine(int objId); + +// rubberbands + void rubberbandPointCluster(); + void rubberbandMultiPoint(); + void rubberbandRectangle(); + void rubberbandSquare(); + void rubberbandEllipse(); + void rubberbandArrow(); + void rubberbandCorridorBoundary(); + void rubberbandCorridorCenterline(); + void rubberbandFilledMultiPoint(); + void rubberbandSmoothLine(); + void rubberbandFilledSmoothLine(); + + void rubberbandCreateRectangle(int objId, int noPoints, bool isSquare); + void rubberbandCreateRectangles(); + void rubberbandCreateSquares(); + + void rubberbandCreateEllipse(int objId, int noPoints); + void rubberbandCreateEllipses(); + + void rubberbandCreatePointClusters(); + void rubberbandCreatePointCluster(int objId, int noPoints); + + void rubberbandCreateMultiPoints(); + void rubberbandCreateMultiPoint(int objId, int noPoints, bool filled = false); + + void rubberbandCreateFilledMultiPoints(); + + void rubberbandCreateSmoothLines(); + void rubberbandCreateSmoothLine(int objId, int noPoints, bool filled = false); + + void rubberbandCreateFilledSmoothLines(); + + void rubberbandCreateCorridorBoundaries(); + void rubberbandCreateCorridor(int objId, int noPoints, bool bound = false); + + void rubberbandCreateCorridorCenterlines(); + + void rubberbandCreateArrows(); + void rubberbandCreateArrow(int objId, int noPoints); + +// void onRubberbandApply( SituationMap::RB::Rubberband * rubberband); + void onRubberbandAbort( SituationMap::RB::RubberbandBase * rubberband); + + void onRubberbandPointClusterApply(SituationMap::RB::RubberbandBase *rubberband); + void onRubberbandMultiPointApply( SituationMap::RB::RubberbandBase *rubberband); + void onRubberbandRectangleApply( SituationMap::RB::RubberbandBase *rubberband); + void onRubberbandEllipseApply( SituationMap::RB::RubberbandBase *rubberband); + void onRubberbandSmoothLineApply( SituationMap::RB::RubberbandBase *rubberband); + void onRubberbandArrowApply( SituationMap::RB::RubberbandBase *rubberband); + void onRubberbandCorridorApply(SituationMap::RB::RubberbandBase *rubberband); + + void endRubberband( SituationMap::RB::RubberbandBase *rubberband); + +}; + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mapsettings.ini b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mapsettings.ini new file mode 100755 index 000000000..01eae740b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/mapsettings.ini @@ -0,0 +1,241 @@ +# $Id$ +# $HeadURL$ +# $Author$ + +[Map] +URL = "http://excgeo21.sl.defence-elec.de/arcgis/rest/services/Bremen/BremenHemelingenAerialGeoAmt_32632/MapServer" +URL1 = "http://excgeo21.sl.defence-elec.de/arcgis/rest/services/Norddeutschland/Norddeutschland_32632/MapServer" +Wkid = 32632 +BasePoint = "32UMD000000000000" ;# if CoordTrans in not initialized: +Scale = 50000.00 +GridType = "MGRS" ;# GridType: NoType, MGRS, UTM, USGN +GridVisible = false +RotationDegree = 0.0 +SymbologyType = "MIL2525D" +FunctionDialogVisible = true + +[MapObjects] +TextScale = 1.00 + +[Building_1] +Identifier = "Building_001" + +P1 = "93064,78331" +P2 = "93069.9,78337.8" +P3 = "93076.8,78331.8" +P4 = "93070.9,78325" + +P11 = "0.155274,0.925975" +P22 = "0.155275,0.925976" +P33 = "0.155277,0.925975" +P44 = "0.155275,0.925974" + +TextColor = "red" +TextSize = 20 +TextAngle = 0 +TextItalic = true +TextAntiAliased = true + +Visibility = true +VisibilityTech = true +VisibilityTact = true + +LineColor = "red" +LineWidth = 6 +LinePattern = "DashDotDot" + +FillColor = "yellow" +FillPattern = "DiagonalCross" + +TextAngleUpdate = 30 + +[Building_2] +Identifier = "Building_002" + +P1 = "93049,78363" +P2 = "93071,78344" +P3 = "93062,78333" +P4 = "93041,78352" + +P11 = "0.155270,0.925980" +P22 = "0.155275,0.925977" +P33 = "0.155273,0.925975" +P44 = "0.155268,0.925978" + +TextColor = "blue" +TextSize = 16 +TextAngle = -45 +TextItalic = true +TextAntiAliased = true + +Visibility = true +VisibilityTech = true +VisibilityTact = true + +LineColor = "red" +LineWidth = 6 +LinePattern = "DashDotDot" + +FillColor = "yellow" +FillPattern = "DiagonalCross" + +TextAngleUpdate = 0 + +[DangerArea_1] +Identifier = "DangerArea_001" + +Extent = 10000 +Offset_X = 60000 +Offset_Y = 90000 + +Visibility = true +VisibilityTech = true +VisibilityTact = true + +LineColor = "red" +LineWidth = 6 +LinePattern = "DashDotDot" + +FillColor = "yellow" +FillPattern = "DiagonalCross" + +[BAgent_1] +Identifier = "BAgent_001" + +Extent = 10000 +Offset_X = 60000 +Offset_Y = 60000 + +Visibility = true +VisibilityTech = true +VisibilityTact = true + +TextColor = "magenta" +TextSize = 16 +TextBold = true + +LineColor = "cyan" +LineWidth = 6 +LinePattern = "DashDotDot" + +FillColor = "blue" +FillPattern = "DiagonalCross" + +[FireArea_1] +Identifier = "FireArea_001" +Extent = 10000 +Offset_X = 110000 +Offset_Y = 50000 + +Visibility = true +VisibilityTech = true +VisibilityTact = true + +LineColor = "red" +LineWidth = 6 +LinePattern = "Solid" + +FillColor = "blue" +FillPattern = "null" + +[HitLine_1] +Identifier = "HitLine_001" +Extent = 25000 +Offset_X = 120000 +Offset_Y = 90000 + +Visibility = true +VisibilityTech = true +VisibilityTact = true + +LineColor = "blue" +LineWidth = 3 +LinePattern = "Solid" + +[RB_PointCluster_1] +Points = 8 +P1 = "97748.9,111304" +P2 = "82617,57261.7" +P3 = "62215.9,78203.2" +P4 = "74510.6,99955.4" +P5 = "91128.7,89011.7" +P6 = "120582,105225" +P7 = "59784,94145.8" +P8 = "104639,70096.8" + +[RB_PointCluster_2] +Points = 4 +P1 = "79509.5,62260.6" +P2 = "103288,77392.5" +P3 = "88156.4,101171" +P4 = "64377.6,86039.4" + +[RB_MultiPoint_1] +P1 = "80185.1,94551.1" +P2 = "59243.5,75636.2" +P3 = "80725.5,61720.2" +P4 = "74510.6,77797.9" +P5 = "95046.8,74960.6" +FillColor = "teal" +MaxLength = 100000 +MaxSegmentLength = 50000 + +[RB_SmoothLine_1] +P1 = "64918,92254.3" +P2 = "75726.6,70637.2" +P3 = "118555,79824.5" +P4 = "93425.5,88336.2" +FillColor = "olive" + +[RB_Arrow_1] +P1 = "58973.3,102657" +P2 = "115853,82256.4" +P3 = "85454.2,53613.8" +P4 = "69106.3,92794.7" + +[RB_Rectangle_1] +// rectangle +P1 = "48570.1,66689.1" +P2 = "122609,66689.1" +P3 = "122609,103438" +P4 = "48570.1,103438" +HandleMargin = 8 + +[RB_Rectangle_2] +// rotated square +P1 = "64377.6,81783.5" +P2 = "90588.3,63138.8" +P3 = "109233,89349.5" +P4 = "83022.3,107994" +HandleMargin = 8 + +[RB_Rectangle_3] +// square +P1 = "84238.3,73339.3" +P2 = "97748.9,73339.3" +P3 = "97748.9,86850" +P4 = "84238.3,86850" +HandleMargin = 8 + +[RB_Ellipse_1] +P1 = "59648.9,66989.3" +P2 = "127337,66989.3" +P3 = "127337,92254.3" +P4 = "59648.9,92254.3" +HandleMargin = 6 + +[RB_Corridor_1] +P1 = "54109.5,63746.8" +P2 = "107477,69556.4" +P3 = "76942.5,97118.1" +P4 = "77077.6,97118.1" +P5 = "55730.8,79959.6" +FillColor = "orange" +Width = 8000 +MinWidth = 5000 +MaxWidth = 10000 +MaxLength = 1000000 +MaxSegmentLength = 200000 +MinPointCount = 3 +MaxPointCount = 8 + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.cpp new file mode 100755 index 000000000..0491d04b5 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.cpp @@ -0,0 +1,222 @@ +/** + * @RDE_HEADER@ + * Settings for sitMapTestApp + * $Id$ + * $HeadURL$ + * $Author$ + */ +#include "nglsettings.h" +#include +#include +#include +#include +#include + +using namespace NGL; + +//----------------------------------------------------------------------------------------- +namespace MAP +{ +class NGLSettingsClass : public NGL::Settings +{ + public: + NGLSettingsClass(); + ~NGLSettingsClass() {} +}; + +Q_GLOBAL_STATIC ( NGLSettingsClass, s ) + +NGLSettingsClass::NGLSettingsClass() + : NGL::Settings(QLatin1String("mapsettings.ini")) +{ +} + +NGL::Settings *Settings::settings() +{ + qDebug("%s", s()->settings()->fileName().toStdString().c_str()); + return s(); +} + +//---------------------------------------- +QString Settings::basePoint() +{ + return s()->getString(QLatin1String("Map/BasePoint"), QLatin1String("32UMD000000000000")); +} + +/* +//----------------------------------------------------------------------------------------- +double Settings::mapBasePointX() +{ + return s()->getDouble(QLatin1String("Map/BasePoint_X"), 0.0); +} + +//---------------------------------------- +QString Settings::centerPointMgrs() +{ + return s()->getString(QLatin1String("Map/Center"), QLatin1String("32UMD8000085000")); +} + +double Settings::mapCenterPointX() +{ + return s()->getDouble(QLatin1String("Map/CenterPoint_X"), 0.0); +} + +double Settings::mapCenterPointY() +{ + return s()->getDouble(QLatin1String("Map/CenterPoint_Y"), 0.0); +} + +double Settings::mapBasePointY() +{ + return s()->getDouble(QLatin1String("Map/BasePoint_Y"), 0.0); +} +*/ + +int Settings::mapWkid() +{ + return s()->getInteger(QLatin1String("Map/Wkid"), 32632); +} + +double Settings::mapObjectsTextScale() +{ + return s()->getDouble(QLatin1String("MapObjects/TextScale"), 1.0); +} + +double Settings::mapScale() +{ + return s()->getDouble(QLatin1String("Map/Scale"), 100000.0); +} + +double Settings::mapRotationDegree() +{ + return s()->getDouble(QLatin1String("Map/RotationDegree"), 0.0); +} +/* +int Settings::XMin() +{ + return s()->getInteger(QLatin1String("Map/XMin"), 0); +} + +int Settings::YMin() +{ + return s()->getInteger(QLatin1String("Map/YMin"), 0); +} + +int Settings::XMax() +{ + return s()->getInteger(QLatin1String("Map/XMax"), 0); +} + +int Settings::YMax() +{ + return s()->getInteger(QLatin1String("Map/YMax"), 0); +} +*/ +bool Settings::mapGridVisible() +{ + return s()->getBoolean(QLatin1String("Map/GridVisible"), false); +} + +bool Settings::mapFunctionDialogVisible() +{ + return s()->getBoolean(QLatin1String("Map/FunctionDialogVisible"), false); +} + +QString Settings::mapSymbologyType() +{ + return s()->getString(QLatin1String("Map/SymbologyType"), QLatin1String("APP6B") ); +} + +QString Settings::mapGridType() +{ + return s()->getString(QLatin1String("Map/GridType"), QLatin1String("MGRS") ); +} + +QString Settings::mapURL() +{ + return s()->getString(QLatin1String("Map/URL"), QString() ); +} + +// conversion functions +//------------------------------------------------------------------------------------- +SituationMap::LinePattern Settings::getLinePattern(const QString &pattern) +{ + SituationMap::LinePattern linePattern = SituationMap::LinePattern::Null; + if (pattern.compare(QLatin1String("Dash")) == 0) + linePattern = SituationMap::LinePattern::Dash; + if (pattern.compare(QLatin1String("DashDot")) == 0) + linePattern = SituationMap::LinePattern::DashDot; + if (pattern.compare(QLatin1String("DashDotDot")) == 0) + linePattern = SituationMap::LinePattern::DashDotDot; + if (pattern.compare(QLatin1String("Dot")) == 0) + linePattern = SituationMap::LinePattern::Dot; + if (pattern.compare(QLatin1String("Solid")) == 0) + linePattern = SituationMap::LinePattern::Solid; + return linePattern; +} + +SituationMap::FillPattern Settings::getFillPattern(const QString &pattern) +{ + SituationMap::FillPattern fillPattern = SituationMap::FillPattern::Null; + if (pattern.compare(QLatin1String("BackwardDiagonal")) == 0) + fillPattern = SituationMap::FillPattern::BackwardDiagonal; + if (pattern.compare(QLatin1String("Cross")) == 0) + fillPattern = SituationMap::FillPattern::Cross; + if (pattern.compare(QLatin1String("DiagonalCross")) == 0) + fillPattern = SituationMap::FillPattern::DiagonalCross; + if (pattern.compare(QLatin1String("ForwardDiagonal")) == 0) + fillPattern = SituationMap::FillPattern::ForwardDiagonal; + if (pattern.compare(QLatin1String("Solid")) == 0) + fillPattern = SituationMap::FillPattern::Solid; + if (pattern.compare(QLatin1String("Horizontal")) == 0) + fillPattern = SituationMap::FillPattern::Horizontal; + if (pattern.compare(QLatin1String("Vertical")) == 0) + fillPattern = SituationMap::FillPattern::Vertical; + return fillPattern; +} + +SituationMap::GridType Settings::getGridType(const QString &pattern) +{ + SituationMap::GridType gridType = SituationMap::GridType::MGRS; + if (pattern.compare(QLatin1String("MGRS")) == 0) + gridType = SituationMap::GridType::MGRS; + if (pattern.compare(QLatin1String("UTM")) == 0) + gridType = SituationMap::GridType::UTM; + if (pattern.compare(QLatin1String("USGN")) == 0) + gridType = SituationMap::GridType::USGN; + return gridType; +} + +//map objects general functions +//----------------------------------------------------------------------------------------- +QString Settings::objectIdentifier(const QString &prefix, int id) +{ + return s()->getString(prefix+QLatin1String("_")+QString::number(id)+QLatin1String("/Identifier"), prefix+QLatin1String("_")+QString::number(id)); +} + +bool Settings::getObjectBoolAttribute(const QString &prefix, const QString &postfix, int objId, bool defaultValue) +{ + return s()->getBoolean(prefix+QLatin1String("_")+QString::number(objId)+QLatin1String("/")+postfix, defaultValue); +} + +int Settings::getObjectIntAttribute(const QString &prefix, const QString &postfix, int objId, int defaultValue) +{ + return s()->getInteger(prefix+QLatin1String("_")+QString::number(objId)+QLatin1String("/")+postfix, defaultValue); +} + +QString Settings::getObjectStringAttribute(const QString &prefix, const QString &postfix, int objId, const QString &defaultValue) +{ + return s()->getString(prefix+QLatin1String("_")+QString::number(objId)+QLatin1String("/")+postfix, defaultValue); +} + +QPointF Settings::getPoint(const QString &prefix, int objId, int nr ) +{ + QString strPoint = s()->getString(prefix+QLatin1String("_")+QString::number(objId)+QLatin1String("/P")+QString::number(nr), QLatin1String("0,0")); + double point_x, point_y; + QStringList list = strPoint.split(QLatin1Char(',')); + point_x = list[0].toDouble(); + point_y = list[1].toDouble(); + return QPointF(point_x,point_y); +} + +} //namespace diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.h new file mode 100755 index 000000000..c5b0ba033 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/nglsettings.h @@ -0,0 +1,63 @@ +/** + * @RDE_HEADER@ + * Settings for sitMapTestApp + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include +#include +#include +#include + + +namespace NGL { + class Settings; +} + +namespace MAP +{ +namespace Settings +{ + // read map settings + double mapBasePointX(); + double mapBasePointY(); + double mapCenterPointX(); + double mapCenterPointY(); + int mapWkid(); + double mapObjectsTextScale(); + double mapScale(); + bool mapGridVisible(); + bool mapFunctionDialogVisible(); + QString mapGridType(); + double mapRotationDegree(); + QString mapSymbologyType(); + QString mapURL(); + QString centerPointMgrs() ; + QString basePoint(); + int XMin(); + int YMin(); + int XMax(); + int YMax(); + +// general functions for map objects + QString objectIdentifier(const QString &prefix, int id); + bool getObjectBoolAttribute(const QString &prefix, const QString &postfix, int objId, bool defaultValue = false); + int getObjectIntAttribute(const QString &prefix, const QString &postfix, int objId, int defaultValue = 0); + QString getObjectStringAttribute(const QString &prefix, const QString &postfix, int objId, const QString &defaultValue = QString()); + QPointF getPoint(const QString &prefix, int objId, int nr ); + +// conversion functions from strings + SituationMap::LinePattern getLinePattern(const QString &pattern); + SituationMap::FillPattern getFillPattern(const QString &pattern); + SituationMap::GridType getGridType(const QString &pattern); + + //!< The real NGL::Settings - object + NGL::Settings *settings(); + +} +}; + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/ui/mainwindow.ui b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/ui/mainwindow.ui new file mode 100755 index 000000000..fae6c9103 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SitMapTestApp/ui/mainwindow.ui @@ -0,0 +1,594 @@ + + + widgetMain + + + + 0 + 0 + 800 + 641 + + + + + 0 + 0 + + + + Qt::NoContextMenu + + + Situation Map Test Application + + + + :/Rde/RdeIcon:/Rde/RdeIcon + + + Situation Map Test Application + + + + 14 + 14 + + + + + + + + + + + + + 0 + 0 + 800 + 19 + + + + + &Map + + + + + + + + + Map &Objects + + + + &Buildings + + + + + + + + + &Danger Areas + + + + + + + + + B-&Agents + + + + + + + + + &Fire Areas + + + + + + + + + Hit &Lines + + + + + + + + + + + + + + + + + + + + &Rubberbands + + + + &Rectangle + + + + + + + S&quare + + + + + + + &Ellipse + + + + + + + &Point Cluster + + + + + + + &Multipoint + + + + + + + &Filled Multipoint + + + + + + + &Smooth Line + + + + + + + Filled Smooth &Line + + + + + + + Corridor &Boundary + + + + + + + Corridor &Centerline + + + + + + + &Arrow + + + + + + + + + + + + + + + + + + + + + + + + + + &Load Map + + + Load Map + + + Loading default map + + + + + &Remove Map + + + Removing current map + + + + + E&xit + + + Exit application + + + + + &Create + + + Create Buildings + + + + + &Remove + + + Remove Buildings + + + + + &Update + + + Update Buildings + + + + + &Create + + + Create Danger Areas + + + + + &Remove + + + Remove Danger Areas + + + + + &Update + + + Update Danger Areas + + + + + &Create + + + Create B Agents + + + + + &Remove + + + Remove B Agents + + + + + &Update + + + Update B Agents + + + + + &Create + + + Create Fire Areas + + + + + &Remove + + + RemoveFireAreas + + + + + &Update + + + Update Fire Areas + + + + + &Create + + + Create Fire Lines + + + + + &Remove + + + Remove Fire Lines + + + + + &Update + + + Update Hit Lines + + + + + &Rubberbands + + + + + &Create + + + Create RB Rectangle + + + + + &Draw + + + Create RB Rectangle Free Hand + + + + + &Create + + + Create RB Square + + + + + &Draw + + + Create RB Square Free Hand + + + + + &Create + + + Create RB Ellipse + + + + + &Draw + + + Draw RB Ellipse + + + + + &Create + + + Create RB PointCluster + + + + + &Draw + + + Draw RB PointCluster + + + + + &Create + + + Create RB MultiPoint + + + + + &Draw + + + Draw RB MultiPoint + + + + + &Create + + + Create RB Filled MultiPoint + + + + + &Draw + + + Draw RB FilledMultiPoint + + + + + &Create + + + Create RB SmoothLine + + + + + &Draw + + + Draw RB SmoothLine + + + + + &Create + + + Create RB FilledSmoothLine + + + + + Draw + + + Draw RB FilledSmoothLine + + + + + &Create + + + Create RB Corridor Boundary + + + + + &Draw + + + Draw RB Corridor Boundary + + + + + &Create + + + Create RB Corridor Centerline + + + + + &Draw + + + Draw RB Corridor Centerline + + + + + &Create + + + Create RB Arrow + + + + + &Draw + + + Draw RB Arrow + + + + + &Hide/Show + + + Toggle Hide/Show Buildings + + + + + &Hide/Show + + + Toggle Hide/Show Danger Areas + + + + + &Hide/Show + + + Toggle Hide/Show B-Agents + + + + + &Hide/Show + + + Toggle Hide/Show Fire Areas + + + + + &Hide/Show + + + Toggle Hide/Show Hit Lines + + + + + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/CMakeLists.txt new file mode 100755 index 000000000..2030d1311 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# SIT_MAP +SET(SIT_MAP_EXE_SRCS + sit_map_main.cpp + sit_map.cpp + sit_map.h + + log4cxx_sitmapMain.props +) + + +NGL_ADD_EXECUTABLE(WS NO_TRANSLATION sitMapExample ${SIT_MAP_EXE_SRCS}) +NGL_TARGET_LINK_LIBRARIES(sitMapExample + Qt6::Network + NGLLogger + nglapplication + LtsPluginManager + LtsPluginLoader + situationMap + situationMapObjects + situationMapFunctionDialog + situationMapSideBar + + advancedSymbologyGui + + ArcGIS::ArcGIS + ArcGISQt5Hack +# situationMapSideBar +) + +# Copy on LIBchanges: +IF(TARGET sitMapExample) + SET(LOG4CXX_PROPS_FILE log4cxx_sitmapMain.props) + ADD_CUSTOM_COMMAND(TARGET sitMapExample POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${LOG4CXX_PROPS_FILE} $/${LOG4CXX_PROPS_FILE} + ) +ENDIF() + +IF(MSVC) + # Configure the template file + SET(VS_USER_FILE sitMapExample.vcxproj.user) + configure_file( ../../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) +ENDIF(MSVC) + +NGL_INSTALL_TARGET(sitMapExample) +NGL_INSTALL(FILES WS ${CMAKE_CURRENT_SOURCE_DIR}/${LOG4CXX_PROPS_FILE} DESTINATION "bin") + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/log4cxx_sitmapMain.props b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/log4cxx_sitmapMain.props new file mode 100755 index 000000000..0a92b46db --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/log4cxx_sitmapMain.props @@ -0,0 +1,175 @@ +log4j.reset=true +log4j.rootLogger=INFO, file, stdout + +# SitMap +########################################################### +log4j.logger.SituationMap.sitmapMain=INFO +log4j.logger.SituationMap.SitMap=INFO + +# AdvancedSymbology (AdvSymbology) +########################################################### +#log4j.logger.AdvSymbology.SymbolDictionary=INFO +#log4j.logger.AdvSymbology.SymbolDictionaryImpl=INFO +#log4j.logger.AdvSymbology.EsriDictionaryProcessor=INFO +#log4j.logger.AdvSymbology.SymbolResult=INFO +#log4j.logger.AdvSymbology.Sidc2525Delta=INFO + +# SituationMap +########################################################### +#log4j.logger.SituationMap.INFO +#log4j.logger.SituationMap.MapImpl=INFO +#log4j.logger.SituationMap.MapSettings=INFO +#log4j.logger.SituationMap.MapInitializer=INFO +#log4j.logger.SituationMap.JumpPoints=INFO +#log4j.logger.SituationMap.LayerFactoryImpl=INFO +#log4j.logger.SituationMap.LogicalLayer=INFO +#log4j.logger.SituationMap.LayerControl=INFO +#log4j.logger.SituationMap.CoordTranslationFunctorImpl=INFO +#log4j.logger.SituationMap.CoordTranslationThread=INFO +#log4j.logger.SituationMap.PositionsTranslated=INFO +#log4j.logger.SituationMap.GridImpl=INFO +#log4j.logger.SituationMap.ObjectFactory=INFO +#log4j.logger.SituationMap.IdGenerator=INFO +#log4j.logger.SituationMap.ObjectContainer=INFO +#log4j.logger.SituationMap.ObjectContainerCTC=INFO +#log4j.logger.SituationMap.OverlayHandlerImpl=INFO +#log4j.logger.SituationMap.OverviewMapHandlerImpl=INFO +#log4j.logger.SituationMap.OverviewMap=INFO +log4j.logger.SituationMap.LocationDisplayImpl=TRACE +log4j.logger.SituationMap.ViewshedImpl=TRACE +log4j.logger.SituationMap.ViewshedBase=TRACE + +log4j.logger.SituationMap.AbstractMapObjectImpl=INFO +log4j.logger.SituationMap.AbstractEffectSimulationImpl=INFO +log4j.logger.SituationMap.OriginTranslated=INFO +log4j.logger.SituationMap.PositionsTranslated=INFO +log4j.logger.SituationMap.AbstractMultiPointMapObjectImpl=INFO +log4j.logger.SituationMap.AbstractSinglePointMapObjectImpl=INFO +log4j.logger.SituationMap.BasicPathObjectImpl=INFO +log4j.logger.SituationMap.IndicationContainer=INFO + +log4j.logger.SituationMap.PolygonImpl=INFO +log4j.logger.SituationMap.PolylineImpl=INFO +log4j.logger.SituationMap.TextImpl=INFO + +log4j.logger.SituationMap.AreaFireImpl=INFO +log4j.logger.SituationMap.BiologicalAgentImpl=INFO +log4j.logger.SituationMap.BuildingOutlineImpl=INFO +log4j.logger.SituationMap.ChemicalAgentImpl=INFO +log4j.logger.SituationMap.DangerAreaImpl=INFO +log4j.logger.SituationMap.FunctionalAreaImpl=INFO +log4j.logger.SituationMap.GeoImageImpl=INFO +log4j.logger.SituationMap.HitLineImpl=INFO +log4j.logger.SituationMap.MapObjectIndicatorImpl=INFO +log4j.logger.SituationMap.MineImpl=INFO +log4j.logger.SituationMap.MineFieldImpl=INFO +log4j.logger.SituationMap.NuclearAgentImpl=INFO +log4j.logger.SituationMap.ParticipantImpl=INFO +log4j.logger.SituationMap.SingleFireImpl=INFO + +log4j.logger.SituationMap.AbstractGraphicalObject=INFO +log4j.logger.SituationMap.GraphicalObjectEllipse=INFO +log4j.logger.SituationMap.GraphicalObjectPath=INFO +log4j.logger.SituationMap.GraphicalObjectMultiPath=INFO +log4j.logger.SituationMap.GraphicalObjectPictureMarker=INFO +log4j.logger.SituationMap.GraphicalObjectRange=INFO +log4j.logger.SituationMap.GraphicalObjectText=INFO +log4j.logger.SituationMap.TacticalAppearance=INFO + +log4j.logger.SituationMap.EventDispatcher=INFO +log4j.logger.SituationMap.Rubberband=DEBUG +log4j.logger.SituationMap.RubberbandRectangle=INFO +log4j.logger.SituationMap.RubberbandDrawablesAndHandles=INFO +log4j.logger.SituationMap.RubberbandFrameHandle=INFO +log4j.logger.SituationMap.RubberbandPointHandle=INFO +log4j.logger.SituationMap.RubberbandStateSelect=INFO + +log4j.logger.SituationMap.ObjectIndicatorImpl=INFO + + +# SituationMap-SideBar +########################################################### +log4j.logger.SituationMap.SideBar=INFO +log4j.logger.SituationMap.SideBarItem=INFO +log4j.logger.SituationMap.SideBarModel=INFO + + +# PlanEditor +########################################################### +log4j.logger.SituationMap.PlanEditor=TRACE +log4j.logger.SituationMap.PlanRenderer=TRACE + + + +# Appnd_File +########################################################### +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_SituationMap.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %-5p %c{1} |#| %m (%-.30F:%L) %n + +#log4j.appender.stdout.layout.ConversionPattern= %d %-5p %-30c |#| %m (%-.30F:%L) %n + + +# Appnd_Syslog +########################################################### +log4j.appender.syslog=org.apache.log4j.SyslogAppender +log4j.appender.syslog.Threshold=ERROR +log4j.appender.syslog.SysLogHost=localhost +log4j.appender.syslog.Facility=LOCAL7 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m +log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true +log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO +log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL + + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.cpp new file mode 100755 index 000000000..5b641105d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.cpp @@ -0,0 +1,688 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "sit_map.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +// PlanModel: + +#include +#include +#include + + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//----------------------------------------------------------------------------- + + +class myMapInitializer : public SituationMap::MapInitializer +{ + Q_OBJECT + + using BaseClass = SituationMap::MapInitializer; + +public: + explicit myMapInitializer(SituationMap::Map &map + , SituationMap::MapSettings &mapSettings + , const QString &debugName) + : SituationMap::MapInitializer(map, mapSettings, debugName) + {} + + bool initializeObjectFactory() override + { + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::MapObjectIndicator), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Cube), new SituationMap::GenericObjectFactory() ); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Polygon), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Polyline), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Text), new SituationMap::GenericObjectFactory() ); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Participant), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::AreaFire), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BiologicalAgent), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::ChemicalAgent), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BuildingOutline), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::DangerArea), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::FunctionalArea), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::GeoImage), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::HitLine), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::LineOfSight), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::MapObjectAttributes), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Mine), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::MineField), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::NuclearAgent), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SingleFire), new SituationMap::GenericObjectFactory() ); +// SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SingleFireCentral), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SoundArea), new SituationMap::GenericObjectFactory() ); + + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SidcSymbol), new SituationMap::GenericObjectFactory() ); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::SightFan), new SituationMap::GenericObjectFactory()); + + return true; + } // initializeObjectFactory() + + AdvancedSymbology::SymbologyType symbologyType() const override + { + return m_symbologyType; + } // symbologyType + + void setSymbologyType(AdvancedSymbology::SymbologyType symbologyType) + { + m_symbologyType = symbologyType; + } + +protected: + AdvancedSymbology::SymbologyType m_symbologyType = AdvancedSymbology::SymbologyType::APP6B; + +}; +//----------------------------------------------------------------------------- + + +class SitMap::Private +{ +public: + Private() = default; + ~Private() = default; + + // map + QPointer m_map = nullptr; + + bool m_demoMode = false; + + QStackedLayout* m_stackedLayout = nullptr; + + + QSharedPointer m_mapSettings; + QSharedPointer m_mapInitializer; + + // admin + QGridLayout* m_mainLayout = nullptr; + QPushButton* m_pbDestroyMap = nullptr; + QPushButton* m_pbToggleMode = nullptr; + SituationMap::Viewpoint2D m_viewpoint4Toggle; + QPushButton* m_pbQuit = nullptr; + QComboBox* m_cbIniFile = nullptr; + QString m_initFilePath; + QComboBox* m_cb2D3D = nullptr; + QComboBox* m_cbSymbologyType = nullptr; + + + int symbologyType() const + { + if (m_cbSymbologyType) + { + return m_cbSymbologyType->currentData().toInt(); + } + return -1; + } + + + QPushButton* m_pbPlan = nullptr; + + QMenu* m_popupMenu = nullptr; + + QTimer* m_getRotationTimer = nullptr; + +}; // class SitMap::Private +//----------------------------------------------------------------------------- + + + + +//----------------------------------------------------------------------------- +//SitMap +//----------------------------------------------------------------------------- +SitMap::SitMap(bool demoMode, QWidget *parent) + : QDialog(parent) + , m_logger(SituationMap::getLogger("SitMap")) + , d(new Private) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->setObjectName( QLatin1String("SitMapExample-Widget") ); + + d->m_mainLayout = new QGridLayout(this); + d->m_mainLayout->setObjectName( QLatin1String("SitMapExample-MainLayoutGrid") ); + + //----------------------------------- + // Buttons + //----------------------------------- + QVariant data; + + QGridLayout* topLayout = new QGridLayout(); + topLayout->setObjectName( QLatin1String("SitMapExample-TopLayoutGrid") ); + d->m_mainLayout->addLayout(topLayout, 0, 0); + + + d->m_cbIniFile = new QComboBox(); + d->m_cbIniFile->setObjectName( QLatin1String("SitMapExample-IniFileComboBox") ); + topLayout->addWidget(d->m_cbIniFile, 0, 0); + + d->m_cbIniFile->addItem(QStringLiteral("situation_map_defaults.ini"), QLatin1String("situation_map_defaults.ini")); + d->m_cbIniFile->addItem(QStringLiteral("situation_map_ahtc.ini"), QLatin1String("situation_map_ahtc.ini")); + d->m_cbIniFile->addItem(QStringLiteral("situation_map_demo_center.ini"), QLatin1String("situation_map_demo_center.ini")); + d->m_cbIniFile->addItem(QStringLiteral("situation_map_guez.ini"), QLatin1String("situation_map_guez.ini")); + d->m_cbIniFile->addItem(QStringLiteral("situation_map_land400.ini"), QLatin1String("situation_map_land400.ini")); + d->m_cbIniFile->addItem(QStringLiteral("situation_map_qatar.ini"), QLatin1String("situation_map_qatar.ini")); + d->m_cbIniFile->addItem(QStringLiteral("situation_map_luxemburg.ini"), QLatin1String("situation_map_luxemburg.ini")); + d->m_cbIniFile->addItem(QStringLiteral("select"), QString()); + onIniFileActivated(d->m_cbIniFile->currentText()); + + connect(d->m_cbIniFile, QOverload::of(&QComboBox::activated), + this, &SitMap::onIniFileActivated); + + + d->m_cbSymbologyType = new QComboBox(); + d->m_cbSymbologyType->setObjectName( QLatin1String("SitMapExample-SymbologyComboBox") ); + topLayout->addWidget(d->m_cbSymbologyType, 0, 1); + + + // debugging : + if (0) + { + // highlighted -> UserInteraction + connect(d->m_cbSymbologyType, static_cast(&QComboBox::highlighted), this, [](int idx){ + qCritical() << " called: highlighted(int) " << idx; + }); + + // currentIndexChanged -> programmatically & UserInteraction + connect(d->m_cbSymbologyType, static_cast(&QComboBox::currentIndexChanged), this, [](int idx){ + qCritical() << " called: currentIndexChanged(int) " << idx; + }); + + // activated -> UserInteraction + connect(d->m_cbSymbologyType, static_cast(&QComboBox::activated), this, [](int idx) + { + qCritical() << " called: activated(int) " << idx; + }); + + } // + + // : debugging + + + d->m_cb2D3D = new QComboBox(); + d->m_cb2D3D->setObjectName( QLatin1String("SitMapExample-cb2D3D") ); + data.setValue(SituationMap::Mode::Mode_2D); + d->m_cb2D3D->addItem(QStringLiteral("2D"), data); + data.setValue(SituationMap::Mode::Mode_3D); + d->m_cb2D3D->addItem(QStringLiteral("3D"), data); + topLayout->addWidget(d->m_cb2D3D, 0, 2); + + d->m_pbDestroyMap = new QPushButton( QLatin1String("Create Map") ); + d->m_pbDestroyMap->setObjectName( QLatin1String("SitMapExample-CreateDestroyMapButton") ); + topLayout->addWidget(d->m_pbDestroyMap, 0, 3); + connect(d->m_pbDestroyMap, &QPushButton::clicked, this, &SitMap::onCreateDestroyClicked); + + d->m_pbToggleMode = new QPushButton( QLatin1String("Toggle Mode") ); + d->m_pbToggleMode->setObjectName( QLatin1String("SitMapExample-ToggleModeButton") ); + topLayout->addWidget(d->m_pbToggleMode, 0, 4); + connect(d->m_pbToggleMode, &QPushButton::clicked, this, &SitMap::onToggleModeClicked); + d->m_pbToggleMode->setDisabled(true); + + + AdvancedSymbology::SymbologyType symType; + + symType = AdvancedSymbology::SymbologyType::MIL2525D; + data.setValue(symType); d->m_cbSymbologyType->addItem(AdvancedSymbology::toString(symType), data); + + symType = AdvancedSymbology::SymbologyType::MIL2525C; + data.setValue(symType); d->m_cbSymbologyType->addItem(AdvancedSymbology::toString(symType), data); + + symType = AdvancedSymbology::SymbologyType::APP6B; + data.setValue(symType); d->m_cbSymbologyType->addItem(AdvancedSymbology::toString(symType), data); + + symType = AdvancedSymbology::SymbologyType::APP6D; + data.setValue(symType); d->m_cbSymbologyType->addItem(AdvancedSymbology::toString(symType), data); + + symType = AdvancedSymbology::SymbologyType::None; + data.setValue(symType); d->m_cbSymbologyType->addItem(AdvancedSymbology::toString(symType), data); + + d->m_cbSymbologyType->setCurrentText(AdvancedSymbology::toString(AdvancedSymbology::SymbologyType::APP6D)); + + //----------------------------------- + // Map + //----------------------------------- +// createMap(); + + { + QGridLayout* bottomLayout = new QGridLayout(); + bottomLayout->setObjectName( QLatin1String("SitMapExample-TopBottomLayoutGrid") ); + d->m_mainLayout->addLayout(bottomLayout, 2, 0); + + d->m_pbPlan = new QPushButton( QLatin1String("Plan") ); + d->m_pbPlan->setObjectName( QLatin1String("SitMapExample-Plan") ); + connect(d->m_pbPlan, &QPushButton::clicked, this, &SitMap::onPlanClicked); + bottomLayout->addWidget(d->m_pbPlan, 0, 0); + + + d->m_pbQuit = new QPushButton( QLatin1String("Quit") ); + d->m_pbQuit->setObjectName( QLatin1String("SitMapExample-Quit") ); + bottomLayout->addWidget(d->m_pbQuit, 1, 0); + connect(d->m_pbQuit, &QPushButton::clicked, []() { + QCoreApplication::quit(); + }); + + // tbc + + } + + if (demoMode) + { + // topLayout + d->m_cbSymbologyType->hide(); + d->m_cb2D3D->hide(); + d->m_pbDestroyMap->hide(); + + // bottomLayout + d->m_pbPlan->hide(); + + // createMap: + onCreateDestroyClicked(); + + } // if (demoMode) + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SitMap::~SitMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +SituationMap::Map *SitMap::map() +{ + return d->m_map; +} + +void SitMap::onCreateDestroyClicked() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + static bool destroy = true; + destroy = !destroy; + if (destroy) + { + d->m_cbIniFile->setEnabled(true); + d->m_cbSymbologyType->setEnabled(true); + d->m_cb2D3D->setEnabled(true); + d->m_pbDestroyMap->setText( QLatin1String("Create Map") ); + if (d->m_getRotationTimer) + d->m_getRotationTimer->stop(); // to avoid slot being called + delete d->m_map; + } else { + d->m_cbIniFile->setEnabled(false); + d->m_cbSymbologyType->setEnabled(false); + d->m_cb2D3D->setEnabled(false); + d->m_pbDestroyMap->setText( QLatin1String("Destroy Map") ); + createMap(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void SitMap::onToggleModeClicked() +{ + NOTICE_LOG(m_logger) << LOGFUNCTION << " START" << " curMode=" << d->m_cb2D3D->currentText(); + + d->m_pbToggleMode->setDisabled(true); + + d->m_viewpoint4Toggle = d->m_map->viewpoint2D(); + qCritical() << " save last d->m_viewpoint4Toggle=" << d->m_viewpoint4Toggle.toString(); + + // toogle mode: + SituationMap::Mode newMapMode = SituationMap::toggleMode(d->m_map->mode()); + + // destroy: + onCreateDestroyClicked(); + + // rebuild + QVariant data; + data.setValue(newMapMode); + int idx = d->m_cb2D3D->findData(data); + d->m_cb2D3D->setCurrentIndex(idx); + + // create + onCreateDestroyClicked(); +} + +void SitMap::createMap() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_map = SituationMap::Map::create(this); + d->m_map->setObjectName(QLatin1String("SitMapExample-Map")); + + // d->m_mainLayout->addWidget(d->m_map, 1, 0); + + if (!d->m_stackedLayout) + d->m_stackedLayout = new QStackedLayout(); + d->m_stackedLayout->setStackingMode(QStackedLayout::StackAll); + d->m_mainLayout->addLayout(d->m_stackedLayout, 1, 0); + d->m_stackedLayout->addWidget(d->m_map); + + NOTICE_LOG(m_logger) << LOGFUNCTION << " iniFile = '" << d->m_initFilePath << "'"; + + d->m_mapSettings = QSharedPointer ( new SituationMap::MapSettings(d->m_initFilePath) ); + + d->m_mapInitializer = QSharedPointer + ( new myMapInitializer(*d->m_map, *d->m_mapSettings, QLatin1String("SitMapExample")) ); + + // toggle? + if (! d->m_viewpoint4Toggle.isNull()) + d->m_mapInitializer->setViewPoint(d->m_viewpoint4Toggle); + + + // Take SymbologyType from UI and not from ini file: + int symbologyTypeInt = d->symbologyType(); + if (symbologyTypeInt == -1) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error retrieving symbologyType"; + abort(); + } + + AdvancedSymbology::SymbologyType symbologyTypeUI = + static_cast(symbologyTypeInt); + d->m_mapInitializer->setSymbologyType(symbologyTypeUI); + + + if (d->m_mapInitializer->hasErrors()) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " MapInitializer-filename='" << d->m_mapInitializer->filename() + << "'' has following errors: " + << " exists = " << d->m_mapInitializer->exists() + << " accessError = " << d->m_mapInitializer->accessError() + << " formatError = " << d->m_mapInitializer->formatError(); + return; + } else { + const SituationMap::Mode mapMode = d->m_cb2D3D->currentData().value(); + if (d->m_mapInitializer->initMap(mapMode) ) + { + INFO_LOG(m_logger) << LOGFUNCTION << " MapInitializer::initMap() ok"; + } else { + ERROR_LOG(m_logger) << LOGFUNCTION << " MapInitializer::initMap() failed"; + return; + } + } + + // 50. turn over to application (if required): + + d->m_map->setTimeString(QStringLiteral("#")); + + +// connect (d->m_map.data(), &SituationMap::Map::mapObjectsClicked, this, &SitMap::onMapObjectClicked); + connect( d->m_map.data() + , &SituationMap::Map::contextMenuClicked + , this + , &SitMap::onMapContextClicked ); + + + if (d->m_map->isReady()) + d->m_pbToggleMode->setEnabled(true); + else + connect(d->m_map.data(), &SituationMap::Map::mapReady, this, [this]() { + d->m_pbToggleMode->setEnabled(true); + }); + + + + // Create FunctionDialog: + SituationMap::FunctionDialog::addButtonToMap(d->m_map); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + +} // SitMap::createMap + + +static QVector>& participantMap() +{ + static QVector> s_participantCont; + return s_participantCont; +} + +void SitMap::onPlanClicked() +{ + if (!d->m_map || !d->m_map->isReady()) + return; + + const SituationMap::PartyIdToColorFunctor::PartyData pData1(QStringLiteral("P1"), + AdvancedSymbology::Role::Friend, + Qt::blue, Qt::blue); + const SituationMap::PartyIdToColorFunctor::PartyData pData2(QStringLiteral("P2"), + AdvancedSymbology::Role::HostileFaker, + Qt::red, Qt::red); + d->m_map->partyIdToColorFunctor().setParty(1, pData1); + d->m_map->partyIdToColorFunctor().setParty(2, pData2); + + const QSize windowSize = this->size(); + const QSize offset(50, 50); + + SituationMap::LayerFactory &layerFac = d->m_map->layerFactory(); + QStringList layernames = layerFac.layernames(SituationMap::LayerType::Operational); + QString layername = layernames[0]; + layername = QStringLiteral("Participants"); + SituationMap::ObjectFactory &objFac = d->m_map->objectFactory(); + + SituationMap::RefPointer participant; + + + int counter = 0; + int partyId = 1; + for (int x = offset.width(); xm_map->viewPortWindowPointToGeoCoordinate(QPointF(x,y)); + + SituationMap::SymbolData symData; + symData.sidc = QStringLiteral("AP6D|CMPL|10031000111205000000"); + symData.color = Qt::cyan; // Will be overwritten via partyId. + + + participant = objFac.create(layername, debugName); + participantMap().append(participant); + + participant->setOrigin(SituationMap::Point3D::fromQPointF(geoCoord)); + participant->setSymbol(symData); + + if (partyId == 2) + partyId = 1; + else + partyId = 2; + + participant->setPartyId(partyId); + + if (!participant->updateView()) + { + qCritical() << "updateView failed: debugName='" << debugName << "'"; + abort(); + } + + } // for + } // for + + + QTimer *timer = new QTimer(this); + timer->setInterval(150); + static int s_partyId = 1; + static int s_finishCounter=0; + connect(timer, &QTimer::timeout, this, [timer]() { + + qCritical() << " new partyId= " << s_partyId << " count=" << participantMap().size() << ")"; + + ++s_finishCounter; + if (s_finishCounter>10) + timer->stop(); + + int counter = rand()%5; + + for(auto participant : std::as_const(participantMap())) + { + int curPartyId = participant->partyId(); + if (curPartyId == 2) + curPartyId = 1; + else + curPartyId = 2; + + participant->setPartyId(curPartyId); + + SituationMap::SymbolData symData = participant->symbol(); + int mod = ((++counter)%9); + switch (mod) + { + case 0: symData.sidc = QStringLiteral("AP6D|CMPL|10031000111205000000"); break; + case 1: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001202000000"); break; + case 2: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001302000000"); break; + case 3: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001402000000"); break; + case 4: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001502000000"); break; + case 5: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001602000000"); break; + case 6: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001702000000"); break; + case 7: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001802000000"); break; + case 8: symData.sidc = QStringLiteral("AP6D|CMPL|10031500001902000000"); break; + } + participant->setSymbol(symData); + + if (!participant->updateView()) + { + qCritical() << "updateView failed:"; + abort(); + } + } + }); + timer->start(); +} + +void SitMap::onMapObjectClicked(const SituationMap::Map::RefPointerMapObjectList & mapObjList, const QPointF &viewportPosition) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << "START onMapObjectClicked" + << " viewportPosition=" << viewportPosition + << " mapObjList.size()=" << mapObjList.size(); + + for (auto mapObj : mapObjList) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " id=" << mapObj->uniqueMapId() + << " object-debugInfo=" << mapObj->debugInfo() + << " object-identifier=" << mapObj->identifier(); + + // Selection: + if (mapObj->pickable()) + { + mapObj->setSelected(!mapObj->selected()); + mapObj->updateView(); + DEBUG_LOG(m_logger) << LOGFUNCTION << (mapObj->selected() ? " is selected" : " is not selected"); + } + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << "END onMapObjectClicked"; +} + +void SitMap::onMapContextClicked(const SituationMap::Map::RefPointerMapObjectList & mapObjList, const QPointF & pos) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << "START onMapContextClicked window pos x=" << pos.x() << " y=" << pos.y() + << " mapObjList.size()=" << mapObjList.size(); + + return; + +// delete d->m_popupMenu; + +// d->m_popupMenu = new QMenu( d->m_map ); + +// d->m_popupMenu->addSection( QLatin1String("Object at mouse position") ); +// for (auto mapObj : mapObjList) +// { +// DEBUG_LOG(m_logger) << LOGFUNCTION << " id=" << mapObj->uniqueMapId() +// << "object-debugInfo=" << mapObj->debugInfo() +// << "object-identifier=" << mapObj->identifier(); +// d->m_popupMenu->addAction( mapObj->identifier() ); +// } +// d->m_popupMenu->addSeparator(); + +// // d->m_popupMenu->popup(d->m_map->getQGraphicsView()->mapToGlobal(pos.toPoint())); +// d->m_popupMenu->popup( QPoint(100, 100) ); + +// DEBUG_LOG(m_logger) << LOGFUNCTION << "END onMapContextClicked"; + +} + +void SitMap::onIniFileActivated(const QString &text) +{ + if (text.compare(QLatin1String("select")) == 0) + { + d->m_initFilePath = QFileDialog::getOpenFileName(this, + QLatin1String("get ini file"), + QLatin1String(""), + QLatin1String("Ini files (*.ini)")); + } else { + // e.g. TDS-build/share/Lts/situation_map/situation_map_defaults.ini + d->m_initFilePath = NGL::findFile(NGL::FileType::ConfigFile, text); + } + NOTICE_LOG(m_logger) << LOGFUNCTION << "selected filename= '" << d->m_initFilePath << "'"; +} +// SitMap::onMapContextClicked(const SituationMap::Map::RefPointerMapObjectList & mapObjList, const QPoint &) + +#include "sit_map.moc" diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.h new file mode 100755 index 000000000..21eaae000 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map.h @@ -0,0 +1,50 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +// Qwn +#include +#include + +#include +#include + +// Qt +#include + +class SitMap : public QDialog +{ + Q_OBJECT + +public: + explicit SitMap(bool demoMode, QWidget *parent = nullptr); + virtual ~SitMap (); + + SituationMap::Map *map(); + +public slots: + void onCreateDestroyClicked(); + void onToggleModeClicked(); + void createMap(); + + void onPlanClicked(); + void onMapObjectClicked (const SituationMap::Map::RefPointerMapObjectList & mapObjList, const QPointF &viewportPosition); + void onMapContextClicked( const SituationMap::Map::RefPointerMapObjectList & mapObjList, const QPointF &pos); + +protected slots: + void onIniFileActivated(const QString &text); + +private: + SituationMap::LOGGER_T m_logger; ///< Interface of the Logging-Facility + + class Private; + Private* d = nullptr; + +}; + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map_main.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map_main.cpp new file mode 100755 index 000000000..3ebd10183 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/SituationMap/sit_map_main.cpp @@ -0,0 +1,119 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own + +#include "sit_map.h" + +#include + +#include +#include +#include + +#include +#include + + +// Qt +#include +#include +#include +#include + + +// ESRI +//#include + + +int main (int argc, char* argv[]) +{ + NGLApplication app ( argc, argv ); + + // ESRI : +#ifdef Q_OS_WIN + // Force usage of OpenGL ES through ANGLE on Windows + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); +#endif + // : ESRI + + app.createLogger("sitmapMain", "log4cxx_sitmapMain.props"); + + STARTUP_LOG( app.baseLogger() ); + + // TODO: load style plugin + //QApplication::setStyle(QLatin1String("rde")); + //QDir::setSearchPaths(QLatin1String("icon"), QStringList(QLatin1String(":/icons/light"))); + + app.setOrganizationName( QLatin1String( "Rheinmetall Defence Electronics" ) ); + app.setApplicationName( QLatin1String( "SituationMap Example Application" ) ); + + LtsPluginManager::LtsPluginLoader *pluginLoader = LtsPluginManager::LtsPluginLoader::getInstance(); + + pluginLoader->loadPlugins(LtsPluginManager::PluginType::STYLE, nullptr, + QStringList() << QStringLiteral("rme.light")); + pluginLoader->loadPlugins(LtsPluginManager::PluginType::PROJECT, nullptr, + QStringList() << QStringLiteral("legatus")); + + // .. to keep some compatibility to old resource structure TODO: rm ! + QDir::setSearchPaths( QLatin1String( "icon" ), QStringList( QLatin1String( ":/style/icons/" ) ) ); + QDir::setSearchPaths( QLatin1String( "icon" ), QStringList( QLatin1String( ":/style/" ) ) ); // FIXME: does qt check for recursion ?? + + if(0) + { + INFO_LOG(app.baseLogger()) << LOGFUNCTION << " set PROXY PROXY PROXY PROXY PROXY PROXY PROXY"; + + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::HttpProxy); + proxy.setHostName(QLatin1String("213.61.254.67")); // proxy-rde > 213.61.254.67 + proxy.setUser(QLatin1String("Norbert Thoden/RH-UBD-BC")); + proxy.setPassword(QLatin1String("RDEWWW11")); + proxy.setPort(8080); + QNetworkProxy::setApplicationProxy(proxy); + } else { + NOTICE_LOG(app.baseLogger()) << LOGFUNCTION << " reset !!! QNETWORKPROXY QNETWORKPROXY QNETWORKPROXY QNETWORKPROXY QNETWORKPROXY !!!"; + QNetworkProxy::setApplicationProxy(QNetworkProxy()); + } + + //QNetworkProxy proxy; + //proxy.setType(QNetworkProxy::HttpProxy); + //proxy.setHostName(QStringLiteral("proxy-rde")); + //proxy.setPort(8080); + //proxy.setUser(QStringLiteral("norbert.thoden@rheinmetall.com")); + //proxy.setPassword(QStringLiteral("RDEWWW11")); + //QNetworkProxy::setApplicationProxy(proxy); + + + bool demoMode = false; + if (argc == 2) + demoMode = true; + + SitMap* sitMap= new SitMap(demoMode); + + sitMap->setMinimumWidth(200); + sitMap->setMinimumHeight(200); + + sitMap->resize(800, 600); + + sitMap->show(); + + + + + + + + + int ret = app.exec(); + + delete sitMap; + + SHUTDOWN_LOG(app.baseLogger()); + + return ret; +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/CMakeLists.txt new file mode 100755 index 000000000..9084bf05c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# EXAMPLE +SET(EXAMPLE_SRCS + exampleMain.cpp +) + +SET(EXAMPLE_HDRS +) + +IF(MSVC) + + # Configure the template file + SET(VS_USER_FILE exampleSvgRenderer.vcxproj.user) + configure_file( ../../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) + +ENDIF(MSVC) + +NGL_ADD_EXECUTABLE(WS NO_TRANSLATION exampleSvgRenderer ${EXAMPLE_SRCS} ${EXAMPLE_HDRS}) +NGL_TARGET_LINK_LIBRARIES(exampleSvgRenderer + NGLLogger + Qt6::Widgets + Qt6::Core + Qt6::Svg + Qt6::Gui +) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/exampleMain.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/exampleMain.cpp new file mode 100755 index 000000000..c9e88e746 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Tests/exampleSvgRenderer/exampleMain.cpp @@ -0,0 +1,108 @@ +/** + * @RDE_HEADER@ + * Interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include +#include +#include +#include +#include +#include + +void renderImpactSymbol(QPainter& painter) +{ + // From SymbolDictionaryExtendedImpl::renderImpactSymbol + + QColor color = Qt::red; + int size = 100; + bool filled = false; + + + QPen pen(color); + pen.setWidth(2); + painter.setPen(pen); + + const int sizeHalf = size/2.0; + + QPointF center(sizeHalf, sizeHalf); + + // circle + if (filled) + painter.setBrush( color ); + painter.drawEllipse(center, sizeHalf*0.5, sizeHalf*0.5); + +// // lines +// QLine newLine(0, sizeHalf, size, sizeHalf); +// painter.drawLine(newLine); + +// QTransform trans; +// trans.translate(sizeHalf, sizeHalf); +// trans.rotate(45); +// trans.translate(-sizeHalf, -sizeHalf); + +// newLine = trans.map(newLine); +// painter.drawLine(newLine); + +// newLine = trans.map(newLine); +// painter.drawLine(newLine); + +// newLine = trans.map(newLine); +// painter.drawLine(newLine); +} + + + +int main (int argc, char* argv[]) +{ + QApplication app ( argc, argv ); + + // under windows for ArcGIS 10.2.6 and higher + #ifdef Q_OS_WIN + QCoreApplication::setAttribute( Qt::AA_UseOpenGLES ); + #endif + + int fullSize = 100; + QImage image(fullSize, fullSize, QImage::Format_ARGB32); + image.fill(qRgba(0,0,0,0)); + + { + // Image: + QPainter painterI(&image); + renderImpactSymbol(painterI); + painterI.end(); + image.save(QLatin1String("dummy.png")); + + // SaveSvg: + QSvgGenerator generator; + generator.setFileName(QLatin1String("dummy.svg")); + generator.setSize(QSize(100, 100)); + generator.setViewBox(QRect(0, 0, 100, 100)); + generator.setTitle(QLatin1String("SVG Generator Example Drawing")); + generator.setDescription(QLatin1String("An SVG drawing created by the SVG Generator")); + + QPainter painterSvg; + painterSvg.begin(&generator); + renderImpactSymbol(painterSvg); + painterSvg.end(); + } + + { + QSvgRenderer svgRenderer; + QImage image(fullSize, fullSize, QImage::Format_ARGB32); + image.fill(qRgba(0,0,0,0)); + + QPainter painter(&image); + svgRenderer.load(QLatin1String("manualDummy.svg")); + svgRenderer.render(&painter); + painter.end(); + + image.save(QLatin1String("manualDummy.png")); + } + + + return app.exec(); +} diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/CMakeLists.txt new file mode 100755 index 000000000..7b148b942 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/CMakeLists.txt @@ -0,0 +1,52 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +SET(LinkedSources +# ../Utils/IdHandler.cpp +# ../Utils/IdHandler.h + + log4cxxSituationMapUnitTest.props +) + +SET(UnitTest_SRCS + unit_test.cpp + unit_test.h + virtual_inheritances.cpp +) + +SOURCE_GROUP( LinkedSources FILES ${LinkedSources} ) +SOURCE_GROUP( UnitTestSources FILES ${UnitTest_SRCS} ) + +IF(BUILD_TESTING AND TARGET situationMap) + ADD_EXECUTABLE(situationMapUnitTest ${LinkedSources} ${UnitTest_SRCS} ) + + TARGET_LINK_LIBRARIES(situationMapUnitTest + ArcGISQt5Hack + NGLLogger + nglapplication + advancedSymbology_static + situationMap_static + situationMapObjects + ArcGIS::ArcGIS + Qt6::Core + Qt6::Test + ) + ADD_TEST(NAME situationMapUnitTest_NONE COMMAND $ none) + ADD_TEST(NAME situationMapUnitTest_MIL2525D COMMAND $ mil2525d) + + SET(LOG4CXX_PROPS_FILE log4cxxSituationMapUnitTest.props) + ADD_CUSTOM_COMMAND(TARGET situationMapUnitTest POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${LOG4CXX_PROPS_FILE} $/${LOG4CXX_PROPS_FILE} + ) + + IF(MSVC) + # Configure the template file + SET(VS_USER_FILE situationMapUnitTest.vcxproj.user) + configure_file( ../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) + ENDIF() + +ENDIF() diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/log4cxxSituationMapUnitTest.props b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/log4cxxSituationMapUnitTest.props new file mode 100755 index 000000000..fa4be4cb6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/log4cxxSituationMapUnitTest.props @@ -0,0 +1,167 @@ +log4j.reset=true +log4j.rootLogger=WARN, file, stdout + +# SitMap +########################################################### +#log4j.logger.SituationMap.SitMapUnitTest=DEBUG +#log4j.logger.SituationMap.SitMap=WARN + +# AdvancedSymbology (AdvSymbology) +########################################################### +#log4j.logger.AdvSymbology.SymbolDictionary=INFO +#log4j.logger.AdvSymbology.SymbolDictionaryImpl=INFO +#log4j.logger.AdvSymbology.EsriDictionaryProcessor=INFO +#log4j.logger.AdvSymbology.SymbolResult=INFO +#log4j.logger.AdvSymbology.Sidc2525Delta=INFO + +# SituationMap +########################################################### +#log4j.logger.SituationMap.INFO + +log4j.logger.SituationMap.LoggingTest=ERROR +log4j.logger.SituationMap.LoggingTest.Chan1=INFO +log4j.logger.SituationMap.LoggingTest.Chan2=ERROR + +#log4j.logger.SituationMap.MapImpl=WARN +#log4j.logger.SituationMap.MapSettings=WARN +#log4j.logger.SituationMap.MapInitializer=WARN +#log4j.logger.SituationMap.LayerFactoryImpl=WARN +#log4j.logger.SituationMap.LayerControl=WARN +#log4j.logger.SituationMap.CoordTranslationFunctorImpl=WARN +#log4j.logger.SituationMap.CoordTranslationThread=WARN +#log4j.logger.SituationMap.PositionsTranslated=WARN +#log4j.logger.SituationMap.GridImpl=WARN +#log4j.logger.SituationMap.ObjectFactory=WARN +#log4j.logger.SituationMap.IdGenerator=TRACE +#log4j.logger.SituationMap.ObjectContainer=TRACE +#log4j.logger.SituationMap.ObjectContainerCTC=TRACE +#log4j.logger.SituationMap.OverlayHandlerImpl=TRACE +#log4j.logger.SituationMap.ViewshedImpl=INFO +#log4j.logger.SituationMap.ViewshedBase=INFO + +#log4j.logger.SituationMap.AbstractMapObjectImpl=INFO +#log4j.logger.SituationMap.AbstractEffectSimulationImpl=INFO +#log4j.logger.SituationMap.OriginTranslated=INFO +#log4j.logger.SituationMap.PositionsTranslated=INFO +#log4j.logger.SituationMap.AbstractMultiPointMapObjectImpl=INFO +#log4j.logger.SituationMap.AbstractSinglePointMapObjectImpl=INFO +#log4j.logger.SituationMap.BasicPathObjectImpl=INFO +#log4j.logger.SituationMap.IndicationContainer=INFO + +#log4j.logger.SituationMap.PolygonImpl=INFO +#log4j.logger.SituationMap.PolylineImpl=INFO +#log4j.logger.SituationMap.TextImpl=INFO + +#log4j.logger.SituationMap.AreaFireImpl=INFO +#log4j.logger.SituationMap.BiologicalAgentImpl=INFO +#log4j.logger.SituationMap.BuildingOutlineImpl=INFO +#log4j.logger.SituationMap.ChemicalAgentImpl=INFO +#log4j.logger.SituationMap.DangerAreaImpl=INFO +#log4j.logger.SituationMap.FunctionalAreaImpl=INFO +#log4j.logger.SituationMap.HitLineImpl=INFO +#log4j.logger.SituationMap.MapObjectIndicatorImpl=INFO +#log4j.logger.SituationMap.MineImpl=INFO +#log4j.logger.SituationMap.MineFieldImpl=INFO +#log4j.logger.SituationMap.NuclearAgentImpl=INFO +#log4j.logger.SituationMap.ParticipantImpl=INFO +#log4j.logger.SituationMap.SingleFireImpl=INFO + +#log4j.logger.SituationMap.AbstractGraphicalObject=INFO +#log4j.logger.SituationMap.GraphicalObjectEllipse=INFO +#log4j.logger.SituationMap.GraphicalObjectPath=INFO +#log4j.logger.SituationMap.GraphicalObjectMultiPath=INFO +#log4j.logger.SituationMap.GraphicalObjectPictureMarker=INFO +#log4j.logger.SituationMap.GraphicalObjectRange=INFO +#log4j.logger.SituationMap.GraphicalObjectText=INFO +#log4j.logger.SituationMap.TacticalAppearance=INFO +#log4j.logger.SituationMap.MessageBase=INFO + +#log4j.logger.SituationMap.EventDispatcher=FATAL +#log4j.logger.SituationMap.Rubberband=FATAL +#log4j.logger.SituationMap.RubberbandRectangle=FATAL +#log4j.logger.SituationMap.RubberbandDrawablesAndHandles=FATAL +#log4j.logger.SituationMap.RubberbandFrameHandle=FATAL +#log4j.logger.SituationMap.RubberbandPointHandle=FATAL +#log4j.logger.SituationMap.RubberbandStateSelect=FATAL + +# SituationMap-SideBar +########################################################### +#log4j.logger.SituationMap.SideBar=INFO +#log4j.logger.SituationMap.SideBarItem=INFO +#log4j.logger.SituationMap.SideBarModel=INFO + + + + +# Appnd_File +########################################################### +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_SituationMap.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %-5p %c{1} |#| %m (%-.30F:%L) %n + +#log4j.appender.stdout.layout.ConversionPattern= %d %-5p %-30c |#| %m (%-.30F:%L) %n + + +# Appnd_Syslog +########################################################### +log4j.appender.syslog=org.apache.log4j.SyslogAppender +log4j.appender.syslog.Threshold=ERROR +log4j.appender.syslog.SysLogHost=localhost +log4j.appender.syslog.Facility=LOCAL7 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m +log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true +log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO +log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL + + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.cpp new file mode 100755 index 000000000..ae6c90172 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.cpp @@ -0,0 +1,3637 @@ +/** + * @RDE_HEADER@ + * Unittest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "unit_test.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +//#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + + +bool CheckSignal::isConnected(quint64 &msec) +{ + QElapsedTimer timeClock; + timeClock.start(); + + bool isConnected = isSignalConnected(QMetaMethod::fromSignal(&CheckSignal::mySignal)); + + msec = timeClock.elapsed(); + return isConnected; +} + + + +//------------------------------------------------------------------------------------------------------- +// Helper class 'IdGeneratorCounting' +//------------------------------------------------------------------------------------------------------- +class IdGeneratorCounting : public SituationMap::IdGenerator +{ +public: + IdGeneratorCounting() : SituationMap::IdGenerator() + { + ++counter; + } + ~IdGeneratorCounting() + { + --counter; + } + static ulong getCounter() + { + return counter; + } + +private: + static ulong counter; +}; +ulong IdGeneratorCounting::counter = 0; + + + + + + +//------------------------------------------------------------------------------------------------------- +int main(int argc, char *argv[]) +{ + NGLApplication app(argc, argv); + app.createLogger("SituationMapUnitTest", "log4cxxSituationMapUnitTest.props"); + + AdvancedSymbology::SymbologyType symbologyType = AdvancedSymbology::SymbologyType::MIL2525C; + + if (argc < 2) + { + // usage: + std::cout << argv[0] + << std::endl + << " usage: append 'mil' | 'app' to argumentlist" << std::endl + << " e.g " << argv[0] << " [QTest options] [testfunction] none" << std::endl + << " e.g " << argv[0] << " [QTest options] [testfunction] mil2525D" << std::endl + << " e.g " << argv[0] << " [QTest options] [testfunction] mil2525C" << std::endl + << " e.g " << argv[0] << " [QTest options] [testfunction] app6b" << std::endl + << " e.g " << argv[0] << " [QTest options] [testfunction] app6d" << std::endl + << std::endl; + + return -1; + } + + const QString strSymType = app.arguments().constLast(); // the last argument + if (strSymType.compare(QLatin1String("none"), Qt::CaseInsensitive) == 0) + symbologyType = AdvancedSymbology::SymbologyType::None; + else if (strSymType.compare(QLatin1String("mil2525D"), Qt::CaseInsensitive) == 0) + symbologyType = AdvancedSymbology::SymbologyType::MIL2525D; + else if (strSymType.compare(QLatin1String("mil2525C"), Qt::CaseInsensitive) == 0) + symbologyType = AdvancedSymbology::SymbologyType::MIL2525C; + else if (strSymType.compare(QLatin1String("app6b"), Qt::CaseInsensitive) == 0) + symbologyType = AdvancedSymbology::SymbologyType::APP6B; + else if (strSymType.compare(QLatin1String("app6d"), Qt::CaseInsensitive) == 0) + symbologyType = AdvancedSymbology::SymbologyType::APP6D; + argc -= 1; // remove last argument for QTest::qExec + + + SituationMap::LayerFactoryImpl::setForceCreationOfGraphicsOverlay(false); // false => createLayerOnDemand + + + QSharedPointer tc = QSharedPointer(new SitMapUnitTest(symbologyType)); + int retCode = QTest::qExec(tc.data(), argc, argv); + + return retCode; +} + + + +class SitMapUnitTest::Private +{ +public: + + Private() + : m_symbologyType(AdvancedSymbology::SymbologyType::MIL2525C) + { +// m_uniqueIdHandler = new SituationMap::IdHandler(m_initReserve,m_blockSize); + } + + ~Private() = default; + + + +// std::unique_ptr m_uniqueIdHandler; + + AdvancedSymbology::SymbologyType m_symbologyType; + + +}; // class Private +//----------------------------------------------------------------------------- + + + +class Foo +{ +public: + virtual ~Foo() = default; + + static Foo *createFoo() + { + return new Foo(); + } + + Foo(const Foo &) = delete; // if needed, put as private + Foo &operator=(const Foo &) = delete; // if needed, put as private + Foo(Foo &&) = delete; // if needed, put as private + Foo &operator=(Foo &&) = delete; // if needed, put as private + +private: + Foo() {} +}; + + + +class baseClass +{ +public: + virtual ~baseClass() = default; + + baseClass(const baseClass &) = delete; + baseClass &operator=(const baseClass &) = delete; + baseClass(baseClass &&) = delete; + baseClass &operator=(baseClass &&) = delete; + + virtual void doSomething() = 0; + + +protected: + baseClass() {} +}; + +class subClass : public baseClass +{ +public: + ~subClass() = default; + + static subClass *create() + { + return new subClass(); + } + + void doSomething() override { } + +private: + subClass() = default; + +}; + + + + +SitMapUnitTest::SitMapUnitTest(AdvancedSymbology::SymbologyType symbologyType) + : m_logger(SituationMap::getLogger("SitMapUnitTest")) + , m_d(new Private) +{ + m_d->m_symbologyType = symbologyType; + + NOTICE_LOG(m_logger) << "Running test with symbologyType " << m_d->m_symbologyType; +} + +SitMapUnitTest::~SitMapUnitTest() +{ + NOTICE_LOG(m_logger) << "Finished running test with symbologyType " << m_d->m_symbologyType; +} + +void SitMapUnitTest::checkLogging() +{ + SituationMap::LOGGER_T myLogger = SituationMap::getLogger("LoggingTest.Chan1"); + FATAL_LOG(myLogger) << LOGFUNCTION << " myLogger.getLoggerName() -> " << myLogger.getLoggerName(); + + + + DEBUG_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan1) DEBUG-MSG" ; + INFO_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan1) INFO-MSG" ; + WARNING_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan1) WARNING-MSG" ; + ERROR_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan1) ERROR-MSG" ; + FATAL_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan1) FATAL-MSG" ; + + FATAL_LOG(myLogger) << LOGFUNCTION << " ------------"; + + { + SituationMap::LOGGER_T myLogger = SituationMap::getLogger("LoggingTest.Chan2"); + FATAL_LOG(myLogger) << LOGFUNCTION << " myLogger.getLoggerName() -> " << myLogger.getLoggerName(); + + DEBUG_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan2) DEBUG-MSG" ; + INFO_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan2) INFO-MSG" ; + WARNING_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan2) WARNING-MSG" ; + ERROR_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan2) ERROR-MSG" ; + FATAL_LOG(myLogger) << LOGFUNCTION << " (LoggingTest.Chan2) FATAL-MSG" ; + } + FATAL_LOG(myLogger) << LOGFUNCTION << " ------------" ; + + // update logger-obj + SituationMap::LOGGER_T myLogger2 = SituationMap::getLogger("LoggingTest.Chan2"); + FATAL_LOG(myLogger2) << LOGFUNCTION << " myLogger.getLoggerName() -> " << myLogger2.getLoggerName() ; + + DEBUG_LOG(myLogger2) << LOGFUNCTION << " (LoggingTest.Chan2) DEBUG-MSG" ; + INFO_LOG(myLogger2) << LOGFUNCTION << " (LoggingTest.Chan2) INFO-MSG" ; + WARNING_LOG(myLogger2) << LOGFUNCTION << " (LoggingTest.Chan2) WARNING-MSG" ; + ERROR_LOG(myLogger2) << LOGFUNCTION << " (LoggingTest.Chan2) ERROR-MSG" ; + FATAL_LOG(myLogger2) << LOGFUNCTION << " (LoggingTest.Chan2) FATAL-MSG" ; + +} + +struct shm_punkt_rt +{ + shm_punkt_rt(double x = 0, double y = 0) + { + p[0] = x; // Ost + p[1] = y; // Nord + } + + double p[2]; +}; + +void SitMapUnitTest::checkQRect() +{ + QRectF rect; + + QVERIFY2((rect.isEmpty() == true), "isEmpty: failed"); + QVERIFY2((rect.isValid() == false), "isValid: failed"); + + rect = QRectF(100, 100, 40, 60); + QVERIFY2((rect.isEmpty() == false), "isEmpty: failed"); + QVERIFY2((rect.isValid() == true), "isValid: failed"); + + QVERIFY2(rect.width() == 40, "center: failed"); + QVERIFY2(rect.height() == 60, "center: failed"); + + qDebug() << " rect.center()" << rect.center(); + QVERIFY2(((rect.center() - QPoint(120, 130)).manhattanLength() <= 2), "center: failed"); + + QVERIFY2(rect.top() == 100, "top: failed"); // <----- + QVERIFY2(rect.bottom() == 160, "bottom: failed"); // <----- + + + QVERIFY2(rect.topLeft() == QPointF(100, 100), "topLeft: failed"); // <----- + qDebug() << " rect.topRight()" << rect.topRight(); + QVERIFY2(rect.topRight() == QPointF(140, 100), "topLeft: failed"); // <----- + +} + + +void SitMapUnitTest::checkQListQVector() +{ + + std::vector refPoints; + + int refSize = 1000000; + refPoints.reserve(refSize); + + for (int i = 0; i < refSize; ++i) + refPoints.emplace_back(i, 100.1 * i); + + // check within 2*5: + QList qContL; // do NOT modify, QList is used to compare with QVector (a couple of lines later) + QVector qContV; + for (int i = 0; i < 10; ++i) + { + if (i % 2 == 0) + { + QElapsedTimer timer; + if (m_logger.isInfoEnabled()) + timer.start(); + + + qContL.clear(); + qContL.reserve(refSize); + for (const shm_punkt_rt &item : refPoints) + qContL.append(QPointF(item.p[0], item.p[1])); + + INFO_LOG(m_logger) << LOGFUNCTION + << " QList -> " + << timer.elapsed() << "ms"; + } + else + { + QElapsedTimer timer; + if (m_logger.isInfoEnabled()) + timer.start(); + + + qContV.clear(); + qContV.reserve(refSize); + for (const shm_punkt_rt &item : refPoints) + qContV.append(QPointF(item.p[0], item.p[1])); + + INFO_LOG(m_logger) << LOGFUNCTION + << " QVector -> " + << timer.elapsed() << "ms"; + } + + } +} + +void SitMapUnitTest::checkQWeakPointer() +{ + QWeakPointer ts_weak; // TextStyle does NOT inherent from QObject + QVERIFY2((ts_weak.isNull() == true), "isNull: failed"); + { + QSharedPointer ts_shared(new SituationMap::TextStyle()); + ts_weak = ts_shared; // 'connected' weak and shared + + QVERIFY2((ts_weak.isNull() == false), "isNull: failed"); + } // shared will delete object + QVERIFY2((ts_weak.isNull() == true), "isNull: failed"); // weak 'knows' the deletion :-) + +// // you should continue like this: +// QSharedPointer ts_shared2 = ts_weak.toStrongRef(); +// if (ts_shared2) +// { +// ts_shared2->doSomething(); +// } else { +// // error, object down not exists anymore +// } +} + +void SitMapUnitTest::checkFactoryCreationContraints() +{ +// Links: http://stackoverflow.com/questions/124880/is-it-possible-to-prevent-stack-allocation-of-an-object-and-only-allow-it-to-be +// Links: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Requiring_or_Prohibiting_Heap-based_Objects + + + // + // Foo + // + + // creation on stack: +// Foo f; // will not compile, ctor is private + + // creation on heap: +// Foo *f_ptr1 = new Foo(); // will not compile, ctor is private + + Foo *f_ptr2 = Foo::createFoo(); + delete f_ptr2; + + + // + // subClass + // + + // creation on stack: +// subClass sC; // will not compile, ctor is private + + // creation on heap: +// subClass *sC_ptr1 = new subClass(); // will not compile, ctor is private + + subClass *sC_ptr2 = subClass::create(); + delete sC_ptr2; + +} + + +//------------------------------------------------------------------------------------------------------- + +/* +void SitMapUnitTest::checkIdHandlerCreate() +{ + quint32 usedIdCount = m_d->m_uniqueIdHandler->countUsedIds(); + QVERIFY2( (usedIdCount == 0), "Check IdHandler initial value failed, wrong id count" ); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " used id count before test = " + << usedIdCount + ; + + // create one object + + SituationMap::IdHandler::AccessableId * myPtr = new SituationMap::IdHandler::AccessableId(); + SituationMap::IdHandler::UniqueMapId myId = m_d->m_uniqueIdHandler->createId( myPtr ); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " created id = " + << myId + << " obj address = " + << myPtr + ; + + // check count of ids, myId and accessId function + QVERIFY2( ((usedIdCount+1) == m_d->m_uniqueIdHandler->countUsedIds()), "Check IdHandler create failed, wrong id count" ); + QVERIFY2( SituationMap::IdHandler::isIdValid(myId) , "Check IdHandler create failed, bad id" ); + QVERIFY2( (m_d->m_uniqueIdHandler->accessId(myId) == myPtr) , "Check IdHandler create failed, pointer mismatch" ); + + // check invalid access + myPtr = m_d->m_uniqueIdHandler->accessId( SituationMap::IdHandler::UniqueMapId(-2) ); + + QVERIFY2( (myPtr == nullptr) , "Check IdHandler create failed, invalid access doesn_t return nullptr" ); + +} + +void SitMapUnitTest::checkIdHandlerFree() +{ + quint32 usedIdCount = m_d->m_uniqueIdHandler->countUsedIds(); + SituationMap::IdHandler::AccessableId * myPtr = new SituationMap::IdHandler::AccessableId(); + SituationMap::IdHandler::UniqueMapId myId = m_d->m_uniqueIdHandler->createId( myPtr ); + + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " created id =" + << myId + << " obj address =" + << myPtr + ; + + bool ok = m_d->m_uniqueIdHandler->freeId( myId ); + + QVERIFY2( ok, "Check IdHandler freeId failed, return value of freeId is false." ); + QVERIFY2( (usedIdCount == m_d->m_uniqueIdHandler->countUsedIds()), "Check IdHandler freeId failed, wrong id count." ); + QVERIFY2( (m_d->m_uniqueIdHandler->accessId(myId) == nullptr) , "Check IdHandler freeId failed, id is still in use." ); + + ok = m_d->m_uniqueIdHandler->freeId( SituationMap::IdHandler::BadId ); + QVERIFY2(ok == false, "Check IdHandler freeId failed, return value of freeId(BadId) is not false." ); + + ok = m_d->m_uniqueIdHandler->freeId( SituationMap::IdHandler::UniqueMapId(-2) ); + QVERIFY2(ok == false, "Check IdHandler freeId failed, return value of freeId() is not false." ); + +} + +void SitMapUnitTest::checkIdHandlerReUseId() +{ + quint32 usedIdCount = m_d->m_uniqueIdHandler->countUsedIds(); + + QList idList; + + for (unsigned int idx = usedIdCount; idx < m_initReserve; ++idx) + { + idList += m_d->m_uniqueIdHandler->createId( new SituationMap::IdHandler::AccessableId() ); + } + + bool ok = m_d->m_uniqueIdHandler->freeId( idList.first() ); + + QVERIFY2( ok, "Check IdHandler reUseId failed, return value of freeId is false." ); + + if (ok) + { + SituationMap::IdHandler::UniqueMapId id = m_d->m_uniqueIdHandler->createId( new SituationMap::IdHandler::AccessableId() ); + QVERIFY2(id == idList.first(), "Check IdHandler reUseId failed, freed id has not been used again." ); + } + +} + +void SitMapUnitTest::checkIdHandlerReserveId() +{ + quint32 usedIdCount = m_d->m_uniqueIdHandler->countUsedIds(); + + SituationMap::IdHandler::UniqueMapId reserveId = usedIdCount + 7; + + // look for a unused id + while (m_d->m_uniqueIdHandler->isIdInUse(reserveId)) + { + reserveId++; + } + + // first reserve should return true + bool ok = m_d->m_uniqueIdHandler->reserveId( reserveId, new SituationMap::IdHandler::AccessableId() ); + + QVERIFY2( ok, "Check IdHandler reserveId failed, reserveId returns false." ); + + if (ok) + { + // second reserve should return false + ok = m_d->m_uniqueIdHandler->reserveId( reserveId, new SituationMap::IdHandler::AccessableId() ); + QVERIFY2( !ok, "Check IdHandler reserveId failed, second reserveId doesn't return false." ); + + for (int idx = reserveId - usedIdCount + 20; idx >= 0; --idx) + { + SituationMap::IdHandler::UniqueMapId myId = m_d->m_uniqueIdHandler->createId( new SituationMap::IdHandler::AccessableId() ); + QVERIFY2( myId != reserveId, "Check IdHandler reserveId failed, createId returns reserved id." ); + } + + } +} +*/ + +void SitMapUnitTest::checkSimpleValue() +{ + const int intVal = 42; + const int intValModified = 123; + + // assign + SituationMap::SimpleValue wrappedValue = intVal; + QVERIFY2((wrappedValue.changed() == true), "SimpleValue: failed"); + QVERIFY2((wrappedValue.value() == 42), "SimpleValue: failed"); + + // reset + wrappedValue.reset(); + QVERIFY2((wrappedValue.changed() == false), "SimpleValue: failed"); + QVERIFY2((wrappedValue.value() == 42), "SimpleValue: failed"); + + // access: + bool ok = false; + ok = (wrappedValue.value() == intVal); + QVERIFY2((ok), "SimpleValue: failed"); + QVERIFY2((wrappedValue.changed() == false), "SimpleValue: failed"); + + ok = (wrappedValue.refValue() == intVal); + QVERIFY2((ok), "SimpleValue: failed"); + QVERIFY2((wrappedValue.changed() == true), "SimpleValue: failed"); + + ok = (wrappedValue() == intVal); + QVERIFY2((ok), "SimpleValue: failed"); + QVERIFY2((wrappedValue.changed() == true), "SimpleValue: failed"); + + // assign + SituationMap::SimpleValue wrappedValue2 = wrappedValue; + QVERIFY2((wrappedValue2.changed() == true), "SimpleValue: failed"); + + wrappedValue.reset(); + wrappedValue = intValModified; + QVERIFY2((wrappedValue.changed() == true), "SimpleValue: failed"); + QVERIFY2((wrappedValue.value() == intValModified), "SimpleValue: failed"); + + // refValue + wrappedValue.reset(); + wrappedValue.refValue() = intVal; + QVERIFY2((wrappedValue.changed() == true), "SimpleValue: failed"); + QVERIFY2((wrappedValue.value() == intVal), "SimpleValue: failed"); + + // setChanged + wrappedValue.reset(); + QVERIFY2((wrappedValue.changed() == false), "SimpleValue: failed"); + wrappedValue.setChanged(); + QVERIFY2((wrappedValue.changed() == true), "SimpleValue: failed"); + + + // ok = wrappedValue == wrappedValue2; // not supported + +} + +void SitMapUnitTest::checkIdGenerator() +{ + QSharedPointer idGenerator(new SituationMap::IdGenerator()); + QVERIFY2((idGenerator.isNull() == false), "Check IdGenerator does not exits"); + + SituationMap::UniqueMapId id = idGenerator->getNextId(); + + QVERIFY2((id == 0), "Check IdGenerator initial value failed, wrong id"); + QVERIFY2((SituationMap::IdGenerator::idIsValid(id)), "Check IdGenerator id invalid"); + QVERIFY2((SituationMap::isUniqueMapIdValid(id)), "Check IdGenerator id invalid"); + + id = idGenerator->getNextId(); + QVERIFY2((id == 1), "Check IdGenerator initial value failed, wrong id"); + QVERIFY2((SituationMap::IdGenerator::idIsValid(id)), "Check IdGenerator id invalid"); + QVERIFY2((SituationMap::isUniqueMapIdValid(id)), "Check IdGenerator id invalid"); + + id = idGenerator->getNextId(); + QVERIFY2((id == 2), "Check IdGenerator initial value failed, wrong id"); + QVERIFY2((SituationMap::IdGenerator::idIsValid(id)), "Check IdGenerator id invalid"); + QVERIFY2((SituationMap::isUniqueMapIdValid(id)), "Check IdGenerator id invalid"); + + id = SituationMap::IdGenerator::INVALID_ID; + QVERIFY2((SituationMap::IdGenerator::idIsValid(id) == false), "Check INVALID_ID is NOT invalid"); + QVERIFY2((SituationMap::isUniqueMapIdValid(id) == false), "Check INVALID_ID is NOT invalid"); +} + +//class MyBuildingOutlineImpl : public SituationMap::BuildingOutlineImpl +//{ +//public: +// MyBuildingOutlineImpl(const QString &debugInfo = QString()) +// : SituationMap::BuildingOutlineImpl(debugInfo) +// {} +//}; + + +// create template to derive from XY-MapObjectImpl to overcome protected ctor: +template +class MyMapObjectType : public T +{ +public: + MyMapObjectType(const QString &debugInfo = QString()) + : T(debugInfo) + {} +}; + +using MyParticipantImpl = MyMapObjectType; +using MyBiologicalAgentImpl = MyMapObjectType; +using MyBuildingOutlineImpl = MyMapObjectType; + + +void SitMapUnitTest::checkIdContainer() +{ + QVERIFY2((IdGeneratorCounting::getCounter() == 0), "Check IdGeneratorCounting, count failed"); + + + // SituationMap::BuildingOutlineImpl bbbbbbbbb( QLatin1String("bio2") ); + + + { + // + // ONE IdGenerator connected to TWO ObjectContainer: + // + + QSharedPointer idGenerator(new IdGeneratorCounting()); + QVERIFY2((idGenerator.isNull() == false), "Check IdGenerator does not exits"); + QVERIFY2((IdGeneratorCounting::getCounter() == 1), "Check IdGeneratorCounting, count failed"); + + { + // + // FIRST ObjectContainer: + // + SituationMap::ObjectContainer *container1 = + new SituationMap::ObjectContainerCTC(idGenerator); + QVERIFY2((container1), "Check ObjectContainerCTC does not exits"); + + qlonglong count = container1->countObjects(); + QVERIFY2((count == 0), "Check ObjectContainer countObjects failed"); + + // create some dummyObjects: + SituationMap::RefPointer part1(new MyParticipantImpl(QLatin1String("part1"))); + QVERIFY2(part1.valid() == true , "create part failed"); + + SituationMap::RefPointer bio1(new MyBiologicalAgentImpl(QLatin1String("bio1"))); + QVERIFY2(bio1.valid() == true , "create failed"); + SituationMap::RefPointer bio2(new MyBiologicalAgentImpl(QLatin1String("bio2"))); + QVERIFY2(bio2.valid() == true , "create failed"); + + SituationMap::RefPointer build1(new MyBuildingOutlineImpl(QLatin1String("build1"))); + QVERIFY2(build1.valid() == true , "create failed"); + SituationMap::RefPointer build2(new MyBuildingOutlineImpl(QLatin1String("build2"))); + QVERIFY2(build2.valid() == true , "create failed"); + SituationMap::RefPointer build3(new MyBuildingOutlineImpl(QLatin1String("build3"))); + QVERIFY2(build3.valid() == true , "create failed"); + + + SituationMap::UniqueMapId id = SituationMap::IdGenerator::INVALID_ID; + uint i = 0; + // add to our idContainer1: + id = container1->insertObject(part1.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + id = container1->insertObject(bio1.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + id = container1->insertObject(bio2.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + id = container1->insertObject(build1.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + id = container1->insertObject(build2.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + id = container1->insertObject(build3.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + +// std::cerr << " id=" << id << std::endl; + + // check countObjects + count = container1->countObjects(); + QVERIFY2((count == 6), "Check IdContainer countObjects failed"); + + // cleanup + delete container1; + } + + // + // Instantiate SECOND IdContainer with SAME IdGenerator: + // + SituationMap::ObjectContainer *container2 = new SituationMap::ObjectContainerCTC(idGenerator); + + // idGenerator is reused, IdGeneratorCounting::getCounter() must stay at '1': + QVERIFY2((IdGeneratorCounting::getCounter() == 1), "Check IdGeneratorCounting, count failed"); + + qint32 count = container2->countObjects(); + QVERIFY2((count == 0), "Check IdContainer countObjects failed"); + + // create some dummyObjects: + SituationMap::RefPointer part1(new MyParticipantImpl(QLatin1String("part1"))); + QVERIFY2(part1.valid() == true , "create part failed"); + + SituationMap::RefPointer bio1(new MyBiologicalAgentImpl(QLatin1String("bio1"))); + QVERIFY2(bio1.valid() == true , "create failed"); + SituationMap::RefPointer bio2(new MyBiologicalAgentImpl(QLatin1String("bio2"))); + QVERIFY2(bio2.valid() == true , "create failed"); + + SituationMap::UniqueMapId id = SituationMap::IdGenerator::INVALID_ID; + // add to our idContainer2: + + // idGenerator is reused and therefore the uniqueId will continue -> start with '6' + uint i = 6; + id = container2->insertObject(part1.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + id = container2->insertObject(bio1.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + id = container2->insertObject(bio2.get()); + QVERIFY2((id == i++), "Check IdContainer wrong id"); + count = container2->countObjects(); + QVERIFY2((count == 3), "Check IdContainer countObjects failed"); + + // check access methods: + QList keyList = container2->getAllKeys(); + QVERIFY2((keyList.count() == 3), "Check IdContainer getAllKeys failed"); + + QList objList = container2->getAllObjects(); + QVERIFY2((objList.count() == 3), "Check IdContainer getAllObjects failed"); + + // SitMapUnitTest: look for BiologicalAgentIMPL + // ^^^^^^^^ ^^^^ +// std::type_index bioTypeIndex(typeid(SituationMap::BiologicalAgentImpl)); + std::type_index bioTypeIndex(typeid(MyBiologicalAgentImpl)); // use 'MyBiologicalAgentImpl' instead of 'SituationMap::BiologicalAgentImpl' - see insertion + objList = container2->getAllObjects(bioTypeIndex); + QVERIFY2((objList.count() == 2), "Check IdContainer getAllObjects(bioTypeIndex) failed"); + + + SituationMap::ObjectContainer::Object *access = container2->getObject(-2) ; + QVERIFY2((!access), "Check IdContainer getObject failed"); + + access = container2->getObject(7) ; + QVERIFY2((access), "Check IdContainer getObject failed"); + + + + // remove + bool success = container2->removeObject(7); + QVERIFY2((success), "Check IdContainer remove failed"); + count = container2->countObjects(); + QVERIFY2((count == 2), "Check IdContainer countObjects failed"); + + // clear: + container2->clear(); + count = container2->countObjects(); + QVERIFY2((count == 0), "Check IdContainer countObjects failed"); + + + // cleanup; + delete container2; + } + + QVERIFY2((IdGeneratorCounting::getCounter() == 0), "Check IdGeneratorCounting, count failed"); + +} + +void SitMapUnitTest::checkIsSignalConnected() +{ + { + CheckSignal cs; + connect(&cs, &CheckSignal::mySignal, this, &SitMapUnitTest::onCheckIsSignalConnected); + quint64 msec = 0; + bool isConnected = cs.isConnected(msec); + INFO_LOG(m_logger) << LOGFUNCTION << " isSignalConnected took " << msec << "[msec]"; + QVERIFY2(isConnected, "isSignalConnected failed (real slot)"); + } + + { + CheckSignal cs; + connect(&cs, &CheckSignal::mySignal, this, [ = ]() + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " mySignal recognized (lambda slot)"; + }); + quint64 msec = 0; + bool isConnected = cs.isConnected(msec); + INFO_LOG(m_logger) << LOGFUNCTION << " isSignalConnected took " << msec << "[msec]"; + QVERIFY2(isConnected, "isSignalConnected failed (lambda slot)"); + } + +} + +void SitMapUnitTest::onCheckIsSignalConnected() +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " mySignal recognized (real slot)"; +} + + +void SitMapUnitTest::checkMapLayer() +{ + SituationMap::MapLayer first; + QVERIFY2(first.isValid() == false, "MapLayer1 isValid failed"); + + SituationMap::MapLayer second(QLatin1String("LayerName"), + SituationMap::LayerType::Basemap); + QVERIFY2(second.isValid() == true, "MapLayer2 isValid failed"); +} + +void SitMapUnitTest::checkMapLayerOrderedList() +{ + SituationMap::MapLayerOrderedList mlol; + QVERIFY2(mlol.count() == 0, "MapLayerOrderedList count failed"); + + bool success = false; + + // add, check LayerType::Undefined + success = mlol.addLayer(QLatin1String("Basemap1"), SituationMap::LayerType::Undefined); + QVERIFY2(success == false, "MapLayerOrderedList: addLayer LayerType::Undefined failed"); + QVERIFY2(mlol.count() == 0, "MapLayerOrderedList count failed"); + + // add, check emptyName + success = mlol.addLayer(QString(), SituationMap::LayerType::Basemap); + QVERIFY2(success == false, "MapLayerOrderedList: addLayer LayerType::Undefined failed"); + QVERIFY2(mlol.count() == 0, "MapLayerOrderedList count failed"); + + // add, check emptyName + success = mlol.addLayer(QString(), SituationMap::LayerType::Operational); + QVERIFY2(success == false, "MapLayerOrderedList: addLayer LayerType::Undefined failed"); + QVERIFY2(mlol.count() == 0, "MapLayerOrderedList count failed"); + + + + // + // add first + // + success = mlol.addLayer(QLatin1String("Basemap1"), SituationMap::LayerType::Basemap); + QVERIFY2(success, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 1, "MapLayerOrderedList count failed"); + + // add again: + success = mlol.addLayer(QLatin1String("Basemap1"), SituationMap::LayerType::Basemap); + QVERIFY2(success == false, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 1, "MapLayerOrderedList count failed"); + + // add again: + success = mlol.addLayer(QLatin1String("Basemap2"), SituationMap::LayerType::Basemap); + QVERIFY2(success == true, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 2, "MapLayerOrderedList count failed"); + + // add again (same name, different LayerType is not allowed) + success = mlol.addLayer(QLatin1String("Basemap2"), SituationMap::LayerType::Operational); + QVERIFY2(success == false, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 2, "MapLayerOrderedList count failed"); + + // add again: + success = mlol.addLayer(QLatin1String("ObjectLayer3"), SituationMap::LayerType::Operational); + QVERIFY2(success == true, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 3, "MapLayerOrderedList count failed"); + + // add again: + success = mlol.addLayer(QLatin1String("EmergencyCall"), SituationMap::LayerType::EmergencyCall); + QVERIFY2(success == true, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 4, "MapLayerOrderedList count failed"); + + // + // contains + // + success = mlol.contains(QLatin1String("Basemap1"), SituationMap::LayerType::Undefined); + QVERIFY2(success == true, "MapLayerOrderedList: contains failed"); + success = mlol.contains(QLatin1String("Basemap1"), SituationMap::LayerType::Basemap); + QVERIFY2(success == true, "MapLayerOrderedList: contains failed"); + success = mlol.contains(QLatin1String("Basemap1"), SituationMap::LayerType::Operational); + QVERIFY2(success == false, "MapLayerOrderedList: contains failed"); + + success = mlol.contains(QLatin1String("ObjectLayer3"), SituationMap::LayerType::Undefined); + QVERIFY2(success == true, "MapLayerOrderedList: contains failed"); + success = mlol.contains(QLatin1String("ObjectLayer3"), SituationMap::LayerType::Basemap); + QVERIFY2(success == false, "MapLayerOrderedList: contains failed"); + success = mlol.contains(QLatin1String("ObjectLayer3"), SituationMap::LayerType::Operational); + QVERIFY2(success == true, "MapLayerOrderedList: contains failed"); + + success = mlol.contains(QLatin1String("EmergencyCall"), SituationMap::LayerType::EmergencyCall); + QVERIFY2(success == true, "MapLayerOrderedList: contains failed"); + + // + // getLayer + // + SituationMap::MapLayer mapLayer; + mapLayer = mlol.getLayer(QLatin1String("Basemap1")); + QVERIFY2(mapLayer.isValid() == true , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.type() == SituationMap::LayerType::Basemap , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.name().compare(QLatin1String("Basemap1")) == 0 , "MapLayerOrderedList: getLayer failed"); + + mapLayer = mlol.getLayer(QLatin1String("Basemap2")); + QVERIFY2(mapLayer.isValid() == true , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.type() == SituationMap::LayerType::Basemap , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.name().compare(QLatin1String("Basemap2")) == 0 , "MapLayerOrderedList: getLayer failed"); + + mapLayer = mlol.getLayer(QLatin1String("ObjectLayer3")); + QVERIFY2(mapLayer.isValid() == true , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.type() == SituationMap::LayerType::Operational , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.name().compare(QLatin1String("ObjectLayer3")) == 0 , "MapLayerOrderedList: getLayer failed"); + + mapLayer = mlol.getLayer(QLatin1String("EmergencyCall")); + QVERIFY2(mapLayer.isValid() == true , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.type() == SituationMap::LayerType::EmergencyCall , "MapLayerOrderedList: getLayer failed"); + QVERIFY2(mapLayer.name().compare(QLatin1String("EmergencyCall")) == 0 , "MapLayerOrderedList: getLayer failed"); + + // + // layerNamesOrdered + // + QStringList slBase; slBase << QLatin1String("Basemap1") << QLatin1String("Basemap2"); + QStringList slObj; slObj << QLatin1String("ObjectLayer3"); + QStringList slEmer; slEmer << QLatin1String("EmergencyCall"); + + QStringList sl; + sl = mlol.layerNamesOrdered(SituationMap::LayerType::Basemap); + QVERIFY2(sl == slBase , "MapLayerOrderedList: layerNamesOrdered failed"); + sl = mlol.layerNamesOrdered(SituationMap::LayerType::Operational); + QVERIFY2(sl == slObj , "MapLayerOrderedList: layerNamesOrdered failed"); + sl = mlol.layerNamesOrdered(SituationMap::LayerType::EmergencyCall); + QVERIFY2(sl == slEmer, "MapLayerOrderedList: layerNamesOrdered failed"); + sl = mlol.layerNamesOrdered(); + QVERIFY2(sl == slBase + slObj + slEmer , "MapLayerOrderedList: layerNamesOrdered failed"); + + qDebug() << "layer11=" << mlol.layerNamesOrdered(); + + // + // addBasemap + // + success = mlol.addLayer(QLatin1String("newBasemapLayer"), SituationMap::LayerType::Basemap); + QVERIFY2(success == true, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 5, "MapLayerOrderedList count failed"); + QStringList slOk = slBase; + slOk << QLatin1String("newBasemapLayer"); + slOk << slObj << slEmer; + sl = mlol.layerNamesOrdered(); + QVERIFY2(sl == slOk , "MapLayerOrderedList: layerNamesOrdered failed"); + + qDebug() << "layer22=" << mlol.layerNamesOrdered(); + + success = mlol.addLayer(QLatin1String("newObjectLayer"), SituationMap::LayerType::Operational); + QVERIFY2(success == true, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 6, "MapLayerOrderedList count failed"); + slOk << QLatin1String("newObjectLayer"); + sl = mlol.layerNamesOrdered(); + QVERIFY2(sl == slOk , "MapLayerOrderedList: layerNamesOrdered failed"); + + + success = mlol.addLayer(QLatin1String("newBasemapLayer2"), SituationMap::LayerType::Basemap); + + qDebug() << "layer33=" << mlol.layerNamesOrdered(); + + QVERIFY2(success == true, "MapLayerOrderedList: addLayer failed"); + QVERIFY2(mlol.count() == 7, "MapLayerOrderedList count failed"); + slOk.insert(3, QLatin1String("newBasemapLayer2")); + sl = mlol.layerNamesOrdered(); + QVERIFY2(sl == slOk , "MapLayerOrderedList: layerNamesOrdered failed"); + + QVERIFY2(mlol.layerNamesOrdered(SituationMap::LayerType::Basemap).size() == 4 , "MapLayerOrderedList: layerNamesOrdered failed"); + QVERIFY2(mlol.layerNamesOrdered(SituationMap::LayerType::Operational).size() == 2 , "MapLayerOrderedList: layerNamesOrdered failed"); + + qDebug() << "layer44=" << mlol.layerNamesOrdered(); + + // + // orderTo + // + QStringList newOrder; + newOrder << QLatin1String("Basemap1") + << QLatin1String("newObjectLayer") + << QLatin1String("newBasemapLayer2") + << QLatin1String("ObjectLayer3") + << QLatin1String("newBasemapLayer") + << QLatin1String("EmergencyCall") // <-- must be moved on top + << QLatin1String("Basemap2") + ; + + success = mlol.orderLayerTo(newOrder); + QVERIFY2(success == true, "MapLayerOrderedList: orderLayerTo failed"); + + slOk.clear(); + slOk << QLatin1String("Basemap1") + << QLatin1String("newBasemapLayer2") + << QLatin1String("newBasemapLayer") + << QLatin1String("Basemap2") + << QLatin1String("newObjectLayer") + << QLatin1String("ObjectLayer3") + << QLatin1String("EmergencyCall") + ; + sl = mlol.layerNamesOrdered(); + QVERIFY2(sl == slOk , "MapLayerOrderedList: orderLayerTo failed"); + + qDebug() << "layer55=" << mlol.layerNamesOrdered(); + + // + // orderTo with missing 'EmergencyCall' + // + { + QStringList newOrder; + newOrder << QLatin1String("Basemap1") + << QLatin1String("newObjectLayer") + << QLatin1String("newBasemapLayer2") + << QLatin1String("ObjectLayer3") + << QLatin1String("newBasemapLayer") +// << QLatin1String("EmergencyCall") // <-- must be moved on top, even if not provided! + << QLatin1String("Basemap2") + ; + + success = mlol.orderLayerTo(newOrder); + QVERIFY2(success == true, "MapLayerOrderedList: orderLayerTo failed"); + + slOk.clear(); + slOk << QLatin1String("Basemap1") + << QLatin1String("newBasemapLayer2") + << QLatin1String("newBasemapLayer") + << QLatin1String("Basemap2") + << QLatin1String("newObjectLayer") + << QLatin1String("ObjectLayer3") + << QLatin1String("EmergencyCall") + ; + sl = mlol.layerNamesOrdered(); + QVERIFY2(sl == slOk , "MapLayerOrderedList: orderLayerTo failed"); + } + + qDebug() << "layer66=" << mlol.layerNamesOrdered(); + + + + + // take EmergencyCall into account: +} + +void SitMapUnitTest::checkLayerFactory() +{ + // Preperations: + bool success = false; + SituationMap::MapImpl mapImplDummy; + QVERIFY2(mapImplDummy.technicalLayerCount() == 0 , "layerFactory: technicalLayerCount failed"); + + SituationMap::LayerFactory &layerFac = mapImplDummy.layerFactory(); + + // real test + QVERIFY2(layerFac.layernames().count() == 0 , "layerFactory: creation failed"); + QVERIFY2(layerFac.count() == 0 , "layerFactory: creation failed"); + + SituationMap::LayerParameter lp; + success = layerFac.setLayerParameter(QLatin1String("test"), lp); + QVERIFY2(success == false , "layerFactory: setLayerParameter failed"); + + // #layer == 0 + QVERIFY2(layerFac.layernames().count() == 0 , "layerFactory: count failed"); + QVERIFY2(layerFac.count() == 0 , "layerFactory: count failed"); + QVERIFY2(mapImplDummy.technicalLayerCount() == 0 , "layerFactory: technicalLayerCount failed"); + + QVERIFY2(layerFac.layernames(SituationMap::LayerType::Basemap) .count() == 0 , "layerFactory: layernames failed"); + QVERIFY2(layerFac.layernames(SituationMap::LayerType::Operational).count() == 0 , "layerFactory: layernames failed"); + QVERIFY2(layerFac.layernames(SituationMap::LayerType::Rubberband) .count() == 0 , "layerFactory: layernames failed"); + +} + +class MyMapImpl : public SituationMap::MapImpl +{ + Q_OBJECT // -> include "unit_test.moc" explicit +public: + bool isReady() const override + { + return true; + } +}; + + + +void SitMapUnitTest::checkObjectFactory() +{ + // Preperations: + + MyMapImpl mapImplDummy; + mapImplDummy.init(SituationMap::Mode::Mode_2D); // to create MpaGraphicsView and m_graphicsOverlayListModel + + SituationMap::LayerFactory &layerFac = mapImplDummy.layerFactory(); + QVERIFY2(layerFac.count() == 0 , "layerFactory: count failed"); + + layerFac.initializeUnitTestLayer(); + + + SituationMap::ObjectFactory &objFac = mapImplDummy.objectFactory(); + + std::cerr << " count " << objFac.count() << std::endl; + + QVERIFY2(objFac.count() <= 0 , "objectFactory: count failed"); // IdHandler -> 0 ; IDContainer -> -1 + + QVERIFY2(layerFac.count() == 3 , "layerFac: count failed"); + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + QString layername = layernames[1]; + + // real test + // ObjectFactory::registerMe not called yet: + + SituationMap::RefPointer objPtr1; + objPtr1 = objFac.create(layername, QLatin1String("UnitTestId1")); + QVERIFY2(objPtr1.valid() == false , "objectFactory: create1 failed"); + + SituationMap::RefPointer objPtr2; + objPtr2 = objFac.create(layername, QLatin1String("UnitTestId2")); + QVERIFY2(objPtr2.valid() == false , "objectFactory: create2 failed"); + + SituationMap::RefPointer objPtr3; + objPtr3 = objFac.create(layername, QLatin1String("UnitTestId3")); + QVERIFY2(objPtr3.valid() == false , "objectFactory: create3 failed"); + + SituationMap::RefPointer objPtr4; + objPtr4 = objFac.create(layername, QLatin1String("UnitTestId4")); + QVERIFY2(objPtr4.valid() == false , "objectFactory: create4 failed"); + + QVERIFY2(layerFac.count() == 3 , "layerFactory: count failed"); + QVERIFY2(mapImplDummy.technicalLayerCount() == 1 , "layerFactory: technicalLayerCount failed"); + + // ObjectFactory::registerMe + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::Participant), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BiologicalAgent), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::BuildingOutline), new SituationMap::GenericObjectFactory()); + SituationMap::ObjectFactory::registerMe(typeid(SituationMap::DangerArea), new SituationMap::GenericObjectFactory()); + + + QVERIFY2(objFac.count() == 0 , "objectFactory: count failed"); + + // now technicalLayerCount == 1 (basemap) + QVERIFY2(mapImplDummy.technicalLayerCount() == 1 , "layerFactory: technicalLayerCount failed"); + + objPtr1 = objFac.create(layername, QLatin1String("UnitTestId1")); + QVERIFY2(objPtr1.valid() == true , "objectFactory: create11 failed"); + + objPtr2 = objFac.create(layername, QLatin1String("UnitTestId2")); + QVERIFY2(objPtr2.valid() == true , "objectFactory: create22 failed"); + + objPtr3 = objFac.create(layername, QLatin1String("UnitTestId3")); + QVERIFY2(objPtr3.valid() == true , "objectFactory: create33 failed"); + + objPtr4 = objFac.create(layername, QLatin1String("UnitTestId4")); + QVERIFY2(objPtr4.valid() == true , "objectFactory: create44 failed"); + + QVERIFY2(objFac.count() == 4 , "objectFactory: count failed"); + + // now technicalLayerCount == 1 (basemap, layername#...) + QVERIFY2(mapImplDummy.technicalLayerCount() == 2 , "layerFactory: technicalLayerCount failed"); + + + // check rubberbandLayer: + // method 'rubberbandLayer' is not available at the interface: + SituationMap::LayerFactoryImpl *layerFacImpl = + dynamic_cast(&layerFac); + QVERIFY2(layerFacImpl , "layerFac: dynamic_cast failed"); + + const QString &rbLayerName = layerFacImpl->rubberbandLayer(); + QVERIFY2(rbLayerName.isEmpty() == false , "layerFac: rubberbandLayer failed"); + + objPtr2 = objFac.create(rbLayerName, QLatin1String("RbLayerTestId1")); + QVERIFY2(objPtr2.valid() == true , "objectFactory: create111 failed"); + + // now technicalLayerCount == 1 (basemap, layername#..., RubberbandLayer#...) + QVERIFY2(mapImplDummy.technicalLayerCount() == 3 , "layerFactory: technicalLayerCount failed"); + + + const QString &toolLayerName = layerFacImpl->toolLayer(); + QVERIFY2(toolLayerName.isEmpty() == false , "layerFac: toolLayer failed"); + + objPtr3 = objFac.create(toolLayerName, QLatin1String("ToolLayerTestId1")); + QVERIFY2(objPtr3.valid() == true , "objectFactory: create222 failed"); + + if (objPtr1.valid()) + { + // test re-use an uniqueId + SituationMap::UniqueMapId uid = objPtr1->uniqueMapId(); + objPtr1->removeMySelf(); + objPtr1 = objFac.create(uid,layername, QLatin1String("UnitTestRecreate")); + QVERIFY2(objPtr1.valid() == true, "objectFactory: re-create failed"); + QVERIFY2(uid == objPtr1->uniqueMapId(), "objectFactory: re-used removed uniqueId failed"); + } + + //further test require GUI -> another UnitTest + +} + +void SitMapUnitTest::checkPickableOnInvalidUniqueMapId() +{ + // Preperations: + + SituationMap::MapImpl mapImplDummy; + + SituationMap::LayerFactory &layerFac = mapImplDummy.layerFactory(); + QVERIFY2(layerFac.count() == 0 , "LayerFactory: count failed"); + + + // use an LOGICAL layer (not basemap) + QStringList layernames = layerFac.layernames(); + + + // real test + + SituationMap::LayerFactoryImpl *layerFacImpl = + dynamic_cast(&layerFac); + QVERIFY2(layerFacImpl , "LayerFactory: layerFacImpl failed"); + + QSharedPointer tactApp + = QSharedPointer ( + new SituationMap::TacticalAppearance(&mapImplDummy, QLatin1String("AES"), QLatin1String("debugIdUnitTest")) + ); + QVERIFY2(tactApp.data() , "TacticalAppearance failed"); + + QVERIFY2(tactApp->uniqueMapId() == SituationMap::INVALID_MAP_ID , "uniqueMapId failed"); + QVERIFY2(tactApp->pickable() == false , "pickable failed"); + + // must not succeed due to invalid uniqueMapId: + tactApp->setPickable(true); // must not succeed due to invalid uniqueMapId + + QVERIFY2(tactApp->pickable() == false , "pickable failed"); + + +} + + +void SitMapUnitTest::checkBoundingBox() +{ + { + SituationMap::BoundingBox bbox(SituationMap::BoundingBox::ExtentType::Sim); + QVERIFY2(bbox.isValid() == false, "isValid failed"); + } + { + SituationMap::BoundingBox bbox(SituationMap::BoundingBox::ExtentType::Pixel); + QVERIFY2(bbox.isValid() == false, "isValid failed"); + } + + const double zVal = 1.23456; + SituationMap::Point3D center(100, 1000, zVal); + double width = 20; + double height = 60; + + SituationMap::BoundingBox bbox(SituationMap::BoundingBox::ExtentType::Sim, + center, width, height); + QVERIFY2(bbox.isValid() == true, "isValid failed"); + QVERIFY2(bbox.extentType() == SituationMap::BoundingBox::ExtentType::Sim, "extentType failed"); + + QVERIFY2(bbox.center() == center, "center failed"); + QVERIFY2(bbox.width() == width, "width failed"); + QVERIFY2(bbox.height() == height, "height failed"); + + QVERIFY2(bbox.topLeft() == SituationMap::Point3D( 90, 1030, zVal), "topLeft failed"); + QVERIFY2(bbox.topRight() == SituationMap::Point3D(110, 1030, zVal), "topLeft failed"); + QVERIFY2(bbox.bottomLeft() == SituationMap::Point3D( 90, 970, zVal), "topLeft failed"); + QVERIFY2(bbox.bottomRight() == SituationMap::Point3D(110, 970, zVal), "topLeft failed"); + + bbox.reset(); + QVERIFY2(bbox.isValid() == false, "isValid failed"); + QVERIFY2(bbox.extentType() == SituationMap::BoundingBox::ExtentType::Undefined, "extentType failed"); + + QVERIFY2(bbox.center().isNull() == true, "center failed"); + QVERIFY2(bbox.width() < 0.001, "width failed"); + QVERIFY2(bbox.height() < 0.001, "height failed"); + +} + +void SitMapUnitTest::checkDistanceMeasurement() +{ + Esri::ArcGISRuntime::SpatialReference sr = Esri::ArcGISRuntime::SpatialReference::wgs84(); + + Esri::ArcGISRuntime::Point p1 = + Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 00001 00001"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + Esri::ArcGISRuntime::Point p2 = + Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 00011 00001"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + + QVERIFY2(p1.isEmpty() == false, "fromMgrs failed"); + QVERIFY2(p1.spatialReference().wkid() == 4326, "spatialReference failed"); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " p.x=" << p1.x() << " p.y=" << p1.y() << " p.z=" << p1.z() + << " sr=" << p1.spatialReference().wkid(); + + // + // 1. checkDistanceMeasurement + // + const double distance = + Esri::ArcGISRuntime::GeometryEngine::distance(Esri::ArcGISRuntime::Point(p1.x(), p1.y(), sr), + Esri::ArcGISRuntime::Point(p2.x(), p2.y(), sr)); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " GeometryEngine::distance() " << distance; + + // + // 2. distanceGeodetic + // + const Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistance = + Esri::ArcGISRuntime::GeometryEngine::distanceGeodetic(Esri::ArcGISRuntime::Point(p1.x(), p1.y(), sr), + Esri::ArcGISRuntime::Point(p2.x(), p2.y(), sr), + Esri::ArcGISRuntime::LinearUnitId::Meters, + Esri::ArcGISRuntime::AngularUnitId::Degrees, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " GeometryEngine::distanceGeodetic() " << geodeticDistance.distance(); + + QVERIFY2(geodeticDistance.distance() -10.0 < 0.01, "distanceGeodetic failed"); + + // ====> use distanceGeodetic +} + + +void SitMapUnitTest::recalculateAngle() +{ + // aus Transformationspunkte GUEZ, Stand 08/2018: + // (Werte sind EPSG-Code 31468) + // BN_94: x=4473185.721 ; y=5817742.350 ; Drehwinkel=149.3805 + // Wir benötigen den Drehwinkel aber in Bezug auf EPSG-Code 32632. + + const Esri::ArcGISRuntime::SpatialReference srcSr(31468); + const Esri::ArcGISRuntime::SpatialReference destSr(32632); + const QPointF srcPoint(4473185.721, 5817742.350); // BN_94 +// const QPointF srcPoint(4472996.165, 5817720.809); // BN_152 + const double srcAngleDegree = 149.3805; + + // Ansatz: + // Einen zusätzlichen Hilfspunkt bestimmen (Winkel == 0.0), + // beiden Punkte in die Zielprojektion überführen, + // dann den Winkel zw. diesen projezierten Punkten bestimmen. + // Somit erhalten wir eine Differenz zum ursprünglichen Winkel + + const double offset = 1.0; // meter + + QPointF h1 = srcPoint; + QPointF h2 = srcPoint + QPointF(offset, 0.0); // nur x, damit Winkel 0.0 ist + qCritical() << " h1= " << h1; + qCritical() << " h2= " << h2; + + // project: + Esri::ArcGISRuntime::Point esriH1 = + Esri::ArcGISRuntime::GeometryEngine::project(Esri::ArcGISRuntime::Point(h1.x(), h1.y(), 0.0, srcSr), destSr); + Esri::ArcGISRuntime::Point esriH2 = + Esri::ArcGISRuntime::GeometryEngine::project(Esri::ArcGISRuntime::Point(h2.x(), h2.y(), 0.0, srcSr), destSr); + + + // Winkel bestimmen: + double dX = esriH1.x() - esriH2.x(); + double dY = esriH1.y() - esriH2.y(); + + double destAngleRad = atan(dY / dX); + double destAngleDeg = destAngleRad / M_PI * 180.0; + qCritical() << " destAngleRad= " << QString::number(destAngleRad, 'f', 6); + qCritical() << " destAngleDeg= " << QString::number(destAngleDeg, 'f', 6); + + + qCritical() << " srcAngle : " << QString::number(srcAngleDegree, 'f', 6); + qCritical() << " destAngle: " << QString::number(srcAngleDegree + destAngleDeg, 'f', 6); +} + + +void SitMapUnitTest::measureDistanceGeodetic() +{ + Esri::ArcGISRuntime::SpatialReference sr = Esri::ArcGISRuntime::SpatialReference::wgs84(); + + Esri::ArcGISRuntime::Point p1 = + Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 00001 00001"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + Esri::ArcGISRuntime::Point p2 = + Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 00011 00001"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + + int jMax = 1000; + QElapsedTimer timer; + timer.start(); + for (int j = 0; j < jMax; ++j) + { + const Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistance = + Esri::ArcGISRuntime::GeometryEngine::distanceGeodetic(Esri::ArcGISRuntime::Point(p1.x(), p1.y(), sr), + Esri::ArcGISRuntime::Point(p2.x(), p2.y(), sr), + Esri::ArcGISRuntime::LinearUnitId::Meters, + Esri::ArcGISRuntime::AngularUnitId::Degrees, + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + if (geodeticDistance.distance() > 11) + qDebug() << " geodeticDistance: "; + } + qDebug() << " distanceGeodetic: " << jMax << " operations took " << timer.elapsed() << "ms"; + +} + +void SitMapUnitTest::checkDensify() +{ + // connect errorHandling: + connect(Esri::ArcGISRuntime::GeometryEngine::instance(), &Esri::ArcGISRuntime::GeometryEngine::errorOccurred, + this, [this](const Esri::ArcGISRuntime::Error &error) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " ---EsriError--- occured: " << error; + }); + + + Esri::ArcGISRuntime::SpatialReference sr = Esri::ArcGISRuntime::SpatialReference::wgs84(); + + Esri::ArcGISRuntime::Point p1, p2; + + + // + // 1. densify + // + { + p1 = Esri::ArcGISRuntime::Point(8.0, 53.0, sr); + p2 = Esri::ArcGISRuntime::Point(9.0, 53.0, sr); + + // create Polyline: + Esri::ArcGISRuntime::PolylineBuilder polylineBuilder(sr, this); + polylineBuilder.addPoint(p1); + polylineBuilder.addPoint(p2); + const Esri::ArcGISRuntime::Polyline &polylineIn = polylineBuilder.toPolyline(); + + + const Esri::ArcGISRuntime::Geometry &geometryOut = Esri::ArcGISRuntime::GeometryEngine::densify(polylineIn, 0.1); + + QVERIFY2(geometryOut.geometryType() == Esri::ArcGISRuntime::GeometryType::Polyline, "geometryType failed"); + + const Esri::ArcGISRuntime::Polyline polylineOut(geometryOut); + + QVERIFY2(polylineOut.parts().size() == 1, "polylineOut.parts().size() failed"); + qCritical() << "polylineOut.parts().part(0).pointCount() = " << polylineOut.parts().part(0).pointCount(); + QVERIFY2(polylineOut.parts().part(0).pointCount() > 10, "polylineOut.parts().size() failed"); + QVERIFY2(polylineOut.parts().part(0).pointCount() < 15, "polylineOut.parts().size() failed"); + } + + // + // 2. densifyGeodetic - ok? + // + { + + p1 = Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 00001 00001"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + p2 = Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 00001 01001"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + + // create Polyline: + Esri::ArcGISRuntime::PolylineBuilder polylineBuilder(sr, this); + polylineBuilder.addPoint(p1); + polylineBuilder.addPoint(p2); + const Esri::ArcGISRuntime::Polyline &polylineIn = polylineBuilder.toPolyline(); + + const Esri::ArcGISRuntime::Geometry &geometryOut = Esri::ArcGISRuntime::GeometryEngine::densifyGeodetic(polylineIn, + 100, + Esri::ArcGISRuntime::LinearUnit::meters(), + Esri::ArcGISRuntime::GeodeticCurveType::Geodesic); + + QVERIFY2(geometryOut.geometryType() == Esri::ArcGISRuntime::GeometryType::Polyline, "geometryType failed"); + + const Esri::ArcGISRuntime::Polyline polylineOut(geometryOut); + + QVERIFY2(polylineOut.parts().size() == 1, "polylineOut.parts().size() failed"); + qCritical() << "polylineOut.parts().part(0).pointCount() = " << polylineOut.parts().part(0).pointCount(); + QVERIFY2(polylineOut.parts().part(0).pointCount() > 10, "polylineOut.parts().size() failed"); + QVERIFY2(polylineOut.parts().part(0).pointCount() < 15, "polylineOut.parts().size() failed"); + } + + +} + + +QPolygonF bufferUtmPolygon(const QPolygonF &polygonUtmIn, double distance, double maxDeviation = 1, QString *errorOut = nullptr) +{ + Q_UNUSED(maxDeviation); + + Esri::ArcGISRuntime::PolygonBuilder polygonBuilder(Esri::ArcGISRuntime::SpatialReference(32632)); + + for (const QPointF &pointUtm : std::as_const(polygonUtmIn)) + polygonBuilder.addPoint(Esri::ArcGISRuntime::Point(pointUtm.x(), pointUtm.y(), 0)); + const Esri::ArcGISRuntime::Geometry &esriGeometry = polygonBuilder.toGeometry(); + + const Esri::ArcGISRuntime::Polygon polyline = + Esri::ArcGISRuntime::GeometryEngine::buffer(esriGeometry, distance); + + if (polyline.parts().isEmpty()) + { + if (errorOut) + *errorOut = QStringLiteral("GeometryEngine::buffer failed (no parts"); + return {}; + } + + + const Esri::ArcGISRuntime::ImmutablePart &immutablePart = polyline.parts().part(0); + const int size = immutablePart.pointCount(); + + QPolygonF bufferedPoly; + bufferedPoly.reserve(size); + + for (int i = 0; i < size; ++i) + { + const Esri::ArcGISRuntime::Point &esriP = immutablePart.point(i); + bufferedPoly.append(QPointF(esriP.x(), esriP.y())); + } + + return bufferedPoly; +} + + +void SitMapUnitTest::checkBuffer() +{ + // Für Gerlinde, Thema Gefährungsabstand + + // Hier der Stellvertreter für das Wirkfeld: + QPolygonF wirkfeldPoly({ {0.0, 0.0}, {1000.0, 0.0}, {1000.0, 1000.0} }); + + NOTICE_LOG(m_logger) << LOGFUNCTION << " wirkfeldPoly.size = " << wirkfeldPoly.size(); + for (auto item : std::as_const(wirkfeldPoly)) + NOTICE_LOG(m_logger) << LOGFUNCTION << " wirkfeldPoly = " << item; + + + QString errorOut; + const QPolygonF &bufferedPoly = bufferUtmPolygon(wirkfeldPoly, 10, 1, &errorOut); + + NOTICE_LOG(m_logger) << LOGFUNCTION << " bufferedPoly.size = " << bufferedPoly.size(); + for (auto item : std::as_const(bufferedPoly)) + NOTICE_LOG(m_logger) << LOGFUNCTION << " bufferedPoly = " << item; +} + +void SitMapUnitTest::checkMovementTrack() +{ + const Esri::ArcGISRuntime::SpatialReference &sr = Esri::ArcGISRuntime::SpatialReference::wgs84(); + + SituationMap::MovementTrack mt(4711); + + bool success; + + QVERIFY2(mt.size() == 0, "size failed"); + QVERIFY2(mt.isActive() == true, "isActive failed"); + + QVERIFY2(mt.getTrack().size() == 0, "getTrack failed"); + QVERIFY2(mt.getVisibleTrack(0, 999999, 999999).size() == 0, "getVisibleTrack failed"); + + Esri::ArcGISRuntime::Point eP1 = + Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 10000 10000"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + SituationMap::Point3D p1(eP1.x()/180.0*M_PI, eP1.y()/180.0*M_PI, eP1.z()); // -> rad + + success = mt.addPosition(p1, 1000, 1, 99999); + QVERIFY2(success, "success failed"); + QVERIFY2(mt.size() == 1, "size failed"); + QVERIFY2(mt.getTrack().size() == 1, "getTrack failed"); + + // check via maxViewAgeMsec (3rd) + QVERIFY2(mt.getVisibleTrack(2000, 999999, 999).size() == 0, "getVisibleTrack failed"); + QVERIFY2(mt.getVisibleTrack(2000, 999999, 1000).size() == 1, "getVisibleTrack failed"); + + // check via maxAgeMsec (2rd) + QVERIFY2(mt.getTrack().size() == 1, "getTrack failed"); + QVERIFY2(mt.getVisibleTrack(2000, 1000, 1000).size() == 1, "getVisibleTrack failed"); + QVERIFY2(mt.getTrack().size() == 1, "getTrack failed"); + QVERIFY2(mt.getVisibleTrack(2000, 999, 1000).size() == 0, "getVisibleTrack failed"); + QVERIFY2(mt.getTrack().size() == 0, "getTrack failed"); + + Esri::ArcGISRuntime::Point eP2 = + Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 10001 10000"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + SituationMap::Point3D p2(eP2.x()/180.0*M_PI, eP2.y()/180.0*M_PI, eP2.z()); // -> rad + + Esri::ArcGISRuntime::Point eP3 = + Esri::ArcGISRuntime::CoordinateFormatter::fromMgrs(QLatin1String("32UMD 10010 10000"), sr, Esri::ArcGISRuntime::MgrsConversionMode::Automatic); + SituationMap::Point3D p3(eP3.x()/180.0*M_PI, eP3.y()/180.0*M_PI, eP3.z()); // -> rad + + success = mt.addPosition(p1, 1000, 2, 99999); + QVERIFY2(success, "success failed addPosition"); + QVERIFY2(mt.getTrack().size() == 1, "getTrack failed"); + + success = mt.addPosition(p2, 1100, 2, 99999); // p2 should NOT be added: + QVERIFY2(!success, "success failed addPosition"); + QVERIFY2(mt.getTrack().size() == 1, "getTrack failed"); + + success = mt.addPosition(p3, 1100, 2, 99999); // p3 should be added: + QVERIFY2(success, "success failed addPosition"); + QVERIFY2(mt.getTrack().size() == 2, "getTrack failed"); + + + QJsonDocument jDoc(mt.toJsonObject()); + { + // to json +// QFile saveFile(QStringLiteral("/tmp/mt.json")); +// if (!saveFile.open(QIODevice::WriteOnly)) { +// qWarning("Couldn't open mt file."); +// return; +// } +// saveFile.write(jDoc.toJson()); +// qCritical() << "wrote " << QFileInfo(saveFile).absoluteFilePath(); +// qCritical() << "mt.toJson()=" << jDoc.toJson(); + } + { + // read json + const QJsonObject &objTemp = jDoc.object(); + QSharedPointer newMt = SituationMap::MovementTrack::read(objTemp); + + QVERIFY2(newMt->trackId() == mt.trackId(), " json: trackId failed"); + QVERIFY2(newMt->isActive() == mt.isActive(), " json: isActive failed"); + QVERIFY2(newMt->getTrack().size() == mt.getTrack().size(), " json: getTrack failed"); + } +} + +void SitMapUnitTest::checkMovementTrackManager() +{ + SituationMap::MovementTrackManager mtMgr; + + mtMgr.setCurrentTimeMsec(123); + QVERIFY2(mtMgr.currentTimeMsec() == 123, "currentTimeMsec failed"); + + mtMgr.setMaxAgeMsec(1234); + QVERIFY2(mtMgr.maxAgeMsec() == 1234, "maxAgeMsec failed"); + + mtMgr.setMaxViewAgeMsec(55); + QVERIFY2(mtMgr.maxViewAgeMsec() == 55, "maxViewAgeMsec failed"); + + mtMgr.setMaxNumberOfTracks(3); + QVERIFY2(mtMgr.maxNumberOfTracks() == 3, "maxNumberOfTracks failed"); + + mtMgr.setMaxNumberOfPointsPerTrack(5); + QVERIFY2(mtMgr.maxNumberOfPointsPerTrack() == 5, "maxNumberOfTracks failed"); + + mtMgr.setMinDistance(7); + QVERIFY2(mtMgr.minDistance() == 7, "minDistance failed"); + + bool success; + + QVERIFY2(mtMgr.size() == 0, "size failed"); + QVERIFY2(mtMgr.hasTrack(0) == false, "hasTrack failed"); + QVERIFY2(mtMgr.activeTrackIds().size() == 0, "objectIdsOfActiveTracks failed"); + QVERIFY2(mtMgr.activeAndVisibleTrack(0).size() == 0, "activeAndVisibleTrack failed"); + + // set some positions: + double lon = 0.155; + double lat = 0.926; + const SituationMap::Point3D p1(lon+0.000, lat, 0.0); + const SituationMap::Point3D p2(lon+0.001, lat, 0.0); + const SituationMap::Point3D p3(lon+0.002, lat, 0.0); + const SituationMap::Point3D p4(lon+0.003, lat, 0.0); + const SituationMap::Point3D p5(lon+0.004, lat, 0.0); + + // check MaxNumberOfPointsPerTrack + { + QVERIFY2(mtMgr.maxNumberOfPointsPerTrack() == 5, "maxNumberOfTracks failed"); + QVERIFY2(mtMgr.minDistance() == 7, "minDistance failed"); + + mtMgr.activateTrack(4711); + + success = true; + success = success && mtMgr.addPosition(4711, p1, 10001); + success = success && mtMgr.addPosition(4711, p2, 10002); + success = success && mtMgr.addPosition(4711, p3, 10003); + success = success && mtMgr.addPosition(4711, p4, 10004); + success = success && mtMgr.addPosition(4711, p5, 10005); + success = success && mtMgr.addPosition(4711, p1, 10006); // maxNumberOfTracks exceeded -> pt1 removed + QVERIFY2(success == true, "addPosition failed"); + QVERIFY2(mtMgr.activeAndVisibleTrack(4711).size() == 5, "activeAndVisibleTrack failed"); + + QVERIFY2(mtMgr.size() == 1, "size failed"); + QVERIFY2(mtMgr.hasTrack(4711) == true, "hasTrack failed"); + + QVERIFY2(mtMgr.activeTrackIds().size() == 1, "objectIdsOfActiveTracks failed"); + QVERIFY2(mtMgr.activeAndVisibleTrack(4711).size() == 5, "activeAndVisibleTrack failed"); + } + + // maxViewAgeMsec + { + int size = 0; + + // a) + mtMgr.setCurrentTimeMsec(10006); + QVERIFY2(mtMgr.currentTimeMsec() == 10006, "currentTimeMsec failed"); + + mtMgr.setMaxViewAgeMsec(2); + size = mtMgr.activeAndVisibleTrack(4711).size(); + QVERIFY2(size == 3, "activeAndVisibleTrack failed"); // 10006, 10005, 10004 + + // b) set currentTimeMsec forward + mtMgr.setCurrentTimeMsec(10008); + + mtMgr.setMaxViewAgeMsec(2); + size = mtMgr.activeAndVisibleTrack(4711).size(); + QVERIFY2(size == 1, "activeAndVisibleTrack failed"); // 10008, 10007, 10006 + + // c) set currentTimeMsec backward + mtMgr.setCurrentTimeMsec(10002); + + size = mtMgr.activeAndVisibleTrack(4711).size(); + QVERIFY2(size == 1, "activeAndVisibleTrack failed"); // 10002 (10001/pt1 removed) + } + + + // check removing of track, 1 + { + QVERIFY2(mtMgr.maxNumberOfTracks() == 3, "maxNumberOfTracks failed"); + + mtMgr.activateTrack(4712); + QVERIFY2(mtMgr.addPosition(4712, p1, 10001) == true, "addPosition failed"); + + mtMgr.activateTrack(4713); + QVERIFY2(mtMgr.addPosition(4713, p1, 10001) == true, "addPosition failed"); + QVERIFY2(mtMgr.size() == 3, "size failed"); + QVERIFY2(mtMgr.activeTrackIds().size() == 3, "objectIdsOfActiveTracks failed"); + + mtMgr.activateTrack(4714); + QVERIFY2(mtMgr.addPosition(4714, p1, 10001) == true, "addPosition failed"); + QVERIFY2(mtMgr.size() == 3, "size failed"); + QVERIFY2(mtMgr.activeTrackIds().size() == 3, "objectIdsOfActiveTracks failed"); + + QVERIFY2(mtMgr.hasTrack(4711) == false, "hasTrack failed"); + } + + // check removing of track, 2 + { + // 4712, 4713, 4714 + const QVector activeTracks1 = mtMgr.activeTrackIds(); + QVERIFY2(activeTracks1 == QVector() << 4712 << 4713 << 4714, "objectIdsOfActiveTracks failed"); + + mtMgr.deactivateTrack(4713); + + // still 3 tracks, but just 2 active: + QVERIFY2(mtMgr.size() == 3, "size failed"); + const QVector activeTracks2 = mtMgr.activeTrackIds(); + QVERIFY2(activeTracks2 == QVector() << 4712 /*<< 4713*/ << 4714, "objectIdsOfActiveTracks failed"); + + + // activate track 4715 -> deactived track 4713 has to be removed: + mtMgr.activateTrack(4715); + QVERIFY2(success == true, "addPosition failed"); + QVERIFY2(mtMgr.size() == 3, "size failed"); + + // 4712, 4714, 4715 + QVERIFY2(mtMgr.activeTrackIds().size() == 3, "objectIdsOfActiveTracks failed"); + const QVector activeTracks3 = mtMgr.activeTrackIds(); + QVERIFY2(activeTracks3 == QVector() << 4712 << 4714 << 4715, "objectIdsOfActiveTracks failed"); + + QVERIFY2(mtMgr.hasTrack(4711) == false, "hasTrack failed"); + QVERIFY2(mtMgr.hasTrack(4712) == true, "hasTrack failed"); + QVERIFY2(mtMgr.hasTrack(4713) == false, "hasTrack failed"); + QVERIFY2(mtMgr.hasTrack(4714) == true, "hasTrack failed"); + QVERIFY2(mtMgr.hasTrack(4715) == true, "hasTrack failed"); + } + + + QVERIFY2(mtMgr.size() > 0, " json: size insufficiant"); + + const qint64 CURRENT_TIME_MSEC = 1516632216.0 * 1000.0; // to use qint64 + mtMgr.setCurrentTimeMsec(CURRENT_TIME_MSEC); + + QJsonDocument jDoc(mtMgr.toJsonObject()); + { + // to json + QFile saveFile(QStringLiteral("/tmp/mtMgr.json")); + if (!saveFile.open(QIODevice::WriteOnly)) { + qWarning("Couldn't open mt file."); + return; + } + saveFile.write(jDoc.toJson()); + qCritical() << "wrote " << QFileInfo(saveFile).absoluteFilePath(); + qCritical() << "mtMgr.toJson()=" << jDoc.toJson(); + } + { + // read json + SituationMap::MovementTrackManager newMtMgr; + const QJsonObject &objTemp = jDoc.object(); + newMtMgr.read(objTemp); + + QVERIFY2(newMtMgr.size() > 0, " json: size failed"); + QVERIFY2(newMtMgr.size() == mtMgr.size(), " json: size failed"); + + qint64 diff; + diff = mtMgr.currentTimeMsec() - CURRENT_TIME_MSEC; + QVERIFY2(qFuzzyIsNull(double(diff)) == true, " json: maxAgeMsec failed"); + diff = newMtMgr.currentTimeMsec() - CURRENT_TIME_MSEC; + QVERIFY2(qFuzzyIsNull(double(diff)) == true, " json: maxAgeMsec failed"); + + diff = newMtMgr.maxAgeMsec() - mtMgr.maxAgeMsec(); + QVERIFY2(qFuzzyIsNull(double(diff)) == true, " json: maxAgeMsec failed"); + + diff = newMtMgr.maxViewAgeMsec() - mtMgr.maxViewAgeMsec(); + QVERIFY2(qFuzzyIsNull(double(diff)) == true, " json: maxViewAgeMsec failed"); + + QVERIFY2(newMtMgr.maxNumberOfTracks() == mtMgr.maxNumberOfTracks(), " json: maxNumberOfTracks failed"); + QVERIFY2(newMtMgr.maxNumberOfPointsPerTrack() == mtMgr.maxNumberOfPointsPerTrack(), " json: maxNumberOfPointsPerTrack failed"); + QVERIFY2(newMtMgr.minDistance() == mtMgr.minDistance(), " json: minDistance failed"); + + const QVector &activeTrackIds = newMtMgr.activeTrackIds(); + for (const auto &trackId : activeTrackIds) + QVERIFY2(newMtMgr.activeAndVisibleTrack(trackId).size() == mtMgr.activeAndVisibleTrack(trackId).size(), " json: activeAndVisibleTrack failed"); + } +} + + +void checkSidcCodecEncoded(const QString &sidc, + const QString &rawSidc, + AdvancedSymbology::SymbologyType symType, + AdvancedSymbology::SidcCodec::SidcType sidcType) +{ + QVERIFY2(AdvancedSymbology::SidcCodec::isEncoded(sidc) == true, "isEncoded"); + QVERIFY2(AdvancedSymbology::SidcCodec::decodeSymbologyType(sidc) == symType, "decodeSymbologyType"); + QVERIFY2(AdvancedSymbology::SidcCodec::decodeSidcType(sidc) == sidcType, "decodeSidcType"); + QVERIFY2(AdvancedSymbology::SidcCodec::decodeRawSidc(sidc).compare(rawSidc) == 0, "decodeRawSidc"); +} + + +void SitMapUnitTest::checkSidcCodec() +{ + // M25D|CMPL|10012500002713000000 + { + { + QString sidc; + sidc = QLatin1String("AP6B|CMPL|10012500002713000000"); + checkSidcCodecEncoded(sidc, QLatin1String("10012500002713000000"), AdvancedSymbology::SymbologyType::APP6B, AdvancedSymbology::SidcCodec::SidcType::Complete); + + sidc = QLatin1String("AP6D|CMPL|10012500002713000000"); + checkSidcCodecEncoded(sidc, QLatin1String("10012500002713000000"), AdvancedSymbology::SymbologyType::APP6D, AdvancedSymbology::SidcCodec::SidcType::Complete); + + sidc = QLatin1String("M25C|CMPL|10012500002713000000"); + checkSidcCodecEncoded(sidc, QLatin1String("10012500002713000000"), AdvancedSymbology::SymbologyType::MIL2525C, AdvancedSymbology::SidcCodec::SidcType::Complete); + + sidc = QLatin1String("M25D|CMPL|10012500002713000000"); + checkSidcCodecEncoded(sidc, QLatin1String("10012500002713000000"), AdvancedSymbology::SymbologyType::MIL2525D, AdvancedSymbology::SidcCodec::SidcType::Complete); + } + + { + QString temp; + temp = AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6B, QLatin1String("25271300")); + QVERIFY2(temp.compare(QLatin1String("AP6B|BASE|25271300")) == 0, "encodeBaseSymbolCode"); + + temp = AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::APP6D, QLatin1String("25271300")); + QVERIFY2(temp.compare(QLatin1String("AP6D|BASE|25271300")) == 0, "encodeBaseSymbolCode"); + + temp = AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::MIL2525C, QLatin1String("25271300")); + QVERIFY2(temp.compare(QLatin1String("M25C|BASE|25271300")) == 0, "encodeBaseSymbolCode"); + + temp = AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(AdvancedSymbology::SymbologyType::MIL2525D, QLatin1String("25271300")); + QVERIFY2(temp.compare(QLatin1String("M25D|BASE|25271300")) == 0, "encodeBaseSymbolCode"); + + + temp = AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::MIL2525D, QLatin1String("10012500002713000000")); + QVERIFY2(temp.compare(QLatin1String("M25D|CMPL|10012500002713000000")) == 0, "encodeComplete"); + } + } + + // Fehlerfälle + { + QString sidc = QLatin1String("10012500002713000000"); + QVERIFY2(AdvancedSymbology::SidcCodec::decodeSymbologyType(sidc) == AdvancedSymbology::SymbologyType::None, "decodeSymbologyType"); + QVERIFY2(AdvancedSymbology::SidcCodec::decodeSidcType(sidc) == AdvancedSymbology::SidcCodec::SidcType::None, "decodeSidcType"); + QVERIFY2(AdvancedSymbology::SidcCodec::decodeRawSidc(sidc).compare(sidc) == 0, "decodeRawSidc"); + } + +} + + +void SitMapUnitTest::checkSIDCDefinitions1() +{ + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + return; + + AdvancedSymbology::SymbolDictionary &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + QSignalSpy spy(&dict, &AdvancedSymbology::SymbolDictionary::loaded); + if(dict.isLoaded() == false) + { + dict.load(); + if (spy.count() == 0) + spy.wait(10000); + } + QVERIFY2((dict.isLoaded() == true), "SymbolDictionary: isLoaded failed"); + + + // real work: + + bool success; + AdvancedSymbology::SidcDefinitions::StandardIdentity standardIdentity; + + QVERIFY2((int)AdvancedSymbology::SidcDefinitions::Identity::Pending == 0 , "Identity emumValue wrong"); + QVERIFY2((int)AdvancedSymbology::SidcDefinitions::Identity::Unknown == 1 , "Identity emumValue wrong"); + QVERIFY2((int)AdvancedSymbology::SidcDefinitions::Identity::AssumedFriend == 2 , "Identity emumValue wrong"); + QVERIFY2((int)AdvancedSymbology::SidcDefinitions::Identity::Friend == 3 , "Identity emumValue wrong"); + QVERIFY2((int)AdvancedSymbology::SidcDefinitions::Identity::Neutral == 4 , "Identity emumValue wrong"); + QVERIFY2((int)AdvancedSymbology::SidcDefinitions::Identity::SuspectJoker == 5 , "Identity emumValue wrong"); + QVERIFY2((int)AdvancedSymbology::SidcDefinitions::Identity::HostileFaker == 6 , "Identity emumValue wrong"); + + // to be continued... + + + const QVector &supportedSmTypes = { AdvancedSymbology::SymbologyType::MIL2525D, AdvancedSymbology::SymbologyType::APP6D}; + + if (supportedSmTypes.contains(m_d->m_symbologyType)) + { + QString baseSymbolCode = AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(m_d->m_symbologyType, "25270100"); + + AdvancedSymbology::SidcBuilder sidcBuilder(&dict); + success = sidcBuilder.initFromSymbologyTypeBaseSymbolCode(m_d->m_symbologyType, baseSymbolCode); + QVERIFY2(success == true , "initFromSymbologyTypeBaseSymbolCode failed"); + + const QString &sidc = sidcBuilder.toSidc(); + qCritical() << "sidc=" << sidc; + // 12345678901234567890 + // 25 270100 // baseSymbolCode (SymbolSet + Entity) + + const QString &rawSidc = AdvancedSymbology::SidcCodec::decodeRawSidc(sidc); + + QVERIFY2(rawSidc.compare(QLatin1String("10012500002701000000")) == 0 , "initFromMil2525DBaseSymbolCode failed"); + } + + + // + // test some arbitrary sidc (MIL2525D): + // + + + + if (supportedSmTypes.contains(m_d->m_symbologyType)) + { + // 1234567890 + QString sidcSTR = AdvancedSymbology::SidcCodec::encodeComplete(m_d->m_symbologyType, "10051000001213030000"); // MIL2525D: 01011000001213030000 -> LandUnits, Observer, Motorized + qDebug() << "----------- " << sidcSTR; + + AdvancedSymbology::SidcBuilder sidcBuilder(&dict); + success = sidcBuilder.initFromSymbologyType(m_d->m_symbologyType, sidcSTR); + QVERIFY2(success == true , "initFromSymbologyTypeBaseSymbolCode failed"); + + // First10 + standardIdentity = sidcBuilder.standardIdentity(); + + QVERIFY2(standardIdentity.context == AdvancedSymbology::SidcDefinitions::Context::Reality , "standardIdentity.context failed"); + QVERIFY2(standardIdentity.identity == AdvancedSymbology::SidcDefinitions::Identity::SuspectJoker , "standardIdentity.identity failed"); + + AdvancedSymbology::SidcDefinitions::SymbolSet symSet = sidcBuilder.symbolSet(); + QVERIFY2(symSet == AdvancedSymbology::SidcDefinitions::SymbolSet::LandUnit, "symbolSet failed"); + + AdvancedSymbology::SidcDefinitions::Status status = sidcBuilder.status(); + QVERIFY2(status == AdvancedSymbology::SidcDefinitions::Status::Present, "status failed"); + + QVERIFY2(sidcBuilder.headquarters() == false , "headquarters failed"); + QVERIFY2(sidcBuilder.taskForce() == false , "taskForce failed"); + QVERIFY2(sidcBuilder.feintDummy() == false , "feintDummy failed"); + + AdvancedSymbology::SidcDefinitions::AmplifierGroup amplGroup = sidcBuilder.amplifierGroup(); + QVERIFY2(amplGroup == AdvancedSymbology::SidcDefinitions::AmplifierGroup::Unknown , "amplifierGroup failed"); + + int amplSub = sidcBuilder.amplifier2ndDigit(); + QVERIFY2(amplSub == 0 , "amplifierSecondDigit failed"); + + // Second10 + + } + + if (supportedSmTypes.contains(m_d->m_symbologyType)) + { + // 1234567890 + QString sidcSTR = AdvancedSymbology::SidcCodec::encodeComplete(m_d->m_symbologyType, "10012547231213030000"); + qDebug() << "----------- " << sidcSTR; + + AdvancedSymbology::SidcBuilder sidcBuilder(&dict); + success = sidcBuilder.initFromSymbologyType(m_d->m_symbologyType, sidcSTR); + QVERIFY2(success == true , "initFromSymbologyType failed"); + + // First10 + standardIdentity = sidcBuilder.standardIdentity(); + + QVERIFY2(standardIdentity.context == AdvancedSymbology::SidcDefinitions::Context::Reality , "standardIdentity.context failed"); + QVERIFY2(standardIdentity.identity == AdvancedSymbology::SidcDefinitions::Identity::Unknown , "standardIdentity.identity failed"); + + AdvancedSymbology::SidcDefinitions::SymbolSet symSet = sidcBuilder.symbolSet(); + QVERIFY2(symSet == AdvancedSymbology::SidcDefinitions::SymbolSet::ControlMeasure, "symbolSet failed"); + + AdvancedSymbology::SidcDefinitions::Status status = sidcBuilder.status(); + QVERIFY2(status == AdvancedSymbology::SidcDefinitions::Status::Present_Destroyed, "status failed"); + + QVERIFY2(sidcBuilder.headquarters() == true , "headquarters failed"); + QVERIFY2(sidcBuilder.taskForce() == true , "taskForce failed"); + QVERIFY2(sidcBuilder.feintDummy() == true , "feintDummy failed"); + + AdvancedSymbology::SidcDefinitions::AmplifierGroup amplGroup = sidcBuilder.amplifierGroup(); + QVERIFY2(amplGroup == AdvancedSymbology::SidcDefinitions::AmplifierGroup::Echelon_DivisionAndAbove , "amplifierGroup failed"); + + int amplSub = sidcBuilder.amplifier2ndDigit(); + QVERIFY2(amplSub == 3 , "amplifierSecondDigit failed"); + + // Second10 + + } + +} + +void SitMapUnitTest::checkSIDCDefinitions2() +{ + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + return; + + checkSymbolDictionaryLoad(); + + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + AdvancedSymbology::SidcDefinitions sidcDefinitions(&dict); + + const QStringList &modifierKeys = sidcDefinitions.modifierKeys(); + QVERIFY2(modifierKeys.isEmpty() == false , "modifierKeys failed"); + + QVERIFY2(sidcDefinitions.modifierList(AdvancedSymbology::SidcDefinitions::SymbolSet::Activities, 1).isEmpty() == false , "modifierList failed"); +// QVERIFY2(sidcDefinitions.modifierList(AdvancedSymbology::SidcDefinitions::SymbolSet::Activities, 2).isEmpty() == false , "modifierList failed"); + + QVERIFY2(sidcDefinitions.modifierList(AdvancedSymbology::SidcDefinitions::SymbolSet::Air, 1).isEmpty() == false , "modifierList failed"); + QVERIFY2(sidcDefinitions.modifierList(AdvancedSymbology::SidcDefinitions::SymbolSet::Air, 2).isEmpty() == false , "modifierList failed"); + + QVERIFY2(sidcDefinitions.modifierList(AdvancedSymbology::SidcDefinitions::SymbolSet::AirMissle, 1).isEmpty() == false , "modifierList failed"); + QVERIFY2(sidcDefinitions.modifierList(AdvancedSymbology::SidcDefinitions::SymbolSet::AirMissle, 2).isEmpty() == false , "modifierList failed"); + + // to be cont. + +} + + +/* +void SitMapUnitTest::checkCreateSidc() +{ + + // + // MIL + // + { + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(AdvancedSymbology::SymbologyType::MIL2525C); + + + { + QString sidc(QLatin1String("S*G*UCD---*****")); // ('taken' from typen@sys_db) + dict.createSidc(sidc, SymbolTypes::FRIEND, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SFGPUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::ENEMY, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SHGPUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::NEUTRAL, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SNGPUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::UNKNOWN, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SUGPUCD---")) == 0, "createSidc failed"); + } + + + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::ENEMY, SymbolTypes::STATE_ANTICIPATED); + // QVERIFY2( dict.contains(sidc), "contains failed"); // ANTICIPATED -> 'A' is not supported by ESRI-Dictionary yet + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SHGAUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---************")); + dict.createSidc(sidc, SymbolTypes::ENEMY, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SHGPUCD---")) == 0, "createSidc failed"); + } + + } + + // + // APP6 + // + + { + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(AdvancedSymbology::SymbologyType::APP6B); + + + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::FRIEND, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SFGPUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::ENEMY, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SHGPUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::NEUTRAL, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SNGPUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::UNKNOWN, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SUGPUCD---")) == 0, "createSidc failed"); + } + + + { + QString sidc(QLatin1String("S*G*UCD---*****")); + dict.createSidc(sidc, SymbolTypes::ENEMY, SymbolTypes::STATE_ANTICIPATED); + // QVERIFY2( dict.contains(sidc), "contains failed"); // ANTICIPATED -> 'A' is not supported by ESRI-Dictionary yet + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SHGAUCD---")) == 0, "createSidc failed"); + } + { + QString sidc(QLatin1String("S*G*UCD---************")); + dict.createSidc(sidc, SymbolTypes::ENEMY, SymbolTypes::STATE_PRESENT); + QVERIFY2(dict.contains(sidc), "contains failed"); + QVERIFY2(sidc.length() == 10, "createSidc failed"); + QVERIFY2(sidc.compare(QLatin1String("SHGPUCD---")) == 0, "createSidc failed"); + } + + } + +} +*/ + +/* +void SitMapUnitTest::checkSymbolProperties() +{ + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + if (dict.symbologyType() == AdvancedSymbology::SymbologyType::None) + return; + + + // 1 + { + QString sidc(QLatin1String("SUGPUSAJ--")); // SUGPUSAJ-- should exist as MIL and APP :-) + QVERIFY2(dict.contains(sidc), "contains failed"); + + const AdvancedSymbology::SymbolProperties &props = dict.symbolProperties(sidc); + QVERIFY2(props.sidc().compare(QLatin1String("SUGPUSAJ--")) == 0, "symbolProperties failed"); + QVERIFY2(props.geometryType() == AdvancedSymbology::GeometryType::Point, "geometryType failed"); + int minPoints = props.minNumberOfPoints(); + QVERIFY2(minPoints == 1, "minNumberOfPoints failed"); + } + + // 2 + { + QString sidc; + switch (m_d->m_symbologyType) + { + case AdvancedSymbology::SymbologyType::MIL2525C: + sidc = QLatin1String("GUGPOLAGM-"); + break; + case AdvancedSymbology::SymbologyType::APP6B: + sidc = QLatin1String("GHTPGC----"); + break; + case AdvancedSymbology::SymbologyType::None: + // empty + break; + } + + if (! sidc.isEmpty()) + { + QVERIFY2(dict.contains(sidc), "contains failed"); + + const AdvancedSymbology::SymbolProperties &props = dict.symbolProperties(sidc); + QVERIFY2(props.sidc().compare(sidc) == 0, "symbolProperties failed"); + QVERIFY2(props.geometryType() == AdvancedSymbology::GeometryType::Polyline, "geometryType failed"); + int minPoints = props.minNumberOfPoints(); + QVERIFY2(minPoints == 2, "minNumberOfPoints failed"); + } + } + + // 3 + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::APP6B) + { + // Check if esriBug is fixed (by our own or by esri) + // Bug 1: Tank Light Recovery: + // APP6: correct: "S*G*EVATW-" + // (MIL: correct: "S*G*EVATLR" + // APP6: 10.2.6: "S*G*EVATLR" (e.g. 0695) + + QString sidc(QLatin1String("SUGPEVATW-")); // SUGPEVATW- + QVERIFY2(dict.contains(sidc), "contains failed"); + + const AdvancedSymbology::SymbolProperties &props = dict.symbolProperties(sidc); + QVERIFY2(props.sidc().compare(sidc) == 0, "symbolProperties failed"); + QVERIFY2(props.geometryType() == AdvancedSymbology::GeometryType::Point, "geometryType failed"); + int minPoints = props.minNumberOfPoints(); + QVERIFY2(minPoints == 1, "minNumberOfPoints failed"); + + // check keywords: + QStringList result = props.keywords().filter(sidc, Qt::CaseInsensitive); + QVERIFY2(result.size() == 1, "keywords failed"); + } + +} +*/ + +QImage createDummyImage() +{ + int widthPixel = 100; + QImage image = QImage(widthPixel, widthPixel, QImage::Format_ARGB32); + image.fill(Qt::transparent); + + QPainter p; + QPen pen(Qt::red); + pen.setWidth(5); + + p.begin(&image); + p.setPen(pen); + p.drawLine(QPointF(0, 0), QPointF(100, 100)); + p.end(); + +// bool success = image.save(QLatin1String("/tmp/dummy.png")); +// qWarning() << " image.save = " << success; + return image; +} + +/* +bool checkAllCount(AdvancedSymbology::SymbolDictionary &dict) +{ + SituationMap::LOGGER_T myLogger = SituationMap::getLogger("SitMapUnitTest"); + + if (dict.symbologyType() == AdvancedSymbology::SymbologyType::None) + { + // None: + if (dict.count() > 100) // 100 -> arbitrary + return false; + } + else + { + // App, Mil: + if (dict.count() <= 0) + return false; + } + + // verify size of different lists: + int count = dict.count(); + int sizeFindSymbolsAll = dict.findSymbols().size(); + QString sidcPattern; // empty + int sizeFindSymbolsSidcPattern = dict.findSymbols(sidcPattern).size(); + int sizeSidcList = dict.sidcList().size(); + + INFO_LOG(myLogger) << LOGFUNCTION + << " count = " << count + << " sizeFindSymbolsAll = " << sizeFindSymbolsAll + << " sizeFindSymbolsSidcPattern = " << sizeFindSymbolsSidcPattern + << " sizeSidcList = " << sizeSidcList; + + bool ok = (count == sizeFindSymbolsAll) + && (count == sizeFindSymbolsSidcPattern) + && (count == sizeSidcList); + if (! ok) + { + ERROR_LOG(myLogger) << LOGFUNCTION + << " count = " << count + << " sizeFindSymbolsAll = " << sizeFindSymbolsAll + << " sizeFindSymbolsSidcPattern = " << sizeFindSymbolsSidcPattern + << " sizeSidcList = " << sizeSidcList; + } + return ok; +} +*/ + +/* +bool addOneSymbol(AdvancedSymbology::SymbolDictionary &dict) +{ + SituationMap::LOGGER_T myLogger = SituationMap::getLogger("SitMapUnitTest"); + Q_UNUSED(myLogger) + + bool success = false; + + QImage image = createDummyImage(); + + const QString sidc = QLatin1String("Nt-sidc"); + const QString name = QLatin1String("NT-name"); + + QStringList keywords; + keywords << QLatin1String("NT-key1"); + keywords << QLatin1String("NT-keyDuplicate") << QLatin1String("NT-keyDuplicate"); + + QHash values; + values[QLatin1String("Category")] = QLatin1String("Other"); + values[QLatin1String("SymbolID")] = sidc; + values[QLatin1String("GeometryType")] = QLatin1String("Point"); + values[QLatin1String("GeometryConversionType")] = QLatin1String("GeometryConversionType-Unknown"); + values[QLatin1String("StyleFile")] = QLatin1String("StyleFile-Unknown"); + + AdvancedSymbology::SymbolProperties props(name, keywords, values); + + success = dict.addSymbol(sidc, props, image); + return success; +} +*/ + +void SitMapUnitTest::checkSymbolDictionaryLoad() +{ + AdvancedSymbology::cleanupSymbolDictionaryInternal(); + + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // SymbologyType::None -> no esriDict -> immediately loaded: + // ! SymbologyType::None -> esriDict -> load... + + QSignalSpy spy(&dict, &AdvancedSymbology::SymbolDictionary::loaded); + dict.load(); + QTRY_VERIFY2(dict.isLoaded(), "SymbolDictionary: isLoaded failed"); + + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + { + QTest::qWait(100); + QVERIFY2(spy.count() == 0, "SymbolDictionary: signalCount failed, layerOrderChanged"); + } + else + { + QTRY_VERIFY2(spy.count() == 1, "SymbolDictionary: signalCount failed, layerOrderChanged"); + } + + // get cached Dictionary: + qWarning() << "get cached Dictionary"; + { + // -> immediately loaded + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + QVERIFY2((dict.isLoaded() == true), "SymbolDictionary: isLoaded failed"); + } + + + +/* + int count = dict.count(); + + bool dictChanged = false; + + // lambda + connect(&dict, &AdvancedSymbology::SymbolDictionary::changed, [&]() + { + dictChanged = true; + }); + + + // + // Initial Check + // + bool countOk = checkAllCount(dict); + QVERIFY2((countOk == true), "at least one count failed"); + + // + // add new symbol an check: + // + { + bool addSym = addOneSymbol(dict); + QVERIFY2((addSym == true), "addSymbol failed"); + QVERIFY2((dictChanged == true), "dictChanged signal not emitted"); + + countOk = checkAllCount(dict); + QVERIFY2((countOk == true), "at least one count failed"); + QVERIFY2(dict.count() == (count + 1), "count failed"); + } +*/ +} + +void SitMapUnitTest::checkSymbolDictionaryLoadSymbolXml() +{ + AdvancedSymbology::cleanupSymbolDictionaryInternal(); + + AdvancedSymbology::SymbolDictionary &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + bool ok; + AdvancedSymbology::SymbolProperties symProps; + + ok = dict.insertXmlTestSymbol(symProps); + QVERIFY2(ok, "insert xml test symbol failed."); + + ok = dict.insertXmlComplexTestSymbol(symProps); + QVERIFY2(ok, "insert xml test complex symbol failed."); + + QVERIFY2( (dict.xmlSymbolCount() == 2), "count of xml symbols has wrong value - should be 2" ); + + const QString &sidc = AdvancedSymbology::SidcCodec::encodeCivilXml(QString::number(4711)); + dict.removeXmlSymbol(sidc); + dict.removeXmlSymbol(4711); + + QVERIFY2( (dict.xmlSymbolCount() == 1), "count of xml symbols has wrong value after removeXmlSymbol() - should be 1" ); + + dict.clearXmlSymbols(); + + QVERIFY2( (dict.xmlSymbolCount() == 0), "count of xml symbols is not zero after clearXmlSymbols()" ); + +} // void SitMapUnitTest::checkSymbolDictionaryLoadSymbolXml() + + +void SitMapUnitTest::checkSymbolDictionaryConsistency() +{ + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + return; + + checkSymbolDictionaryLoad(); + auto &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // get access to esriDict: + AdvancedSymbology::SymbolDictionaryImpl &dictImpl = static_cast(dict); + Esri::ArcGISRuntime::DictionarySymbolStyle *esriDict = const_cast(dictImpl.getEsriSymbolDictionary()); + + QHash baseSymbolCodeToSymbolPropertyTable; + + + // Use QFuture-based async API + Esri::ArcGISRuntime::SymbolStyleSearchParameters sssp; + QFuture future = esriDict->searchSymbolsAsync(sssp); + + // Wait for completion + QFutureWatcher watcher; + watcher.setFuture(future); + watcher.waitForFinished(); + + Esri::ArcGISRuntime::SymbolStyleSearchResultListModel *searchResults = future.result(); + + INFO_LOG(m_logger) << LOGFUNCTION << " proceessing..."; + + // + // process all results and store them in member: + // + const QList &resultList = + searchResults->searchResults(); + + int symbolClass; + for (const Esri::ArcGISRuntime::SymbolStyleSearchResult &resultItem : resultList) + { + // INFO_LOG(m_logger) << LOGFUNCTION << " resultItem.key ='" << resultItem.key() << "'"; + // INFO_LOG(m_logger) << LOGFUNCTION << " resultItem.name ='" << resultItem.name() << "'"; + // INFO_LOG(m_logger) << LOGFUNCTION << " resultItem.category ='" << resultItem.category() << "'"; + // INFO_LOG(m_logger) << LOGFUNCTION << " resultItem.symbolClass ='" << resultItem.symbolClass() << "'"; + // INFO_LOG(m_logger) << LOGFUNCTION << " resultItem.tags ='" << resultItem.tags().join(QLatin1String(";")) << "'"; + + const QString &originalKey = resultItem.key(); + + // to reduce empty keys: + symbolClass = resultItem.symbolClass().toInt(); + if ( (symbolClass == 6) || // symbolClass ( 6) == TextSymbol + (symbolClass == 10) // symbolClass (10) == MaplexLabelPlacement + ) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " continue:: symbolClass does not match " + << " originalKey='" << originalKey << "'" + << " name='" << resultItem.name() << "'"; + continue; + } + + if (originalKey.isEmpty()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " continue:: empty key, " + << " name='" << resultItem.name() << "'"; + continue; + } + + // + // size (25270100 || 25270100_4) + // + const int keySize = originalKey.size(); + if ( ! ((keySize == 8) || (keySize == 10)) ) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " continue:: keySize does not match " + << " originalKey='" << originalKey << "'" + << " name='" << resultItem.name() << "'"; + continue; + } + + // + // isFrame (key like '0_110_1c') + // + if ( QStringRef(&originalKey, 1, 1).compare(QLatin1String("_"))==0 ) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " continue:: isFrame " + << " originalKey='" << originalKey << "'" + << " name='" << resultItem.name() << "'"; + continue; + } + + // + // isCharlie (key like '46120203_C') + // + if ( (keySize == 10) && (originalKey.rightRef(2).compare(QLatin1String("_C"))==0) ) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " continue:: isCharlie" + << " originalKey='" << originalKey << "'" + << " name='" << resultItem.name() << "'"; + continue; + } + + const QString &key = originalKey; + + // + // just digits: + // + bool ok = false; + const QStringRef &key8 = originalKey.leftRef(8); + key8.toInt(&ok); + if (! ok) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " continue:: chars" + << " originalKey='" << originalKey << "'" + << " name='" << resultItem.name() << "'"; + continue; + } + + // + // special (46110304_X) + // + if ( (keySize == 10) && (originalKey.rightRef(2).compare(QLatin1String("_X"))==0) ) + { + DEBUG_LOG(m_logger) << LOGFUNCTION + << " continue:: is _X" + << " originalKey='" << originalKey << "'" + << " name='" << resultItem.name() << "'"; + continue; + } + + + AdvancedSymbology::SymbolProperties symProps; + symProps.setKey (key); + symProps.setSidc (AdvancedSymbology::SidcCodec::encodeBaseSymbolCode(m_d->m_symbologyType,key8)); + symProps.setName (resultItem.name()); + symProps.setCategory (resultItem.category()); + symProps.setSymbolClass(resultItem.symbolClass()); + symProps.setTags (resultItem.tags()); + + baseSymbolCodeToSymbolPropertyTable[originalKey] = symProps; + } // Q_FOREACH + + FATAL_LOG(m_logger) << LOGFUNCTION << " baseSymbolCodeToSymbolPropertyTable.size=" << baseSymbolCodeToSymbolPropertyTable.size(); + + + QStringList noKey8Entries; + QStringList noKey8EntriesOfUnknown; + QStringList propsDiffer; + + for (const AdvancedSymbology::SymbolProperties &symPropItem : std::as_const(baseSymbolCodeToSymbolPropertyTable)) + { + const QString &curKey = symPropItem.key(); +// INFO_LOG(m_logger) << LOGFUNCTION << " curKey ='" << curKey << "'"; + const QString &key8 = curKey.left(8); + + // check 1a: + // are there entries with postfix (_1, _2, _3) but not without?? + + bool noKey8EntriesFlag = false; + // 25270100_1 but not 25270100 ? + // (e.g. '10120100_0' ; '10120100_1' ; '10120100_2' ; '10120100_3' ; but no '10120100' + // 10120100 = Movement And Maneuver ; Air Assult with Organic Lift - FullFrame + // These are the FULL_FRAME symbols. (except "Maneuver Lines : Line of Contact" = 25140200_6) + // + // Exception: "Maneuver Lines : Line of Contact" = 25140200_6, _4, _3, _1) + + if (curKey.size() == 10) + { + if (! baseSymbolCodeToSymbolPropertyTable.contains(key8)) + { + noKey8Entries.append(curKey); + noKey8EntriesFlag = true; + } + } // if (curKey.size() == 10) + + // check 1b + if (noKey8EntriesFlag && + symPropItem.name().contains(QLatin1String("Unknown"))) + { + noKey8EntriesOfUnknown.append(curKey); + } + + + + // check 2: + AdvancedSymbology::SymbolProperties curSymProps = baseSymbolCodeToSymbolPropertyTable[curKey]; + { + QStringList sl = curSymProps.name().split(QLatin1Char(':')); + sl.removeOne(QLatin1String("Friend")); + sl.removeOne(QLatin1String("Friendly")); + sl.removeOne(QLatin1String("Hostile")); + sl.removeOne(QLatin1String("Neutral")); + sl.removeOne(QLatin1String("Unknown")); + curSymProps.setName(sl.join(QLatin1Char(':'))); + } + + if (baseSymbolCodeToSymbolPropertyTable.contains(key8)) + { + AdvancedSymbology::SymbolProperties symPropsKey8 = baseSymbolCodeToSymbolPropertyTable[key8]; + QStringList sl = symPropsKey8.name().split(QLatin1Char(':')); + sl.removeOne(QLatin1String("Friend")); + sl.removeOne(QLatin1String("Friendly")); + sl.removeOne(QLatin1String("Hostile")); + sl.removeOne(QLatin1String("Neutral")); + sl.removeOne(QLatin1String("Unknown")); + symPropsKey8.setName(sl.join(QLatin1Char(':'))); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " key8=" << key8 << " curSymProps.name = " << curSymProps.name(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " key8=" << key8 << " symPropsKey8.name = " << symPropsKey8.name(); + + + bool ok = + // (curSymProps.name() == symPropsKey8.name()) && +// (curSymProps.category() == symPropsKey8.category()) && + // // (curSymProps.symbolClass() == symPropsKey8.symbolClass()) && // do not compare SymbolClass + (curSymProps.tags() == symPropsKey8.tags()); + // (curSymProps.geometryType() == symPropsKey8.geometryType()); + + if (! ok) + { + propsDiffer.append(curKey); + } + } + } // Q_FOREACH + + DEBUG_LOG(m_logger) << LOGFUNCTION << " noKey8Entries.size = " << noKey8Entries.size(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " noKey8Entries = " << noKey8Entries.join(QLatin1String(" ; ")); + DEBUG_LOG(m_logger) << LOGFUNCTION << " noKey8EntriesOfUnknown.size = " << noKey8EntriesOfUnknown.size(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " noKey8EntriesOfUnknown = " << noKey8EntriesOfUnknown.join(QLatin1String(" ; ")); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " propsDiffer.size = " << propsDiffer.size(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " propsDiffer = " << propsDiffer.join(QLatin1String(" ; ")); + + // dump differ: + for (const auto &curKey : propsDiffer) + { + AdvancedSymbology::SymbolProperties curSymProps = baseSymbolCodeToSymbolPropertyTable[curKey]; + + const QString &key8 = curKey.left(8); + AdvancedSymbology::SymbolProperties symPropsKey8 = baseSymbolCodeToSymbolPropertyTable[key8]; + + DEBUG_LOG(m_logger) << LOGFUNCTION << " curSymProps " << curSymProps.tags().join(QLatin1String(" @ ")); + DEBUG_LOG(m_logger) << LOGFUNCTION << " symPropsKey8 " << symPropsKey8.tags().join(QLatin1String(" @ ")); + } + + +} + +void SitMapUnitTest::checkSymbolDictionaryFetchSymbol() +{ + checkSymbolDictionaryLoad(); + auto &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::MIL2525D) + { + + { + const QString sidc = AdvancedSymbology::SidcCodec::encodeComplete(AdvancedSymbology::SymbologyType::MIL2525D, "10042500002701000000"); // baseSymbolCode = 25 270100 + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + QVERIFY2((symbolResultWatcher.resultIsValid() == false), "SymbolDictionary: resultIsValid failed"); + + symbolResultWatcher.setSymbolResult(dict.fetchSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + const QUuid &taskId1 = symbolResultWatcher.symbolResult()->taskId(); + QVERIFY2((taskId1.isNull() == false), "SymbolDictionary: taskId failed"); + + // inspect result: + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polygon), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + } + + // find different: (PointSymbol) + { + const QString sidc = AdvancedSymbology::SidcCodec::encodeComplete( AdvancedSymbology::SymbologyType::MIL2525D,"10031500001110000000"); // baseSymbolCode = 15 111000 Missle Launcher + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.fetchSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + const QUuid &taskId2 = symbolResultWatcher.symbolResult()->taskId(); + QVERIFY2((taskId2.isNull() == false), "SymbolDictionary: taskId failed"); + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Point), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + } + + } +} + +void SitMapUnitTest::checkSymbolDictionaryFetchSymbol2() +{ + checkSymbolDictionaryLoad(); + auto &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + if (m_d->m_symbologyType != AdvancedSymbology::SymbologyType::MIL2525D) + { + return; + } + + // find some PointSymbols: + int counter = 0; + + const QStringList sidcs = AdvancedSymbology::listOfPointSidcs_Internal(); + + for (const auto &sidc : sidcs) + { + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.fetchSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + const QUuid &taskId2 = symbolResultWatcher.symbolResult()->taskId(); + QVERIFY2((taskId2.isNull() == false), "SymbolDictionary: taskId failed"); + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Point), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + ++counter; + } + + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter=" << counter; +} + +void SitMapUnitTest::checkSymbolDictionaryFetchSymbolAll() +{ + checkSymbolDictionaryLoad(); + auto &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // add ExtraSymbols: + const QString extraSymbolDir(QLatin1String(":/advancedSymbology/CivilSymbols")); + dict.addEtsSymbols(extraSymbolDir); + + + // fetch all Extra symbols: + int counter = 0; + + const QStringList extraSymbolSidcs = dict.etsSymbols(); + qCritical() << " extraSymbolSidcs.size = " << extraSymbolSidcs.size(); + + for (const auto &sidc : extraSymbolSidcs) + { + AdvancedSymbology::SymbolResultWatcher *symbolResultWatcher = new AdvancedSymbology::SymbolResultWatcher(this); + connect(symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady, this, [sidc, &counter, symbolResultWatcher]() + { + // lambda: +// qCritical() << " sidc=" << sidc; + QVERIFY2((symbolResultWatcher->resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher->symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + // Note: TaskId is null (esri not needed) + QSharedPointer symResult = symbolResultWatcher->symbolResult(); + + QVERIFY2((symResult), "SymbolDictionary: symResult failed"); + QVERIFY2((symResult->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + QVERIFY2((symResult->symbolImage().image().size().isEmpty() == false), "SymbolDictionary: symbolImage failed"); + ++counter; + }); + + if (1) { + symbolResultWatcher->setSymbolResult(dict.fetchSymbol(sidc)); + } + + if (0) { + symbolResultWatcher->setSymbolResult(dict.findPreview(sidc)); + } + + if (0) { + AdvancedSymbology::SymbolDataToRender symDataToRender; + symDataToRender.sidc = sidc; + symbolResultWatcher->setSymbolResult(dict.findImage(symDataToRender)); + } + } + + QTRY_VERIFY2_WITH_TIMEOUT((counter == extraSymbolSidcs.size()), "SymbolDictionary: something failed", 20000); + qWarning() << " counter=" << counter; +} + + + +void SitMapUnitTest::checkSymbolDictionaryExtraDirFetchSymbol() +{ + checkSymbolDictionaryLoad(); + auto &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + const QString extraSymbolDir(QLatin1String(":/advancedSymbology/CivilSymbols")); + + int addedFiles = dict.addEtsSymbols(extraSymbolDir); + int extraSymbolCount = dict.etsSymbolCount(); + + qWarning() << "addedFiles=" << addedFiles; + qWarning() << "extraSymbolCount=" << extraSymbolCount; + + QVERIFY2((addedFiles > 500), "SymbolDictionary: addExtraSymbols failed"); + QVERIFY2((addedFiles == extraSymbolCount), "SymbolDictionary: extraSymbolCount failed"); + + // find: + { + const QString sidc( QLatin1String("DOES_NOT_EXISTS") ); + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.fetchSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + + // Here is a notFoundSymbolResult: ('?') returned. + // Because it´s not really correct, the symbolImage is marked as 'not valid': + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().isValid() == false), "SymbolDictionary: symbolImage should be invalid (notFoundSymbolResult: '?')"); + } + + // find: + { + const QString sidc = AdvancedSymbology::SidcCodec::encodeCivilEts("FzSanKrad"); // FzSanKrad.gif + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.fetchSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // inspect result: + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Point), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + } +} + + +void SitMapUnitTest::checkSymbolDictionaryFetchSymbolStress() +{ + return; // just for manual usage + + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + return; + + checkSymbolDictionaryLoad(); + auto &dict = AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + QStringList sidcs = AdvancedSymbology::listOfPointSidcs_Internal(); + QString sidc = sidcs.first(); + +// AdvancedSymbology::SymbolResultWatcher *symbolResultWatcher = +// new AdvancedSymbology::SymbolResultWatcher(this); + + int counter = 0; + + QTimer *timer = new QTimer(this); + timer->setInterval(1); + connect(timer, &QTimer::timeout, this, [timer, &dict, sidc, &counter]() + { + if ((++counter%100) == 0) + qCritical() << " timeout... counter = " << counter; + + QSharedPointer result = dict.fetchSymbol(sidc); + // symbolResultWatcher->setSymbolResult(dict.fetchSymbol(sidc)); + + if (counter == 10000) + timer->stop(); + }); + + timer->start(); + + QTRY_VERIFY_WITH_TIMEOUT(!timer->isActive(), 60 * 1000); +} + +void SitMapUnitTest::checkSymbolDictionaryBulkRenderer() +{ + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::None) + return; + + checkSymbolDictionaryLoad(); + + QStringList pointSidcs = AdvancedSymbology::listOfPointSidcs_Internal(); + QStringList lineSidcs = AdvancedSymbology::listOfLineSidcs_Internal(); + + + { + AdvancedSymbology::BulkRendererFindBaseSymbol bulkRenderer(m_d->m_symbologyType); + + QSignalSpy spy(&bulkRenderer, &AdvancedSymbology::BulkRendererFindBaseSymbol::resultsReady); + + bulkRenderer.addRequest(pointSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + bulkRenderer.addRequest(lineSidcs.first()); + + bulkRenderer.run(); // <--- run + + QTRY_VERIFY2((spy.count() == 1), "BulkRenderer: resultsReady-signal failed"); + + int successfullCnt = 0; + AdvancedSymbology::BulkRendererFindBaseSymbol::RequestResultVector rrVec = bulkRenderer.results(); + for (const AdvancedSymbology::BulkRendererFindBaseSymbol::RequestResult &rr : std::as_const(rrVec)) + { + if (rr.second + && rr.second->symbolResult() + && rr.second->symbolResult()->isResultReady()) + { + const QImage img = rr.second->symbolResult()->symbolImage().image(); + if (img.width() > 10) + ++successfullCnt; + // const QString sidc = rr.first; qCritical() << " sidc= '" << sidc << "'" << " img.size(): " << img.size(); + // img.save(QStringLiteral("/tmp/img.png")); + } + } // for + + QVERIFY2((successfullCnt == rrVec.size()), "BulkRenderer: size comparision failed"); + + bulkRenderer.reset(); + } +} + +void fetchSymbolGeoType(const AdvancedSymbology::SymbolDictionary &dict, + const QString &sidc, + AdvancedSymbology::GeometryType geoType) +{ + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + QVERIFY2((symbolResultWatcher.resultIsValid() == false), "SymbolDictionary: resultIsValid failed"); + + symbolResultWatcher.setSymbolResult(dict.fetchSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + + + // inspect result: + QSharedPointer symResult = symbolResultWatcher.symbolResult(); + QVERIFY2((symResult.isNull() == false), "SymbolDictionary: isNull failed"); + + QVERIFY2((symResult->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + + if (symResult->geometryType() != geoType) + { + qWarning() << " sidc=" << sidc << "'" << ", geometryType, failed, geoType="<< (int)symResult->geometryType(); + } + + QVERIFY2((symResult->geometryType() == geoType), "SymbolDictionary: geometryType failed"); +} + + +void SitMapUnitTest::checkSymbolDictionaryFindImage() +{ + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // SymbologyType::None -> no esriDict -> immediately loaded: + // ! SymbologyType::None -> esriDict -> load... + + if (!dict.isLoaded()) + { + dict.load(); + QTRY_VERIFY2((dict.isLoaded() == true), "SymbolDictionary: isLoaded failed"); + } + + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::MIL2525D) + { + QStringList sidcs = AdvancedSymbology::listOfPointSidcs_Internal(); + + int counter = 0; + + // + // SinglePoint: + // + for (const QString &sidc : std::as_const(sidcs)) + { + SituationMap::SymbolData symData; + AdvancedSymbology::SymbolDataToRender symDataToRender = symData.getDefaultRenderData(); + symDataToRender.sidc = sidc; + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findImage(symDataToRender)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + const QUuid &taskId2 = symbolResultWatcher.symbolResult()->taskId(); + QVERIFY2((taskId2.isNull() == false), "SymbolDictionary: taskId failed"); + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Point), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter1=" << counter; + + // + // Line: + // + counter = 0; + + sidcs = AdvancedSymbology::listOfLineSidcs_Internal(); + + for (const QString &sidc : std::as_const(sidcs)) + { + qWarning() << " sidc=" << sidc; + SituationMap::SymbolData symData; + AdvancedSymbology::SymbolDataToRender symDataToRender = symData.getDefaultRenderData(); + symDataToRender.sidc = sidc; + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findImage(symDataToRender)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // TaskId is skipped here : + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polyline), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter2=" << counter; + + // + // Area: + // + counter = 0; + + sidcs = AdvancedSymbology::listOfAreaSidcs_Internal(); + + for (const QString &sidc : std::as_const(sidcs)) + { + // qWarning() << " sidc=" << sidc; + SituationMap::SymbolData symData; + AdvancedSymbology::SymbolDataToRender symDataToRender = symData.getDefaultRenderData(); + symDataToRender.sidc = sidc; + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findImage(symDataToRender)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // TaskId is skipped here : + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polygon), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter3=" << counter; + } + +} + +void SitMapUnitTest::checkSymbolDictionaryFindPreview() +{ + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // SymbologyType::None -> no esriDict -> immediately loaded: + // ! SymbologyType::None -> esriDict -> load... + + dict.load(); + QTRY_VERIFY2((dict.isLoaded() == true), "SymbolDictionary: isLoaded failed"); + + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::MIL2525D) + { + QStringList sidcs = AdvancedSymbology::listOfPointSidcs_Internal(); + + // + // SinglePoint: + // + int counter = 0; + for (const QString &sidc : std::as_const(sidcs)) + { + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findPreview(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + const QUuid &taskId2 = symbolResultWatcher.symbolResult()->taskId(); + QVERIFY2((taskId2.isNull() == false), "SymbolDictionary: taskId failed"); + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Point), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter1=" << counter; + + // + // Line: + // + counter = 0; + + sidcs = AdvancedSymbology::listOfLineSidcs_Internal(); + + for (const QString &sidc : std::as_const(sidcs)) + { + // qWarning() << " sidc=" << sidc; + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findPreview(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // TaskId is slipped here : + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polyline), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter2=" << counter; + + // + // Area: + // + counter = 0; + + sidcs = AdvancedSymbology::listOfAreaSidcs_Internal(); + + for (const QString &sidc : std::as_const(sidcs)) + { + // qWarning() << " sidc=" << sidc; + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findPreview(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // TaskId is slipped here : + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polygon), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter3=" << counter; + + } +} + +void SitMapUnitTest::checkSymbolDictionaryFindBaseSymbol() +{ + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // SymbologyType::None -> no esriDict -> immediately loaded: + // ! SymbologyType::None -> esriDict -> load... + + dict.load(); + QTRY_VERIFY2((dict.isLoaded() == true), "SymbolDictionary: isLoaded failed"); + + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::MIL2525D) + { + // + // SinglePoint: + // + QStringList sidcs = AdvancedSymbology::listOfPointSidcs_Internal(); + + int counter = 0; + for (const QString &sidc : std::as_const(sidcs)) + { + // qWarning() << " sidc=" << sidc; + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findBaseSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // TaskId is skipped here + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Point), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter1=" << counter; + + // + // Line: + // + counter = 0; + + sidcs = AdvancedSymbology::listOfLineSidcs_Internal(); + + for (const QString &sidc : std::as_const(sidcs)) + { + // qWarning() << " sidc=" << sidc; + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findBaseSymbol(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // TaskId is skipped here: + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polyline), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter2=" << counter; + + + // + // Area: + // + counter = 0; + + sidcs = AdvancedSymbology::listOfAreaSidcs_Internal(); + + for (const QString &sidc : std::as_const(sidcs)) + { + // qWarning() << " sidc=" << sidc; + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + symbolResultWatcher.setSymbolResult(dict.findPreview(sidc)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: resultIsValid failed"); + + // TaskId is slipped here : + + QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polygon), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + counter++; + } + QVERIFY2((counter == sidcs.size()), "SymbolDictionary: something failed"); + qWarning() << " counter3=" << counter; + } +} + + + +void SitMapUnitTest::checkSymbolDictionaryGctString() +{ + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // SymbologyType::None -> no esriDict -> immediately loaded: + // ! SymbologyType::None -> esriDict -> load... + + dict.load(); + QTRY_VERIFY2((dict.isLoaded() == true), "SymbolDictionary: isLoaded failed"); + + // search: + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::MIL2525D) + { + QStringList slGctLine; + QStringList slGctArea; + { + slGctLine += QLatin1String("gct_arrow"); + + slGctLine += QLatin1String("gct_arrow_with_offset"); + slGctLine += QLatin1String("gct_parallel_lines"); + slGctLine += QLatin1String("gct_parallel_lines_mid_line"); + slGctLine += QLatin1String("gct_parallel_lines_with_ticks"); + slGctLine += QLatin1String("gct_two_line"); + slGctLine += QLatin1String("gct_arrow_with_tail"); + slGctLine += QLatin1String("gct_open_triangle"); + + slGctLine += QLatin1String("gct_freehand_line"); + slGctLine += QLatin1String("gct_freehand_arrow"); + slGctLine += QLatin1String("gct_freehand_reverse_arrow"); + slGctLine += QLatin1String("gct_freehand_u"); + + slGctLine += QLatin1String("gct_circle"); + slGctLine += QLatin1String("gct_hook"); + slGctLine += QLatin1String("gct_horns"); + slGctLine += QLatin1String("gct_parallel_lines_with_ticks"); + slGctLine += QLatin1String("gct_t"); + slGctLine += QLatin1String("gct_triple_arrow"); + slGctLine += QLatin1String("gct_two_line_3_or_4_pt"); + slGctLine += QLatin1String("gct_u_or_t_shape"); + + slGctArea += QLatin1String("gct_rectangular_1_pt"); + slGctArea += QLatin1String("gct_circular"); + slGctArea += QLatin1String("gct_rectangular"); + slGctArea += QLatin1String("gct_parallel_lines_with_ticks"); + } + + unsigned int symbolCounter = 0; + unsigned int unexpectedCounter = 0; + unsigned int emptyCounter = 0; + + AdvancedSymbology::SymbolDictionaryImpl &dictImpl = + static_cast(dict); + + const AdvancedSymbology::SidcCodec::SidcHashTable &allSymbolProps = + dictImpl.getEsriDictionaryProcessor()->baseSymbolCodeToSymbolPropertyTable(); + + + for (const AdvancedSymbology::SymbolProperties &prop : std::as_const(allSymbolProps)) + { + QString baseKey = prop.key(); + if (baseKey.isEmpty()) + continue; + + ++symbolCounter; + const QString & gctString = prop.gctString(); + if (! gctString.isEmpty()) + { + + switch (prop.geometryType()) + { + case AdvancedSymbology::GeometryType::Point: + { + if (! gctString.isEmpty()) + { + ++unexpectedCounter; + qDebug() << " Point feature has none empty gctString, baseKey=" << baseKey << " gctString =" << gctString << endl; + continue; + } + } + break; + + case AdvancedSymbology::GeometryType::Polyline: + { + if (! slGctLine.contains(gctString) ) + { + ++unexpectedCounter; + qDebug() << " Unexpected gctString for line symbol found, baseKey=" << baseKey << " gctString =" << gctString << endl; + } + continue; + } + break; + + case AdvancedSymbology::GeometryType::Polygon: + { + if (! slGctArea.contains(gctString) ) + { + ++unexpectedCounter; + qDebug() << " Unexpected gctString for area symbol found, baseKey=" << baseKey << " gctString =" << gctString << endl; + } + continue; + } + break; + + default : break; + } // switch + } + else + { + switch (prop.geometryType()) + { + case AdvancedSymbology::GeometryType::Polyline: qDebug() << " Empty gctString found for line symbol, baseKey=" << baseKey << endl; ++emptyCounter; break; + case AdvancedSymbology::GeometryType::Polygon : qDebug() << " Empty gctString found for area symbol, baseKey=" << baseKey << endl; ++emptyCounter; break; + case AdvancedSymbology::GeometryType::Point : + default : break; + } + } + } // foreach + + qDebug() << "Checked symbols: " << symbolCounter << " empty gctString at line or areals: " << emptyCounter << endl; + + static char msg[128]; + sprintf( msg, "%d unexpected gctStrings found.", unexpectedCounter); + QVERIFY2( (unexpectedCounter == 0), msg ); + + } + return; + +} // void SitMapUnitTest::checkSymbolDictionaryGctString() + + +void SitMapUnitTest::checkSymbolDictionarySymbolPropertyImage() +{ + AdvancedSymbology::SymbolDictionary &dict = + AdvancedSymbology::SymbolDictionary::dictionary(m_d->m_symbologyType); + + // SymbologyType::None -> no esriDict -> immediately loaded: + // ! SymbologyType::None -> esriDict -> load... + + dict.load(); + QTRY_VERIFY2((dict.isLoaded() == true), "SymbolDictionary: isLoaded failed"); + + if (m_d->m_symbologyType == AdvancedSymbology::SymbologyType::MIL2525D) + { + AdvancedSymbology::SymbolProperties symProps; + symProps.setOriginalKey(QLatin1String("25270100")); // obstacle / building + + + AdvancedSymbology::SymbolResultWatcher symbolResultWatcher; + QSignalSpy spy(&symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady); + + QVERIFY2((symbolResultWatcher.resultIsValid() == false), "SymbolDictionary: resultIsValid failed"); + + symbolResultWatcher.setSymbolResult(dict.symbolPropertyImage(symProps)); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: symbolPropertyImage resultIsValid failed"); + QVERIFY2((symbolResultWatcher.symbolResult().isNull() == false), "SymbolDictionary: symbolPropertyImage symbolResult failed"); + + QTRY_VERIFY2((spy.count() == 1), "SymbolDictionary: symbolPropertyImage resultReady-signal failed"); + QVERIFY2((symbolResultWatcher.resultIsValid() == true), "SymbolDictionary: symbolPropertyImage resultIsValid failed"); + + // inspect result: +// QVERIFY2((symbolResultWatcher.symbolResult()->geometryType() == AdvancedSymbology::GeometryType::Polygon), "SymbolDictionary: geometryType failed"); + QVERIFY2((symbolResultWatcher.symbolResult()->symbolImage().image().isNull() == false), "SymbolDictionary: symbolImage failed"); + QImage img = symbolResultWatcher.symbolResult()->symbolImage().image(); + img.save(QLatin1String("/tmp/unitTest.png")); + + } +} + + + + +void SitMapUnitTest::checkMapSettings() +{ + if (1) + { + // use absolute path: + // SituationMap::MapSettings ms(QLatin1String("/shd/CTC/TOOLS/TDS/TDS-build-product/bin/situation_map_defaults.ini")); + + QString initFilePath = NGL::findFile(NGL::FileType::ConfigFile, QStringLiteral("situation_map_defaults.ini")); + + if (initFilePath.isEmpty()) + { + FATAL_LOG(m_logger) << " initFilePath " << initFilePath; + abort(); + } + SituationMap::MapSettings ms(initFilePath); + + QVERIFY2(ms.hasErrors() == false, "hasError failed"); + QVERIFY2(ms.exists() == true, "exists failed"); + QVERIFY2(ms.accessError() == false, "accessError failed"); + QVERIFY2(ms.formatError() == false, "formatError failed"); + + std::cerr << " operator<<: \n" << ms << std::endl; + + SituationMap::GridType gridType = ms.gridType(); + QVERIFY2(gridType == SituationMap::GridType::MGRS, "gridType failed"); + } +} + + +void SitMapUnitTest::checkMatrix2D() +{ + SituationMap::MathUtils::Matrix2D matrix; + + matrix.setToIdentity(); + + QVERIFY2( ( (matrix(0,0) == 1.0) && (matrix(0,1) == 0.0) && (matrix(0,2) == 0.0) && + (matrix(1,0) == 0.0) && (matrix(1,1) == 1.0) && (matrix(1,2) == 0.0) && + (matrix(2,0) == 0.0) && (matrix(2,1) == 0.0) && (matrix(2,2) == 1.0) ), "setToIdentity failed" ); + + + SituationMap::MathUtils::Matrix2D matrix2; + + QVERIFY2( ( (matrix2(0,0) == 1.0) && (matrix2(0,1) == 0.0) && (matrix2(0,2) == 0.0) && + (matrix2(1,0) == 0.0) && (matrix2(1,1) == 1.0) && (matrix2(1,2) == 0.0) && + (matrix2(2,0) == 0.0) && (matrix2(2,1) == 0.0) && (matrix2(2,2) == 1.0) ), "standard constructor does not set the identity matrix"); + + matrix2(0,0) = 11; matrix2(0,1) = 12; matrix2(0,2) = 13; + matrix2(1,0) = 21; matrix2(1,1) = 22; matrix2(1,2) = 23; + matrix2(2,0) = 31; matrix2(2,1) = 32; matrix2(2,2) = 33; + + QVERIFY2( ( (matrix2(0,0) == 11) && (matrix2(0,1) == 12) && (matrix2(0,2) == 13) && + (matrix2(1,0) == 21) && (matrix2(1,1) == 22) && (matrix2(1,2) == 23) && + (matrix2(2,0) == 31) && (matrix2(2,1) == 32) && (matrix2(2,2) == 33) ), "set matrix elements failed"); + + SituationMap::MathUtils::Matrix2D matrix3(matrix2); + + QVERIFY2( ( (matrix3(0,0) == 11) && (matrix3(0,1) == 12) && (matrix3(0,2) == 13) && + (matrix3(1,0) == 21) && (matrix3(1,1) == 22) && (matrix3(1,2) == 23) && + (matrix3(2,0) == 31) && (matrix3(2,1) == 32) && (matrix3(2,2) == 33) ), "copy constructor failed"); + + matrix = matrix2; + + QVERIFY2( ( (matrix(0,0) == 11) && (matrix(0,1) == 12) && (matrix(0,2) == 13) && + (matrix(1,0) == 21) && (matrix(1,1) == 22) && (matrix(1,2) == 23) && + (matrix(2,0) == 31) && (matrix(2,1) == 32) && (matrix(2,2) == 33) ), "assign operator failed"); + + matrix.setToIdentity(); + matrix.putTranslation(QPointF(5,7)); + QPointF p = matrix.apply(QPointF(2,1)); + + QVERIFY2( ((p.x() == 7) && (p.y() == 8)), "putTranslation failed"); + + matrix.setToIdentity(); + matrix.putScale(QPointF(2,3)); + p = matrix.apply(QPointF(2,1)); + + QVERIFY2( ((p.x() == 4) && (p.y() == 3)), "putScale failed"); + + matrix.setToIdentity(); + matrix.putScale(QPointF(2,2)); + matrix.putTranslation(QPointF(5,7)); + p = matrix.apply(QPointF(2,1)); + QVERIFY2( ((p.x() == 9) && (p.y() == 9)), "putScale combined with putTranslate failed"); + + matrix.setToIdentity(); + matrix.putRotate(QPointF(0,1)); + p = matrix.apply(QPointF(2,1)); + + QVERIFY2( ((p.x() == 1) && (p.y() == -2)), "putRotated failed"); + + matrix.setToIdentity(); + matrix.putRotate(QPointF(0.5,0.8660254)); // 60 degrees + p = matrix.apply(QPointF(1,0)); + + QVERIFY2( ((int(p.x()*10) == 5) && (int(p.y()*1000000) == -866025)), "putRotated 60 degrees failed"); + + matrix.setToIdentity(); + matrix.putTranslation(QPointF(-5,-7)); + matrix.putRotate(QPointF(0,1)); + matrix.putTranslation(QPointF(5,7)); + p = matrix.apply(QPointF(7,8)); + + QVERIFY2( ((p.x() == 6) && (p.y() == 5)), "putRotated combined with putTranslate failed"); + + matrix.setToIdentity(); + matrix.putTranslation(QPointF(-5,-7)); + matrix.putRotate(QPointF(0.5,0.8660254)); // 60 degrees + matrix.putTranslation(QPointF(5,7)); + p = matrix.apply(QPointF(6,7)); + + QVERIFY2( ((int(p.x()*10) == 55) && (int(p.y()*10000000) == 61339746)), "putRotated 60 degrees combined with putTranslate failed"); + +} // SitMapUnitTtest::checkMatrix2D() + + +//------------------------------------------------------------------------------------------------------- + +#include "unit_test.moc" diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.h new file mode 100755 index 000000000..595727222 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/unit_test.h @@ -0,0 +1,142 @@ +/** + * @RDE_HEADER@ + * Unittest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace AdvancedSymbology +{ +enum class SymbologyType; +} + + +class CheckSignal : public QObject +{ + Q_OBJECT +public: + bool isConnected(quint64 &msec); + +signals: + void mySignal(); +}; + + + +class SitMapUnitTest : public QObject +{ + Q_OBJECT + +public: + SitMapUnitTest(AdvancedSymbology::SymbologyType symbologyType); + virtual ~SitMapUnitTest(); + +private: + void checkSymbolDictionaryLoad(); + void checkSymbolDictionaryLoad(AdvancedSymbology::SymbologyType st); +private Q_SLOTS: + + void checkLogging(); + + void checkQRect(); + void checkQListQVector(); + void checkQWeakPointer(); + + void checkFactoryCreationContraints(); + +// void checkIdHandlerCreate(); +// void checkIdHandlerFree(); +// void checkIdHandlerReUseId(); +// void checkIdHandlerReserveId(); + + void checkSimpleValue(); + + void checkIdGenerator(); + void checkIdContainer(); + + void checkIsSignalConnected(); + void onCheckIsSignalConnected(); + + void checkMapLayer(); + void checkMapLayerOrderedList(); + + void checkLayerFactory(); + + void checkObjectFactory(); + + void checkPickableOnInvalidUniqueMapId(); + + void checkBoundingBox(); + + void checkDistanceMeasurement(); + void measureDistanceGeodetic(); + + void recalculateAngle(); + + void checkDensify(); + void checkBuffer(); + + void checkMovementTrack(); + void checkMovementTrackManager(); + + + void checkSidcCodec(); + + void checkSIDCDefinitions1(); + void checkSIDCDefinitions2(); +// void checkCreateSidc(); +// void checkSymbolProperties(); + void checkSymbolDictionaryLoadSymbolXml(); + + void checkSymbolDictionaryConsistency(); + + void checkSymbolDictionaryFetchSymbol(); + void checkSymbolDictionaryFetchSymbol2(); + void checkSymbolDictionaryFetchSymbolAll(); + void checkSymbolDictionaryExtraDirFetchSymbol(); + void checkSymbolDictionaryFetchSymbolStress(); + + void checkSymbolDictionaryBulkRenderer(); + + void checkSymbolDictionarySymbolPropertyImage(); + + void checkSymbolDictionaryFindImage(); + void checkSymbolDictionaryFindPreview(); + void checkSymbolDictionaryFindBaseSymbol(); + + void checkSymbolDictionaryGctString(); + + + void checkMapSettings(); + + void checkMatrix2D(); + + void virtualInheritances(); + + void multiInheritancesVisualStudio2015Bug(); + +protected: + +private: + + class Private; + + static const unsigned int m_initReserve = 20; + static const unsigned int m_blockSize = 10; + + SituationMap::LOGGER_T m_logger; + std::unique_ptr m_d; + +}; + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/virtual_inheritances.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/virtual_inheritances.cpp new file mode 100755 index 000000000..cd0e167fa --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTests/virtual_inheritances.cpp @@ -0,0 +1,252 @@ +/** + * @RDE_HEADER@ + * Unittest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "unit_test.h" + +#include +#include +#include + + +#include + +#include + + + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Test of virtual inheritance combined with constuctor delegation. With gcc all works fine. With Visual Studio 2013 on Windows + * the constructor of VirtualBaseInterface class is called twice. This may crash the program or result in other unexpected behaviour. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + + + +class VirtualBaseInterface +{ + +public: + + static unsigned int _ctorCount; + + VirtualBaseInterface() + { + qDebug() << "ctor of class VirtualBaseInterface() has been called"; + _ctorCount++; + } + + VirtualBaseInterface(int, int) + { + // in case of virtual inheritance this ctor will never be used. + qDebug() << "ctor of class VirtualBaseInterface(int x, int y) has been called"; + _ctorCount++; + } + + ~VirtualBaseInterface() = default; + + virtual void foo() = 0; +}; + +unsigned int VirtualBaseInterface::_ctorCount = 0; + + +class BaseClass : virtual public VirtualBaseInterface , public QObject +{ +protected: + + int c; + +public: + + BaseClass(int x, int y) : BaseClass(x, y, 10) + { + qDebug() << "ctor of class BaseClass(int x,int y) has been called"; + } + + BaseClass(int x, int y, int z) : VirtualBaseInterface(), QObject(), c(z) + { + Q_UNUSED(x) + Q_UNUSED(y) + qDebug() << "ctor of class BaseClass(int x,int y,int z) has been called"; + } + + virtual ~BaseClass() = default; + + void foo() override + { + c = 10; + } +}; + + +class Derived1 : public BaseClass +{ +protected: + + int d; + +public: + + Derived1(int x, int y) : BaseClass(x, y), d(10) + {} + + Derived1(int x, int y, int z, int w) : BaseClass(x, y, z), d(w) + {} + + void foo() override + { + d = 13; + } +}; + +class Derived2 : public Derived1 +{ +public: + + Derived2(int x, int y) : Derived1(x, y) + {} + + Derived2(int x, int y, int z, int w) : Derived1(x, y, z, w) + {} + + void foo() override + { + d = 17; + } +}; + + +void SitMapUnitTest::virtualInheritances() +{ + bool ok = true; + qDebug() << endl << "Check Derived2(int x,int y,int z,int w)" << endl; + try + { + Derived2 d(1, 2, 3, 4); + d.foo(); + } + catch (...) + { + ok = false; + } + QVERIFY2(ok, "virtualInheritances for Derived2(int x,int y,int z,int w) failed."); + + qDebug() << endl << "Check Derived2(int x,int y)" << endl; + try + { + Derived2 d(1, 2); + d.foo(); + } + catch (...) + { + ok = false; + } + QVERIFY2(ok, "virtualInheritances for Derived2(int x,int y) failed."); + + QVERIFY2(VirtualBaseInterface::_ctorCount >= 2, "Too few calles of VirtualBaseInterface ctor."); + QVERIFY2(VirtualBaseInterface::_ctorCount <= 2, "VirtualBaseInterface ctor has been called too often."); + +} // SitMapUnitTest::virtualInheritances + + + +class Referenced +{ + public: + Referenced() { _refCount++; } + ~Referenced() = default; + + unsigned int refcount() { return _refCount; } + + static unsigned int _refCount; +}; + +unsigned int Referenced::_refCount = 0; + + +class AbstractInterface : public virtual Referenced +{ + public: + AbstractInterface() = default; + ~AbstractInterface() = default; + + virtual void foo() = 0; +}; + + +class AbstractInterfaceImpl : public virtual AbstractInterface +{ + unsigned int _data; + + public: + + AbstractInterfaceImpl() : AbstractInterface() { _data = 42; } + ~AbstractInterfaceImpl() { _data = 0; } + void foo() override; + +}; + + +class ObjectInterface : public virtual AbstractInterface +{ + public: + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Using the default constructor here like + // ObjectInterface() = default; + // gives a crash!!!! + // Implementing the ctor works. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ObjectInterface() : AbstractInterface() {;} + ~ObjectInterface() = default; + + virtual void foo2() = 0; +}; + + +class ObjectInterfaceImpl : public AbstractInterfaceImpl, public virtual ObjectInterface +{ + unsigned int _objData; +public: + explicit ObjectInterfaceImpl(unsigned int data = 7) + : ObjectInterface(), AbstractInterfaceImpl() + { + _objData = data; + } + + ~ObjectInterfaceImpl() + { + _objData = 0; + } + + void foo2() override; +}; + +void AbstractInterfaceImpl::foo() { _data = 4711; } +void ObjectInterfaceImpl::foo2() { _objData = 815; } + + +void SitMapUnitTest::multiInheritancesVisualStudio2015Bug() +{ + Referenced::_refCount = 0; + + bool ok = true; + qDebug() << endl << "Check ctor ObjectInterfaceImpl " << endl; + try + { + ObjectInterfaceImpl obj; + obj.foo(); + if (obj.refcount() != 1) ok = false; + } + catch (...) + { + ok = false; + } + QVERIFY2(ok, "Check ctor ObjectInterfaceImpl failed."); +} +//------------------------------------------------------------------------------------------------------- + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/CMakeLists.txt new file mode 100755 index 000000000..191839b85 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/CMakeLists.txt @@ -0,0 +1,47 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +SET(LinkedSources + log4cxxGeometryModelUnitTest.props +) + +SET(UnitTestSources + unit_test.cpp + unit_test.h +) + +SOURCE_GROUP( LinkedSources FILES ${LinkedSources} ) +SOURCE_GROUP( UnitTestSources FILES ${UnitTestSources} ) + +IF(BUILD_TESTING AND TARGET situationMap) + ADD_EXECUTABLE(situationMapGeometryModelUnitTest ${LinkedSources} ${UnitTestSources} ) + + TARGET_LINK_LIBRARIES(situationMapGeometryModelUnitTest + NGLLogger + nglcoreapplication + situationMapCommon + situationMapGeometryModel + ArcGIS::ArcGIS + Qt6::Core + Qt6::Test + ArcGISQt5Hack + ) + + ADD_TEST(NAME situationMapGeometryModelUnitTest COMMAND $) + + SET(LOG4CXX_PROPS_FILE log4cxxGeometryModelUnitTest.props) + ADD_CUSTOM_COMMAND(TARGET situationMapGeometryModelUnitTest POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${LOG4CXX_PROPS_FILE} $/${LOG4CXX_PROPS_FILE} + ) + + IF(MSVC) + # Configure the template file + SET(VS_USER_FILE situationMapGeometryModelUnitTest.vcxproj.user) + configure_file( ../SituationMap.vcxproj.user.in ${CMAKE_CURRENT_BINARY_DIR}/${VS_USER_FILE} @ONLY) + ENDIF() + +ENDIF() diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/log4cxxGeometryModelUnitTest.props b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/log4cxxGeometryModelUnitTest.props new file mode 100755 index 000000000..a45f1301c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/log4cxxGeometryModelUnitTest.props @@ -0,0 +1,129 @@ +log4j.reset=true +log4j.rootLogger=WARN, file + +# SituationMap +########################################################### +#log4j.logger.SituationMap.SitMapUnitTest=DEBUG +#log4j.logger.SituationMap.SitMap=WARN + +log4j.logger.SituationMap.LoggingTest=ERROR +log4j.logger.SituationMap.LoggingTest.Chan1=INFO +log4j.logger.SituationMap.LoggingTest.Chan2=ERROR + +#log4j.logger.SituationMap.MapImpl=WARN +#log4j.logger.SituationMap.LayerFactoryImpl=INFO +#log4j.logger.SituationMap.CoordTranslationFunctorImpl=INFO +#log4j.logger.SituationMap.GridImpl=WARN +#log4j.logger.SituationMap.ObjectFactory=WARN +#log4j.logger.SituationMap.IdGenerator=TRACE +#log4j.logger.SituationMap.ObjectContainer=TRACE +#log4j.logger.SituationMap.ObjectContainerCTC=TRACE +#log4j.logger.SituationMap.ViewshedImpl=INFO +#log4j.logger.SituationMap.ViewshedBase=INFO +#log4j.logger.SituationMap.SymbolDictionary=WARN +#log4j.logger.SituationMap.SymbolDictionaryImpl=WARN +#log4j.logger.SituationMap.Sidc2525Delta=WARN + +#log4j.logger.SituationMap.AbstractMapObjectImpl=INFO +#log4j.logger.SituationMap.AbstractEffectSimulationImpl=INFO +#log4j.logger.SituationMap.OriginTranslated=INFO +#log4j.logger.SituationMap.PositionsTranslated=INFO +#log4j.logger.SituationMap.AbstractMultiPointMapObjectImpl=INFO +#log4j.logger.SituationMap.AbstractSinglePointMapObjectImpl=INFO +#log4j.logger.SituationMap.BasicPathObjectImpl=INFO +#log4j.logger.SituationMap.IndicationContainer=INFO + +#log4j.logger.SituationMap.AreaFireImpl=INFO +#log4j.logger.SituationMap.BiologicalAgentImpl=INFO +#log4j.logger.SituationMap.BuildingOutlineImpl=INFO +#log4j.logger.SituationMap.ChemicalAgentImpl=INFO +#log4j.logger.SituationMap.DangerAreaImpl=INFO +#log4j.logger.SituationMap.FunctionalAreaImpl=INFO +#log4j.logger.SituationMap.HitLineImpl=INFO +#log4j.logger.SituationMap.MineImpl=INFO +#log4j.logger.SituationMap.NuclearAgentImpl=INFO +#log4j.logger.SituationMap.ParticipantImpl=INFO +#log4j.logger.SituationMap.SingleFireImpl=INFO + +#log4j.logger.SituationMap.AbstractGraphicalObject=WARN +#log4j.logger.SituationMap.GraphicalObjectEllipse=WARN +#log4j.logger.SituationMap.GraphicalObjectPath=WARN +#log4j.logger.SituationMap.GraphicalObjectMultiPath=WARN +#log4j.logger.SituationMap.GraphicalObjectPictureMarker=WARN +#log4j.logger.SituationMap.GraphicalObjectText=WARN +#log4j.logger.SituationMap.TacticalAppearance=INFO +#log4j.logger.SituationMap.MessageBase=INFO + + +# Appnd_File +########################################################### +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_SituationMap.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %-5p %c{1} |#| %m (%-.30F:%L) %n + +#log4j.appender.stdout.layout.ConversionPattern= %d %-5p %-30c |#| %m (%-.30F:%L) %n + + +# Appnd_Syslog +########################################################### +log4j.appender.syslog=org.apache.log4j.SyslogAppender +log4j.appender.syslog.Threshold=ERROR +log4j.appender.syslog.SysLogHost=localhost +log4j.appender.syslog.Facility=LOCAL7 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m +log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true +log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO +log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL + + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.cpp new file mode 100755 index 000000000..109790719 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.cpp @@ -0,0 +1,807 @@ +/** + * @RDE_HEADER@ + * Unittest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "unit_test.h" + +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#include + + +using namespace SituationMap; + + +bool CheckSignal::isConnected() +{ + bool isConnected = isSignalConnected( QMetaMethod::fromSignal(&CheckSignal::mySignal) ); + return isConnected; +} + + +GeoModelUnitTest::GeoModelUnitTest() : m_logger(getLogger("GeometryModelUnitTest")) +{ +} + +void GeoModelUnitTest::init() +{ + qRegisterMetaType("GeometryModel::Node"); // not really working? + qRegisterMetaType("SituationMap::GeometryModel::Node"); // not really working? + qRegisterMetaType("GeometryModel::Attribute"); +} + + +void GeoModelUnitTest::checkSignals1() +{ + GeometryModel * geoModelW = GeometryModelFactory::singleton()->create(); + QList vertexListW; + QList> attrListW; + + attrListW += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + + vertexListW << QPointF(1, 1) << QPointF(2,2); + + QVERIFY2( (geoModelW != nullptr), "Creating empty GeometryModel failed."); + + // connect signals... + QSignalSpy spyTypeError(geoModelW, &GeometryModel::typeError); + QSignalSpy spyAttributeError(geoModelW, &GeometryModel::attributeError); + QSignalSpy spyModelError(geoModelW, &GeometryModel::modelError); + QSignalSpy spyUserDataCleanup(geoModelW,&GeometryModel::userDataCleanup); + QSignalSpy spyNodeCleanup(geoModelW, &GeometryModel::nodeCleanup); + QSignalSpy spyChanged(geoModelW, &GeometryModel::changed); + + // + // addPrimitive + // + + // do operation : + const GeometryModel::Node & nodeWP = geoModelW->addPrimitive( GeometryModel::PrimitiveType::Line, vertexListW, attrListW ); + + // verify, how often signals are emitted: + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 0, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 0, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 1, "GeometryModel: signalCount failed, spyChanged"); + // : do operation + + // + // setUserData + // + { + // do operation : + qWarning() << "setUserData..."; + spyTypeError.clear();spyAttributeError.clear();spyModelError.clear();spyUserDataCleanup.clear();spyNodeCleanup.clear();spyChanged.clear(); + geoModelW->setUserData( nodeWP, 4711 ); + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 0, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 0, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 1, "GeometryModel: signalCount failed, spyChanged"); + // : do operation + } + + // + // Transfer via QByteArray: + // + + const QByteArray stringRep = geoModelW->toByteArray(); // qDebug() << stringRep; + + GeometryModel * geoModelR = GeometryModelFactory::singleton()->create(); + QVERIFY2(geoModelR, "Creating second GeometryModel failed."); + + { + // + // change to mode geoModelR + // + + QSignalSpy spyTypeError(geoModelR, &GeometryModel::typeError); + QSignalSpy spyAttributeError(geoModelR, &GeometryModel::attributeError); + QSignalSpy spyModelError(geoModelR, &GeometryModel::modelError); + QSignalSpy spyUserDataCleanup(geoModelR,&GeometryModel::userDataCleanup); + QSignalSpy spyNodeCleanup(geoModelR, &GeometryModel::nodeCleanup); + QSignalSpy spyChanged(geoModelR, &GeometryModel::changed); + + // + // fromString + // + { + // do operation : + qWarning() << "fromString..."; + spyTypeError.clear();spyAttributeError.clear();spyModelError.clear();spyUserDataCleanup.clear();spyNodeCleanup.clear();spyChanged.clear(); + QVERIFY2( geoModelR->fromByteArray(stringRep) , "Building GeometryModel from String failed." ); + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 0, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 0, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 0, "GeometryModel: signalCount failed, spyChanged"); // correct? + // : do operation + } + + + // + // removePrimitive + // + + { + geoModelR->enableUndo( true ); + geoModelR->startUndoBlock(); + + // do operation : + qWarning() << "removePrimitive..."; + spyTypeError.clear();spyAttributeError.clear();spyModelError.clear();spyUserDataCleanup.clear();spyNodeCleanup.clear();spyChanged.clear(); + geoModelR->removePrimitive( nodeWP ); + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 1, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 0, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 0, "GeometryModel: signalCount failed, spyChanged"); // see r45452, 'avoid invalid errorMsg' + //QVERIFY2(spyChanged.count() == 1, "GeometryModel: signalCount failed, spyChanged"); + + + // : do operation + } + geoModelR->endUndoBlock( QLatin1String("Remove Primitive") ); + + + // + // undo + // + { + geoModelR->enableUndo( true ); + geoModelR->startUndoBlock(); + + // do operation : + qWarning() << "undo..."; + spyTypeError.clear();spyAttributeError.clear();spyModelError.clear();spyUserDataCleanup.clear();spyNodeCleanup.clear();spyChanged.clear(); + geoModelR->undo(); + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 0, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 1, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 1, "GeometryModel: signalCount failed, spyChanged"); + // : do operation + } + } +} // GeoModelUnitTest::checkSignals1 + + +void GeoModelUnitTest::checkSignals2() +{ + // create Reference model (...W) + GeometryModel * geoModelW = GeometryModelFactory::singleton()->create(); + QList vertexListW; + QList> attrListW; + + attrListW += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + vertexListW << QPointF(1, 1) << QPointF(2,2); + + geoModelW->addPrimitive( GeometryModel::PrimitiveType::Line, vertexListW, attrListW, 42 ); + // ==> root -> line42 + + const GeometryModel::Node & rootNode = geoModelW->getRootNode(); + const GeometryModel::Node & nodeWP = geoModelW->addPrimitive( GeometryModel::PrimitiveType::Line, vertexListW, attrListW, 815 ); + GeometryModel::Node && groupWP = geoModelW->createGroup( GeometryModel::NodeList() ); + // ==> root -> line42 + // -> line815 + // -> group + + QVERIFY2( groupWP.parentNode() == rootNode, "Group parent is not the root node." ); + + + QSignalSpy spyTypeError(geoModelW, &GeometryModel::typeError); + QSignalSpy spyAttributeError(geoModelW, &GeometryModel::attributeError); + QSignalSpy spyModelError(geoModelW, &GeometryModel::modelError); + QSignalSpy spyUserDataCleanup(geoModelW,&GeometryModel::userDataCleanup); + QSignalSpy spyNodeCleanup(geoModelW, &GeometryModel::nodeCleanup); + QSignalSpy spyChanged(geoModelW, &GeometryModel::changed); + + // + // movePrimitiveIntoGroup + // + { + // do operation : + qWarning() << "movePrimitiveIntoGroup..."; + spyTypeError.clear();spyAttributeError.clear();spyModelError.clear();spyUserDataCleanup.clear();spyNodeCleanup.clear();spyChanged.clear(); + geoModelW -> movePrimitiveIntoGroup( nodeWP, groupWP ); + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 0, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 0, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 1, "GeometryModel: signalCount failed, spyChanged"); + // : do operation + } + // ==> root -> line42 + // -> group -> line815 + + + + // + // createGroup + // + // do operation : + qWarning() << "createGroup..."; + spyTypeError.clear();spyAttributeError.clear();spyModelError.clear();spyUserDataCleanup.clear();spyNodeCleanup.clear();spyChanged.clear(); + GeometryModel::Node && group = geoModelW->createGroup( GeometryModel::NodeList() ); + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 0, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 0, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 1, "GeometryModel: signalCount failed, spyChanged"); + // : do operation + + + // + // createGroup + // + { + // do operation : + qWarning() << "deleteGroup..."; + spyTypeError.clear();spyAttributeError.clear();spyModelError.clear();spyUserDataCleanup.clear();spyNodeCleanup.clear();spyChanged.clear(); + geoModelW -> deleteGroup( group ); + QVERIFY2(spyTypeError.count() == 0, "GeometryModel: signalCount failed, spyTypeError"); + QVERIFY2(spyAttributeError.count() == 0, "GeometryModel: signalCount failed, spyAttributeError"); + QVERIFY2(spyModelError.count() == 0, "GeometryModel: signalCount failed, spyModelError"); + QVERIFY2(spyUserDataCleanup.count() == 0, "GeometryModel: signalCount failed, spyUserDataCleanup"); + QVERIFY2(spyNodeCleanup.count() == 0, "GeometryModel: signalCount failed, spyNodeCleanup"); + QVERIFY2(spyChanged.count() == 1, "GeometryModel: signalCount failed, spyChanged"); + // : do operation + } + + qDebug() << geoModelW->toByteArray(); + delete geoModelW; + +} // GeoModelUnitTest::checkSignals2 + + +void GeoModelUnitTest::checkCoordinatePrecision() +{ + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + QList vertexList; + QList> attrList; + QPointF p1( 0.00913852259360125798040230648137, 0.92502450355699467576955610729896 ); + QPointF p2( 0.00913852261853453300889272900885, 0.92502450357271836272445997736134 ); // distance to p1 is 0.0001 mm in geographic coordinates + + vertexList << p1 << p2; + + QVERIFY2( (geoModel != nullptr), "Creating empty GeometryModel failed." ); + + const GeometryModel::Node & node = geoModel->addPrimitive( GeometryModel::PrimitiveType::Line, vertexList, attrList ); + + vertexList = geoModel->getPrimitivePoints( node ); // read coordinates back from model + + QVERIFY2((vertexList.size() == 2), "Unexpected size of coordinate list after read." ); + + const QPointF & v1 = vertexList[0]; + const QPointF & v2 = vertexList[1]; + + QVERIFY2(((p1 == v1) && (p2 == v2) && (v1 != v2)), "Precision test failed." ); + +} // GeoModelUnitTest::checkCoordinatePrecision + + +void GeoModelUnitTest::createModelWithLine() +{ + GeometryModel * geoModelW = GeometryModelFactory::singleton()->create(); + QList vertexListW; + QList> attrListW; + + attrListW += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + + vertexListW << QPointF(1, 1) << QPointF(2,2); + + QVERIFY2( (geoModelW != nullptr), "Creating empty GeometryModel failed."); + + const GeometryModel::Node & nodeWP = geoModelW->addPrimitive( GeometryModel::PrimitiveType::Line, vertexListW, attrListW ); + + geoModelW->setUserData( nodeWP, 4711 ); + + const QByteArray stringRep = geoModelW->toByteArrayDebug(); // qDebug() << stringRep; + + const GeometryModel::Node & nodeW = geoModelW->getRootNode(); + GeometryModel::PrimitiveType nodeTypeW = geoModelW->getType( nodeW ); + + // + // transfer via string: + // + + GeometryModel * geoModelR = GeometryModelFactory::singleton()->create(); + + QVERIFY2( (GeometryModel::PrimitiveType::RootNode == nodeTypeW), "Unexpected root node type."); + QVERIFY2( (geoModelR != nullptr) , "Creating second GeometryModel failed."); + QVERIFY2( geoModelR->fromByteArrayDebug(stringRep) , "Building GeometryModel from String failed." ); + + const GeometryModel::Node & nodeR = geoModelR->getRootNode(); + GeometryModel::PrimitiveType nodeTypeR = geoModelR->getType( nodeR ); + + QVERIFY2( (nodeTypeR == nodeTypeW) , "Second GeometryModel differs from first one."); + + const GeometryModel::NodeList & nodeList = geoModelR->getGroupPrimitives( nodeR ); + + QVERIFY2( nodeList.size() == 1 , "Unexpected size of root node list."); + + const GeometryModel::Node & nodeRP = nodeList.first(); + + QVERIFY2( (geoModelR->getType(nodeRP) == GeometryModel::PrimitiveType::Line), "Unexpected primitive type."); + + const QList & vertexListR = geoModelR->getPrimitivePoints(nodeRP); + + QVERIFY2( geoModelR->hasAttribute(nodeRP,GeometryModel::Attribute::Color), "Attribute color is missing." ); + + QColor color = geoModelR->getAttribute(nodeRP,GeometryModel::Attribute::Color); + + QVERIFY2( color == QColor("red") , "Attribute color has wrong value." ); + QVERIFY2( geoModelR->getUserData(nodeRP) == 4711, "UserData has wrong value." ); + + QVERIFY2( ((vertexListR[0] == QPointF(1,1)) && (vertexListR[1] == QPointF(2,2))), "Wrong coordinate values." ); + QVERIFY2( nodeW.hasChildNodes() , "Wrong return value of Node::hasChildNodes()" ); + + const GeometryModel::Node & lineNode = nodeW.firstChildNode(); + + QVERIFY2( geoModelR->getUserData(lineNode) == 4711, "Wrong return value of Node::firstChildNode()." ); + + delete geoModelW; + delete geoModelR; +} // GeoModelUnitTest::createModelWithLine() + + +void GeoModelUnitTest::createModelWithGroup() +{ + + // create Reference model (...W) + GeometryModel * geoModelW = GeometryModelFactory::singleton()->create(); + QList vertexListW; + QList> attrListW; + + attrListW += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + vertexListW << QPointF(1, 1) << QPointF(2,2); + + geoModelW->addPrimitive( GeometryModel::PrimitiveType::Line, vertexListW, attrListW, 42 ); + // ==> root -> line42 + + const GeometryModel::Node & rootNode = geoModelW->getRootNode(); + const GeometryModel::Node & nodeWP = geoModelW->addPrimitive( GeometryModel::PrimitiveType::Line, vertexListW, attrListW, 815 ); + GeometryModel::Node && groupWP = geoModelW->createGroup( GeometryModel::NodeList() ); + // ==> root -> line42 + // -> line815 + // -> group + + QVERIFY2( groupWP.parentNode() == rootNode, "Group parent is not the root node." ); + + geoModelW -> movePrimitiveIntoGroup( nodeWP, groupWP ); + // ==> root -> line42 + // -> group -> line815 + + const QByteArray stringRep = geoModelW->toByteArrayDebug(); qDebug() << stringRep; + + delete geoModelW; + + // + // Rebuild model + // + { + GeometryModel * geoModelRebuilded = GeometryModelFactory::singleton()->create(); + QVERIFY2( geoModelRebuilded->fromByteArrayDebug(stringRep), "Building GeometryModel from String failed." ); + + qDebug() << geoModelRebuilded->toByteArray(); + + // compare string: + QVERIFY2( (stringRep == geoModelRebuilded->toByteArrayDebug()), "GeometryModel stringCompare failed." ); + + + // Find primitive: + const GeometryModel::Node & lineNode42 = geoModelRebuilded -> findPrimitive( 42 ); + QVERIFY2( lineNode42.isValid(), "GeometryModel::findPrimitive failed." ); + + const GeometryModel::Node & lineNode815 = geoModelRebuilded -> findPrimitive( 815 ); + QVERIFY2( lineNode815.isValid(), "GeometryModel::findPrimitive failed." ); + + + // check isInGroup: + const GeometryModel::Node & groupNode = geoModelRebuilded->isInGroup( lineNode815 ); + QVERIFY2( groupNode.isValid(), "GeometryModel::isInGroup failed." ); + + const GeometryModel::Node & groupNode42 = geoModelRebuilded -> isInGroup( lineNode42 ); + QVERIFY2( groupNode42.isValid() == false, "GeometryModel::isInGroup failed." ); + + // check isRootNode: + + QVERIFY2( (groupNode42.isRootNode() == false) , "GeometryModel::Node::isRootNode for id=42 returns wrong value" ); + + QVERIFY2( (!geoModelRebuilded->isInGroup(lineNode815).isRootNode()), "GeometryModel::Node::isRootNode for id=815 returns wrong value" ); + + delete geoModelRebuilded; + } + +} // GeoModelUnitTest::createModelWithGroup + + +void GeoModelUnitTest::createAndDeletePrimitives() +{ + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + if (geoModel == nullptr) return; + + const GeometryModel::Node & rootNode = geoModel->getRootNode(); + QList vertexList; + QList> attrList; + + attrList += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + + vertexList << QPointF(1, 1) << QPointF(2,2); + + geoModel->addPrimitive( GeometryModel::PrimitiveType::Line, vertexList, attrList, 42 ); + + // qDebug() << geoModel->toString(); + + const GeometryModel::Node & group = geoModel->createGroup( GeometryModel::NodeList() ); + + geoModel->setUserData( group, 8888 ); + + QVERIFY2( group.parentNode() == rootNode , "Group parent is not the root node." ); + QVERIFY2( !geoModel->findPrimitive(8888).isValid(), "findPrimitive returns group node" ); + + const GeometryModel::Node & lineNode = geoModel->findPrimitive(42); + QVERIFY2( lineNode.isValid(), "findPrimitive failed to find primitive" ); + + geoModel->movePrimitiveIntoGroup( lineNode, group ); + + geoModel->enableUndo( true ); + geoModel->startUndoBlock(); + geoModel->removePrimitive( lineNode ); + geoModel->endUndoBlock( QLatin1String("Remove Primitive") ); + + QVERIFY2( !geoModel->findPrimitive(42).isValid(), "deleted primitive found." ); + +} // GeoModelUnitTest::createAndDeletePrimitives + + +void GeoModelUnitTest::createAndDeleteGroups() +{ + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + if (geoModel == nullptr) return; + + const QByteArray & modelXML = geoModel->toByteArray(); + + const GeometryModel::Node & rootNode = geoModel->getRootNode(); + QList vertexList; + QList> attrList; + + attrList += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + + vertexList << QPointF(1, 1) << QPointF(2,2); + + geoModel->addPrimitive( GeometryModel::PrimitiveType::Line, vertexList, attrList, 42 ); + + // qDebug() << geoModel->toString(); + + const GeometryModel::Node & group = geoModel->createGroup( GeometryModel::NodeList() ); + + geoModel->setUserData( group, 8888 ); + + QVERIFY2( group.parentNode() == rootNode , "Group parent is not the root node." ); + QVERIFY2( !geoModel->findPrimitive(8888).isValid(), "findPrimitive returns group node" ); + + const GeometryModel::Node & lineNode = geoModel->findPrimitive(42); + QVERIFY2( lineNode.isValid(), "findPrimitive failed to find primitive" ); + + geoModel->movePrimitiveIntoGroup( lineNode, group ); + + geoModel -> deleteGroup( group ); + + // qDebug() << geoModel->toString(); + + QVERIFY2( geoModel->toByteArray() == modelXML, "delete group failed." ); + +} // GeoModelUnitTest::createAndDeleteGroups + + +void GeoModelUnitTest::checkUndo() +{ + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + if (geoModel == nullptr) return; + + geoModel->enableUndo( true ); + + const QByteArray geoModelClean = geoModel->toByteArray(); + + QList vertexList; + QList> attrList; + + attrList += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + + vertexList << QPointF(1, 1) << QPointF(2,2); + + geoModel->addPrimitive( GeometryModel::PrimitiveType::Line, vertexList, attrList, 42 ); + + GeometryModel::Node group = geoModel->createGroup( GeometryModel::NodeList() ); + const GeometryModel::Node & lineNode = geoModel->findPrimitive(42); + + QVERIFY2( group.isValid() , "Group node isn't valid." ); + QVERIFY2( lineNode.isValid(), "Line node isn't valid." ); + + geoModel->movePrimitiveIntoGroup( lineNode, group ); + + const QByteArray geoModelXml = geoModel->toByteArray(); + + geoModel->startUndoBlock(); + geoModel->removePrimitive( lineNode ); + geoModel->endUndoBlock( QLatin1String("Remove Primitive") ); + + geoModel->undo(); + + group = geoModel->isInGroup( geoModel->findPrimitive(42) ); + + QVERIFY2( geoModel->toByteArray() == geoModelXml, "Undo doesn't restore deleted nodes." ); + QVERIFY2( group.isValid() , "Group node isn't valid after undo." ); + + geoModel -> deleteGroup( group ); + + // qDebug() << geoModelClean; + // qDebug() << geoModel->toString(); + + QVERIFY2( geoModel->toByteArray() == geoModelClean, "Delete group failed." ); + +} // GeoModelUnitTest::checkUndo + + +void GeoModelUnitTest::checkMatrixTransform() +{ + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + if (geoModel == nullptr) return; + + geoModel->enableUndo( true ); + + QList vertexList; + QList> attrList; + + attrList += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + + vertexList << QPointF(1, 1) << QPointF(2,2); + + const GeometryModel::Node & lineNode = geoModel->addPrimitive( GeometryModel::PrimitiveType::Line, vertexList, attrList, 42 ); + const GeometryModel::Node & group = geoModel->createGroup( GeometryModel::NodeList() ); + + geoModel->movePrimitiveIntoGroup( lineNode, group ); + + geoModel->startUndoBlock(); + geoModel->translateNode( group, 500, 1000 ); + vertexList = geoModel->getPrimitivePoints( lineNode ); + QVERIFY2( ((vertexList[0].x() == 501) && (vertexList[0].y() == 1001) && (vertexList[1].x() == 502) && (vertexList[1].y() == 1002)), "TranslateNode failed." ); + + geoModel->scaleNode( group, 0.5, -0.5 ); + geoModel->scaleNode( group, 4 , 4 ); + + vertexList = geoModel->getPrimitivePoints( lineNode ); + + QVERIFY2( ((vertexList[0].x() == 500.5) && (vertexList[0].y() == 1002.5) && (vertexList[1].x() == 502.5) && (vertexList[1].y() == 1000.5)), "ScaleNode failed." ); + + geoModel->rotateNode(group,90); + + vertexList = geoModel->getPrimitivePoints( lineNode ); + + QVERIFY2( ((vertexList[0].x() == 500.5) && (vertexList[0].y() == 1000.5) && (vertexList[1].x() == 502.5) && (vertexList[1].y() == 1002.5)), "RotateNode failed." ); + + geoModel->endUndoBlock( QLatin1String("Move and scale primitive node") ); + +} // GeoModelUnitTest::checkMatrixTransform + + +void GeoModelUnitTest::checkMerge() +{ + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + if (geoModel == nullptr) return; + + geoModel->enableUndo( true ); + + QList vertexList; + QList> attrList; + + attrList += QPair(GeometryModel::Attribute::Color,QVariant(QColor("red"))); + + vertexList << QPointF(1, 1) << QPointF(2,2); + + const GeometryModel::Node & lineNode = geoModel->addPrimitive( GeometryModel::PrimitiveType::Line, vertexList, attrList, 42 ); + const GeometryModel::Node & group = geoModel->createGroup( GeometryModel::NodeList() ); + + geoModel->movePrimitiveIntoGroup( lineNode, group ); + + GeometryModel * geoModel2Merge = GeometryModelFactory::singleton()->create(); + if (geoModel2Merge == nullptr) return; + + geoModel2Merge->enableUndo( true ); + + geoModel2Merge->movePrimitiveIntoGroup( geoModel2Merge->addPrimitive( GeometryModel::PrimitiveType::Line, vertexList, attrList, 815 ) + , geoModel2Merge->createGroup( GeometryModel::NodeList() ) ); + + geoModel->merge( geoModel2Merge->getRootNode() ); + + // qDebug() << geoModel->toString(); + + QVERIFY2( geoModel->findPrimitive(42).isValid(), "Primitive node (id=42) is missing." ); + QVERIFY2( geoModel->findPrimitive(815).isValid(), "Primitive node (id=815) is missing." ); + +} // GeoModelUnitTest::checkMerge + + +void GeoModelUnitTest::checkDictionarySymbol() +{ + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + if (geoModel == nullptr) return; + + geoModel->enableUndo( true ); + + QList vertexList; + QList> attrList; + + attrList += QPair(GeometryModel::Attribute::SIDC,QVariant(QLatin1String("GFMPOGL---"))); + + vertexList << QPointF(0, 1) << QPointF(20,1) << QPointF(20,5); + geoModel->addPrimitive( GeometryModel::PrimitiveType::DictionarySymbol, vertexList, attrList, 42 ); + + const GeometryModel::Node & symbolNode = geoModel->findPrimitive(42); + vertexList = geoModel->getPrimitivePoints( symbolNode ); + + // qDebug() << geoModel->toString(); + + QVERIFY2( symbolNode.isValid() , "DictionarySymbol node isn't valid." ); + const QString attr = geoModel->getAttribute(symbolNode, GeometryModel::Attribute::SIDC).toString(); + QVERIFY2( attr == QLatin1String("GFMPOGL---"), "Wrong SIDC at dictionary symbol." ); + QVERIFY2( vertexList.size() == 3 , "Wrong size of primitive point list." ); + + QVERIFY2(vertexList[0] == QPointF( 0, 1), "Wrong coordinate." ); + QVERIFY2(vertexList[1] == QPointF(20, 1), "Wrong coordinate." ); + QVERIFY2(vertexList[2] == QPointF(20, 5), "Wrong coordinate." ); +} + +void GeoModelUnitTest::checkPixelTextBoxData() +{ + // toJsonStr: + SituationMap::PixelTextBoxData inData; + inData.m_htmlText = QStringLiteral("meinText");; + inData.m_backgroundColor = QColor(Qt::green); + inData.m_position = QPoint(11, 22); + inData.m_size = QSize(555, 666); + + QString jsonStr = inData.toJsonStr(); + qCritical() << " jsonStr: '" << jsonStr << "'" << " size=" << jsonStr.size(); + + QVERIFY2(jsonStr.size() == 184, "Error at pixelTextBoxToJson: wrong size "); + + // fromJsonStr: + { + PixelTextBoxData read = PixelTextBoxData::fromJsonStr(jsonStr); + QVERIFY2(read.m_htmlText == inData.m_htmlText , "Error at pixelTextBoxFromJson: htmlText "); + QVERIFY2(read.m_backgroundColor == inData.m_backgroundColor, "Error at pixelTextBoxFromJson: backgroundColor "); + QVERIFY2(read.m_position == inData.m_position, "Error at pixelTextBoxFromJson: position "); + QVERIFY2(read.m_size == inData.m_size, "Error at pixelTextBoxFromJson: m_size "); + + qDebug() << read.m_position; + qDebug() << read.m_size; + } +} + +void GeoModelUnitTest::checkPixelTextBoxDataCont() +{ + SituationMap::PixelTextBoxData inData1(QStringLiteral("meinText11"), QColor(Qt::red)); + SituationMap::PixelTextBoxData inData2(QStringLiteral("meinText22"), QColor(Qt::green)); + + QVector vec = {inData1, inData2}; + + // debug + const QJsonObject &jObj = PixelTextBoxData::pixelTextBoxContainerToJson(vec); + qDebug() << " pixelTextBoxContainerToJson =" << QJsonDocument(jObj).toJson().toStdString().c_str(); + + // toJson/fromJson: + { + QVector read = PixelTextBoxData::pixelTextBoxContainerFromJson(jObj); + qCritical() << " pixelTextBoxContainerFromJson: read.size() =" << read.size(); + QVERIFY2(read.size() == 2 , "Error at pixelTextBoxContainerFromJson: size"); + QVERIFY2(read.first() == inData1, "Error at pixelTextBoxContainerFromJson: inData1"); + } + + // toJson/fromJson STR: + { + const QString &strRef = PixelTextBoxData::pixelTextBoxContainerToJsonStr(vec, QJsonDocument::JsonFormat::Compact); + qCritical() << " pixelTextBoxContainerToJsonStr Compact: " << strRef; + { + QVector read = PixelTextBoxData::pixelTextBoxContainerFromJsonStr(strRef); + qCritical() << " pixelTextBoxContainerFromJsonStr: read.size() =" << read.size(); + + QVERIFY2(read.size() == 2 , "Error at pixelTextBoxContainerFromJsonStr: size"); + QVERIFY2(read.first() == inData1, "Error at pixelTextBoxContainerFromJsonStr: inData1"); + } + } +} + +void GeoModelUnitTest::checkMetaData() +{ + // test GeometryModel::Attributes + // introduced for storing "MetaPixelBasedTextBoxes" (multiple): + + GeometryModel * geoModel = GeometryModelFactory::singleton()->create(); + if (geoModel == nullptr) return; + + geoModel->enableUndo(true); + + const QString &metaData = QStringLiteral("MyMetaDataString"); + + QVector attCont = + { + GeometryModel::Attribute::MetaDataTitle, + GeometryModel::Attribute::MetaDataAuthor, + GeometryModel::Attribute::MetaDataDate, + GeometryModel::Attribute::MetaDataDesciption, + }; + + for (auto att : attCont) + { + geoModel->setModelAttribute(att, metaData); + { + const QString get = geoModel->getModelAttribute(att).toString(); + QVERIFY2(get == metaData, "set get mismatch"); + } + geoModel->setModelAttribute(att, {}); + } + + SituationMap::GeometryModel::Attribute att; + { + att = GeometryModel::Attribute::MetaDataMinScale; + geoModel->setModelAttribute(att, 1234); + const int get = geoModel->getModelAttribute(att).toInt(); + QVERIFY2(get == 1234, "set get mismatch MetaDataMinScale"); + } + { + att = GeometryModel::Attribute::MetaDataMaxScale; + geoModel->setModelAttribute(att, 123456); + const int get = geoModel->getModelAttribute(att).toInt(); + QVERIFY2(get == 123456, "set get mismatch MetaDataMaxScale"); + } + + // GeometryModel::Attribute::MetaPixelBasedTextBoxes; + { + const QString dummyStrIn = QStringLiteral("setModelAttribute(GeometryModel::Attribute::MetaPixelBasedTextBoxes, dummyStrIn); + + { + const QString read = geoModel->getModelAttribute(GeometryModel::Attribute::MetaPixelBasedTextBoxes).toString(); + QVERIFY2(dummyStrIn == read, "Modelerror set/getPixelTextData"); + } + } +} + + +int main(int argc, char *argv[]) +{ + NGLCoreApplication app(argc, argv); + app.createLogger("GeometryModelUnitTest", "log4cxxGeometryModelUnitTest.props"); + + QSharedPointer tc = QSharedPointer( new GeoModelUnitTest() ); + int retCode = QTest::qExec(tc.data(), argc, argv); + + return retCode; +} // main(int argc, char *argv[]) diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.h new file mode 100755 index 000000000..36345d54c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/UnitTestsGeoModel/unit_test.h @@ -0,0 +1,66 @@ +/** + * @RDE_HEADER@ + * Unittest + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include +#include + +#include + + + +class CheckSignal : public QObject +{ + Q_OBJECT + + public: + bool isConnected(); + + signals: + void mySignal(); +}; // CheckSignal + + + +class GeoModelUnitTest : public QObject +{ + Q_OBJECT + + public: + + GeoModelUnitTest(); + ~GeoModelUnitTest() = default; + + private: + + SituationMap::LOGGER_T m_logger; + + private Q_SLOTS: + + void init(); + + void checkSignals1(); + void checkSignals2(); + void checkCoordinatePrecision(); + void createModelWithLine(); + void createModelWithGroup(); + void createAndDeletePrimitives(); + void createAndDeleteGroups(); + void checkUndo(); + void checkMatrixTransform(); + void checkMerge(); + void checkDictionarySymbol(); + + void checkPixelTextBoxData(); + void checkPixelTextBoxDataCont(); + void checkMetaData(); + + +}; // GeoModelUnitTest + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.cpp new file mode 100755 index 000000000..0f5e4f808 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.cpp @@ -0,0 +1,102 @@ +/** + * @RDE_HEADER@ + * MouseMoveOverlay + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#include "mouse_move_overlay.h" + +// Own +#include + +// ESRI +#include +#include +#include + +// Qt +#include +#include + + +namespace SituationMap +{ + +class MouseMoveOverlay::Private +{ +public: + Private() = default; + ~Private() = default; + + // ESRI data: + EsriRuntimeQt::Map *m_map = nullptr; + + // OWN data: + SituationMap::MapImpl *m_mapImpl = nullptr; + +}; // class MouseMoveOverlay::Private +//----------------------------------------------------------------------------- + + + + +MouseMoveOverlay::MouseMoveOverlay(MapImpl *mapImpl) + : QGraphicsItem() + , d(new Private) +{ + qDebug() << LOGFUNCTION; + + // object to be called: + d->m_mapImpl = mapImpl; + + if (d->m_mapImpl) + d->m_map = d->m_mapImpl->getEsriMap(); + + setAcceptHoverEvents(true); +} + +MouseMoveOverlay::~MouseMoveOverlay() +{ + qDebug() << LOGFUNCTION; + delete d; +} + +QRectF MouseMoveOverlay::boundingRect() const +{ + if (d->m_map) + return QRect(0, 0, d->m_map->width(), d->m_map->height()); + return QRect(); // fallback +} + +void MouseMoveOverlay::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + // No need to paint + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(widget) +} + + +void MouseMoveOverlay::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ +// qDebug() << LOGFUNCTION; + + if (d->m_map) + { + if (!d->m_map->isReady()) + return; + + QPointF scenePoint = event->scenePos(); + int scenePointX = scenePoint.x(); + int scenePointY = scenePoint.y(); + if (scenePointX < 0 || scenePointY < 0) + return; + + d->m_mapImpl->onHandleMouseMove(scenePointX, scenePointY); + } +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.h new file mode 100755 index 000000000..2cdc0d3dc --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/mouse_move_overlay.h @@ -0,0 +1,57 @@ +/** + * @RDE_HEADER@ + * MouseMoveOverlay + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + +#include + +namespace EsriRuntimeQt +{ +class MapGraphicsView; +} + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class MapImpl; + +class MouseMoveOverlay : public QGraphicsItem +{ + // Q_OBJECT +public: + + explicit MouseMoveOverlay(MapImpl *mapImpl); + virtual ~MouseMoveOverlay(); + +signals: + // void mousePositionChanged(const QPointF& screenPos, QPointF& mapPos); + // void mousePositionChanged(); + +protected: + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + +private : + + MouseMoveOverlay(const MouseMoveOverlay &other) = delete; + MouseMoveOverlay &operator=(const MouseMoveOverlay &other) = delete; + + class Private; + /** + * The private d-pointer to not break BC when changing internals of this class + */ + Private *d = nullptr; +}; + + + +} // namespace SituationMap + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.cpp new file mode 100755 index 000000000..4ed27702e --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.cpp @@ -0,0 +1,268 @@ +/** + * @RDE_HEADER@ + * Class MovementTrack + * $Id$ + * $HeadURL$ + * $Author$ + */ + +// Own +#include "movement_track.h" + +#include + +// Qt +#include +#include +#include + + +namespace SituationMap +{ +int MovementTrack::m_count = 0; + +MovementTrack::MovementTrack(int trackId) +// : m_logger(getLogger("MovementTrack", trackId)) + : m_logger(getLogger("MovementTrack")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << " trackId/participantId=" << trackId + << " m_count=" << m_count; + ++m_count; + + m_trackId = trackId; + m_active = true; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_trackId=" << m_trackId << " m_count=" << m_count; +} + +MovementTrack::~MovementTrack() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_trackId=" << m_trackId << " m_count=" << m_count; + --m_count; + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_trackId=" << m_trackId << " m_count=" << m_count; +} + +void MovementTrack::setActive(bool active) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START " << " active=" << active << " m_count=" << m_count; + m_active = active; +} + +bool MovementTrack::addPosition(const COORD_T &newPosition, + qint64 currentTimeMsec, double minDistance, int maxNumberOfPositions) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " START" + << " currentTimeMsec=" << currentTimeMsec + << " newPosition=" << newPosition + << " #ofCoords=" << m_cont.size(); + bool success = false; + + // Very first access? + if (m_cont.isEmpty()) + { + m_cont.insert(currentTimeMsec, newPosition); + success = true; + } + else + { + // Avoid identical values: + const COORD_T &lastPosition = m_cont.last(); + + // isEmpty already checked: + if (lastPosition != newPosition) + { + const double distance = LtsCoordTrans::distance(lastPosition.x(), lastPosition.y(), + newPosition.x(), newPosition.y()); + DEBUG_LOG(m_logger) << LOGFUNCTION << " distance " << distance; + + if (distance > minDistance) + { + m_cont.insert(currentTimeMsec, newPosition); + success = true; + } + } // if (lastPosition != newPosition) + } + + if (!m_cont.isEmpty() && + (m_cont.size() > maxNumberOfPositions) && (maxNumberOfPositions > 0)) + { + // see QTBUG-59373 + m_cont.erase(m_cont.begin()); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END " << "success=" << success << " #ofCoords=" << m_cont.size(); + return success; +} + +bool MovementTrack::removeEntriesNewer(qint64 currentTimeMsec) +{ +// TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_active=" << m_active << " m_count=" << m_count; + + bool modified = false; + QMapIterator mapIt(m_cont); + mapIt.toBack(); + + while (mapIt.hasPrevious()) + { + mapIt.previous(); + if (mapIt.key() > currentTimeMsec) + { + m_cont.remove(mapIt.key()); + modified = true; + } + else + break; + } // while + +// TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_active=" << m_active << " m_count=" << m_count << " modified=" << modified; + return modified; +} + +bool MovementTrack::removeEntriesOlder(qint64 currentTimeMsec, qint64 maxAgeMsec) +{ +// TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_active=" << m_active << " m_count=" << m_count << " currentTimeMsec=" << currentTimeMsec << " maxAgeMsec=" << maxAgeMsec; + + bool modified = false; + auto it = m_cont.begin(); + while (it != m_cont.end()) + { + const KEY_T &ageMsec = currentTimeMsec - it.key(); + if (ageMsec > maxAgeMsec) + { + it = m_cont.erase(it); + modified = true; + } + else + ++it; + } // while + +// TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_active=" << m_active << " m_count=" << m_count << " modified=" << modified; + return modified; +} + +QVector MovementTrack::getTrack() const +{ + QVector ret; + ret.reserve(m_cont.size()); + for (const auto &pos : m_cont) + ret.push_back(pos); + return ret; +} + +QVector MovementTrack::getVisibleTrack(qint64 currentTimeMsec, + qint64 maxAgeMsec, + qint64 maxViewAgeMsec) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " m_trackId=" << m_trackId << " currentTimeMsec=" << currentTimeMsec << " maxAgeMsec=" << maxAgeMsec << " maxViewAgeMsec=" << maxViewAgeMsec; + + + removeEntriesOlder(currentTimeMsec, maxAgeMsec); + + // Auch wenn keine neuen Positionen eingegangen sind, + // hat sich die Spur evtl. wegen der modifizierten Zeit 'currentTimeMsec' + // geändert: + // uebertragen in validCoords + + QVector validCoords; + validCoords.reserve(m_cont.size()); + for (auto it = m_cont.cbegin(); it != m_cont.cend(); ++it) + { + const KEY_T &ageMsec = currentTimeMsec - it.key(); + if (ageMsec > maxViewAgeMsec && maxViewAgeMsec > 0) + continue; + + if (ageMsec < 0) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " age is negative!!! - " + << " currentTimeMsec=" << currentTimeMsec + << " m_trackId=" << m_trackId + << " ageMsec=" << ageMsec + << " - " + << " size = " << m_cont.size() + << ", dis = " << std::distance(m_cont.cbegin(), it); + if (ageMsec < -1500) + WARNING_LOG(m_logger) << LOGFUNCTION << " age is negative!!! - " + << " currentTimeMsec=" << currentTimeMsec + << " m_trackId=" << m_trackId + << " ageMsec=" << ageMsec + << " - " + << " size = " << m_cont.size() + << ", dis = " << std::distance(m_cont.cbegin(), it); + + } + + DEBUG_LOG(m_logger) << LOGFUNCTION << " m_trackId=" << m_trackId << " ageMsec=" << ageMsec << " coord=" << it.value(); + validCoords.push_back(it.value()); + } // for + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " validCoords.size()/m_cont.size(): " << validCoords.size() << " / " << m_cont.size(); + + return validCoords; +} + +QSharedPointer MovementTrack::read(const QJsonObject &json) +{ + if (!json.contains(QLatin1String("trackId"))) + return nullptr; + + int trackId = -1; + if (json.contains(QLatin1String("trackId"))) + trackId = json[QLatin1String("trackId")].toInt(); + + if (trackId < 0) + return nullptr; + + QSharedPointer mt = + QSharedPointer(new MovementTrack(trackId)); + + // active + if (json.contains(QLatin1String("active"))) + mt->setActive(json[QLatin1String("active")].toBool()); + + // coordinates + QJsonArray jArray = json[QLatin1String("coordinates")].toArray(); + for (int idx = 0; idx < jArray.size(); ++idx) + { + const QJsonObject &objTemp = jArray[idx].toObject(); + const KEY_T &timeMsec = objTemp[QLatin1String("pointInTimeMsec")].toDouble(); // toDouble!!! + + Point3D p3D; + p3D.rx() = objTemp[QLatin1String("loc_x_lon")].toDouble(); + p3D.ry() = objTemp[QLatin1String("loc_y_lat")].toDouble(); + if (json.contains(QLatin1String("loc_z"))) + p3D.rz() = objTemp[QLatin1String("loc_z")].toDouble(); + mt->addPosition(p3D, timeMsec, 0, 0); + } // for + + return mt; +} + +QJsonObject MovementTrack::toJsonObject() const +{ + QJsonObject json; + + json[QLatin1String("trackId")] = m_trackId; + json[QLatin1String("active")] = m_active; + + QJsonArray jArray; + QMapIterator it(m_cont); + while (it.hasNext()) + { + it.next(); + const KEY_T &key = it.key(); + const COORD_T &coord = it.value(); + + QJsonObject jsonCoord; + jsonCoord[QLatin1String("loc_x_lon")] = coord.x(); + jsonCoord[QLatin1String("loc_y_lat")] = coord.y(); + jsonCoord[QLatin1String("loc_z")] = coord.z(); + jsonCoord[QLatin1String("pointInTimeMsec")] = key; + jArray.append(jsonCoord); + } + json[QLatin1String("coordinates")] = jArray; + return json; +} + + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.h new file mode 100755 index 000000000..a3566b0b8 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track.h @@ -0,0 +1,85 @@ +/** + * @RDE_HEADER@ + * Class MovementTrack + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + + +// Own +#include +#include +#include + + +// Qt +#include +#include + + +class QJsonObject; + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SITUATIONMAP_EXPORT MovementTrack +{ + +public: + + using KEY_T = qint64; // msec + using COORD_T = Point3D; // lon, lat + using CONT_T = QMap; // ordered by time [msec] + +public: + /// Constructor + explicit MovementTrack(int trackId); + + /// Destructor + virtual ~MovementTrack(); + + int trackId() const { return m_trackId; } + int size() const { return m_cont.size(); } + void clear() { m_cont.clear(); } + + void setActive(bool active); + bool isActive() const { return m_active; } + + bool addPosition(const COORD_T &newPosition, + qint64 currentTimeMsec, double minDistance, int maxNumberOfPositions); + + const COORD_T lastPosition() const + { + if (this->m_cont.empty()) + return COORD_T(); + return this->m_cont.last(); + } + + bool removeEntriesOlder(qint64 currentTimeMsec, qint64 maxAgeMsec); + bool removeEntriesNewer(qint64 currentTimeMsec); + + QVector getTrack() const; + QVector getVisibleTrack(qint64 currentTimeMsec, qint64 maxAgeMsec, qint64 maxViewAgeMsec); + + + static QSharedPointer read(const QJsonObject &json); + QJsonObject toJsonObject() const; + +protected: + static int m_count; + int m_trackId; // UniqueMapId + bool m_active; + + CONT_T m_cont; + + +private: + LOGGER_T m_logger; ///< Interface to the Logging-Facility +}; + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.cpp new file mode 100755 index 000000000..7fd79cfe1 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.cpp @@ -0,0 +1,338 @@ +/** + * @RDE_HEADER@ + * Class MovementTrackManager + * $Id$ + * $HeadURL$ + * $Author$ + */ + + +// Own +#include "movement_track_manager.h" + + +// Qt +#include +#include +#include +#include + +namespace SituationMap +{ + +MovementTrackManager::MovementTrackManager(QObject *parent) + : BaseClass(parent) + , m_logger(getLogger("MovementTrackManager")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + init(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +MovementTrackManager::~MovementTrackManager() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " size=" << m_cont.size(); + m_cont.clear(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END "; +} + +void MovementTrackManager::init() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + m_cont.clear(); + m_currentTimeMsec = 0; + m_maxNumberOfTracks = 10; + m_maxNumberOfPointsPerTrack = 600; // 600 + + // min sec msec + m_maxAgeMsec = 30 * 60 * 1000; // (30Min * 60 * 1000= 1 800 000sec) + m_maxViewAgeMsec = 30 * 60 * 1000; // (30Min * 60 * 1000= 1 800 000sec) + + m_minDistance = 2; // 2 Meter + + // just for testing: + // m_maxNumberOfTracks = 3; + // m_maxAgeMsec = 6000 * 60 * 1000; + // m_maxViewAgeMsec = 6000 * 60 * 1000; + // m_maxNumberOfPointsPerTrack = 10; + // m_minDistance = 0; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QSet MovementTrackManager::setCurrentTimeMsec(qint64 timeMsec) +{ +// TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " timeMsec=" << timeMsec; + + QSet modifiedMapObjects; + + if (m_currentTimeMsec == timeMsec) + return modifiedMapObjects; + + // forward or backward? + bool forward = timeMsec > m_currentTimeMsec; + m_currentTimeMsec = timeMsec; + + + QVectorIterator vecIt(m_cont); + while (vecIt.hasNext()) + { + DATA_T item = vecIt.next(); + + if (!item) + continue; + + bool mod; + if (forward) + mod = item->removeEntriesOlder(timeMsec, m_maxAgeMsec); + else + mod = item->removeEntriesNewer(timeMsec); // set time backward -> remove all entries whose time is too new + + if (mod) + { + int trackId = item->trackId(); + DEBUG_LOG(m_logger) << LOGFUNCTION << " Track changes, simId/trackId=" << trackId + << " (m_maxAgeMsec=" << m_maxAgeMsec << ")" + << " (m_maxViewAgeMsec=" << m_maxViewAgeMsec << ")"; + modifiedMapObjects.insert(trackId); + } + } // while + +// TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " timeMsec=" << timeMsec << " modifiedMapObjects=" << modifiedMapObjects.size(); + return modifiedMapObjects; +} + + +bool MovementTrackManager::addTrack(int trackId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " size=" << m_cont.size() << " trackId=" << trackId; + + if (hasTrack(trackId)) + return false; + + m_cont.push_back(DATA_T(new MovementTrack(trackId))); + + if (!m_cont.isEmpty() && (m_cont.size() > m_maxNumberOfTracks)) + { + const int trackIdSave = m_cont.first()->trackId(); + m_cont.pop_front(); + emit trackRemoved(trackIdSave); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END " << " size=" << m_cont.size() << " trackId=" << trackId; + return true; +} + +MovementTrackManager::DATA_T MovementTrackManager::getTrack(int trackId) const +{ + QVectorIterator vecIt(m_cont); + while (vecIt.hasNext()) + { + DATA_T item = vecIt.next(); + + if (item && item->trackId() == trackId) + return item; + } // while + + return nullptr; +} + +void MovementTrackManager::removeTrack(int trackId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " size=" << m_cont.size() << " trackId=" << trackId; + + for (int idx = m_cont.size() - 1; idx >= 0; --idx) + { + const DATA_T &item = m_cont.at(idx); + if (item && item->trackId() == trackId) + { + m_cont.removeAt(idx); + emit trackRemoved(trackId); + } + } // for + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " size=" << m_cont.size() << " trackId=" << trackId; +} + +void MovementTrackManager::activateTrack(int trackId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " size=" << m_cont.size() << " trackId=" << trackId; + + // Eintrag finden + DATA_T track = getTrack(trackId); + + if (track) + { + if (!track->isActive()) + { + track->setActive(true); + emit trackActivityChanged(trackId, true); + } + } + else + { + addTrack(trackId); // new Track -> active + emit trackActivityChanged(trackId, true); + } + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " size=" << m_cont.size() << " trackId=" << trackId; +} + +void MovementTrackManager::deactivateTrack(int trackId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " size=" << m_cont.size() << " trackId=" << trackId; + + // 1. find track and save: + DATA_T savedTrack = getTrack(trackId); + if (!savedTrack) + return; + + if (!savedTrack->isActive()) // is already deactivated + return; + + // 2. remove track: + for (int idx = m_cont.size() - 1; idx >= 0; --idx) + { + const DATA_T &item = m_cont.at(idx); + if (item && item->trackId() == trackId) + m_cont.removeAt(idx); + } // for + + // 3. re-add savedTrack: + savedTrack->setActive(false); + m_cont.push_front(savedTrack); // front -> remove candidate / Guez: was push_back!? + + emit trackActivityChanged(trackId, false); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " size=" << m_cont.size() << " trackId=" << trackId; +} + +bool MovementTrackManager::addPosition(int trackId, const MovementTrack::COORD_T &coord, qint64 timeMsec) +{ + DATA_T track = getTrack(trackId); + if (track.isNull()) + return false; + return track->addPosition(coord, timeMsec, m_minDistance, m_maxNumberOfPointsPerTrack); +} + +PositionCont MovementTrackManager::activeAndVisibleTrack(int trackId) const +{ + DATA_T track = getTrack(trackId); + if (!track || !track->isActive()) + return PositionCont(); + + return track->getVisibleTrack(m_currentTimeMsec, m_maxAgeMsec, m_maxViewAgeMsec); +} + +void MovementTrackManager::emit_activeSimIdsChanged(const QVector &activeSimIds) +{ + emit activeSimIdsChanged(activeSimIds); +} + +bool MovementTrackManager::read(const QJsonObject &json) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + this->init(); + + if (json.contains(QLatin1String("currentTimeMsec"))) + this->setCurrentTimeMsec(json[QLatin1String("currentTimeMsec")].toDouble()); // qint64 -> toDouble + if (json.contains(QLatin1String("maxAgeMsec"))) + this->setMaxAgeMsec(json[QLatin1String("maxAgeMsec")].toDouble()); // qint64 -> toDouble + if (json.contains(QLatin1String("maxViewAgeMsec"))) + this->setMaxViewAgeMsec(json[QLatin1String("maxViewAgeMsec")].toDouble()); // qint64 -> toDouble + + if (json.contains(QLatin1String("minDistance"))) + this->setMinDistance(json[QLatin1String("minDistance")].toInt()); + if (json.contains(QLatin1String("maxNumberOfTracks"))) + this->setMaxNumberOfTracks(json[QLatin1String("maxNumberOfTracks")].toInt()); + if (json.contains(QLatin1String("maxNumberOfPointsPerTrack"))) + this->setMaxNumberOfPointsPerTrack(json[QLatin1String("maxNumberOfPointsPerTrack")].toInt()); + + + // coords + const QJsonArray &jArray = json[QLatin1String("movementTracks")].toArray(); + + DEBUG_LOG(m_logger) << LOGFUNCTION << " movementTrack QJsonArray size=" << jArray.size(); + + for (int idx = 0; idx < jArray.size(); ++idx) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " movementTrack QJsonArray, idx=" << idx; + + const QJsonObject &objTemp = jArray[idx].toObject(); + const QSharedPointer &mt = MovementTrack::read(objTemp); + if (!mt) + continue; + if (mt->trackId() >= 0) + m_cont.append(mt); + + } // for + + // send signals for all tracks: + for (const QSharedPointer &mt : std::as_const(m_cont)) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " emit trackActivityChanged," + << " mt->trackId()=" << mt->trackId() + << " mt->isActive()=" << mt->isActive(); + emit trackActivityChanged(mt->trackId(), mt->isActive()); + } + + return true; +} + +QJsonObject MovementTrackManager::toJsonObject() const +{ + QJsonObject json; + + json[QLatin1String("currentTimeMsec")] = m_currentTimeMsec; + json[QLatin1String("maxAgeMsec")] = m_maxAgeMsec; + json[QLatin1String("maxViewAgeMsec")] = m_maxViewAgeMsec; + json[QLatin1String("minDistance")] = m_minDistance; + json[QLatin1String("maxNumberOfTracks")] = m_maxNumberOfTracks; + json[QLatin1String("maxNumberOfPointsPerTrack")] = m_maxNumberOfPointsPerTrack; + + QJsonArray jArray; + for (const auto &item : m_cont) + { + if (!item) + continue; + QJsonObject objTemp = item->toJsonObject(); + jArray.append(objTemp); + } + json[QLatin1String("movementTracks")] = jArray; + return json; +} + +QByteArray MovementTrackManager::toJson() const +{ + // final - toJson + QJsonDocument jDoc(toJsonObject()); + return jDoc.toJson(); +} + +QVector MovementTrackManager::activeTrackIds() const +{ + QVector trackIds; + trackIds.reserve(m_cont.size()); + + for (const auto &item : m_cont) + { + if (item && item->isActive()) + trackIds.append(item->trackId()); + } + return trackIds; +} + +QVector MovementTrackManager::deactiveTrackIds() const +{ + QVector deactiveTrackIds; + deactiveTrackIds.reserve(m_cont.size()); + + for (const auto &item : m_cont) + { + if (item && !item->isActive()) + deactiveTrackIds.append(item->trackId()); + } + return deactiveTrackIds; +} + +} // namespace GDW_CTC diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.h new file mode 100755 index 000000000..10363e755 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/movement_track_manager.h @@ -0,0 +1,114 @@ +/** + * @RDE_HEADER@ + * Class MovementTrackManager + * $Id$ + * $HeadURL$ + * $Author$ + */ + +#pragma once + + +// Own +#include "movement_track.h" +#include + + +// Qt +#include +#include +#include +#include + + +//---------------------------------------------------------------------------- +namespace SituationMap +{ + +class SITUATIONMAP_EXPORT MovementTrackManager : public QObject +{ + Q_OBJECT + +public: + using COORD_T = MovementTrack::COORD_T; + using BaseClass = QObject; + + /// Constructor + explicit MovementTrackManager(QObject *parent = nullptr); + + /// Destructor + virtual ~MovementTrackManager(); + + void init(); + + qint64 currentTimeMsec() { return m_currentTimeMsec; } + QSet setCurrentTimeMsec(qint64 timeMsec); + + qint64 maxAgeMsec() { return m_maxAgeMsec; } + void setMaxAgeMsec(qint64 timeMsec) { m_maxAgeMsec = timeMsec; } + + qint64 maxViewAgeMsec() { return m_maxViewAgeMsec; } + void setMaxViewAgeMsec(qint64 maxViewAgeMsec) { m_maxViewAgeMsec = maxViewAgeMsec; } + + int maxNumberOfTracks() const { return m_maxNumberOfTracks; } + void setMaxNumberOfTracks(int maxNumberOfTracks) { m_maxNumberOfTracks = maxNumberOfTracks; } + + int maxNumberOfPointsPerTrack() const { return m_maxNumberOfPointsPerTrack; } + void setMaxNumberOfPointsPerTrack(int maxPoints) { m_maxNumberOfPointsPerTrack = maxPoints; } + + int minDistance() const { return m_minDistance; } + void setMinDistance(int minDistance) { m_minDistance = minDistance; } + + + + bool addPosition(int trackId, const COORD_T &coord, qint64 timeMsec); // if track exists + void removeTrack(int trackId); + + void activateTrack(int trackId); + void deactivateTrack(int trackId); + + bool hasTrack(int trackId) const { return static_cast(getTrack(trackId)); } + int size() const { return m_cont.size(); } + + QVector activeTrackIds() const; + QVector deactiveTrackIds() const; + PositionCont activeAndVisibleTrack(int trackId) const; + + void emit_activeSimIdsChanged(const QVector &activeSimIds); + + bool read(const QJsonObject &json); + QJsonObject toJsonObject() const; + QByteArray toJson() const; + + +Q_SIGNALS: + void trackActivityChanged(int trackId, bool active); + void trackRemoved(int trackId); + + void activeSimIdsChanged(const QVector &activeSimIds); + +private: + bool addTrack(int trackId); + + using DATA_T = QSharedPointer; + using CONT_T = QVector; + + DATA_T getTrack(int trackId) const; + + +protected: + CONT_T m_cont; + + qint64 m_currentTimeMsec; + qint64 m_maxAgeMsec; + qint64 m_maxViewAgeMsec; + int m_minDistance; + int m_maxNumberOfTracks; + int m_maxNumberOfPointsPerTrack; + + +private: + LOGGER_T m_logger; ///< Interface to the Logging-Facility +}; + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.cpp b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.cpp new file mode 100755 index 000000000..dc2e4fd09 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.cpp @@ -0,0 +1,75 @@ +/** + * @RDE_HEADER@ + * TerrainProfileDialog/ Höhendialog + * $Id: zwmsui_TerrainProfileDialog.cpp 21168 2014-10-27 07:35:54Z ehrlicher $ + * $HeadURL: svn://slssvn/TDS/branches/product/devel/vor_cleanup_zw_zs/ZW/ZWMS/ZWMSUI/zwmsui_TerrainProfileDialog.cpp $ + * $Author: ehrlicher $ + */ + +#include "terrain_profile_dialog.h" +#include "ui_terrain_profile_dialog.h" + +//#include +#include + +namespace SituationMap +{ +TerrainProfileDialog::TerrainProfileDialog(QWidget *parent, LtsWidgets::ButtonCombination buttonCombination) + : LtsWidgets::LtsDialog(parent, buttonCombination) + , m_ui(new Ui::TerrainProfile) + , m_logger(getLogger("TerrainProfileDialog")) +{ + m_ui->setupUi(this); + + connect(m_ui->heightPlotWidget, &LtsHeightPlotWidget::mousePositionChanged, this, &TerrainProfileDialog::onUpdatePosition); + + // SIGNAL-SIGNAL um die Informationen nach aussen zu tragen + connect(m_ui->heightPlotWidget, &LtsHeightPlotWidget::mousePositionChanged, this, &TerrainProfileDialog::mousePositionChanged); + connect(m_ui->heightPlotWidget, &LtsHeightPlotWidget::startLineOfSight, this, &TerrainProfileDialog::startLineOfSight); + connect(m_ui->heightPlotWidget, &LtsHeightPlotWidget::stopLineOfSight, this, &TerrainProfileDialog::stopLineOfSight); + + setOnlineHelpFilename(QStringLiteral("Situation-Start_Terrain_Profil")); +} + +TerrainProfileDialog::~TerrainProfileDialog() +{ + delete m_ui; +} + +void TerrainProfileDialog::setMap(Map *map) +{ + m_map = map; +} + +void TerrainProfileDialog::retranslate() +{ + translate(m_ui, QString(), false, tr("Terrain Profile")); +} + +void TerrainProfileDialog::setData(const QVector &data) +{ + m_ui->heightPlotWidget->setData(data); +} + +void TerrainProfileDialog::setMarks(const QVector &data) +{ + m_ui->heightPlotWidget->setMarks(data); +} + +void TerrainProfileDialog::setMarkerPen(const QPen &pen) +{ + m_ui->heightPlotWidget->setMarkerPen(pen); +} + +void TerrainProfileDialog::onUpdatePosition(const QPointF &position, double curHeight, double mouseHeight) +{ + QString str; // = LtsCoordTrans::UtmCoordinate::fromGeographic(position).mgrs(); + if (m_map) + str = m_map->mapToMgrsString(position); + + m_ui->lblPosition->setText(str); + m_ui->lblHeight->setText(QString::number(curHeight, 'f', 2)); + m_ui->lblHeightCursor->setText(QString::number(mouseHeight, 'f', 2)); +} + +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.h new file mode 100755 index 000000000..d9f6407e0 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.h @@ -0,0 +1,61 @@ +/** + * @RDE_HEADER@ + * TerrainProfileDialog/ Höhendialog + * $Id: zwmsui_TerrainProfileDialog.h 4471 2010-06-15 06:56:51Z ehrlicher $ + * $HeadURL: svn://slssvn/TDS/branches/product/devel/vor_cleanup_zw_zs/ZW/ZWMS/ZWMSUI/zwmsui_TerrainProfileDialog.h $ + * $Author: ehrlicher $ + */ + +#pragma once + +#include +#include +#include + +namespace Ui +{ +class TerrainProfile; +} + +namespace QtCore +{ +class QList; +class QPair; +class QPointF; +} + +namespace SituationMap +{ +class Map; + +class SITUATIONMAP_EXPORT TerrainProfileDialog final : public LtsWidgets::LtsDialog +{ + Q_OBJECT + +public: + using heightData = QPair; + +public: + explicit TerrainProfileDialog(QWidget *parent = nullptr, LtsWidgets::ButtonCombination buttonCombination = LtsWidgets::ButtonCombination::CloseOnly); + ~TerrainProfileDialog(); + void setMap(Map *map); + + void setData(const QVector &data); + void setMarks(const QVector &data); + void setMarkerPen(const QPen &pen); + void retranslate() override; + +signals: + void mousePositionChanged(const QPointF &curPoint); + void startLineOfSight(const QPointF &startPoint); + void stopLineOfSight(); + +protected slots: + void onUpdatePosition(const QPointF &position, double curHeight, double mouseHeight); + +private: + Ui::TerrainProfile *m_ui; + Map *m_map = nullptr; + LOGGER_T m_logger; +}; +} // namespace SituationMap diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.ui b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.ui new file mode 100755 index 000000000..158f3bd77 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/Utils/terrain_profile_dialog.ui @@ -0,0 +1,111 @@ + + + TerrainProfile + + + + 0 + 0 + 500 + 300 + + + + Terrain Profile + + + + + + + + Position: + + + + + + + XXXXX YYYYY + + + + + + + Qt::Horizontal + + + + 17 + 20 + + + + + + + + Height: + + + + + + + xxxx m + + + + + + + Qt::Horizontal + + + + 17 + 20 + + + + + + + + Cursor height: + + + + + + + xxxx m + + + + + + + + + + 0 + 0 + + + + + + + + + LtsHeightPlotWidget + QWidget +

    LtsWidgets/lts_height_plot_widget.h
    + 1 + + + + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/CMakeLists.txt b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/CMakeLists.txt new file mode 100755 index 000000000..38c3b0836 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# FILE ctc_check_situation_map.sh +configure_file(ctc_check_situation_map.sh ${CMAKE_BINARY_DIR}/admin_scripts/ctc_check_situation_map.sh COPYONLY USE_SOURCE_PERMISSIONS) +NGL_INSTALL(WS FILES ctc_check_situation_map.sh DESTINATION ./admin_scripts) +NGL_INSTALL(WS FILES ctc_operator_app_removal_tool.sh DESTINATION ./admin_scripts) + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_check_situation_map.sh b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_check_situation_map.sh new file mode 100755 index 000000000..93c6474d3 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_check_situation_map.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# Helper script to check presence of some rpms +# $Id$ +# $HeadURL$ +# $Author$ + + +RPMS="" + +# Stand 2022/09: + +# Bremen +RPMS="${RPMS} ctc_arcgis-100.8" +RPMS="${RPMS} legatus_basemap_bremen_osm_32632" +RPMS="${RPMS} legatus_basemap_bremen_osm_WebMerc" +RPMS="${RPMS} legatus_basemap_bremen_hemelingen_aerial_32632" +RPMS="${RPMS} legatus_basemap_bremen_hemelingen_aerial_WebMerc" +RPMS="${RPMS} ctc_arcgis_elevation_map_bremen" +RPMS="${RPMS} ctc_arcgis_BT_56" + +# GUEZ +RPMS="${RPMS} guez_basemap_topo2015_32632" +RPMS="${RPMS} guez_basemap_topo2015_WebMerc" +RPMS="${RPMS} guez_basemap_aerial2017_32632" +RPMS="${RPMS} guez_basemap_aerial2017_WebMerc" +RPMS="${RPMS} guez_basemap_schnoeggersburg_drone_32632" +RPMS="${RPMS} guez_basemap_schnoeggersburg_drone_WebMerc" +RPMS="${RPMS} guez_arcgis_elevation_map_letzlingen" +RPMS="${RPMS} guez_arcgis_bldgs" +RPMS="${RPMS} guez_arcgis_3Dmodels" + +###RPMS="${RPMS} DoeNotExt" + + +numOk=0; +numFailure=0; + +echo "---one moment please...."; +rpm -qa > /tmp/allRpms.txt + +for RPM in ${RPMS} +do + #echo "------------------------------------"; + #echo " RPM = '$RPM' "; + grep $RPM /tmp/allRpms.txt > /dev/null + if [ $? -eq 0 ]; + then + echo " RPM seems to be installed: '$RPM'"; + ((numOk=numOk+1)) + else + echo "-> missing -> RPM '$RPM' "; + ((numFailure=numFailure+1)) + fi +done + +echo "------------------------------------"; +echo " installed = $numOk "; +echo " missing = $numFailure "; + +# cleanup +rm /tmp/allRpms.txt + + +echo "------------------------------------"; +echo "------------------------------------"; +echo "------------------------------------"; + +echo ""; +read -p "Press [Enter] key to continue" +echo ""; + + +echo "------------------------------------"; +zypper --repo RDE_15.0 se --details --installed-only ctc_ +zypper --repo RDE_15.0 se --details --installed-only legatus +zypper --repo RDE_15.0 se --details --installed-only guez + +echo "------------------------------------"; +zypper --repo RDE_15.0 se --not-installed-only ctc_ +zypper --repo RDE_15.0 se --not-installed-only legatus +zypper --repo RDE_15.0 se --not-installed-only guez + + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_find_getlogger.sh b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_find_getlogger.sh new file mode 100755 index 000000000..7058d100d --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_find_getlogger.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Helper script to check presence of some rpms +# $Id$ +# $HeadURL$ +# $Author$ + + +# Stand 2024 + + +echo "--- part 1"; + +# suche alle cpp file, mit 'getLogger' +#find . -iname "*.cpp" | xargs grep getLogger + +# ersetze a) getLogger durch @ b) entferne " c) entferne ) | nur das rechts vom @ +#find . -iname "*.cpp" | xargs grep getLogger | sed -e 's/getLogger(/@/g' -e 's/\"//g' -e 's/)//g' | cut -d "@" -f2 + +# sortiere und entferne doppelte: +#find . -iname "*.cpp" | xargs grep getLogger | sed -e 's/getLogger(/@/g' -e 's/\"//g' -e 's/)//g' | cut -d "@" -f2 | sort -u + +# prepend +find . -iname "*.cpp" | xargs grep getLogger | sed -e 's/getLogger(/@/g' -e 's/\"//g' -e 's/)//g' | cut -d "@" -f2 | sort -u | awk '{print "#log4j.logger.AES." $0 "=DEBUG"}' + + +echo "--- part 2"; + +find . -iname "*.cpp" | xargs grep ": BaseEdit" | sed -e 's/QLatin1String(/@/g' -e 's/QStringLiteral(/@/g' -e 's/\")/@/g' -e 's/\"//g' | cut -d "@" -f2 | sort -u | awk '{print "#log4j.logger.AES." $0 "=DEBUG"}' + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_operator_app_removal_tool.sh b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_operator_app_removal_tool.sh new file mode 100755 index 000000000..b5c12a12b --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/admin_scripts/ctc_operator_app_removal_tool.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Helper script to cleanup (temporary) files +# $Id$ +# $HeadURL$ +# $Author$ + + +# Stand 2023/06: + +# +# cleanup (temporary) files +# +# Buildingviewer: +# problems may arise due to different users crating the dir/files... +echo "------------------------------------"; +echo "cleanup /tmp/excon_bgviewer_symbols"; +rm -rf /tmp/excon_bgviewer_symbols + diff --git a/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/situation_map_export.h b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/situation_map_export.h new file mode 100755 index 000000000..d8e20d986 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/Lts/Viewer/SituationMap/situation_map_export.h @@ -0,0 +1,34 @@ +/** + * @RDE_HEADER@ + * Export definitions + * $Id$ + * $HeadURL$ + * $Author$ + */ +#pragma once + +#include + +#ifndef SITUATIONMAP_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define SITUATIONMAP_EXPORT +# else +# if defined(MAKE_SITUATIONMAP_LIB) +# define SITUATIONMAP_EXPORT Q_DECL_EXPORT +# else +# define SITUATIONMAP_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif + +#ifndef SITMAPGUIPLUGIN_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define SITMAPGUIPLUGIN_EXPORT +# else +# if defined(MAKE_SITMAPGUIPLUGIN_LIB) +# define SITMAPGUIPLUGIN_EXPORT Q_DECL_EXPORT +# else +# define SITMAPGUIPLUGIN_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif