├── .gitignore ├── LICENSE ├── README.md └── merge_map ├── config └── merge_map.rviz ├── launch └── merge_map_launch.py ├── merge_map ├── __init__.py └── merge_map.py ├── package.xml ├── resource └── merge_map ├── setup.cfg ├── setup.py └── test ├── test_copyright.py ├── test_flake8.py └── test_pep257.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Abdulkadir TÜRE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mapMergeForMultiRobotMapping-ROS2 2 | This ROS2 node subscribes to two different map messages, merges them, and 3 | publishes the merged map to the /merge_map topic. 4 | 5 | ## How does it work 6 | 7 | The Merge Map ROS2 package is designed to merge map messages from `/map1` and `/map2` topics, and publish the merged map to the `/merge_map` topic. To use this package, follow the instructions below: 8 | 9 | 1. Copy the `merge_map` folder to the `src` directory of your ROS2 workspace. 10 | 2. Run the following command: `ros2 launch merge_map merge_map_launch.py` This command starts the map merging node and opens an RViz2 window. 11 | 3. In the RViz2 window, you can observe the merged map. Whenever one of the maps being merged is updated, the `merge_map` node will also update the merged map accordingly. 12 | 13 | Make sure to have ROS2 installed and properly configured in your environment before using this package. For more information, please refer to the ROS2 documentation. 14 | 15 | 16 | ## launch.py customization 17 | 18 | The Merge Map ROS2 package can be customized and launched in your own `launch.py` file by adding the following code block. You can update `/yourMapName1` and `/yourMapName2` topics according to your project: 19 | 20 | ``` 21 | Node( 22 | package='merge_map', 23 | executable='merge_map', 24 | output='screen', 25 | parameters=[{'use_sim_time': True}], 26 | remappings=[ 27 | ("/map1", "/yourMapName1"), 28 | ("/map2", "/yourMapName2"), 29 | ], 30 | ), 31 | ``` 32 | 33 | ## Example 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /merge_map/config/merge_map.rviz: -------------------------------------------------------------------------------- 1 | Panels: 2 | - Class: rviz_common/Displays 3 | Help Height: 70 4 | Name: Displays 5 | Property Tree Widget: 6 | Expanded: 7 | - /Global Options1 8 | Splitter Ratio: 0.5 9 | Tree Height: 70 10 | - Class: rviz_common/Selection 11 | Name: Selection 12 | - Class: rviz_common/Tool Properties 13 | Expanded: 14 | - /2D Goal Pose1 15 | - /Publish Point1 16 | Name: Tool Properties 17 | Splitter Ratio: 0.5886790156364441 18 | - Class: rviz_common/Views 19 | Expanded: 20 | - /Current View1 21 | Name: Views 22 | Splitter Ratio: 0.5 23 | - Class: rviz_common/Time 24 | Experimental: false 25 | Name: Time 26 | SyncMode: 0 27 | SyncSource: "" 28 | Visualization Manager: 29 | Class: "" 30 | Displays: 31 | - Alpha: 0.5 32 | Cell Size: 1 33 | Class: rviz_default_plugins/Grid 34 | Color: 160; 160; 164 35 | Enabled: true 36 | Line Style: 37 | Line Width: 0.029999999329447746 38 | Value: Lines 39 | Name: Grid 40 | Normal Cell Count: 0 41 | Offset: 42 | X: 0 43 | Y: 0 44 | Z: 0 45 | Plane: XY 46 | Plane Cell Count: 10 47 | Reference Frame: 48 | Value: true 49 | - Alpha: 0.699999988079071 50 | Class: rviz_default_plugins/Map 51 | Color Scheme: map 52 | Draw Behind: false 53 | Enabled: true 54 | Name: Map 55 | Topic: 56 | Depth: 5 57 | Durability Policy: Volatile 58 | Filter size: 10 59 | History Policy: Keep Last 60 | Reliability Policy: Reliable 61 | Value: /merge_map 62 | Update Topic: 63 | Depth: 5 64 | Durability Policy: Volatile 65 | History Policy: Keep Last 66 | Reliability Policy: Reliable 67 | Value: /merge_map_updates 68 | Use Timestamp: false 69 | Value: true 70 | - Alpha: 1 71 | Buffer Length: 1 72 | Class: rviz_default_plugins/Path 73 | Color: 246; 211; 45 74 | Enabled: true 75 | Head Diameter: 0.30000001192092896 76 | Head Length: 0.20000000298023224 77 | Length: 0.30000001192092896 78 | Line Style: Billboards 79 | Line Width: 0.10000000149011612 80 | Name: Path 81 | Offset: 82 | X: 0 83 | Y: 0 84 | Z: 0 85 | Pose Color: 255; 85; 255 86 | Pose Style: None 87 | Radius: 0.029999999329447746 88 | Shaft Diameter: 0.10000000149011612 89 | Shaft Length: 0.10000000149011612 90 | Topic: 91 | Depth: 5 92 | Durability Policy: Volatile 93 | Filter size: 10 94 | History Policy: Keep Last 95 | Reliability Policy: Reliable 96 | Value: /tb3_1/visual_path 97 | Value: true 98 | - Alpha: 1 99 | Buffer Length: 1 100 | Class: rviz_default_plugins/Path 101 | Color: 25; 255; 0 102 | Enabled: true 103 | Head Diameter: 0.30000001192092896 104 | Head Length: 0.20000000298023224 105 | Length: 0.30000001192092896 106 | Line Style: Billboards 107 | Line Width: 0.10000000149011612 108 | Name: Path 109 | Offset: 110 | X: 0 111 | Y: 0 112 | Z: 0 113 | Pose Color: 255; 85; 255 114 | Pose Style: None 115 | Radius: 0.029999999329447746 116 | Shaft Diameter: 0.10000000149011612 117 | Shaft Length: 0.10000000149011612 118 | Topic: 119 | Depth: 5 120 | Durability Policy: Volatile 121 | Filter size: 10 122 | History Policy: Keep Last 123 | Reliability Policy: Reliable 124 | Value: /tb3_0/visual_path 125 | Value: true 126 | - Class: rviz_default_plugins/Image 127 | Enabled: true 128 | Max Value: 1 129 | Median window: 5 130 | Min Value: 0 131 | Name: Image 132 | Normalize Range: true 133 | Topic: 134 | Depth: 5 135 | Durability Policy: Volatile 136 | History Policy: Keep Last 137 | Reliability Policy: Reliable 138 | Value: /drone/bottom/image_raw 139 | Value: true 140 | Enabled: true 141 | Global Options: 142 | Background Color: 48; 48; 48 143 | Fixed Frame: merge_map 144 | Frame Rate: 30 145 | Name: root 146 | Tools: 147 | - Class: rviz_default_plugins/Interact 148 | Hide Inactive Objects: true 149 | - Class: rviz_default_plugins/MoveCamera 150 | - Class: rviz_default_plugins/Select 151 | - Class: rviz_default_plugins/FocusCamera 152 | - Class: rviz_default_plugins/Measure 153 | Line color: 128; 128; 0 154 | - Class: rviz_default_plugins/SetInitialPose 155 | Covariance x: 0.25 156 | Covariance y: 0.25 157 | Covariance yaw: 0.06853891909122467 158 | Topic: 159 | Depth: 5 160 | Durability Policy: Volatile 161 | History Policy: Keep Last 162 | Reliability Policy: Reliable 163 | Value: /initialpose 164 | - Class: rviz_default_plugins/SetGoal 165 | Topic: 166 | Depth: 5 167 | Durability Policy: Volatile 168 | History Policy: Keep Last 169 | Reliability Policy: Reliable 170 | Value: /goal_pose 171 | - Class: rviz_default_plugins/PublishPoint 172 | Single click: true 173 | Topic: 174 | Depth: 5 175 | Durability Policy: Volatile 176 | History Policy: Keep Last 177 | Reliability Policy: Reliable 178 | Value: /clicked_point 179 | Transformation: 180 | Current: 181 | Class: rviz_default_plugins/TF 182 | Value: true 183 | Views: 184 | Current: 185 | Class: rviz_default_plugins/Orbit 186 | Distance: 23.42510223388672 187 | Enable Stereo Rendering: 188 | Stereo Eye Separation: 0.05999999865889549 189 | Stereo Focal Distance: 1 190 | Swap Stereo Eyes: false 191 | Value: false 192 | Focal Point: 193 | X: 0.16153769195079803 194 | Y: 2.438749074935913 195 | Z: 0.00016973871970549226 196 | Focal Shape Fixed Size: true 197 | Focal Shape Size: 0.05000000074505806 198 | Invert Z Axis: false 199 | Name: Current View 200 | Near Clip Distance: 0.009999999776482582 201 | Pitch: 1.5697963237762451 202 | Target Frame: 203 | Value: Orbit (rviz) 204 | Yaw: 3.1404001712799072 205 | Saved: ~ 206 | Window Geometry: 207 | Displays: 208 | collapsed: false 209 | Height: 604 210 | Hide Left Dock: false 211 | Hide Right Dock: true 212 | Image: 213 | collapsed: false 214 | QMainWindow State: 000000ff00000000fd0000000400000000000001e5000001befc0200000009fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073000000003d000000c9000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000a0049006d006100670065010000003d000001be0000002800ffffff000000010000010f00000168fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d00000168000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000002fb0000003efc0100000002fb0000000800540069006d00650100000000000002fb000002fb00fffffffb0000000800540069006d0065010000000000000450000000000000000000000110000001be00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 215 | Selection: 216 | collapsed: false 217 | Time: 218 | collapsed: false 219 | Tool Properties: 220 | collapsed: false 221 | Views: 222 | collapsed: true 223 | Width: 763 224 | X: 603 225 | Y: 127 226 | -------------------------------------------------------------------------------- /merge_map/launch/merge_map_launch.py: -------------------------------------------------------------------------------- 1 | # Authors: Abdulkadir Ture 2 | # Github : abdulkadrtr 3 | 4 | import os 5 | 6 | from ament_index_python.packages import get_package_share_directory 7 | from launch import LaunchDescription 8 | from launch.actions import IncludeLaunchDescription 9 | from launch.launch_description_sources import PythonLaunchDescriptionSource 10 | from launch.substitutions import LaunchConfiguration 11 | from launch_ros.actions import Node 12 | 13 | def generate_launch_description(): 14 | rviz_file = os.path.join(get_package_share_directory('merge_map'), 'config', 'merge_map.rviz') 15 | return LaunchDescription([ 16 | Node( 17 | package='rviz2', 18 | executable='rviz2', 19 | name='rviz2', 20 | output='screen', 21 | arguments=['-d', rviz_file], 22 | parameters=[{'use_sim_time': True}] 23 | ), 24 | Node( 25 | package='merge_map', 26 | executable='merge_map', 27 | output='screen', 28 | parameters=[{'use_sim_time': True}] 29 | ), 30 | ]) 31 | -------------------------------------------------------------------------------- /merge_map/merge_map/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdulkadrtr/mapMergeForMultiRobotMapping-ROS2/321c0bd3182fc00e725569e2b4e07721f5e4d4cd/merge_map/merge_map/__init__.py -------------------------------------------------------------------------------- /merge_map/merge_map/merge_map.py: -------------------------------------------------------------------------------- 1 | # Authors: Abdulkadir Ture 2 | # Github : abdulkadrtr 3 | 4 | import rclpy 5 | from rclpy.node import Node 6 | from nav_msgs.msg import OccupancyGrid 7 | import numpy as np 8 | 9 | def merge_maps(map1, map2): 10 | merged_map = OccupancyGrid() 11 | merged_map.header = map1.header 12 | merged_map.header.frame_id = 'merge_map' 13 | min_x = min(map1.info.origin.position.x, map2.info.origin.position.x) 14 | min_y = min(map1.info.origin.position.y, map2.info.origin.position.y) 15 | max_x = max(map1.info.origin.position.x + (map1.info.width * map1.info.resolution), 16 | map2.info.origin.position.x + (map2.info.width * map2.info.resolution)) 17 | max_y = max(map1.info.origin.position.y + (map1.info.height * map1.info.resolution), 18 | map2.info.origin.position.y + (map2.info.height * map2.info.resolution)) 19 | merged_map.info.origin.position.x = min_x 20 | merged_map.info.origin.position.y = min_y 21 | merged_map.info.resolution = min(map1.info.resolution, map2.info.resolution) 22 | merged_map.info.width = int(np.ceil((max_x - min_x) / merged_map.info.resolution)) 23 | merged_map.info.height = int(np.ceil((max_y - min_y) / merged_map.info.resolution)) 24 | merged_map.data = [-1] * (merged_map.info.width * merged_map.info.height) 25 | for y in range(map1.info.height): 26 | for x in range(map1.info.width): 27 | i = x + y * map1.info.width 28 | merged_x = int(np.floor((map1.info.origin.position.x + x * map1.info.resolution - min_x) / merged_map.info.resolution)) 29 | merged_y = int(np.floor((map1.info.origin.position.y + y * map1.info.resolution - min_y) / merged_map.info.resolution)) 30 | merged_i = merged_x + merged_y * merged_map.info.width 31 | merged_map.data[merged_i] = map1.data[i] 32 | for y in range(map2.info.height): 33 | for x in range(map2.info.width): 34 | i = x + y * map2.info.width 35 | merged_x = int(np.floor((map2.info.origin.position.x + x * map2.info.resolution - min_x) / merged_map.info.resolution)) 36 | merged_y = int(np.floor((map2.info.origin.position.y + y * map2.info.resolution - min_y) / merged_map.info.resolution)) 37 | merged_i = merged_x + merged_y * merged_map.info.width 38 | if merged_map.data[merged_i] == -1: 39 | merged_map.data[merged_i] = map2.data[i] 40 | return merged_map 41 | 42 | class MergeMapNode(Node): 43 | def __init__(self): 44 | super().__init__('merge_map_node') 45 | self.publisher = self.create_publisher(OccupancyGrid, '/merge_map', 10) 46 | self.subscription = self.create_subscription(OccupancyGrid, '/map1', self.map1_callback, 10) 47 | self.subscription = self.create_subscription(OccupancyGrid, '/map2', self.map2_callback, 10) 48 | self.map1 = None 49 | self.map2 = None 50 | 51 | def map1_callback(self, msg): 52 | self.map1 = msg 53 | if self.map2 is not None: 54 | msg = merge_maps(self.map1, self.map2) 55 | self.publisher.publish(msg) 56 | 57 | def map2_callback(self, msg): 58 | self.map2 = msg 59 | if self.map1 is not None: 60 | msg = merge_maps(self.map1, self.map2) 61 | self.publisher.publish(msg) 62 | 63 | def main(args=None): 64 | rclpy.init(args=args) 65 | merge_map_node = MergeMapNode() 66 | rclpy.spin(merge_map_node) 67 | merge_map_node.destroy_node() 68 | rclpy.shutdown() 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /merge_map/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | merge_map 5 | 0.0.0 6 | TODO: Package description 7 | abd 8 | TODO: License declaration 9 | 10 | ament_copyright 11 | ament_flake8 12 | ament_pep257 13 | python3-pytest 14 | 15 | 16 | ament_python 17 | rclpy 18 | nav_msgs 19 | geometry_msgs 20 | sensor_msgs 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /merge_map/resource/merge_map: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdulkadrtr/mapMergeForMultiRobotMapping-ROS2/321c0bd3182fc00e725569e2b4e07721f5e4d4cd/merge_map/resource/merge_map -------------------------------------------------------------------------------- /merge_map/setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script_dir=$base/lib/merge_map 3 | [install] 4 | install_scripts=$base/lib/merge_map 5 | -------------------------------------------------------------------------------- /merge_map/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | from setuptools import setup 4 | 5 | package_name = 'merge_map' 6 | 7 | setup( 8 | name=package_name, 9 | version='0.0.0', 10 | packages=[package_name], 11 | data_files=[ 12 | ('share/ament_index/resource_index/packages', 13 | ['resource/' + package_name]), 14 | ('share/' + package_name, ['package.xml']), 15 | (os.path.join('share', package_name, 'launch'), glob('launch/*.py')), 16 | (os.path.join('share', package_name, 'config'), glob('config/*')), 17 | ], 18 | install_requires=['setuptools'], 19 | zip_safe=True, 20 | maintainer='abdulkadrtr', 21 | maintainer_email='abdulkadirthe@gmail.com', 22 | description='ROS2 Merge_Map package', 23 | license='TODO: License declaration', 24 | tests_require=['pytest'], 25 | entry_points={ 26 | 'console_scripts': [ 27 | 'merge_map = merge_map.merge_map:main' 28 | ], 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /merge_map/test/test_copyright.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_copyright.main import main 16 | import pytest 17 | 18 | 19 | # Remove the `skip` decorator once the source file(s) have a copyright header 20 | @pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') 21 | @pytest.mark.copyright 22 | @pytest.mark.linter 23 | def test_copyright(): 24 | rc = main(argv=['.', 'test']) 25 | assert rc == 0, 'Found errors' 26 | -------------------------------------------------------------------------------- /merge_map/test/test_flake8.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_flake8.main import main_with_errors 16 | import pytest 17 | 18 | 19 | @pytest.mark.flake8 20 | @pytest.mark.linter 21 | def test_flake8(): 22 | rc, errors = main_with_errors(argv=[]) 23 | assert rc == 0, \ 24 | 'Found %d code style errors / warnings:\n' % len(errors) + \ 25 | '\n'.join(errors) 26 | -------------------------------------------------------------------------------- /merge_map/test/test_pep257.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_pep257.main import main 16 | import pytest 17 | 18 | 19 | @pytest.mark.linter 20 | @pytest.mark.pep257 21 | def test_pep257(): 22 | rc = main(argv=['.', 'test']) 23 | assert rc == 0, 'Found code style errors / warnings' 24 | --------------------------------------------------------------------------------