├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── example.py ├── examples ├── full_junction.py ├── highway_example.py ├── junction_trippel_twoway.py ├── junction_with_signals.py ├── lane_number_change_merge.py ├── multiple_geometries_one_road.py ├── multiple_geometries_one_road_with_objects.py ├── road_merge.py ├── road_merge_w_lane_merge.py ├── road_split.py ├── road_split_w_lane_split.py └── two_roads.py ├── html └── pyodrx │ ├── enumerations.html │ ├── exceptions.html │ ├── generators.html │ ├── geometry.html │ ├── helpers.html │ ├── index.html │ ├── lane.html │ ├── links.html │ ├── opendrive.html │ └── signals.html ├── pyodrx ├── __init__.py ├── enumerations.py ├── exceptions.py ├── generators.py ├── geometry.py ├── helpers.py ├── lane.py ├── links.py ├── opendrive.py └── signals_objects.py ├── requirements.txt ├── setup.py ├── tests ├── test_geometry.py ├── test_lane.py ├── test_links.py ├── test_opendrive.py └── test_signals_objects.py └── xodr_coverage.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | venv/* 4 | requirements.txt 5 | .idea/* 6 | pyodrx.egg-info/* 7 | *.xodr -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyodrx 2 | 3 | This project has been absorbed into the [scenariogeneration](https://github.com/pyoscx/scenariogeneration) project. 4 | 5 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyoscx/pyodrx/a65407e0fe597131970ec70dfd0fd14c41e70fb7/__init__.py -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import numpy as np 3 | import os 4 | 5 | line1 = pyodrx.Line(100) 6 | 7 | arc1 = pyodrx.Arc(0.05,angle=np.pi/2) 8 | line2 = pyodrx.Line(100) 9 | arc2 = pyodrx.Arc(-0.05,angle=3*np.pi/4) 10 | 11 | cloth = pyodrx.Spiral(0.001,0.009,100) 12 | 13 | planview = pyodrx.PlanView() 14 | 15 | 16 | planview.add_geometry(line1) 17 | 18 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 19 | 20 | 21 | lane1 = pyodrx.Lane(a=2) 22 | lane1.add_roadmark(rm) 23 | lanesec = pyodrx.LaneSection(0,lane1) 24 | 25 | lane2 = pyodrx.Lane(a=4) 26 | lane2.add_roadmark(rm) 27 | lane3 = pyodrx.Lane(a=3) 28 | lane3.add_roadmark(rm) 29 | lane4 = pyodrx.Lane(a=3) 30 | lane4.add_roadmark(rm) 31 | lane5 = pyodrx.Lane(a=3) 32 | lane5.add_roadmark(rm) 33 | 34 | lanesec.add_left_lane(lane2) 35 | lanesec.add_left_lane(lane3) 36 | lanesec.add_right_lane(lane4) 37 | lanesec.add_right_lane(lane5) 38 | 39 | lanes = pyodrx.Lanes() 40 | lanes.add_lanesection(lanesec) 41 | 42 | 43 | road = pyodrx.Road(1,planview,lanes) 44 | 45 | odr = pyodrx.OpenDrive('myroad') 46 | 47 | odr.add_road(road) 48 | 49 | odr.adjust_roads_and_lanes() 50 | 51 | pyodrx.run_road(odr,os.path.join('..','esmini')) -------------------------------------------------------------------------------- /examples/full_junction.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | 4 | import pyodrx 5 | 6 | 7 | 8 | 9 | roads = [] 10 | numintersections = 4 # 3 or 4 11 | angles = [] 12 | for i in range(numintersections): 13 | roads.append(pyodrx.create_straight_road(i)) 14 | # use this instead to change the number of lanes in the crossing 15 | #roads.append(pyodrx.generators.create_straight_road(i, length=100, junction=-1, n_lanes=2, lane_offset=3)) 16 | angles.append(i * 2*np.pi/numintersections) 17 | 18 | # use this for a T-crossing instead 19 | # angles = [0,np.pi/2, 3*np.pi/2] 20 | 21 | print(roads) 22 | junc = pyodrx.create_junction_roads(roads,angles,8) 23 | 24 | odr = pyodrx.OpenDrive('myroad') 25 | junction = pyodrx.create_junction(junc,1,roads) 26 | 27 | odr.add_junction(junction) 28 | for r in roads: 29 | odr.add_road(r) 30 | for j in junc: 31 | odr.add_road(j) 32 | 33 | odr.adjust_roads_and_lanes() 34 | 35 | # write the OpenDRIVE file as xodr using current script name 36 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 37 | 38 | # uncomment the following line to display the road using esmini 39 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/highway_example.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import os 3 | 4 | # create some simple roads 5 | roads= [] 6 | # start road 7 | roads.append(pyodrx.create_road([pyodrx.Spiral(-0.004,0.00001,100), pyodrx.Spiral(0.00001,0.005,50), pyodrx.Arc(0.005,50)],id =0,left_lanes=3,right_lanes=4)) 8 | # intermittent road 9 | roads.append(pyodrx.create_road([pyodrx.Spiral(0.0001,0.003,65), pyodrx.Spiral(0.003,0.00001,50) ],id =1,left_lanes=3,right_lanes=3)) 10 | 11 | 12 | #exit road 13 | roads.append(pyodrx.create_road(pyodrx.Line(50),id =2,left_lanes=0,right_lanes=1)) 14 | # junctions for exit 15 | roads.append(pyodrx.create_road(pyodrx.Spiral(0.005,0.0001,50),id =3,left_lanes=3,right_lanes=3,road_type=1)) # continue 16 | roads.append(pyodrx.create_road(pyodrx.Spiral(0.005,-0.02,100),id =4,left_lanes=0,right_lanes=1,road_type=1)) # exit 17 | 18 | # final road 19 | roads.append(pyodrx.create_road([pyodrx.Spiral(-0.00001,-0.003,45),pyodrx.Arc(-0.003,60)],id =5,left_lanes=2,right_lanes=3)) 20 | 21 | # entry junction 22 | roads.append(pyodrx.create_road([pyodrx.Line(30) ],id =6,left_lanes=2,right_lanes=3,road_type=2)) # continue 23 | roads.append(pyodrx.create_road([pyodrx.Spiral(0.004,0.000001,50) ],id =7,left_lanes=1,right_lanes=0,road_type=2)) # entry 24 | 25 | # entry road 26 | roads.append(pyodrx.create_road(pyodrx.Arc(0.004,60),id =8,left_lanes=1,right_lanes=0)) 27 | 28 | 29 | # add predecessors and succesors to the non junction roads 30 | roads[0].add_successor(pyodrx.ElementType.junction,1) 31 | roads[1].add_predecessor(pyodrx.ElementType.junction,1) 32 | roads[1].add_successor(pyodrx.ElementType.junction,2) 33 | roads[2].add_predecessor(pyodrx.ElementType.junction,1) 34 | 35 | # add connections to the first junction road 36 | roads[3].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 37 | roads[3].add_successor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.start) 38 | 39 | # add connections to the second junction road, the exit 40 | roads[4].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end,lane_offset=-3) 41 | roads[4].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start) 42 | 43 | # add connections to the final road 44 | roads[5].add_predecessor(pyodrx.ElementType.junction,2) 45 | 46 | # add connections to the junctionroad that continues 47 | roads[6].add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.end) 48 | roads[6].add_successor(pyodrx.ElementType.road,5,pyodrx.ContactPoint.start) 49 | 50 | # add connections to the entry junction road 51 | roads[7].add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.end,lane_offset=2) 52 | roads[7].add_successor(pyodrx.ElementType.road,8,pyodrx.ContactPoint.start) 53 | 54 | # add connection to the entry road 55 | roads[8].add_predecessor(pyodrx.ElementType.junction,2) 56 | 57 | # create the junction struct 58 | exit_junction = pyodrx.create_junction(roads[3:5],1,roads[0:3]) 59 | entry_junction = pyodrx.create_junction(roads[6:8],2,[roads[x] for x in [1,5,8]]) 60 | # create the opendrive 61 | odr = pyodrx.OpenDrive('myroad') 62 | for r in roads: 63 | odr.add_road(r) 64 | odr.adjust_roads_and_lanes() 65 | odr.add_junction(exit_junction) 66 | odr.add_junction(entry_junction) 67 | 68 | # write the OpenDRIVE file as xodr using current script name 69 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 70 | 71 | # uncomment the following line to display the road using esmini 72 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/junction_trippel_twoway.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import numpy as np 3 | import os 4 | 5 | 6 | 7 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2) 8 | 9 | # create geometries 10 | 11 | geoms = [] 12 | geoms.append(pyodrx.Line(100)) 13 | geoms.append(pyodrx.Spiral(0.001,0.019,30)) 14 | geoms.append(pyodrx.Line(100)) 15 | geoms.append(pyodrx.Spiral(-0.001,-0.1,30)) 16 | geoms.append(pyodrx.Line(100)) 17 | geoms.append(pyodrx.Line(20)) 18 | geoms.append(pyodrx.Line(100)) 19 | numberofroads = len(geoms) 20 | 21 | # create planviews 22 | planviews = [] 23 | for g in geoms: 24 | pv = pyodrx.PlanView() 25 | pv.add_geometry(g) 26 | planviews.append(pv) 27 | 28 | 29 | 30 | # create centerlanes 31 | lanecenters = [] 32 | for i in range(numberofroads): 33 | lc = pyodrx.Lane(a=3) 34 | lc.add_roadmark(rm) 35 | lanecenters.append(lc) 36 | 37 | # create lanes 38 | rightlanes = [] 39 | leftlanes = [] 40 | for i in range(numberofroads): 41 | right = pyodrx.Lane(a=3) 42 | right.add_roadmark(rm) 43 | rightlanes.append(right) 44 | left = pyodrx.Lane(a=3) 45 | left.add_roadmark(rm) 46 | leftlanes.append(left) 47 | 48 | # create lanesections 49 | lanesections = [] 50 | for i in range(numberofroads): 51 | lsec = pyodrx.LaneSection(0,lanecenters[i]) 52 | lsec.add_right_lane(rightlanes[i]) 53 | lsec.add_left_lane(leftlanes[i]) 54 | lanesections.append(lsec) 55 | 56 | ## create lanes 57 | lanes = [] 58 | for l in lanesections: 59 | lanes1 = pyodrx.Lanes() 60 | lanes1.add_lanesection(l) 61 | lanes.append(lanes1) 62 | 63 | 64 | # finally create the roads 65 | roads = [] 66 | roadtypes = [-1,1,-1,1,-1,1,-1] 67 | for i in range(numberofroads): 68 | roads.append(pyodrx.Road(i,planviews[i],lanes[i],road_type=roadtypes[i])) 69 | 70 | roads[0].add_successor(pyodrx.ElementType.junction,1) 71 | 72 | roads[1].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 73 | roads[1].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start) 74 | 75 | roads[2].add_predecessor(pyodrx.ElementType.junction,1) 76 | 77 | roads[3].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 78 | roads[3].add_successor(pyodrx.ElementType.road,4,pyodrx.ContactPoint.start) 79 | 80 | roads[4].add_predecessor(pyodrx.ElementType.junction,1) 81 | 82 | roads[5].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 83 | roads[5].add_successor(pyodrx.ElementType.road,6,pyodrx.ContactPoint.start) 84 | 85 | roads[6].add_predecessor(pyodrx.ElementType.junction,1) 86 | 87 | # create the opendrive 88 | odr = pyodrx.OpenDrive('myroad') 89 | for r in roads: 90 | odr.add_road(r) 91 | 92 | # create junction 93 | junction = pyodrx.Junction('test',1) 94 | con1 = pyodrx.Connection(0,1,pyodrx.ContactPoint.start) 95 | con1.add_lanelink(-1,-1) 96 | con2 = pyodrx.Connection(0,3,pyodrx.ContactPoint.start) 97 | con2.add_lanelink(-1,-1) 98 | con3 = pyodrx.Connection(0,5,pyodrx.ContactPoint.start) 99 | con3.add_lanelink(-1,-1) 100 | 101 | con4 = pyodrx.Connection(2,1,pyodrx.ContactPoint.end) 102 | con4.add_lanelink(1,1) 103 | con5 = pyodrx.Connection(4,3,pyodrx.ContactPoint.end) 104 | con5.add_lanelink(1,1) 105 | con6 = pyodrx.Connection(6,5,pyodrx.ContactPoint.end) 106 | con6.add_lanelink(1,1) 107 | 108 | junction.add_connection(con1) 109 | junction.add_connection(con2) 110 | junction.add_connection(con3) 111 | junction.add_connection(con4) 112 | junction.add_connection(con5) 113 | junction.add_connection(con6) 114 | 115 | # odr.create_junction() 116 | odr.add_junction(junction) 117 | odr.adjust_roads_and_lanes() 118 | # pyodrx.prettyprint(odr.get_element()) 119 | 120 | # write the OpenDRIVE file as xodr using current script name 121 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 122 | 123 | # uncomment the following line to display the road using esmini 124 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/junction_with_signals.py: -------------------------------------------------------------------------------- 1 | # Same approach to creating a junction as "full_junction.py" but with signals for each incoming road. 2 | import numpy as np 3 | import os 4 | import pyodrx 5 | #import pyoscx 6 | 7 | roads = [] 8 | incoming_roads = 4 9 | angles = [] 10 | for i in range(incoming_roads): 11 | roads.append(pyodrx.create_straight_road(i)) 12 | # use this instead to change the number of lanes in the crossing 13 | # roads.append(pyodrx.generators.create_straight_road(i, length=100, junction=-1, n_lanes=2, lane_offset=3)) 14 | angles.append(i * 2 * np.pi / incoming_roads) 15 | if angles[-1] == 0: 16 | roads[-1].add_signal(pyodrx.Signal(s=98.0, t=-4, country="USA", Type="R1", subtype="1")) 17 | else: 18 | roads[-1].add_signal(pyodrx.Signal(s=2.0, t=4, country="USA", Type="R1", subtype="1", orientation=pyodrx.Orientation.negative)) 19 | 20 | 21 | 22 | # use this for a T-crossing instead 23 | # angles = [0,np.pi/2, 3*np.pi/2] 24 | 25 | print(roads) 26 | junc = pyodrx.create_junction_roads(roads, angles, 8) 27 | odr = pyodrx.OpenDrive('myroad') 28 | junction = pyodrx.create_junction(junc, 1, roads) 29 | 30 | odr.add_junction(junction) 31 | for r in roads: 32 | odr.add_road(r) 33 | for j in junc: 34 | odr.add_road(j) 35 | 36 | odr.adjust_roads_and_lanes() 37 | 38 | # write the OpenDRIVE file as xodr using current script name 39 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 40 | 41 | # uncomment the following line to display the road using esmini 42 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/lane_number_change_merge.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import os 3 | 4 | # create the planview and the geometry 5 | planview = pyodrx.PlanView() 6 | 7 | 8 | planview.add_geometry(pyodrx.Line(500)) 9 | 10 | 11 | # create two different roadmarkings 12 | rm_solid = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2) 13 | rm_dashed = pyodrx.RoadMark(pyodrx.RoadMarkType.broken,0.2) 14 | 15 | # create a centerlane (same centerlane can be used since no linking is needed for this) 16 | centerlane = pyodrx.Lane(a=2) 17 | centerlane.add_roadmark(rm_solid) 18 | 19 | # create the first lanesection with two lanes 20 | lanesec1 = pyodrx.LaneSection(0,centerlane) 21 | lane1 = pyodrx.Lane(a=3) 22 | lane1.add_roadmark(rm_dashed) 23 | 24 | lane2 = pyodrx.Lane(a=3) 25 | lane2.add_roadmark(rm_solid) 26 | 27 | lanesec1.add_right_lane(lane1) 28 | lanesec1.add_right_lane(lane2) 29 | 30 | # create the second lanesection with one lane merging 31 | lanesec2 = pyodrx.LaneSection(250,centerlane) 32 | lane3 = pyodrx.Lane(a=3) 33 | lane3.add_roadmark(rm_dashed) 34 | 35 | lane4 = pyodrx.Lane(a=3,b=-0.1) 36 | lane4.add_roadmark(rm_solid) 37 | 38 | lanesec2.add_right_lane(lane3) 39 | lanesec2.add_right_lane(lane4) 40 | 41 | # create the last lanesection with one lane 42 | lanesec3 = pyodrx.LaneSection(280,centerlane) 43 | 44 | lane5 = pyodrx.Lane(a=3) 45 | lane5.add_roadmark(rm_solid) 46 | 47 | lanesec3.add_right_lane(lane5) 48 | 49 | # create the lane links 50 | lanelinker = pyodrx.LaneLinker() 51 | lanelinker.add_link(predlane=lane1,succlane=lane3) 52 | lanelinker.add_link(predlane=lane2,succlane=lane4) 53 | lanelinker.add_link(predlane=lane3,succlane=lane5) 54 | 55 | # create the lanes with the correct links 56 | lanes = pyodrx.Lanes() 57 | lanes.add_lanesection(lanesec1,lanelinker) 58 | lanes.add_lanesection(lanesec2,lanelinker) 59 | lanes.add_lanesection(lanesec3,lanelinker) 60 | 61 | # create the road 62 | road = pyodrx.Road(1,planview,lanes) 63 | 64 | # create the opendrive 65 | odr = pyodrx.OpenDrive('myroad') 66 | odr.add_road(road) 67 | 68 | # adjust the roads and lanes 69 | odr.adjust_roads_and_lanes() 70 | 71 | # write the OpenDRIVE file as xodr using current script name 72 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 73 | 74 | # uncomment the following line to display the road using esmini 75 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/multiple_geometries_one_road.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import numpy as np 3 | import os 4 | 5 | ## EXAMPLE 1 6 | ## Multiple geometries in one only road. 7 | 8 | ##1. Create the planview 9 | planview = pyodrx.PlanView() 10 | 11 | ##2. Create some geometries and add them to the planview 12 | line1 = pyodrx.Line(100) 13 | arc1 = pyodrx.Arc(0.05,angle=np.pi/2) 14 | line2 = pyodrx.Line(100) 15 | cloth1 = pyodrx.Spiral(0.05,-0.1,30) 16 | line3 = pyodrx.Line(100) 17 | 18 | planview.add_geometry(line1) 19 | planview.add_geometry(arc1) 20 | planview.add_geometry(line2) 21 | planview.add_geometry(cloth1) 22 | planview.add_geometry(line3) 23 | 24 | 25 | ##3. Create a solid roadmark 26 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2) 27 | 28 | ##4. Create centerlane 29 | centerlane = pyodrx.Lane(a=2) 30 | centerlane.add_roadmark(rm) 31 | 32 | ##5. Create lane section form the centerlane 33 | lanesec = pyodrx.LaneSection(0,centerlane) 34 | 35 | ##6. Create left and right lanes 36 | lane2 = pyodrx.Lane(a=3) 37 | lane2.add_roadmark(rm) 38 | lane3 = pyodrx.Lane(a=3) 39 | lane3.add_roadmark(rm) 40 | 41 | ##7. Add lanes to lane section 42 | lanesec.add_left_lane(lane2) 43 | lanesec.add_right_lane(lane3) 44 | 45 | ##8. Add lane section to Lanes 46 | lanes = pyodrx.Lanes() 47 | lanes.add_lanesection(lanesec) 48 | 49 | ##9. Create Road from Planview and Lanes 50 | road = pyodrx.Road(1,planview,lanes) 51 | 52 | ##10. Create the OpenDrive class (Master class) 53 | odr = pyodrx.OpenDrive('myroad') 54 | 55 | ##11. Finally add roads to Opendrive 56 | odr.add_road(road) 57 | 58 | ##12. Adjust initial positions of the roads looking at succ-pred logic 59 | odr.adjust_roads_and_lanes() 60 | 61 | ##13. Print the .xodr file 62 | pyodrx.prettyprint(odr.get_element()) 63 | 64 | # write the OpenDRIVE file as xodr using current script name 65 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 66 | 67 | # uncomment the following line to display the road using esmini 68 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/multiple_geometries_one_road_with_objects.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import numpy as np 3 | import os 4 | ## EXAMPLE 1 5 | ## Multiple geometries in one only road. Additionally adding objects. 6 | 7 | ##1. Create the planview 8 | planview = pyodrx.PlanView() 9 | 10 | ##2. Create some geometries and add them to the planview 11 | line1 = pyodrx.Line(100) 12 | arc1 = pyodrx.Arc(0.05,angle=np.pi/2) 13 | line2 = pyodrx.Line(100) 14 | cloth1 = pyodrx.Spiral(0.05,-0.1,30) 15 | line3 = pyodrx.Line(100) 16 | 17 | planview.add_geometry(line1) 18 | planview.add_geometry(arc1) 19 | planview.add_geometry(line2) 20 | planview.add_geometry(cloth1) 21 | planview.add_geometry(line3) 22 | 23 | 24 | ##3. Create a solid roadmark 25 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2) 26 | 27 | ##4. Create centerlane 28 | centerlane = pyodrx.Lane(a=2) 29 | centerlane.add_roadmark(rm) 30 | 31 | ##5. Create lane section form the centerlane 32 | lanesec = pyodrx.LaneSection(0,centerlane) 33 | 34 | ##6. Create left and right lanes 35 | lane2 = pyodrx.Lane(a=3) 36 | lane2.add_roadmark(rm) 37 | lane3 = pyodrx.Lane(a=3) 38 | lane3.add_roadmark(rm) 39 | 40 | ##7. Add lanes to lane section 41 | lanesec.add_left_lane(lane2) 42 | lanesec.add_right_lane(lane3) 43 | 44 | ##8. Add lane section to Lanes 45 | lanes = pyodrx.Lanes() 46 | lanes.add_lanesection(lanesec) 47 | 48 | ##9. Create Road from Planview and Lanes 49 | road = pyodrx.Road(1,planview,lanes) 50 | 51 | 52 | ##10. Create the OpenDrive class (Master class) 53 | odr = pyodrx.OpenDrive('myroad') 54 | 55 | ##11. Finally add roads to Opendrive 56 | odr.add_road(road) 57 | 58 | ##12. Adjust initial positions of the roads looking at succ-pred logic 59 | odr.adjust_roads_and_lanes() 60 | 61 | ##13. After adjustment, repeating objects on side of the road can be added automatically 62 | guardrail = pyodrx.Object(0,0,height=0.3,zOffset=0.4,Type=pyodrx.ObjectType.barrier,name="guardRail") 63 | road.add_object_roadside(guardrail, 0, 0, tOffset = 0.8) 64 | 65 | delineator = pyodrx.Object(0,0,height=1,zOffset=0,Type=pyodrx.ObjectType.pole,name="delineator") 66 | road.add_object_roadside(delineator, 50, sOffset = 25, tOffset = 0.85) 67 | 68 | ##14. Add some other objects at specific positions 69 | #single emergency callbox 70 | emergencyCallbox = pyodrx.Object(30,-6,Type=pyodrx.ObjectType.pole,name="emergencyCallBox") 71 | road.add_object(emergencyCallbox) 72 | 73 | #repeating jersey barrier 74 | jerseyBarrier = pyodrx.Object(0,0,height=0.75,zOffset=0,Type=pyodrx.ObjectType.barrier,name="jerseyBarrier") 75 | jerseyBarrier.repeat(repeatLength=25,repeatDistance=0,sStart=240) 76 | road.add_object(jerseyBarrier) 77 | 78 | ##15. Print the .xodr file 79 | pyodrx.prettyprint(odr.get_element()) 80 | 81 | # write the OpenDRIVE file as xodr using current script name 82 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 83 | 84 | # uncomment the following line to display the road using esmini 85 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/road_merge.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | 3 | import os 4 | 5 | # create some roads 6 | roads= [] 7 | roads.append(pyodrx.create_road(pyodrx.Line(100),id = 0, left_lanes=1,right_lanes=2)) 8 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =1,left_lanes=0,right_lanes=1)) 9 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =2,left_lanes=1,right_lanes=3)) 10 | roads.append(pyodrx.create_road(pyodrx.Spiral(0.001,0.02,30),id =3,left_lanes=1,right_lanes=2,road_type=1)) 11 | roads.append(pyodrx.create_road(pyodrx.Spiral(-0.001,-0.02,30),id =4,left_lanes=0,right_lanes=1,road_type=1)) 12 | 13 | # add some connections to non junction roads 14 | roads[0].add_successor(pyodrx.ElementType.junction,1) 15 | roads[1].add_successor(pyodrx.ElementType.junction,1) 16 | roads[2].add_predecessor(pyodrx.ElementType.junction,1) 17 | 18 | # add connections to the first connecting road 19 | roads[3].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 20 | roads[3].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start) 21 | 22 | # add connections to the second connecting road with an offset 23 | roads[4].add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.end) 24 | roads[4].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start,lane_offset=-2) 25 | 26 | 27 | 28 | junction = pyodrx.create_junction(roads[3:],1,roads[0:3]) 29 | 30 | 31 | # create the opendrive 32 | odr = pyodrx.OpenDrive('myroad') 33 | for r in roads: 34 | odr.add_road(r) 35 | odr.adjust_roads_and_lanes() 36 | odr.add_junction(junction) 37 | 38 | # write the OpenDRIVE file as xodr using current script name 39 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 40 | 41 | # uncomment the following line to display the road using esmini 42 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/road_merge_w_lane_merge.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | 3 | import os 4 | 5 | # create some roads 6 | roads= [] 7 | 8 | # create two simple roads to merge 9 | roads.append(pyodrx.create_road(pyodrx.Line(100),id = 0, left_lanes=0,right_lanes=2)) 10 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =1,left_lanes=0,right_lanes=1)) 11 | 12 | # manually create the final road 13 | 14 | 15 | # create the planview and the geometry 16 | planview = pyodrx.PlanView() 17 | planview.add_geometry(pyodrx.Line(200)) 18 | 19 | # create two different roadmarkings 20 | rm_solid = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2) 21 | rm_dashed = pyodrx.RoadMark(pyodrx.RoadMarkType.broken,0.2) 22 | 23 | # create a centerlane (same centerlane can be used since no linking is needed for this) 24 | centerlane = pyodrx.Lane(a=2) 25 | centerlane.add_roadmark(rm_solid) 26 | 27 | # create the first lanesection with three lanes 28 | lanesec1 = pyodrx.LaneSection(0,centerlane) 29 | lane1 = pyodrx.Lane(a=3) 30 | lane1.add_roadmark(rm_dashed) 31 | 32 | lane2 = pyodrx.Lane(a=3) 33 | lane2.add_roadmark(rm_dashed) 34 | 35 | lane3 = pyodrx.Lane(a=3) 36 | lane3.add_roadmark(rm_solid) 37 | 38 | lanesec1.add_right_lane(lane1) 39 | lanesec1.add_right_lane(lane2) 40 | lanesec1.add_right_lane(lane3) 41 | 42 | # create the second lanesection with one lane merging 43 | lanesec2 = pyodrx.LaneSection(70,centerlane) 44 | lane4 = pyodrx.Lane(a=3) 45 | lane4.add_roadmark(rm_dashed) 46 | 47 | lane5 = pyodrx.Lane(a=3) 48 | lane5.add_roadmark(rm_dashed) 49 | 50 | lane6 = pyodrx.Lane(a=3,b=-0.1) 51 | lane6.add_roadmark(rm_solid) 52 | 53 | lanesec2.add_right_lane(lane4) 54 | lanesec2.add_right_lane(lane5) 55 | lanesec2.add_right_lane(lane6) 56 | 57 | # create the last lanesection with one lane 58 | lanesec3 = pyodrx.LaneSection(100,centerlane) 59 | 60 | lane7 = pyodrx.Lane(a=3) 61 | lane7.add_roadmark(rm_dashed) 62 | 63 | lane8 = pyodrx.Lane(a=3) 64 | lane8.add_roadmark(rm_solid) 65 | 66 | lanesec3.add_right_lane(lane7) 67 | lanesec3.add_right_lane(lane8) 68 | 69 | # create the lane links 70 | lanelinker = pyodrx.LaneLinker() 71 | lanelinker.add_link(predlane=lane1,succlane=lane4) 72 | lanelinker.add_link(predlane=lane2,succlane=lane5) 73 | lanelinker.add_link(predlane=lane3,succlane=lane6) 74 | 75 | lanelinker.add_link(predlane=lane5,succlane=lane7) 76 | lanelinker.add_link(predlane=lane6,succlane=lane8) 77 | 78 | # create the lanes with the correct links 79 | lanes = pyodrx.Lanes() 80 | lanes.add_lanesection(lanesec1,lanelinker) 81 | lanes.add_lanesection(lanesec2,lanelinker) 82 | lanes.add_lanesection(lanesec3,lanelinker) 83 | 84 | # create the road 85 | roads.append(pyodrx.Road(2,planview,lanes)) 86 | 87 | 88 | # create junction roads 89 | roads.append(pyodrx.create_road(pyodrx.Spiral(0.001,0.02,30),id =3,left_lanes=0,right_lanes=2,road_type=1)) 90 | roads.append(pyodrx.create_road(pyodrx.Spiral(-0.001,-0.02,30),id =4,left_lanes=0,right_lanes=1,road_type=1)) 91 | 92 | # add some connections to non junction roads 93 | roads[0].add_successor(pyodrx.ElementType.junction,1) 94 | roads[1].add_successor(pyodrx.ElementType.junction,1) 95 | roads[2].add_predecessor(pyodrx.ElementType.junction,1) 96 | 97 | # add connections to the first connecting road 98 | roads[3].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 99 | roads[3].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start) 100 | 101 | # add connections to the second connecting road with an offset 102 | roads[4].add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.end) 103 | roads[4].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start,lane_offset=-2) 104 | 105 | 106 | 107 | junction = pyodrx.create_junction(roads[3:],1,roads[0:3]) 108 | 109 | 110 | # create the opendrive 111 | odr = pyodrx.OpenDrive('myroad') 112 | for r in roads: 113 | odr.add_road(r) 114 | odr.adjust_roads_and_lanes() 115 | odr.add_junction(junction) 116 | 117 | # write the OpenDRIVE file as xodr using current script name 118 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 119 | 120 | # uncomment the following line to display the road using esmini 121 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/road_split.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import os 3 | 4 | # create some simple roads 5 | roads= [] 6 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =0,left_lanes=0,right_lanes=2)) 7 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =1,left_lanes=0,right_lanes=1)) 8 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =2,left_lanes=0,right_lanes=1)) 9 | roads.append(pyodrx.create_road(pyodrx.Spiral(0.001,0.02,30),id =3,left_lanes=0,right_lanes=1,road_type=1)) 10 | roads.append(pyodrx.create_road(pyodrx.Spiral(-0.001,-0.02,30),id =4,left_lanes=0,right_lanes=1,road_type=1)) 11 | 12 | # add predecessors and succesors to the non junction roads 13 | roads[0].add_successor(pyodrx.ElementType.junction,1) 14 | roads[1].add_predecessor(pyodrx.ElementType.junction,1) 15 | roads[2].add_predecessor(pyodrx.ElementType.junction,1) 16 | 17 | # add connections to the first junction road 18 | roads[3].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 19 | roads[3].add_successor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.start) 20 | 21 | # add connections to the second junction road, together with an offset 22 | roads[4].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end,lane_offset=-1) 23 | roads[4].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start) 24 | 25 | # create the junction struct 26 | junction = pyodrx.create_junction(roads[3:],1,roads[0:3]) 27 | 28 | # create the opendrive 29 | odr = pyodrx.OpenDrive('myroad') 30 | for r in roads: 31 | odr.add_road(r) 32 | odr.adjust_roads_and_lanes() 33 | odr.add_junction(junction) 34 | 35 | # write the OpenDRIVE file as xodr using current script name 36 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 37 | 38 | # uncomment the following line to display the road using esmini 39 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/road_split_w_lane_split.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import os 3 | 4 | # create some simple roads 5 | roads= [] 6 | 7 | # create the planview and the geometry 8 | planview = pyodrx.PlanView() 9 | planview.add_geometry(pyodrx.Line(200)) 10 | 11 | 12 | # create two different roadmarkings 13 | rm_solid = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2, 14 | rm_dashed = pyodrx.RoadMark(pyodrx.RoadMarkType.broken,0.2) 15 | 16 | 17 | # create a centerlane (same centerlane can be used since no linking is needed for this) 18 | centerlane = pyodrx.Lane(a=2) 19 | centerlane.add_roadmark(rm_solid) 20 | 21 | # create first lanesection with two lanes 22 | lanesec1 = pyodrx.LaneSection(0,centerlane) 23 | lane0 = pyodrx.Lane(a=3) 24 | lane0.add_roadmark(rm_dashed) 25 | lane1 = pyodrx.Lane(a=3) 26 | lane1.add_roadmark(rm_solid) 27 | 28 | lanesec1.add_right_lane(lane0) 29 | lanesec1.add_right_lane(lane1) 30 | 31 | # create the second lanesection with a third lane emerging 32 | lanesec2 = pyodrx.LaneSection(100,centerlane) 33 | lane2 = pyodrx.Lane(a=3) 34 | lane2.add_roadmark(rm_dashed) 35 | 36 | lane3 = pyodrx.Lane(a=3) 37 | lane3.add_roadmark(rm_dashed) 38 | 39 | lane4 = pyodrx.Lane(a=0,b=0.1) 40 | lane4.add_roadmark(rm_solid) 41 | 42 | lanesec2.add_right_lane(lane2) 43 | lanesec2.add_right_lane(lane3) 44 | lanesec2.add_right_lane(lane4) 45 | 46 | # create the last lanesection with two paralell lanes 47 | lanesec3 = pyodrx.LaneSection(130,centerlane) 48 | lane5 = pyodrx.Lane(a=3) 49 | lane5.add_roadmark(rm_dashed) 50 | 51 | lane6 = pyodrx.Lane(a=3) 52 | lane6.add_roadmark(rm_dashed) 53 | 54 | lane7 = pyodrx.Lane(a=3) 55 | lane7.add_roadmark(rm_solid) 56 | 57 | lanesec3.add_right_lane(lane5) 58 | lanesec3.add_right_lane(lane6) 59 | lanesec3.add_right_lane(lane7) 60 | 61 | # create the lane links 62 | lanelinker = pyodrx.LaneLinker() 63 | lanelinker.add_link(predlane=lane0,succlane=lane2) 64 | lanelinker.add_link(predlane=lane1,succlane=lane3) 65 | lanelinker.add_link(predlane=lane2,succlane=lane5) 66 | lanelinker.add_link(predlane=lane3,succlane=lane6) 67 | lanelinker.add_link(predlane=lane4,succlane=lane7) 68 | 69 | # create the lanes with the correct links 70 | lanes = pyodrx.Lanes() 71 | lanes.add_lanesection(lanesec1,lanelinker) 72 | lanes.add_lanesection(lanesec2,lanelinker) 73 | lanes.add_lanesection(lanesec3,lanelinker) 74 | 75 | # create the road 76 | roads.append(pyodrx.Road(0,planview,lanes)) 77 | 78 | # create the other roads 79 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =1,left_lanes=0,right_lanes=2)) 80 | roads.append(pyodrx.create_road(pyodrx.Line(100),id =2,left_lanes=0,right_lanes=1)) 81 | # create the junction roads 82 | roads.append(pyodrx.create_road(pyodrx.Spiral(0.001,0.02,30),id =3,left_lanes=0,right_lanes=2,road_type=1)) 83 | roads.append(pyodrx.create_road(pyodrx.Spiral(-0.001,-0.02,30),id =4,left_lanes=0,right_lanes=1,road_type=1)) 84 | 85 | # add predecessors and succesors to the non junction roads 86 | roads[0].add_successor(pyodrx.ElementType.junction,1) 87 | roads[1].add_predecessor(pyodrx.ElementType.junction,1) 88 | roads[2].add_predecessor(pyodrx.ElementType.junction,1) 89 | 90 | # add connections to the first junction road 91 | roads[3].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end) 92 | roads[3].add_successor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.start) 93 | 94 | # add connections to the second junction road, together with an offset 95 | roads[4].add_predecessor(pyodrx.ElementType.road,0,pyodrx.ContactPoint.end,lane_offset=-2) 96 | roads[4].add_successor(pyodrx.ElementType.road,2,pyodrx.ContactPoint.start) 97 | 98 | # create the junction struct 99 | junction = pyodrx.create_junction(roads[3:],1,roads[0:3]) 100 | 101 | # create the opendrive 102 | odr = pyodrx.OpenDrive('myroad') 103 | for r in roads: 104 | odr.add_road(r) 105 | odr.adjust_roads_and_lanes() 106 | odr.add_junction(junction) 107 | 108 | # write the OpenDRIVE file as xodr using current script name 109 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 110 | 111 | # uncomment the following line to display the road using esmini 112 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) -------------------------------------------------------------------------------- /examples/two_roads.py: -------------------------------------------------------------------------------- 1 | import pyodrx 2 | import numpy as np 3 | import os 4 | 5 | 6 | odr = pyodrx.OpenDrive('myroad') 7 | 8 | #---------------- Road 1 9 | planview = pyodrx.PlanView(0,0,0) 10 | 11 | # create some geometries and add to the planview 12 | planview.add_geometry(pyodrx.Line(100)) 13 | 14 | # create a solid roadmark 15 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid, 0.2) 16 | 17 | # create centerlane 18 | centerlane_1 = pyodrx.Lane(a=2) 19 | centerlane_1.add_roadmark(rm) 20 | lanesec_1 = pyodrx.LaneSection(0,centerlane_1) 21 | 22 | # add a driving lane 23 | lane2_1 = pyodrx.Lane(a=3.1) 24 | lane2_1.add_roadmark(rm) 25 | lanesec_1.add_left_lane(lane2_1) 26 | 27 | lane3_1 = pyodrx.Lane(a=3.1) 28 | lane3_1.add_roadmark(rm) 29 | lanesec_1.add_right_lane(lane3_1) 30 | 31 | ## finalize the road 32 | lanes_1 = pyodrx.Lanes() 33 | lanes_1.add_lanesection(lanesec_1) 34 | 35 | road = pyodrx.Road(1,planview,lanes_1) 36 | 37 | 38 | odr.add_road(road) 39 | 40 | 41 | #---------------- Road 2 42 | 43 | planview2 = pyodrx.PlanView(x_start = 0, y_start = 10,h_start=np.pi/2) 44 | # planview2 = pyodrx.PlanView() 45 | 46 | # create some geometries and add to the planview 47 | planview2.add_geometry(pyodrx.Line(200)) 48 | 49 | # create a solid roadmark 50 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid, 0.2) 51 | 52 | # create centerlane 53 | centerlane = pyodrx.Lane(a=2) 54 | centerlane.add_roadmark(rm) 55 | lanesec = pyodrx.LaneSection(0,centerlane) 56 | 57 | # add a driving lane 58 | lane2 = pyodrx.Lane(a=3.1) 59 | lane2.add_roadmark(rm) 60 | lanesec.add_left_lane(lane2) 61 | 62 | lane3 = pyodrx.Lane(a=3.1) 63 | lane3.add_roadmark(rm) 64 | lanesec.add_right_lane(lane3) 65 | 66 | ## finalize the road 67 | lanes = pyodrx.Lanes() 68 | lanes.add_lanesection(lanesec) 69 | 70 | road2 = pyodrx.Road(2,planview2,lanes) 71 | 72 | odr.add_road(road2) 73 | 74 | 75 | 76 | 77 | #------------------ Finalize 78 | odr.adjust_roads_and_lanes() 79 | pyodrx.prettyprint(odr.get_element()) 80 | 81 | # write the OpenDRIVE file as xodr using current script name 82 | odr.write_xml(os.path.basename(__file__).replace('.py','.xodr')) 83 | 84 | # uncomment the following line to display the road using esmini 85 | # pyodrx.run_road(odr,os.path.join('..','..','esmini')) 86 | -------------------------------------------------------------------------------- /html/pyodrx/exceptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | pyodrx.exceptions API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module pyodrx.exceptions

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
class NotSameAmountOfLanesError(Exception):
 30 |     """ Raised when the amount of Lanes are not the same (used for the automation of linking)
 31 |     """
 32 |     pass
 33 | 
 34 | class NotEnoughInputArguments(Exception):
 35 |     """ Raised when one of the needed "optional" inputs are not used
 36 |     """
 37 |     pass
 38 | 
 39 | class ToManyOptionalArguments(Exception):
 40 |     """ Raised when one of the needed "optional" inputs are not used
 41 |     """
 42 |     pass
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |

Classes

53 |
54 |
55 | class NotEnoughInputArguments 56 | (*args, **kwargs) 57 |
58 |
59 |

Raised when one of the needed "optional" inputs are not used

60 |
61 | 62 | Expand source code 63 | 64 |
class NotEnoughInputArguments(Exception):
 65 |     """ Raised when one of the needed "optional" inputs are not used
 66 |     """
 67 |     pass
68 |
69 |

Ancestors

70 |
    71 |
  • builtins.Exception
  • 72 |
  • builtins.BaseException
  • 73 |
74 |
75 |
76 | class NotSameAmountOfLanesError 77 | (*args, **kwargs) 78 |
79 |
80 |

Raised when the amount of Lanes are not the same (used for the automation of linking)

81 |
82 | 83 | Expand source code 84 | 85 |
class NotSameAmountOfLanesError(Exception):
 86 |     """ Raised when the amount of Lanes are not the same (used for the automation of linking)
 87 |     """
 88 |     pass
89 |
90 |

Ancestors

91 |
    92 |
  • builtins.Exception
  • 93 |
  • builtins.BaseException
  • 94 |
95 |
96 |
97 | class ToManyOptionalArguments 98 | (*args, **kwargs) 99 |
100 |
101 |

Raised when one of the needed "optional" inputs are not used

102 |
103 | 104 | Expand source code 105 | 106 |
class ToManyOptionalArguments(Exception):
107 |     """ Raised when one of the needed "optional" inputs are not used
108 |     """
109 |     pass
110 |
111 |

Ancestors

112 |
    113 |
  • builtins.Exception
  • 114 |
  • builtins.BaseException
  • 115 |
116 |
117 |
118 |
119 |
120 | 146 |
147 | 150 | 151 | -------------------------------------------------------------------------------- /html/pyodrx/helpers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | pyodrx.helpers API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module pyodrx.helpers

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
import xml.etree.ElementTree as ET
 30 | import xml.dom.minidom as mini
 31 | 
 32 | 
 33 | import os
 34 | 
 35 | 
 36 | def run_road(opendrive,esminipath = 'esmini'):
 37 |     """ write a scenario and runs it in esminis OpenDriveViewer with some random traffic
 38 |         Parameters
 39 |         ----------
 40 |             opendrive (OpenDrive): the pyodrx road to run
 41 | 
 42 |             esminipath (str): the path to esmini 
 43 |                 Default: pyoscx
 44 | 
 45 |     """
 46 |     _scenariopath = os.path.join(esminipath,'resources','xodr')
 47 |     print(_scenariopath)
 48 |     opendrive.write_xml(os.path.join(_scenariopath,'pythonroad.xodr'),True)
 49 |     
 50 |     if os.name == 'posix':
 51 |         os.system(os.path.join('.', esminipath, 'bin','odrviewer') + ' --odr ' + os.path.join(esminipath,'resources','xodr','pythonroad.xodr') + ' --osi_features on --clear-color 0.2,0.2,0.2 --window 50 50 800 400 --density 15' )
 52 |     elif os.name == 'nt':
 53 |         os.system(os.path.join(esminipath,'bin','odrviewer.exe') + ' --odr ' + os.path.join(esminipath,'resources','xodr','pythonroad.xodr') + ' --osi_features on --clear-color 0.2,0.2,0.2 --window 50 50 800 400 --density 15' )
 54 | 
 55 | 
 56 | def enum2str(enum):
 57 |     """ helper to create strings from enums that should contain space but have to have _
 58 | 
 59 |         Parameters
 60 |         ----------
 61 |             enum (Enum): a enum of pyodrx
 62 | 
 63 |         Returns
 64 |         -------
 65 |             enumstr (str): the enum as a string replacing _ with ' '
 66 | 
 67 |     """
 68 |     return enum.name.replace('_',' ')
 69 | 
 70 | def prettyprint(element):
 71 |     """ prints the element to the commandline
 72 | 
 73 |         Parameters
 74 |         ----------
 75 |             element (Element): element to print
 76 | 
 77 |     """
 78 |     rough = ET.tostring(element,'utf-8')
 79 |     reparsed = mini.parseString(rough)
 80 |     print(reparsed.toprettyxml(indent="\t"))
 81 | 
 82 | 
 83 | def printToFile(element,filename,prettyprint=True):
 84 |     """ prints the element to a xml file
 85 | 
 86 |         Parameters
 87 |         ----------
 88 |             element (Element): element to print
 89 | 
 90 |             filename (str): file to save to
 91 | 
 92 |             prettyprint (bool): pretty or "ugly" print
 93 | 
 94 |     """
 95 |     if prettyprint:    
 96 |         rough = ET.tostring(element,'utf-8').replace(b'\n',b'').replace(b'\t',b'')
 97 |         reparsed = mini.parseString(rough)
 98 |         towrite = reparsed.toprettyxml(indent="\t")
 99 |         with open(filename,"w") as file_handle:
100 |             file_handle.write(towrite)
101 |     else:
102 |         tree = ET.ElementTree(element)
103 |         with open(filename,"wb") as file_handle:
104 |             tree.write(file_handle)
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |

Functions

113 |
114 |
115 | def enum2str(enum) 116 |
117 |
118 |

helper to create strings from enums that should contain space but have to have _

119 |

Parameters

120 |
enum (Enum): a enum of pyodrx
121 | 
122 |

Returns

123 |
enumstr (str): the enum as a string replacing _ with ' '
124 | 
125 |
126 | 127 | Expand source code 128 | 129 |
def enum2str(enum):
130 |     """ helper to create strings from enums that should contain space but have to have _
131 | 
132 |         Parameters
133 |         ----------
134 |             enum (Enum): a enum of pyodrx
135 | 
136 |         Returns
137 |         -------
138 |             enumstr (str): the enum as a string replacing _ with ' '
139 | 
140 |     """
141 |     return enum.name.replace('_',' ')
142 |
143 |
144 |
145 | def prettyprint(element) 146 |
147 |
148 |

prints the element to the commandline

149 |

Parameters

150 |
element (Element): element to print
151 | 
152 |
153 | 154 | Expand source code 155 | 156 |
def prettyprint(element):
157 |     """ prints the element to the commandline
158 | 
159 |         Parameters
160 |         ----------
161 |             element (Element): element to print
162 | 
163 |     """
164 |     rough = ET.tostring(element,'utf-8')
165 |     reparsed = mini.parseString(rough)
166 |     print(reparsed.toprettyxml(indent="\t"))
167 |
168 |
169 |
170 | def printToFile(element, filename, prettyprint=True) 171 |
172 |
173 |

prints the element to a xml file

174 |

Parameters

175 |
element (Element): element to print
176 | 
177 | filename (str): file to save to
178 | 
179 | prettyprint (bool): pretty or "ugly" print
180 | 
181 |
182 | 183 | Expand source code 184 | 185 |
def printToFile(element,filename,prettyprint=True):
186 |     """ prints the element to a xml file
187 | 
188 |         Parameters
189 |         ----------
190 |             element (Element): element to print
191 | 
192 |             filename (str): file to save to
193 | 
194 |             prettyprint (bool): pretty or "ugly" print
195 | 
196 |     """
197 |     if prettyprint:    
198 |         rough = ET.tostring(element,'utf-8').replace(b'\n',b'').replace(b'\t',b'')
199 |         reparsed = mini.parseString(rough)
200 |         towrite = reparsed.toprettyxml(indent="\t")
201 |         with open(filename,"w") as file_handle:
202 |             file_handle.write(towrite)
203 |     else:
204 |         tree = ET.ElementTree(element)
205 |         with open(filename,"wb") as file_handle:
206 |             tree.write(file_handle)
207 |
208 |
209 |
210 | def run_road(opendrive, esminipath='esmini') 211 |
212 |
213 |

write a scenario and runs it in esminis OpenDriveViewer with some random traffic 214 | Parameters

215 |
216 |
opendrive (OpenDrive): the pyodrx road to run
217 | 
218 | esminipath (str): the path to esmini 
219 |     Default: pyoscx
220 | 
221 |
222 | 223 | Expand source code 224 | 225 |
def run_road(opendrive,esminipath = 'esmini'):
226 |     """ write a scenario and runs it in esminis OpenDriveViewer with some random traffic
227 |         Parameters
228 |         ----------
229 |             opendrive (OpenDrive): the pyodrx road to run
230 | 
231 |             esminipath (str): the path to esmini 
232 |                 Default: pyoscx
233 | 
234 |     """
235 |     _scenariopath = os.path.join(esminipath,'resources','xodr')
236 |     print(_scenariopath)
237 |     opendrive.write_xml(os.path.join(_scenariopath,'pythonroad.xodr'),True)
238 |     
239 |     if os.name == 'posix':
240 |         os.system(os.path.join('.', esminipath, 'bin','odrviewer') + ' --odr ' + os.path.join(esminipath,'resources','xodr','pythonroad.xodr') + ' --osi_features on --clear-color 0.2,0.2,0.2 --window 50 50 800 400 --density 15' )
241 |     elif os.name == 'nt':
242 |         os.system(os.path.join(esminipath,'bin','odrviewer.exe') + ' --odr ' + os.path.join(esminipath,'resources','xodr','pythonroad.xodr') + ' --osi_features on --clear-color 0.2,0.2,0.2 --window 50 50 800 400 --density 15' )
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 | 271 |
272 | 275 | 276 | -------------------------------------------------------------------------------- /html/pyodrx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | pyodrx API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Package pyodrx

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
# __init__.py
 30 | 
 31 | from .geometry import *
 32 | from .helpers import *
 33 | from .opendrive import *
 34 | from .lane import *
 35 | from .enumerations import *
 36 | from .links import *
 37 | from .generators import *
38 |
39 |
40 |
41 |

Sub-modules

42 |
43 |
pyodrx.enumerations
44 |
45 |
46 |
47 |
pyodrx.exceptions
48 |
49 |
50 |
51 |
pyodrx.generators
52 |
53 |

This is a collection of ready to use functions, to generate standard road snipets, like: 54 | - Simple straight road 55 | - Spiral-Arc-Spiral type of turns 56 | - …

57 |
58 |
pyodrx.geometry
59 |
60 |
61 |
62 |
pyodrx.helpers
63 |
64 |
65 |
66 |
pyodrx.lane
67 |
68 |
69 |
70 |
pyodrx.links
71 |
72 |
73 |
74 |
pyodrx.opendrive
75 |
76 |
77 |
78 |
pyodrx.signals
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 112 |
113 | 116 | 117 | -------------------------------------------------------------------------------- /html/pyodrx/signals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | pyodrx.signals API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module pyodrx.signals

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
import xml.etree.ElementTree as ET
 30 | 
 31 | 
 32 | 
 33 | 
 34 | class Signal():
 35 |     """ the Signal describes a signal along a road
 36 |         
 37 |         Parameters
 38 |         ----------
 39 |             s (float): the start s value (along road) of the Signal
 40 |                
 41 |             x (float): start x coordinate of the Signal
 42 |                 
 43 |             zoffset (float): height of the signal
 44 | 
 45 |             name (str): name of the signal
 46 |             
 47 |             country (int): country code
 48 | 
 49 |             type (str): type of signal
 50 | 
 51 |             subtype (str): subtype of signal
 52 | 
 53 | 
 54 |             
 55 | 
 56 | 
 57 |         Attributes
 58 |         ----------
 59 |             s (float): the start s value (along road) of the Signal
 60 |                
 61 |             x (float): start x coordinate of the Signal
 62 | 
 63 |             id (str): id of the signal
 64 | 
 65 |             name (str): name of the signal
 66 |         Methods
 67 |         -------
 68 |             get_element()
 69 |                 Returns the full ElementTree of the class
 70 | 
 71 |             get_attributes()
 72 |                 Returns a dictionary of all attributes of the class
 73 |     """
 74 | 
 75 |     def __init__(self,s,x,y,heading,geom_type):
 76 |         """ initalizes the Signal
 77 | 
 78 |         Parameters
 79 |         ----------
 80 |             s (float): the start s value (along road) of the geometry
 81 |                
 82 |             x (float): start x coordinate of the geometry
 83 |                 
 84 |             y (float):  start y coordinate of the geometry
 85 |                 
 86 |             heading (float): heading of the geometry
 87 | 
 88 |             geom_type (Line, Spiral,ParamPoly3, or Arc): the type of geometry
 89 | 
 90 |         """ 
 91 |         self.s = s
 92 |         self.x = x
 93 |         self.y = y
 94 |   
 95 |         
 96 |         self.heading = heading
 97 |         self.geom_type = geom_type
 98 |         _,_,_,self.length = self.geom_type.get_end_data(self.x,self.y,self.heading)
 99 | 
100 |     def get_end_data(self):
101 |         return self.geom_type.get_end_data(self.x,self.y,self.heading)
102 |         
103 |     def get_start_data(self):
104 |         x,y,heading,self.length = self.geom_type.get_start_data(self.x,self.y,self.heading)
105 |         self.x = x
106 |         self.y = y
107 |         self.heading = heading + np.pi
108 |         self.s = None
109 |         return x,y,heading,self.length
110 | 
111 |     def set_s(self,s):
112 |         self.s = s
113 | 
114 |     def get_attributes(self):
115 |         """ returns the attributes of the _Geometry as a dict
116 | 
117 |         """
118 |         retdict = {}
119 |         retdict['s'] = str(self.s)
120 |         retdict['x'] = str(self.x)
121 |         retdict['y'] = str(self.y)
122 |         retdict['hdg'] = str(self.heading)
123 |         retdict['length'] = str(self.length)
124 |         return retdict
125 | 
126 | 
127 |     def get_element(self):
128 |         """ returns the elementTree of the _Geometry
129 | 
130 |         """
131 |         element = ET.Element('geometry',attrib=self.get_attributes())
132 |         element.append(self.geom_type.get_element())
133 |         return element
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |

Classes

144 |
145 |
146 | class Signal 147 | (s, x, y, heading, geom_type) 148 |
149 |
150 |

the Signal describes a signal along a road

151 |

Parameters

152 |
s (float): the start s value (along road) of the Signal
153 | 
154 | x (float): start x coordinate of the Signal
155 | 
156 | zoffset (float): height of the signal
157 | 
158 | name (str): name of the signal
159 | 
160 | country (int): country code
161 | 
162 | type (str): type of signal
163 | 
164 | subtype (str): subtype of signal
165 | 
166 |

Attributes

167 |
s (float): the start s value (along road) of the Signal
168 | 
169 | x (float): start x coordinate of the Signal
170 | 
171 | id (str): id of the signal
172 | 
173 | name (str): name of the signal
174 | 
175 |

Methods

176 |
get_element()
177 |     Returns the full ElementTree of the class
178 | 
179 | get_attributes()
180 |     Returns a dictionary of all attributes of the class
181 | 
182 |

initalizes the Signal

183 |

Parameters

184 |
s (float): the start s value (along road) of the geometry
185 | 
186 | x (float): start x coordinate of the geometry
187 | 
188 | y (float):  start y coordinate of the geometry
189 | 
190 | heading (float): heading of the geometry
191 | 
192 | geom_type (Line, Spiral,ParamPoly3, or Arc): the type of geometry
193 | 
194 |
195 | 196 | Expand source code 197 | 198 |
class Signal():
199 |     """ the Signal describes a signal along a road
200 |         
201 |         Parameters
202 |         ----------
203 |             s (float): the start s value (along road) of the Signal
204 |                
205 |             x (float): start x coordinate of the Signal
206 |                 
207 |             zoffset (float): height of the signal
208 | 
209 |             name (str): name of the signal
210 |             
211 |             country (int): country code
212 | 
213 |             type (str): type of signal
214 | 
215 |             subtype (str): subtype of signal
216 | 
217 | 
218 |             
219 | 
220 | 
221 |         Attributes
222 |         ----------
223 |             s (float): the start s value (along road) of the Signal
224 |                
225 |             x (float): start x coordinate of the Signal
226 | 
227 |             id (str): id of the signal
228 | 
229 |             name (str): name of the signal
230 |         Methods
231 |         -------
232 |             get_element()
233 |                 Returns the full ElementTree of the class
234 | 
235 |             get_attributes()
236 |                 Returns a dictionary of all attributes of the class
237 |     """
238 | 
239 |     def __init__(self,s,x,y,heading,geom_type):
240 |         """ initalizes the Signal
241 | 
242 |         Parameters
243 |         ----------
244 |             s (float): the start s value (along road) of the geometry
245 |                
246 |             x (float): start x coordinate of the geometry
247 |                 
248 |             y (float):  start y coordinate of the geometry
249 |                 
250 |             heading (float): heading of the geometry
251 | 
252 |             geom_type (Line, Spiral,ParamPoly3, or Arc): the type of geometry
253 | 
254 |         """ 
255 |         self.s = s
256 |         self.x = x
257 |         self.y = y
258 |   
259 |         
260 |         self.heading = heading
261 |         self.geom_type = geom_type
262 |         _,_,_,self.length = self.geom_type.get_end_data(self.x,self.y,self.heading)
263 | 
264 |     def get_end_data(self):
265 |         return self.geom_type.get_end_data(self.x,self.y,self.heading)
266 |         
267 |     def get_start_data(self):
268 |         x,y,heading,self.length = self.geom_type.get_start_data(self.x,self.y,self.heading)
269 |         self.x = x
270 |         self.y = y
271 |         self.heading = heading + np.pi
272 |         self.s = None
273 |         return x,y,heading,self.length
274 | 
275 |     def set_s(self,s):
276 |         self.s = s
277 | 
278 |     def get_attributes(self):
279 |         """ returns the attributes of the _Geometry as a dict
280 | 
281 |         """
282 |         retdict = {}
283 |         retdict['s'] = str(self.s)
284 |         retdict['x'] = str(self.x)
285 |         retdict['y'] = str(self.y)
286 |         retdict['hdg'] = str(self.heading)
287 |         retdict['length'] = str(self.length)
288 |         return retdict
289 | 
290 | 
291 |     def get_element(self):
292 |         """ returns the elementTree of the _Geometry
293 | 
294 |         """
295 |         element = ET.Element('geometry',attrib=self.get_attributes())
296 |         element.append(self.geom_type.get_element())
297 |         return element
298 |
299 |

Methods

300 |
301 |
302 | def get_attributes(self) 303 |
304 |
305 |

returns the attributes of the _Geometry as a dict

306 |
307 | 308 | Expand source code 309 | 310 |
def get_attributes(self):
311 |     """ returns the attributes of the _Geometry as a dict
312 | 
313 |     """
314 |     retdict = {}
315 |     retdict['s'] = str(self.s)
316 |     retdict['x'] = str(self.x)
317 |     retdict['y'] = str(self.y)
318 |     retdict['hdg'] = str(self.heading)
319 |     retdict['length'] = str(self.length)
320 |     return retdict
321 |
322 |
323 |
324 | def get_element(self) 325 |
326 |
327 |

returns the elementTree of the _Geometry

328 |
329 | 330 | Expand source code 331 | 332 |
def get_element(self):
333 |     """ returns the elementTree of the _Geometry
334 | 
335 |     """
336 |     element = ET.Element('geometry',attrib=self.get_attributes())
337 |     element.append(self.geom_type.get_element())
338 |     return element
339 |
340 |
341 |
342 | def get_end_data(self) 343 |
344 |
345 |
346 |
347 | 348 | Expand source code 349 | 350 |
def get_end_data(self):
351 |     return self.geom_type.get_end_data(self.x,self.y,self.heading)
352 |
353 |
354 |
355 | def get_start_data(self) 356 |
357 |
358 |
359 |
360 | 361 | Expand source code 362 | 363 |
def get_start_data(self):
364 |     x,y,heading,self.length = self.geom_type.get_start_data(self.x,self.y,self.heading)
365 |     self.x = x
366 |     self.y = y
367 |     self.heading = heading + np.pi
368 |     self.s = None
369 |     return x,y,heading,self.length
370 |
371 |
372 |
373 | def set_s(self, s) 374 |
375 |
376 |
377 |
378 | 379 | Expand source code 380 | 381 |
def set_s(self,s):
382 |     self.s = s
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 | 417 |
418 | 421 | 422 | -------------------------------------------------------------------------------- /pyodrx/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | 3 | from .geometry import * 4 | from .helpers import * 5 | from .opendrive import * 6 | from .lane import * 7 | from .enumerations import * 8 | from .links import * 9 | from .generators import * 10 | from .signals_objects import * 11 | -------------------------------------------------------------------------------- /pyodrx/enumerations.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | 5 | class MarkRule(Enum): 6 | """ Enum for MarkRule 7 | """ 8 | no_passing = auto() 9 | caution = auto() 10 | none = auto() 11 | 12 | class LaneType(Enum): 13 | """ Enum for LaneType 14 | """ 15 | none = auto() 16 | driving = auto() 17 | stop = auto() 18 | shoulder = auto() 19 | biking = auto() 20 | sidewalk = auto() 21 | border = auto() 22 | restricted = auto() 23 | parking = auto() 24 | bidirectional = auto() 25 | median = auto() 26 | special1 = auto() 27 | special2 = auto() 28 | special3 = auto() 29 | roadWorks = auto() 30 | tram = auto() 31 | rail = auto() 32 | entry = auto() 33 | exit = auto() 34 | offRamp = auto() 35 | onRamp = auto() 36 | connectingRamp = auto() 37 | bus = auto() 38 | taxi = auto() 39 | HOV = auto() 40 | mwyEntry = auto() 41 | mwyExit = auto() 42 | 43 | class RoadMarkColor(Enum): 44 | """ Enum for RoadMarkColor 45 | """ 46 | standard = auto() 47 | blue = auto() 48 | green = auto() 49 | red = auto() 50 | white = auto() 51 | yellow = auto() 52 | orange = auto() 53 | 54 | 55 | 56 | class RoadMarkWeight(Enum): 57 | """ Enum for RoadMarkWeight 58 | """ 59 | standard = auto() 60 | bold = auto() 61 | 62 | class RoadMarkType(Enum): 63 | """ Enum for RoadMarkType 64 | """ 65 | none = auto() 66 | solid = auto() 67 | broken = auto() 68 | solid_solid = auto() 69 | solid_broken = auto() 70 | broken_solid = auto() 71 | broken_broken = auto() 72 | botts_dots = auto() 73 | grass = auto() 74 | curb = auto() 75 | custom = auto() 76 | edge = auto() 77 | 78 | class RoadType(): 79 | """ Enum for RoadType 80 | """ 81 | unknown = auto() 82 | rural = auto() 83 | motorway = auto() 84 | town = auto() 85 | lowSpeed = auto() 86 | pedestrian = auto() 87 | bicycle = auto() 88 | townExpressway = auto() 89 | townCollector = auto() 90 | townArterial = auto() 91 | townPrivate = auto() 92 | townLocal = auto() 93 | townPlayStreet = auto() 94 | 95 | 96 | class LaneChange(Enum): 97 | """ Enum for LaneChange 98 | """ 99 | increase = auto() 100 | decrease = auto() 101 | both = auto() 102 | none = auto() 103 | 104 | class ElementType(Enum): 105 | """ Enum for LaneChange 106 | """ 107 | road = auto() 108 | junction = auto() 109 | 110 | class ContactPoint(Enum): 111 | """ Enum for ContactPoint 112 | """ 113 | start = auto() 114 | end = auto() 115 | 116 | class Direction(Enum): 117 | """ Enum for Direction 118 | """ 119 | same = auto() 120 | opposite = auto() 121 | 122 | class Orientation(Enum): 123 | """ Enum for Orientation 124 | """ 125 | positive = auto() 126 | negative = auto() 127 | none = auto() 128 | 129 | class ObjectType(Enum): 130 | """ Enum for ObjectType taken from OpenDRIVE 1.6 without deprecated types 131 | """ 132 | none = auto() 133 | obstacle = auto() 134 | pole = auto() 135 | tree = auto() 136 | vegetation = auto() 137 | barrier = auto() 138 | building = auto() 139 | parkingSpace = auto() 140 | patch = auto() 141 | railing = auto() 142 | trafficIsland = auto() 143 | crosswalk = auto() 144 | streetLamp = auto() 145 | gantry = auto() 146 | soundBarrier = auto() 147 | roadMark = auto() 148 | 149 | class Dynamic(Enum): 150 | """ Enum for Dynamic 151 | """ 152 | yes = auto() 153 | no = auto() 154 | 155 | class RoadSide(Enum): 156 | """ Enum for RoadSide 157 | """ 158 | both = auto() 159 | left = auto() 160 | right = auto() -------------------------------------------------------------------------------- /pyodrx/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | class NotSameAmountOfLanesError(Exception): 5 | """ Raised when the amount of Lanes are not the same (used for the automation of linking) 6 | """ 7 | pass 8 | 9 | class NotEnoughInputArguments(Exception): 10 | """ Raised when one of the needed "optional" inputs are not used 11 | """ 12 | pass 13 | 14 | class ToManyOptionalArguments(Exception): 15 | """ Raised when one of the needed "optional" inputs are not used 16 | """ 17 | pass 18 | 19 | class UndefinedRoadNetwork(Exception): 20 | """ Raised when the user haven't connected the roads in a correct way 21 | """ 22 | pass 23 | 24 | class RoadsAndLanesNotAdjusted(Exception): 25 | """ Raised when the user tries to perform an action on non-adjusted lanes and roads that requires adjusted lanes and roads 26 | """ 27 | pass -------------------------------------------------------------------------------- /pyodrx/generators.py: -------------------------------------------------------------------------------- 1 | """ This is a collection of ready to use functions, to generate standard road snipets, like: 2 | - Simple straight road 3 | - Spiral-Arc-Spiral type of turns 4 | - Simple junctions, including the connecting roads 5 | limited to 3/4-way crossings with 90degree turns (3-way can be 120 deg aswell) 6 | - 7 | """ 8 | import numpy as np 9 | 10 | from .lane import Lane, RoadMark, LaneSection, Lanes 11 | from .enumerations import RoadMarkType, MarkRule, ContactPoint, ElementType, ObjectType 12 | 13 | from .geometry import Line, Arc, Spiral, EulerSpiral, PlanView 14 | from .opendrive import Road, OpenDrive 15 | from .links import Junction, Connection, _get_related_lanesection 16 | 17 | 18 | STD_ROADMARK_SOLID = RoadMark(RoadMarkType.solid,0.2) 19 | STD_ROADMARK_BROKEN = RoadMark(RoadMarkType.broken,0.2) 20 | STD_START_CLOTH = 1/1000000000 21 | def standard_lane(offset=3,rm = STD_ROADMARK_BROKEN): 22 | """ standard_lane creates a simple lane with an offset an a roadmark 23 | 24 | Parameters 25 | ---------- 26 | offset (int): width of the lane 27 | default: 3 28 | 29 | rm (RoadMark): road mark used for the standard lane 30 | default: RoadMark(STD_ROADMARK_BROKEN) 31 | Returns 32 | ------- 33 | lane (Lane): the lane 34 | 35 | """ 36 | lc = Lane(a=offset) 37 | lc.add_roadmark(rm) 38 | return lc 39 | 40 | 41 | def create_road(geometry,id,left_lanes = 1, right_lanes = 1,road_type=-1,center_road_mark = STD_ROADMARK_SOLID, lane_width=3): 42 | """ create_road creates a road with one lanesection with different number of lanes, lane marks will be of type broken, 43 | except the outer lane, that will be solid. 44 | 45 | Parameters 46 | ---------- 47 | geometry (Line, Spiral, ParamPoly3, or Arc, or list with these): geometries to build the road 48 | 49 | id (int): id of the new road 50 | 51 | left_lanes (int): number of left lanes wanted 52 | Default: 1 53 | 54 | right_lanes (int): number of right lanes wanted 55 | Default: 1 56 | 57 | road_type (int): type of road, -1 normal road, otherwise connecting road 58 | 59 | center_road_mark (RoadMark): roadmark for the center line 60 | 61 | lane_width (float): the with of all lanes 62 | 63 | Returns 64 | ------- 65 | road (Road): a straight road 66 | """ 67 | pv = PlanView() 68 | raw_length = 0 69 | if isinstance(geometry,list): 70 | for g in geometry: 71 | pv.add_geometry(g) 72 | raw_length += g.length 73 | else: 74 | pv.add_geometry(geometry) 75 | raw_length += geometry.length 76 | 77 | # create centerlane 78 | lc = Lane(a=0) 79 | lc.add_roadmark(center_road_mark) 80 | 81 | lsec = LaneSection(0,lc) 82 | # create left lanes 83 | for i in range(left_lanes): 84 | if i == left_lanes-1: 85 | leftlane = standard_lane(lane_width,STD_ROADMARK_SOLID) 86 | else: 87 | leftlane = standard_lane(lane_width,STD_ROADMARK_BROKEN) 88 | lsec.add_left_lane(leftlane) 89 | 90 | for i in range(right_lanes): 91 | if i == right_lanes-1: 92 | rightlane = standard_lane(lane_width,STD_ROADMARK_SOLID) 93 | else: 94 | rightlane = standard_lane(lane_width,STD_ROADMARK_BROKEN) 95 | lsec.add_right_lane(rightlane) 96 | lanes = Lanes() 97 | lanes.add_lanesection(lsec) 98 | 99 | road = Road(id,pv,lanes,road_type=road_type) 100 | 101 | return road 102 | 103 | def create_straight_road(road_id, length=100,junction = -1, n_lanes=1, lane_offset=3): 104 | """ creates a standard straight road with two lanes 105 | 106 | Parameters 107 | ---------- 108 | road_id (int): id of the road to create 109 | 110 | length (float): length of the road 111 | default: 100 112 | 113 | junction (int): if the road belongs to a junction or not 114 | default: -1 115 | Returns 116 | ------- 117 | road (Road): a straight road 118 | """ 119 | # create geometry 120 | line1 = Line(length) 121 | 122 | # create planviews 123 | planview1 = PlanView() 124 | planview1.add_geometry(line1) 125 | 126 | # create lanesections 127 | lanesec1 = LaneSection(0,standard_lane()) 128 | for i in range(1, n_lanes+1, 1): 129 | lanesec1.add_right_lane(standard_lane(lane_offset)) 130 | lanesec1.add_left_lane(standard_lane(lane_offset)) 131 | 132 | # create lanes 133 | lanes1 = Lanes() 134 | lanes1.add_lanesection(lanesec1) 135 | 136 | # finally create the roads 137 | return Road(road_id,planview1,lanes1,road_type=junction) 138 | 139 | 140 | def create_cloth_arc_cloth(arc_curv, arc_angle, cloth_angle, r_id, junction = 1,cloth_start = STD_START_CLOTH, n_lanes=1, lane_offset=3): 141 | """ creates a curved Road with a Spiral - Arc - Spiral, and two lanes 142 | 143 | Parameters 144 | ---------- 145 | arc_curv (float): curvature of the arc (and max clothoid of clothoids) 146 | 147 | arc_angle (float): how much of the curv should be the arc 148 | 149 | cloth_angle (float): how much of the curv should be the clothoid (will be doubled since there are two clothoids) 150 | 151 | r_id (int): the id of the road 152 | 153 | junction (int): if the Road belongs to a junction 154 | default: 1 155 | 156 | cloth_start (float): staring curvature of clothoids 157 | 158 | Returns 159 | ------- 160 | road (Road): a road built up of a Spiral-Arc-Spiral 161 | """ 162 | 163 | pv = PlanView() 164 | # adjust sign if angle is negative 165 | if cloth_angle < 0 and arc_curv > 0: 166 | 167 | cloth_angle = -cloth_angle 168 | arc_curv = -arc_curv 169 | cloth_start = -cloth_start 170 | arc_angle = -arc_angle 171 | 172 | # create geometries 173 | spiral1 = Spiral(cloth_start, arc_curv, angle=cloth_angle) 174 | arc = Arc(arc_curv, angle=arc_angle ) 175 | spiral2 = Spiral(arc_curv, cloth_start, angle=cloth_angle) 176 | 177 | pv.add_geometry(spiral1) 178 | pv.add_geometry(arc) 179 | pv.add_geometry(spiral2) 180 | 181 | # create lanes 182 | lsec = LaneSection(0,standard_lane()) 183 | for i in range(1, n_lanes+1, 1): 184 | lsec.add_right_lane(standard_lane(lane_offset)) 185 | lsec.add_left_lane(standard_lane(lane_offset)) 186 | lanes = Lanes() 187 | lanes.add_lanesection(lsec) 188 | 189 | # create road 190 | return Road(r_id,pv,lanes,road_type=junction) 191 | 192 | def get_lanes_offset(road1, road2, contactpoint): 193 | """ returns number of lanes (hp #left lanes = # right lanes) and their offset (hp offset is constant) 194 | 195 | 196 | Parameters 197 | ---------- 198 | road1 (Road): first road 199 | 200 | road2 (Road): second road 201 | 202 | Returns 203 | ------- 204 | n_lanes (int): 205 | 206 | lane_offset (int): 207 | """ 208 | #now we always look at lanesection[0] to take the number of lanes 209 | #TO DO - understand if the roads are connect through end or start and then take the relative lane section 210 | if contactpoint == ContactPoint.end: 211 | n_lanesection = 0 212 | else: 213 | n_lanesection = -1 214 | if len(road1.lanes.lanesections[n_lanesection].leftlanes) == len(road2.lanes.lanesections[0].leftlanes) and len(road1.lanes.lanesections[n_lanesection].rightlanes) == len(road2.lanes.lanesections[0].rightlanes): 215 | n_lanes = len(road1.lanes.lanesections[n_lanesection].leftlanes) 216 | lane_offset = road1.lanes.lanesections[n_lanesection].leftlanes[0].a 217 | else: 218 | raise NotSameAmountOfLanesError('Incoming road ',road1.id, ' and outcoming road ', road2.id, 'do not have the same number of left lanes.') 219 | 220 | return n_lanes, lane_offset 221 | 222 | 223 | 224 | 225 | def create_junction_roads(roads,angles,r,junction=1,spiral_part = 1/3, arc_part = 1/3,startnum=100): 226 | """ creates all needed roads for some simple junctions 227 | - 3way crossings (either a T junction, or 120 deg junction) 228 | - 4way crossing (all 90 degree turns) 229 | 230 | Parameters 231 | ---------- 232 | roads (list of Road): all roads that should go into the junction 233 | 234 | angles (list of float): the angles which the roads should be going out (see description for what is supported), 235 | should be defined mathimatically positive (incoming road 0) 236 | 237 | r (float): the radius of the arcs in the junction (will determine the size of the junction) 238 | 239 | junction (int): the id of the junction 240 | default: 1 241 | 242 | spiral_part (float): the part of the curve that should be spirals (two of these) spiral_part*2 + arcpart = angle of the turn 243 | default: (1/3) 244 | 245 | arc_part (float): the part of the curve that should be an arc: spiral_part*2 + arcpart = angle of the turn 246 | default: (1/3) 247 | 248 | startnum (int): start number of the roads in the junctions (will increase with 1 for each road) 249 | 250 | Returns 251 | ------- 252 | junction_roads (list of Road): a list of all roads needed for all traffic connecting the roads 253 | """ 254 | 255 | # if a straight line is used, calculate the length of it. Some Spiral Magic going on... 256 | # http://www.jerrymahun.com/index.php/home/open-access/viii-curves/76-chapter-e-spirals?showall=1 257 | 258 | angle = np.pi/2 259 | angle_cloth = angle*spiral_part 260 | spiral_length = 2*abs(angle_cloth*r) 261 | 262 | spiral = EulerSpiral.createFromLengthAndCurvature(spiral_length, STD_START_CLOTH, 1/r) 263 | (X, Y, _) = spiral.calc(spiral_length, 0, 0, STD_START_CLOTH, 0) 264 | 265 | X0 = X-r*np.sin(angle_cloth) 266 | Y0 = Y-r*(1-np.cos(angle_cloth)) 267 | linelength = 2*(X0 + r + Y0) 268 | 269 | junction_roads = [] 270 | 271 | # loop over the roads to get all possible combinations of connecting roads 272 | for i in range(len(roads)-1): 273 | # for now the first road is place as base, 274 | if i == 0: 275 | cp = ContactPoint.end 276 | roads[i].add_successor(ElementType.junction,junction) 277 | else: 278 | cp = ContactPoint.start 279 | roads[i].add_predecessor(ElementType.junction,junction) 280 | 281 | for j in range(1+i,len(roads)): 282 | # check angle needed for junction 283 | an = np.sign(angles[j]-angles[i]-np.pi) 284 | an1 = angles[j]-angles[i] -np.pi 285 | angle_arc = an1*arc_part 286 | 287 | angle_cloth = an1*spiral_part 288 | 289 | #adjust angle if multiple of pi 290 | if an1 > np.pi: 291 | an1 = -(2*np.pi - an1) 292 | 293 | # create road, either straight or curved 294 | n_lanes, lanes_offset = get_lanes_offset(roads[i], roads[j], cp ) 295 | if an == 0: 296 | print('n_lanes is ', n_lanes) 297 | print('lane offset is ', lanes_offset ) 298 | tmp_junc = create_straight_road(startnum,length= linelength,junction=junction, n_lanes=n_lanes, lane_offset=lanes_offset) 299 | else: 300 | tmp_junc = create_cloth_arc_cloth( 1/r , angle_arc , angle_cloth , startnum , junction, n_lanes=n_lanes, lane_offset=lanes_offset ) 301 | 302 | # add predecessor and successor 303 | tmp_junc.add_predecessor(ElementType.road,roads[i].id,cp) 304 | tmp_junc.add_successor(ElementType.road,roads[j].id,ContactPoint.start) 305 | startnum += 1 306 | junction_roads.append(tmp_junc) 307 | 308 | # add junction to the last road aswell since it's not part of the loop 309 | roads[-1].add_predecessor(ElementType.junction,junction) 310 | 311 | return junction_roads 312 | 313 | def _create_junction_links(connection, nlanes,r_or_l,sign,from_offset=0,to_offset=0): 314 | """ helper function to create junction links 315 | 316 | Parameters 317 | ---------- 318 | connection (Connection): the connection to fill 319 | 320 | nlanes (int): number of lanes 321 | 322 | r_or_l (1 or -1): if the lane should start from -1 or 1 323 | 324 | sign (1 or -1): if the sign should change 325 | 326 | from_offset (int): if there is an offset in the beginning 327 | Default: 0 328 | 329 | to_offset (int): if there is an offset in the end of the road 330 | Default: 0 331 | """ 332 | for i in range(1, nlanes+1, 1): 333 | connection.add_lanelink( r_or_l*i+from_offset, r_or_l*sign*i+to_offset) 334 | 335 | 336 | def create_junction(junction_roads, id, roads): 337 | """ create_junction creates the junction struct for a set of roads 338 | 339 | 340 | Parameters 341 | ---------- 342 | junction_roads (list of Road): all connecting roads in the junction 343 | 344 | id (int): the id of the junction 345 | 346 | roads (list of Road): all incomming roads to the junction 347 | 348 | Returns 349 | ------- 350 | junction (Junction): the junction struct ready to use 351 | 352 | """ 353 | 354 | 355 | 356 | junc = Junction('my junction',id) 357 | 358 | for jr in junction_roads: 359 | # handle succesor lanes 360 | conne1 = Connection(jr.successor.element_id,jr.id,ContactPoint.end) 361 | _, sign, _ = _get_related_lanesection(jr,get_road_by_id(roads,jr.successor.element_id) ) 362 | 363 | _create_junction_links(conne1,len(jr.lanes.lanesections[-1].rightlanes),-1,sign,to_offset=jr.lane_offset_suc) 364 | _create_junction_links(conne1,len(jr.lanes.lanesections[-1].leftlanes),1,sign,to_offset=jr.lane_offset_suc) 365 | junc.add_connection(conne1) 366 | 367 | # handle predecessor lanes 368 | conne2 = Connection(jr.predecessor.element_id,jr.id,ContactPoint.start) 369 | _, sign, _ = _get_related_lanesection( jr,get_road_by_id(roads,jr.predecessor.element_id)) 370 | _create_junction_links(conne2,len(jr.lanes.lanesections[0].rightlanes),-1,sign,from_offset=jr.lane_offset_pred) 371 | _create_junction_links(conne2,len(jr.lanes.lanesections[0].leftlanes),1,sign,from_offset=jr.lane_offset_pred) 372 | junc.add_connection(conne2) 373 | return junc 374 | 375 | def get_road_by_id(roads,id): 376 | """ get_road_by_id returns a road based on the road id 377 | 378 | Parameters 379 | ---------- 380 | roads (list of Roads): a list of roads to seach through 381 | 382 | id (int): the id of the road wanted 383 | 384 | Returns 385 | ------- 386 | Road 387 | """ 388 | for r in roads: 389 | if r.id == id: 390 | return r -------------------------------------------------------------------------------- /pyodrx/helpers.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | import xml.dom.minidom as mini 3 | 4 | 5 | import os 6 | 7 | 8 | def run_road(opendrive,esminipath = 'esmini',density=15): 9 | """ write a scenario and runs it in esminis OpenDriveViewer with some random traffic 10 | Parameters 11 | ---------- 12 | opendrive (OpenDrive): the pyodrx road to run 13 | 14 | esminipath (str): the path to esmini 15 | Default: pyoscx 16 | 17 | """ 18 | _scenariopath = os.path.join(esminipath,'resources','xodr') 19 | print(_scenariopath) 20 | opendrive.write_xml(os.path.join(_scenariopath,'pythonroad.xodr'),True) 21 | 22 | if os.name == 'posix': 23 | os.system(os.path.join('.', esminipath, 'bin','odrviewer') + ' --odr ' + os.path.join(esminipath,'resources','xodr','pythonroad.xodr') + ' --osi_features on --clear-color 0.2,0.2,0.2 --window 60 60 800 400 --density ' + str(density) ) 24 | elif os.name == 'nt': 25 | os.system(os.path.join(esminipath,'bin','odrviewer.exe') + ' --odr ' + os.path.join(esminipath,'resources','xodr','pythonroad.xodr') + ' --osi_features on --clear-color 0.2,0.2,0.2 --window 60 60 800 400 --density ' + str(density) ) 26 | 27 | 28 | def enum2str(enum): 29 | """ helper to create strings from enums that should contain space but have to have _ 30 | 31 | Parameters 32 | ---------- 33 | enum (Enum): a enum of pyodrx 34 | 35 | Returns 36 | ------- 37 | enumstr (str): the enum as a string replacing _ with ' ' 38 | 39 | """ 40 | return enum.name.replace('_',' ') 41 | 42 | def prettyprint(element): 43 | """ prints the element to the commandline 44 | 45 | Parameters 46 | ---------- 47 | element (Element): element to print 48 | 49 | """ 50 | rough = ET.tostring(element,'utf-8') 51 | reparsed = mini.parseString(rough) 52 | print(reparsed.toprettyxml(indent="\t")) 53 | 54 | 55 | def printToFile(element,filename,prettyprint=True): 56 | """ prints the element to a xml file 57 | 58 | Parameters 59 | ---------- 60 | element (Element): element to print 61 | 62 | filename (str): file to save to 63 | 64 | prettyprint (bool): pretty or "ugly" print 65 | 66 | """ 67 | if prettyprint: 68 | rough = ET.tostring(element,'utf-8').replace(b'\n',b'').replace(b'\t',b'') 69 | reparsed = mini.parseString(rough) 70 | towrite = reparsed.toprettyxml(indent="\t") 71 | with open(filename,'w') as file_handle: 72 | file_handle.write(towrite) 73 | else: 74 | tree = ET.ElementTree(element) 75 | with open(filename,"wb") as file_handle: 76 | tree.write(file_handle) -------------------------------------------------------------------------------- /pyodrx/lane.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | from .helpers import enum2str 3 | from .enumerations import LaneType, LaneChange, RoadMarkWeight, RoadMarkColor, RoadMarkType, MarkRule 4 | from .links import _Links,_Link 5 | 6 | 7 | class Lanes(): 8 | """ creates the Lanes element of opendrive 9 | 10 | 11 | Attributes 12 | ---------- 13 | lane_sections (list of LaneSection): a list of all lanesections 14 | 15 | Methods 16 | ------- 17 | get_element(elementname) 18 | Returns the full ElementTree of the class 19 | 20 | add_lanesection(lanesection) 21 | adds a lane section to Lanes 22 | """ 23 | def __init__(self): 24 | """ initalize Lanes 25 | 26 | """ 27 | self.lanesections = [] 28 | def add_lanesection(self,lanesection, lanelinks=None): 29 | """ creates the Lanes element of opendrive 30 | 31 | 32 | Parameters 33 | ---------- 34 | lanesection (LaneSection): a LaneSection to add 35 | 36 | lanelink (LaneLinker): (optional) a LaneLink to add 37 | 38 | """ 39 | # add links to the lanes 40 | if lanelinks: 41 | #loop over all links 42 | if not isinstance(lanelinks, list): 43 | lanelinks = [lanelinks] 44 | for lanelink in lanelinks: 45 | for link in lanelink.links: 46 | # check if link already added 47 | if not link.used: 48 | print(link) 49 | link.predecessor.add_link('successor',link.successor.lane_id) 50 | link.successor.add_link('predecessor',link.predecessor.lane_id) 51 | link.used = True 52 | 53 | self.lanesections.append(lanesection) 54 | 55 | def get_element(self): 56 | """ returns the elementTree of Lanes 57 | 58 | """ 59 | element = ET.Element('lanes') 60 | for l in self.lanesections: 61 | element.append(l.get_element()) 62 | return element 63 | 64 | class LaneSection(): 65 | """ Creates the LaneSection element of opendrive 66 | 67 | Parameters 68 | ---------- 69 | s (float): start of lanesection 70 | 71 | centerlane (Lane): the centerline of the road 72 | 73 | Attributes 74 | ---------- 75 | s (float): start of lanesection 76 | 77 | centerlane (Lane): the centerline of the road 78 | 79 | leftlanes (list of Lane): the lanes left to the center 80 | 81 | rightlanes (list of Lane): the lanes right to the center 82 | 83 | Methods 84 | ------- 85 | get_element() 86 | Returns the full ElementTree of the class 87 | 88 | get_attributes() 89 | Returns a dictionary of all attributes of class 90 | 91 | add_left_lane(Lane) 92 | adds a new lane to the left 93 | 94 | add_right_lane(Lane) 95 | adds a new lane to the right 96 | """ 97 | 98 | def __init__(self,s,centerlane): 99 | """ initalize the LaneSection 100 | 101 | Parameters 102 | ---------- 103 | s (float): start of lanesection 104 | 105 | centerlane (Lane): the centerline of the road 106 | """ 107 | self.s = s 108 | self.centerlane = centerlane 109 | self.centerlane._set_lane_id(0) 110 | self.leftlanes = [] 111 | self.rightlanes = [] 112 | self._left_id = 1 113 | self._right_id = -1 114 | 115 | 116 | def add_left_lane(self,lane): 117 | """ adds a lane to the left of the center, add from center outwards 118 | 119 | Parameters 120 | ---------- 121 | lane (Lane): the lane to add 122 | """ 123 | lane._set_lane_id(self._left_id) 124 | self._left_id += 1 125 | self.leftlanes.append(lane) 126 | 127 | def add_right_lane(self,lane): 128 | """ adds a lane to the right of the center, add from center outwards 129 | 130 | Parameters 131 | ---------- 132 | lane (Lane): the lane to add 133 | """ 134 | lane._set_lane_id(self._right_id) 135 | self._right_id -= 1 136 | self.rightlanes.append(lane) 137 | 138 | def get_attributes(self): 139 | """ returns the attributes of the Lane as a dict 140 | 141 | """ 142 | retdict = {} 143 | retdict['s'] = str(self.s) 144 | return retdict 145 | 146 | def get_element(self): 147 | """ returns the elementTree of the WorldPostion 148 | 149 | """ 150 | element = ET.Element('laneSection',attrib=self.get_attributes()) 151 | 152 | if self.leftlanes: 153 | left = ET.SubElement(element,'left') 154 | for l in self.leftlanes: 155 | left.append(l.get_element()) 156 | 157 | 158 | center = ET.SubElement(element,'center') 159 | center.append(self.centerlane.get_element()) 160 | 161 | if self.rightlanes: 162 | right = ET.SubElement(element,'right') 163 | for l in self.rightlanes: 164 | right.append(l.get_element()) 165 | 166 | return element 167 | 168 | 169 | class Lane(): 170 | """ creates a Lane of opendrive 171 | 172 | the inputs are on the following format: 173 | f(s) = a + b*s + c*s^2 + d*s^3 174 | 175 | Parameters 176 | ---------- 177 | 178 | lane_type (LaneType): type of lane 179 | Default: LaneType.driving 180 | 181 | a (float): a coefficient 182 | Default: 0 183 | 184 | b (float): b coefficient 185 | Default: 0 186 | 187 | c (float): c coefficient 188 | Default: 0 189 | 190 | d (float): d coefficient 191 | Default: 0 192 | 193 | soffset (float): soffset of lane 194 | Default: 0 195 | 196 | 197 | Attributes 198 | ---------- 199 | lane_id (int): id of the lane (automatically assigned by LaneSection) 200 | 201 | lane_type (LaneType): type of lane 202 | 203 | a (float): a coefficient 204 | 205 | b (float): b coefficient 206 | 207 | c (float): c coefficient 208 | 209 | d (float): d coefficient 210 | 211 | soffset (float): soffset of lane 212 | 213 | roadmark (RoadMark): roadmarks related to the lane 214 | 215 | links (_Links): Lane links to the lane 216 | 217 | Methods 218 | ------- 219 | get_element(elementname) 220 | Returns the full ElementTree of the class 221 | 222 | get_attributes() 223 | Returns a dictionary of all attributes of class 224 | 225 | add_roadmark(roadmark) 226 | adds a new roadmark to the lane 227 | 228 | """ 229 | def __init__(self,lane_type=LaneType.driving,a=0,b=0,c=0,d=0,soffset=0): 230 | """ initalizes the Lane 231 | 232 | Parameters 233 | ---------- 234 | 235 | lane_type (LaneType): type of lane 236 | Default: LaneType.driving 237 | 238 | a (float): a coefficient 239 | Default: 0 240 | 241 | b (float): b coefficient 242 | Default: 0 243 | 244 | c (float): c coefficient 245 | Default: 0 246 | 247 | d (float): d coefficient 248 | Default: 0 249 | 250 | soffset (float): soffset of lane 251 | Default: 0 252 | 253 | """ 254 | self.lane_id = None 255 | self.lane_type = lane_type 256 | self.a = a 257 | self.b = b 258 | self.c = c 259 | self.d = d 260 | self.soffset = soffset 261 | self.roadmark = None 262 | self.links = _Links() 263 | 264 | 265 | #TODO: add more features to add for lane 266 | def add_link(self,link_type,id): 267 | """ adds a link to the lane section 268 | 269 | Parameters 270 | ---------- 271 | link_type (str): type of link, successor or predecessor 272 | 273 | id (str/id): id of the linked lane 274 | """ 275 | self.links.add_link(_Link(link_type,str(id))) 276 | 277 | def _set_lane_id(self,lane_id): 278 | """ set the lane id of the lane 279 | 280 | """ 281 | self.lane_id = lane_id 282 | 283 | def add_roadmark(self,roadmark): 284 | """ add_roadmark adds a roadmark to the lane 285 | 286 | Parameters 287 | ---------- 288 | roadmark (RoadMark): roadmark of the lane 289 | 290 | """ 291 | self.roadmark = roadmark 292 | 293 | 294 | def get_attributes(self): 295 | """ returns the attributes of the Lane as a dict 296 | 297 | """ 298 | retdict = {} 299 | if self.lane_id == None: 300 | raise ValueError('lane id is not set correctly.') 301 | retdict['id'] = str(self.lane_id) 302 | retdict['type'] = enum2str(self.lane_type) 303 | retdict['level'] = 'false' 304 | return retdict 305 | 306 | def get_element(self): 307 | """ returns the elementTree of the WorldPostion 308 | 309 | """ 310 | element = ET.Element('lane',attrib=self.get_attributes()) 311 | element.append(self.links.get_element()) 312 | 313 | widthdict = {} 314 | widthdict['a'] = str(self.a) 315 | widthdict['b'] = str(self.b) 316 | widthdict['c'] = str(self.c) 317 | widthdict['d'] = str(self.d) 318 | widthdict['sOffset'] = str(self.soffset) 319 | 320 | ET.SubElement(element,'width',attrib=widthdict) 321 | if self.roadmark: 322 | element.append(self.roadmark.get_element()) 323 | return element 324 | 325 | 326 | 327 | 328 | class RoadLine(): 329 | """ creates a Line type of to be used in roadmark 330 | 331 | Parameters 332 | ---------- 333 | width (float): with of the line 334 | Default: 0 335 | length (float): length of the line 336 | Default: 0 337 | space (float): length of space between (broken) lines 338 | Default: 0 339 | toffset (float): offset in t 340 | Default: 0 341 | soffset (float): offset in s 342 | Default: 0 343 | rule (MarkRule): mark rule (optional) 344 | 345 | color (RoadMarkColor): color of line (optional) 346 | 347 | Attributes 348 | ---------- 349 | length (float): length of the line 350 | 351 | space (float): length of space between (broken) lines 352 | 353 | toffset (float): offset in t 354 | 355 | soffset (float): offset in s 356 | 357 | rule (MarkRule): mark rule 358 | 359 | width (float): with of the line 360 | 361 | color (RoadMarkColor): color of line 362 | 363 | Methods 364 | ------- 365 | get_element(elementname) 366 | Returns the full ElementTree of the class 367 | 368 | get_attributes() 369 | Returns a dictionary of all attributes of FileHeader 370 | 371 | """ 372 | # TODO: check this for 1.5 373 | def __init__(self,width = 0,length=0,space=0,toffset=0,soffset=0,rule=None,color=None): 374 | """ initalizes the RoadLine 375 | 376 | Parameters 377 | ---------- 378 | width (float): with of the line 379 | Default: 0 380 | length (float): length of the line 381 | Default: 0 382 | space (float): length of space between (broken) lines 383 | Default: 0 384 | toffset (float): offset in t 385 | Default: 0 386 | soffset (float): offset in s 387 | Default: 0 388 | rule (MarkRule): mark rule (optional) 389 | 390 | color (RoadMarkColor): color of line (optional) 391 | 392 | 393 | """ 394 | self.length = length 395 | self.space = space 396 | self.toffset = toffset 397 | self.rule = rule 398 | self.soffset = soffset 399 | self.width = width 400 | self.color = color 401 | 402 | 403 | 404 | 405 | 406 | def get_attributes(self): 407 | """ returns the attributes of the Lane as a dict 408 | 409 | """ 410 | retdict = {} 411 | retdict['length'] = str(self.length) 412 | retdict['space'] = str(self.space) 413 | retdict['tOffset'] = str(self.toffset) 414 | retdict['width'] = str(self.width) 415 | retdict['sOffset'] = str(self.soffset) 416 | # if self.color: 417 | # retdict['color'] = enum2str(self.color) 418 | if self.rule: 419 | retdict['rule'] = enum2str(self.rule) 420 | return retdict 421 | 422 | def get_element(self): 423 | """ returns the elementTree of the WorldPostion 424 | 425 | """ 426 | element = ET.Element('line',attrib=self.get_attributes()) 427 | return element 428 | 429 | 430 | class RoadMark(): 431 | """ creates a RoadMark of opendrive 432 | 433 | Parameters 434 | ---------- 435 | marking_type (RoadMarkType): the type of marking 436 | 437 | width (float): with of the line 438 | 439 | length (float): length of the line 440 | Default: 0 441 | toffset (float): offset in t 442 | Default: 0 443 | soffset (float): offset in s 444 | Default: 0 445 | rule (MarkRule): mark rule (optional) 446 | 447 | color (RoadMarkColor): color of line (optional) 448 | 449 | Attributes 450 | ---------- 451 | marking_type (str): the type of marking 452 | 453 | width (float): with of the line 454 | 455 | length (float): length of the line 456 | Default: 0 457 | toffset (float): offset in t 458 | Default: 0 459 | soffset (float): offset in s 460 | Default: 0 461 | rule (MarkRule): mark rule (optional) 462 | 463 | color (RoadMarkColor): color of line (optional) 464 | 465 | Methods 466 | ------- 467 | get_element(elementname) 468 | Returns the full ElementTree of the class 469 | 470 | get_attributes() 471 | Returns a dictionary of all attributes of FileHeader 472 | 473 | add_roadmark(roadmark) 474 | adds a new roadmark to the lane 475 | 476 | """ 477 | def __init__(self,marking_type,width=None,length=None,space=None,toffset=None,soffset=0,rule=None,color=RoadMarkColor.standard,marking_weight=RoadMarkWeight.standard,height=0.02,laneChange=None): 478 | """ initializes the RoadMark 479 | 480 | Parameters 481 | ---------- 482 | marking_type (str): the type of marking 483 | 484 | width (float): width of the marking / line 485 | Default: None 486 | length (float): length of the visible, marked part of the line 487 | Default: None 488 | space (float): length of the invisible, unmarked part of the line 489 | Default: None 490 | toffset (float): offset in t 491 | Default: None 492 | soffset (float): offset in s 493 | Default: 0 494 | rule (MarkRule): mark rule (optional) 495 | Default: None 496 | color (RoadMarkColor): color of marking 497 | Default: 'standard' 498 | marking_weight (str): the weight of marking 499 | Default: standard 500 | height (float): thickness of marking 501 | Default: 0.02 502 | laneChange (LaneChange): indicates direction in which lane change is allowed 503 | Default: none 504 | 505 | """ 506 | #required arguments - must be provided by user 507 | self.marking_type = marking_type 508 | 509 | #required arguments - must be provided by user or taken from defaults 510 | self.marking_weight = marking_weight 511 | self.color = color 512 | self.soffset = soffset 513 | self.height = height 514 | self.laneChange = laneChange 515 | 516 | #optional arguments - roadmark is valid without them being defined 517 | self.width = width 518 | self.length = length 519 | self.space = space 520 | self.toffset = toffset 521 | self.rule = rule 522 | 523 | 524 | #TODO: there may be more line child elements per roadmark, which is currently unsupported 525 | self._line = None 526 | #check if arguments were passed that require line child element 527 | if any([length, space, toffset, rule]): 528 | #set defaults in case no values were provided 529 | #values for broken lines 530 | if marking_type == RoadMarkType.broken: 531 | self.length = length or 3 532 | self.space = space or 3 533 | #values for solid lines 534 | elif marking_type == RoadMarkType.solid: 535 | self.length = length or 3 536 | self.space = space or 0 537 | #create empty line if arguments are missing 538 | else: 539 | self.length = length or 0 540 | self.space = length or 0 541 | print ("No defaults for arguments 'space' and 'length' for roadmark type", enum2str(marking_type), "available and no values were passed. Creating an empty roadmark.") 542 | #set remaining defaults 543 | self.width = width or 0.2 544 | self.toffset = toffset or 0 545 | self.rule = rule or MarkRule.none 546 | self._line = RoadLine(self.width,self.length,self.space,self.toffset,self.soffset,self.rule,self.color) 547 | 548 | def get_attributes(self): 549 | """ returns the attributes of the Lane as a dict 550 | 551 | """ 552 | retdict = {} 553 | retdict['sOffset'] = str(self.soffset) 554 | retdict['type'] = enum2str(self.marking_type) 555 | retdict['weight'] = enum2str(self.marking_weight) 556 | retdict['color'] = enum2str(self.color) 557 | retdict['height'] = str(self.height) 558 | if self.width is not None: 559 | retdict['width'] = str(self.width) 560 | if self.laneChange is not None: 561 | retdict['laneChange'] = enum2str(self.laneChange) 562 | return retdict 563 | 564 | def get_element(self): 565 | """ returns the elementTree of the WorldPostion 566 | 567 | """ 568 | element = ET.Element('roadMark',attrib=self.get_attributes()) 569 | if self._line != None: 570 | typeelement = ET.SubElement(element,'type', attrib={'name':enum2str(self.marking_type),'width':str(self.width)}) 571 | typeelement.append(self._line.get_element()) 572 | return element 573 | 574 | 575 | -------------------------------------------------------------------------------- /pyodrx/links.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | from .helpers import enum2str 3 | from .enumerations import ElementType 4 | 5 | from .exceptions import NotSameAmountOfLanesError 6 | 7 | class _Links(): 8 | """ Link creates a Link element used for roadlinking in OpenDrive 9 | 10 | Parameters 11 | ---------- 12 | 13 | Attributes 14 | ---------- 15 | links (_Link): all links added 16 | 17 | Methods 18 | ------- 19 | get_element() 20 | Returns the full ElementTree of the class 21 | 22 | add_link(link) 23 | adds a link to links 24 | 25 | """ 26 | def __init__(self): 27 | """ initalize the _Links 28 | 29 | """ 30 | 31 | self.links = [] 32 | 33 | def add_link(self,link): 34 | """ Adds a _Link 35 | 36 | Parameters 37 | ---------- 38 | link (_Link): a link to be added to the Links 39 | 40 | """ 41 | self.links.append(link) 42 | def get_predecessor_contact_point(self): 43 | """ returns the predecessor contact_point of the link (if exists) 44 | 45 | Return 46 | id (int): id of the predecessor road 47 | """ 48 | retval = None 49 | for l in self.links: 50 | if l.link_type == 'predecessor': 51 | retval = l.contact_point 52 | return retval 53 | def get_successor_contact_point(self): 54 | """ returns the successor contact_point of the link (if exists) 55 | 56 | Return 57 | id (int): id of the successor road (None if no successor available) 58 | """ 59 | retval = None 60 | for l in self.links: 61 | if l.link_type == 'successor': 62 | retval = l.contact_point 63 | return retval 64 | def get_predecessor_type(self): 65 | """ returns the predecessor id of the link (if exists) 66 | 67 | Return 68 | id (int): id of the predecessor road 69 | """ 70 | retval = None 71 | for l in self.links: 72 | if l.link_type == 'predecessor': 73 | retval = l.element_type 74 | return retval 75 | def get_successor_type(self): 76 | """ returns the successor id of the link (if exists) 77 | 78 | Return 79 | id (int): id of the successor road (None if no successor available) 80 | """ 81 | retval = None 82 | for l in self.links: 83 | if l.link_type == 'successor': 84 | retval = l.element_type 85 | return retval 86 | def get_predecessor_id(self): 87 | """ returns the predecessor id of the link (if exists) 88 | 89 | Return 90 | id (int): id of the predecessor road 91 | """ 92 | retval = None 93 | for l in self.links: 94 | if l.link_type == 'predecessor': 95 | retval = l.element_id 96 | return retval 97 | 98 | def get_successor_id(self): 99 | """ returns the successor id of the link (if exists) 100 | 101 | Return 102 | id (int): id of the successor road (None if no successor available) 103 | """ 104 | retval = None 105 | for l in self.links: 106 | if l.link_type == 'successor': 107 | retval = l.element_id 108 | return retval 109 | def get_element(self): 110 | """ returns the elementTree of the _Link 111 | 112 | """ 113 | 114 | element = ET.Element('link') 115 | for l in self.links: 116 | element.append(l.get_element()) 117 | return element 118 | 119 | 120 | class _Link(): 121 | """ Link creates a predecessor/successor/neghbor element used for Links in OpenDrive 122 | 123 | Parameters 124 | ---------- 125 | link_type (str): the type of link (successor, predecessor, or neighbor) 126 | 127 | element_id (str): name of the linked road 128 | 129 | element_type (ElementType): type of element the linked road 130 | Default: None 131 | 132 | contact_point (ContactPoint): the contact point of the link 133 | Default: None 134 | 135 | direction (Direction): the direction of the link (used for neighbor) 136 | Default: None 137 | 138 | Attributes 139 | ---------- 140 | link_type (str): the type of link (successor, predecessor, or neighbor) 141 | 142 | element_type (ElementType): type of element the linked road 143 | 144 | element_id (str): name of the linked road 145 | 146 | contact_point (ContactPoint): the contact point of the link (used for successor and predecessor) 147 | 148 | direction (Direction): the direction of the link (used for neighbor) 149 | 150 | Methods 151 | ------- 152 | get_element() 153 | Returns the full ElementTree of the class 154 | 155 | get_attributes() 156 | Returns a dictionary of all attributes of the class 157 | 158 | """ 159 | 160 | def __init__(self,link_type,element_id,element_type=None,contact_point=None,direction=None): 161 | """ initalize the _Link 162 | 163 | Parameters 164 | ---------- 165 | link_type (str): the type of link (successor, predecessor, or neighbor) 166 | 167 | element_id (str): name of the linked road 168 | 169 | element_type (ElementType): type of element the linked road 170 | Default: None 171 | 172 | contact_point (ContactPoint): the contact point of the link 173 | Default: None 174 | 175 | direction (Direction): the direction of the link (used for neighbor) 176 | Default: None 177 | """ 178 | 179 | 180 | if link_type == 'neighbor': 181 | if direction == None: 182 | raise ValueError('direction has to be defined for neighbor') 183 | 184 | self.link_type = link_type 185 | self.element_type = element_type 186 | self.element_id = element_id 187 | self.contact_point = contact_point 188 | self.direction = direction 189 | 190 | def get_attributes(self): 191 | """ returns the attributes as a dict of the _Link 192 | 193 | """ 194 | retdict = {} 195 | if self.element_type == None: 196 | retdict['id'] = str(self.element_id) 197 | else: 198 | retdict['elementType'] = enum2str(self.element_type) 199 | retdict['elementId'] = str(self.element_id) 200 | 201 | if self.contact_point: 202 | retdict['contactPoint'] = enum2str(self.contact_point) 203 | elif self.link_type == 'neighbor': 204 | retdict['direction'] = enum2str(self.direction) 205 | return retdict 206 | 207 | def get_element(self): 208 | """ returns the elementTree of the _Link 209 | 210 | """ 211 | element = ET.Element(self.link_type,attrib=self.get_attributes()) 212 | return element 213 | 214 | class LaneLinker(): 215 | """ LaneLinker stored information for linking lane sections 216 | 217 | Parameters 218 | ---------- 219 | 220 | Attributes 221 | ---------- 222 | links: all lane links added (predlane (Lane), succlane (Lane), found=bool) 223 | 224 | Methods 225 | ------- 226 | add_link(predlane, succlane) 227 | adds a lane link 228 | 229 | """ 230 | def __init__(self): 231 | """ initalize the _Links 232 | 233 | """ 234 | 235 | self.links = [] 236 | 237 | def add_link(self,predlane, succlane,connecting_road=None): 238 | """ Adds a _Link 239 | 240 | Parameters 241 | ---------- 242 | predlane (Lane): predecessor lane 243 | 244 | succlane (Lane): successor lane 245 | 246 | connecting_road (id): id of a connecting road (used for junctions) 247 | 248 | """ 249 | self.links.append(_lanelink(predlane,succlane,connecting_road)) 250 | 251 | class _lanelink(): 252 | """ helper class for LaneLinker 253 | 254 | """ 255 | def __init__(self,predecessor,successor,connecting_road): 256 | self.predecessor = predecessor 257 | self.successor = successor 258 | self.connecting_road = connecting_road 259 | self.used = False 260 | 261 | 262 | class Connection(): 263 | """ Connection creates a connection as a base of junction 264 | 265 | Parameters 266 | ---------- 267 | incoming_road (int): the id of the incoming road to the junciton 268 | 269 | connecting_road (int): id of the connecting road (type junction) 270 | 271 | contact_point (ContactPoint): the contact point of the link 272 | 273 | id (int): id of the junction (automated?) 274 | 275 | Attributes 276 | ---------- 277 | incoming_road (int): the id of the incoming road to the junciton 278 | 279 | connecting_road (int): id of the connecting road (type junction) 280 | 281 | contact_point (ContactPoint): the contact point of the link 282 | 283 | id (int): id of the connection (automated?) 284 | 285 | links (list of tuple(int) ): a list of all lanelinks in the connection 286 | 287 | Methods 288 | ------- 289 | get_element() 290 | Returns the full ElementTree of the class 291 | 292 | get_attributes() 293 | Returns a dictionary of all attributes of the class 294 | 295 | add_lanelink(in_lane,out_lane) 296 | Adds a lane link to the connection 297 | """ 298 | 299 | def __init__(self,incoming_road,connecting_road,contact_point,id=None): 300 | """ initalize the Connection 301 | 302 | Parameters 303 | ---------- 304 | incoming_road (int): the id of the incoming road to the junciton 305 | 306 | connecting_road (int): id of the connecting road (type junction) 307 | 308 | contact_point (ContactPoint): the contact point of the link 309 | 310 | id (int): id of the junction (automated) 311 | """ 312 | 313 | self.incoming_road = incoming_road 314 | self.connecting_road = connecting_road 315 | self.contact_point = contact_point 316 | self.id = id 317 | self.links = [] 318 | 319 | def _set_id(self,id): 320 | """ id is set 321 | 322 | Parameters 323 | ---------- 324 | id (int): the id of the connection 325 | """ 326 | if self.id == None: 327 | self.id = id 328 | 329 | def add_lanelink(self,in_lane,out_lane): 330 | """ Adds a new link to the connection 331 | 332 | Parameters 333 | ---------- 334 | in_lane: lane id of the incoming road 335 | 336 | out_lane: land id of the outgoing road 337 | """ 338 | self.links.append((in_lane,out_lane)) 339 | def get_attributes(self): 340 | """ returns the attributes as a dict of the Connection 341 | 342 | """ 343 | retdict = {} 344 | retdict['incomingRoad'] = str(self.incoming_road) 345 | retdict['id'] = str(self.id) 346 | retdict['contactPoint'] = enum2str(self.contact_point) 347 | retdict['connectingRoad'] = str(self.connecting_road) 348 | return retdict 349 | 350 | def get_element(self): 351 | """ returns the elementTree of the Connection 352 | 353 | """ 354 | element = ET.Element('connection',attrib=self.get_attributes()) 355 | for l in self.links: 356 | ET.SubElement(element,'laneLink',attrib={'from':str(l[0]),'to':str(l[1])}) 357 | return element 358 | 359 | 360 | 361 | class Junction(): 362 | """ Junction creates a junction of OpenDRIVE 363 | 364 | Parameters 365 | ---------- 366 | name (str): name of the junction 367 | 368 | id (int): id of the junction 369 | 370 | Attributes 371 | ---------- 372 | name (str): name of the junction 373 | 374 | id (int): id of the junction 375 | 376 | connections (list of Connection): all the connections in the junction 377 | 378 | Methods 379 | ------- 380 | get_element() 381 | Returns the full ElementTree of the class 382 | 383 | get_attributes() 384 | Returns a dictionary of all attributes of the class 385 | 386 | add_connection(connection) 387 | Adds a connection to the junction 388 | """ 389 | ##TODO: add type 390 | def __init__(self,name,id): 391 | """ initalize the Junction 392 | 393 | Parameters 394 | ---------- 395 | name (str): name of the junction 396 | 397 | id (int): id of the junction 398 | """ 399 | self.name = name 400 | self.id = id 401 | self.connections = [] 402 | self._id_counter = 0 403 | 404 | def add_connection(self,connection): 405 | """ Adds a new link to the Junction 406 | 407 | Parameters 408 | ---------- 409 | connection (Connection): adds a connection to the junction 410 | 411 | """ 412 | connection._set_id(self._id_counter) 413 | self._id_counter += 1 414 | self.connections.append(connection) 415 | 416 | def get_attributes(self): 417 | """ returns the attributes as a dict of the Junction 418 | 419 | """ 420 | retdict = {} 421 | retdict['name'] = self.name 422 | retdict['id'] = str(self.id) 423 | return retdict 424 | 425 | def get_element(self): 426 | """ returns the elementTree of the Junction 427 | 428 | """ 429 | element = ET.Element('junction',attrib=self.get_attributes()) 430 | for con in self.connections: 431 | element.append(con.get_element()) 432 | return element 433 | 434 | #from .exceptions import NotSameAmountOfLanesError 435 | from .enumerations import ContactPoint 436 | 437 | def are_roads_consecutive(road1, road2): 438 | 439 | if road1.successor is not None and road2.predecessor is not None: 440 | if road1.successor.element_type == ElementType.road and road2.predecessor.element_type == ElementType.road: 441 | if road1.successor.element_id == road2.id and road2.predecessor.element_id == road1.id: 442 | return True 443 | 444 | return False 445 | 446 | def create_lane_links(road1,road2): 447 | """ create_lane_links takes to roads and if they are connected, match their lanes 448 | and creates lane links. 449 | NOTE: now only works for roads/connecting roads with the same amount of lanes 450 | 451 | Parameters 452 | ---------- 453 | road1 (Road): first road to be lane linked 454 | 455 | road2 (Road): second road to be lane linked 456 | """ 457 | if road1.road_type == -1 and road2.road_type == -1: 458 | #both are roads 459 | if are_roads_consecutive(road1, road2): 460 | _create_links_roads(road1,road2) 461 | elif are_roads_consecutive(road2, road1): 462 | _create_links_roads(road2,road1) 463 | 464 | # if road1.road_type == -1 and road2.road_type == -1: 465 | # #both are roads 466 | # if road1.successor is not None and road2.successor is not None: 467 | # if road1.successor.element_type == ElementType.road and road2.successor.element_type == ElementType.road: 468 | # if road1.successor and road1.successor.element_id == road2.id: 469 | # _create_links_roads(road1,road2) 470 | # elif road1.predecessor and road1.predecessor.element_id == road2.id: 471 | # _create_links_roads(road2,road1) 472 | elif road1.road_type != -1: 473 | _create_links_connecting_road(road1,road2) 474 | elif road2.road_type != -1: 475 | 476 | _create_links_connecting_road(road2,road1) 477 | 478 | def _create_links_connecting_road(connecting,road): 479 | """ _create_links_connecting_road will create lane links between a connecting road and a normal road 480 | 481 | Parameters 482 | ---------- 483 | connecting (Road): a road of type connecting road (not -1) 484 | 485 | road (Road): a that connects to the connecting road 486 | 487 | """ 488 | linktype, sign, connecting_lanesec = _get_related_lanesection(connecting,road) 489 | _, _, road_lanesection_id = _get_related_lanesection(road,connecting) 490 | 491 | if connecting_lanesec != None: 492 | if connecting.lanes.lanesections[connecting_lanesec].leftlanes: 493 | # do left lanes 494 | for i in range(len(connecting.lanes.lanesections[road_lanesection_id].leftlanes)): 495 | linkid = road.lanes.lanesections[road_lanesection_id].leftlanes[i].lane_id*sign 496 | if linktype == 'predecessor': 497 | linkid += connecting.lane_offset_pred 498 | else: 499 | linkid += connecting.lane_offset_suc 500 | connecting.lanes.lanesections[connecting_lanesec].leftlanes[i].add_link(linktype,linkid) 501 | 502 | if connecting.lanes.lanesections[connecting_lanesec].rightlanes: 503 | # do right lanes 504 | for i in range(len(connecting.lanes.lanesections[connecting_lanesec].rightlanes)): 505 | linkid = road.lanes.lanesections[road_lanesection_id].rightlanes[i].lane_id*sign 506 | if linktype == 'predecessor': 507 | linkid += connecting.lane_offset_pred 508 | else: 509 | linkid += connecting.lane_offset_suc 510 | connecting.lanes.lanesections[connecting_lanesec].rightlanes[i].add_link(linktype,linkid) 511 | 512 | 513 | def _get_related_lanesection(road,connected_road): 514 | """ _get_related_lanesection takes to roads, and gives the correct lane section to use 515 | the type of link and if the sign of lanes should be switched 516 | 517 | Parameters 518 | ---------- 519 | road (Road): the road that you want the information about 520 | 521 | connected_road (Road): the connected road 522 | 523 | Returns 524 | ------- 525 | linktype (str): the linktype of road to connected road (successor or predecessor) 526 | 527 | sign (int): +1 or -1 depending on if the sign should change in the linking 528 | 529 | road_lanesection_id (int): what lanesection in the road that should be used to link 530 | """ 531 | linktype = None 532 | sign = None 533 | road_lanesection_id = None 534 | 535 | if road.successor and road.successor.element_id == connected_road.id: 536 | linktype = 'successor' 537 | if road.successor.contact_point == ContactPoint.start: 538 | sign = 1 539 | else: 540 | sign = -1 541 | road_lanesection_id = -1 542 | 543 | elif road.predecessor and road.predecessor.element_id == connected_road.id: 544 | linktype = 'predecessor' 545 | if road.predecessor.contact_point == ContactPoint.start: 546 | sign = -1 547 | else: 548 | sign = 1 549 | road_lanesection_id = 0 550 | 551 | if connected_road.road_type != -1: 552 | # treat connecting road in junction differently 553 | if connected_road.predecessor.element_id == road.id: 554 | if connected_road.predecessor.link_type == ContactPoint.start: 555 | road_lanesection_id = -1 556 | sign = -1 557 | else: 558 | road_lanesection_id = 0 559 | sign = 1 560 | elif connected_road.successor.element_id == road.id: 561 | if connected_road.predecessor.link_type == ContactPoint.start: 562 | road_lanesection_id = 0 563 | sign = 1 564 | else: 565 | road_lanesection_id = -1 566 | sign = -1 567 | return linktype, sign, road_lanesection_id 568 | 569 | def _create_links_roads(pre_road,suc_road): 570 | """ _create_links_roads takes two roads and connect the lanes with links, if they have the same amount. 571 | 572 | Parameters 573 | ---------- 574 | pre_road (Road): the predecessor road 575 | 576 | suc_road (Road): the successor road 577 | 578 | """ 579 | pre_linktype, pre_sign, pre_connecting_lanesec = _get_related_lanesection(pre_road,suc_road) 580 | suc_linktype, _, suc_connecting_lanesec = _get_related_lanesection(suc_road,pre_road) 581 | if len(pre_road.lanes.lanesections[pre_connecting_lanesec].leftlanes) == len(suc_road.lanes.lanesections[-1].leftlanes): 582 | for i in range(len(pre_road.lanes.lanesections[pre_connecting_lanesec].leftlanes)): 583 | linkid = pre_road.lanes.lanesections[pre_connecting_lanesec].leftlanes[i].lane_id*pre_sign 584 | print(linkid) 585 | pre_road.lanes.lanesections[pre_connecting_lanesec].leftlanes[i].add_link(pre_linktype,linkid) 586 | 587 | 588 | suc_road.lanes.lanesections[suc_connecting_lanesec].leftlanes[i].add_link(suc_linktype,linkid*pre_sign) 589 | else: 590 | raise NotSameAmountOfLanesError('Road ' + str(pre_road.id) + ' and road ' + str(suc_road.id) + ' does not have the same number of right lanes.') 591 | 592 | 593 | if len(pre_road.lanes.lanesections[pre_connecting_lanesec].rightlanes) == len(suc_road.lanes.lanesections[-1].rightlanes): 594 | for i in range(len(pre_road.lanes.lanesections[pre_connecting_lanesec].rightlanes)): 595 | linkid = pre_road.lanes.lanesections[pre_connecting_lanesec].rightlanes[i].lane_id 596 | pre_road.lanes.lanesections[pre_connecting_lanesec].rightlanes[i].add_link(pre_linktype,linkid) 597 | suc_road.lanes.lanesections[suc_connecting_lanesec].rightlanes[i].add_link(suc_linktype,linkid) 598 | else: 599 | raise NotSameAmountOfLanesError('Road ' + str(pre_road.id) + ' and road ' + str(suc_road.id) + ' does not have the same number of right lanes.') 600 | 601 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest~=6.2.1 2 | pyodrx~=0.1 3 | setuptools~=52.0.0 4 | numpy~=1.19.5 5 | scipy~=1.6.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | setup( 3 | name="pyodrx", 4 | version="0.01", 5 | packages=find_packages(), 6 | ) -------------------------------------------------------------------------------- /tests/test_geometry.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | 5 | import pyodrx 6 | 7 | def test_line(): 8 | line = pyodrx.Line(1) 9 | 10 | p = line.get_element() 11 | pyodrx.prettyprint(p) 12 | 13 | 14 | @pytest.mark.parametrize("data, expdata",[\ 15 | ([1, 0,0,0], [1,0,0]),\ 16 | ([1, 0,0,np.pi/2], [0,1,np.pi/2]),\ 17 | ([1, 1,1,0], [2,1,0]),\ 18 | ([1, 1,1,0], [2,1,0]),\ 19 | ([1, 1,1,np.pi/4], [1.7071067811865476,1.7071067811865476,np.pi/4]),\ 20 | ]) 21 | 22 | # data: length, x, y, h 23 | # expdata: new x, new y, new h 24 | def test_line_calc(data,expdata): 25 | line = pyodrx.Line(data[0]) 26 | x,y,h,l = line.get_end_data(data[1],data[2],data[3]) 27 | assert pytest.approx(x,0.000001) == expdata[0] 28 | assert pytest.approx(y,0.000001) == expdata[1] 29 | assert pytest.approx(h,0.000001) == expdata[2] 30 | assert pytest.approx(l,0.000001) == data[0] 31 | 32 | def test_spiral(): 33 | spiral = pyodrx.Spiral(0,1, 10) 34 | 35 | p = spiral.get_element() 36 | pyodrx.prettyprint(p) 37 | 38 | def test_spiral_inputs(): 39 | cloth = pyodrx.Spiral(0.0,0.05,10.0) 40 | assert cloth.curvstart == 0 41 | assert cloth.curvend == 0.05 42 | assert cloth.length == 10 43 | 44 | 45 | 46 | @pytest.mark.parametrize("data, expdata",[\ 47 | ([0, 0 , 10, 0, 0, 0], [10,0,0]), 48 | ([0, 0 , 10, 10, 0, 0], [20,0,0]), 49 | ([0, 0 , 10, 0, 10, 0], [10,10,0]), 50 | ([0, 0 , 10, 0, 0, np.pi/2], [0,10,np.pi/2]), 51 | ([0, 0 , 10, 0, 0, -np.pi/2], [0,-10,-np.pi/2]), 52 | ]) 53 | 54 | # data: curvestart, curveend, length, x, y, initial heading 55 | # expdata: new x, new y, new h, new l 56 | def test_spiral_zero_curv(data,expdata): 57 | cloth = pyodrx.Spiral(data[0], data[1], data[2]) 58 | x,y,h,l = cloth.get_end_data(data[3], data[4], data[5]) 59 | 60 | assert pytest.approx(x,0.000001) == expdata[0] 61 | assert pytest.approx(y,0.000001) == expdata[1] 62 | assert pytest.approx(h,0.000001) == expdata[2] 63 | assert pytest.approx(l,0.000001) == data[2] 64 | 65 | @pytest.mark.parametrize("data, expdata",[\ 66 | ([0, 0.05, 10, 0, 0, 0], [9.937680,0.829620,0.25]),\ 67 | ([0, -0.05, 10, 0, 0, 0], [9.937680,-0.829620,-0.25]),\ 68 | ([0, 0.08, 20, 0, 0, 0], [18.757370,5.094433,0.8]),\ 69 | ([0, -0.08, 20, 0, 0, 0], [18.757370,-5.094433,-0.8]),\ 70 | ([0, 0.05, 10, 10, 0, 0], [19.937680,0.829620,0.25]),\ 71 | ([0, 0.05, 10, 0, 10, 0], [9.9376805,0.829620+10,0.25]),\ 72 | ([0, 0.05, 10, -10, 0, 0], [-0.062319415,0.829620,0.25]),\ 73 | ([0, 0.05, 10, 0, -10, 0], [9.937680,-9.17037951,0.25]),\ 74 | ([0, 0.05, 10, 0, 0, np.pi/2], [-0.829620,9.937680,0.25+np.pi/2]),\ 75 | ([0, 0.05, 10, 0, 0, -np.pi/2], [0.829620,-9.937680,-np.pi/2+0.25]),\ 76 | ]) 77 | 78 | # data: curvestart, curveend, length, x, y, initial heading 79 | # expdata: new x, new y, new h, new l 80 | def test_spiral_from_zero_curv(data,expdata): 81 | cloth = pyodrx.Spiral(data[0], data[1], data[2]) 82 | x,y,h,l = cloth.get_end_data(data[3], data[4], data[5]) 83 | 84 | assert pytest.approx(x,0.000001) == expdata[0] 85 | assert pytest.approx(y,0.000001) == expdata[1] 86 | assert pytest.approx(h,0.000001) == expdata[2] 87 | assert pytest.approx(l,0.000001) == data[2] 88 | 89 | @pytest.mark.parametrize("data, expdata",[\ 90 | ([0.05, 0, 10, 0, 0, 0], [9.833993,1.654791,0.25]),\ 91 | ([-0.05, 0, 10, 0, 0, 0], [9.833993,-1.654791,-0.25]),\ 92 | ([0.05, 0, 20, 0, 0, 0], [18.687683,6.478104,0.5]),\ 93 | ([-0.05, 0, 20, 0, 0, 0], [18.687683,-6.478104, -0.5]),\ 94 | ]) 95 | 96 | # data: curvestart, curveend, length, x, y, initial heading 97 | # expdata: new x, new y, new h, new l 98 | def test_spiral_to_zero_curv(data,expdata): 99 | cloth = pyodrx.Spiral(data[0], data[1], data[2]) 100 | x,y,h,l = cloth.get_end_data(data[3], data[4], data[5]) 101 | 102 | assert pytest.approx(x,0.000001) == expdata[0] 103 | assert pytest.approx(y,0.000001) == expdata[1] 104 | assert pytest.approx(h,0.000001) == expdata[2] 105 | assert pytest.approx(l,0.000001) == data[2] 106 | 107 | @pytest.mark.parametrize("data, expdata",[\ 108 | ([-0.05, 0.05, 10, 0, 0, 0], [9.958374, -0.831846, 0.0]),\ 109 | ([0.05, -0.05, 10, 0, 0, 0], [9.958374, 0.831846, 0.0]),\ 110 | ]) 111 | 112 | # data: curvestart, curveend, length, x, y, initial heading 113 | # expdata: new x, new y, new h, new l 114 | def test_spiral_from_neg_to_pos_curv(data,expdata): 115 | cloth = pyodrx.Spiral(data[0], data[1], data[2]) 116 | x,y,h,l = cloth.get_end_data(data[3], data[4], data[5]) 117 | 118 | assert pytest.approx(x,0.000001) == expdata[0] 119 | assert pytest.approx(y,0.000001) == expdata[1] 120 | assert pytest.approx(h,0.000001) == expdata[2] 121 | assert pytest.approx(l,0.000001) == data[2] 122 | 123 | def test_arc(): 124 | arc = pyodrx.Arc(1,length = 1) 125 | 126 | p = arc.get_element() 127 | pyodrx.prettyprint(p) 128 | 129 | arc = pyodrx.Arc(1,angle = 1) 130 | 131 | p = arc.get_element() 132 | pyodrx.prettyprint(p) 133 | 134 | @pytest.mark.parametrize("data, expdata",[\ 135 | ([np.pi, 0,0,0,1], [0,2,np.pi]), 136 | ([2*np.pi, 0,0,0,1/2], [0,4,np.pi]), 137 | ([np.pi, 0,0,0,-1], [0,-2,-np.pi]),\ 138 | ([np.pi/2, 0,0,0,1], [1,1,np.pi/2]),\ 139 | ([np.pi/2, 1,1,0,1], [2,2,np.pi/2]),\ 140 | ([np.pi/2, 0,0,-np.pi/2,1], [1,-1,0]),\ 141 | ([np.pi/2, 0,0,-np.pi/2,-1], [-1,-1,-np.pi]),\ 142 | ]) 143 | 144 | # data: length, x, y, h, curvature 145 | # expdata: new x, new y, new h 146 | def test_arc_calc_length(data,expdata): 147 | arc = pyodrx.Arc(data[4],data[0]) 148 | x,y,h,l = arc.get_end_data(data[1],data[2],data[3]) 149 | assert pytest.approx(x,0.000001) == expdata[0] 150 | assert pytest.approx(y,0.000001) == expdata[1] 151 | assert pytest.approx(h,0.000001) == expdata[2] 152 | assert pytest.approx(l,0.000001) == data[0] 153 | 154 | 155 | @pytest.mark.parametrize("data, expdata",[\ 156 | ([np.pi, 0,0,0,1], [0,2,np.pi,np.pi]), 157 | ([np.pi, 0,0,0,1/2], [0,4,np.pi,np.pi*2]), 158 | ([np.pi/2, 1,1,0,1], [2,2,np.pi/2,np.pi/2]), 159 | ([-np.pi, 0,0,0,-1], [0,-2,-np.pi,np.pi]), 160 | ([-np.pi, 0,0,0,-0.5], [0,-4,-np.pi,2*np.pi]), 161 | ([-np.pi/2, 0,0,0,-1], [1,-1,-np.pi/2,np.pi/2]), 162 | ([-np.pi, 1,0,0,-1], [1,-2,-np.pi,np.pi]), 163 | ([-np.pi, 0,1,0,-1], [0,-1,-np.pi,np.pi]), 164 | ([-np.pi, 0,0,np.pi,-1], [0,2,0,np.pi]), 165 | ]) 166 | # data: angle, x, y, h, curvature 167 | # expdata: new x, new y, new h, length 168 | def test_arc_calc_angle(data,expdata): 169 | arc = pyodrx.Arc(data[4],angle=data[0]) 170 | x,y,h,l = arc.get_end_data(data[1],data[2],data[3]) 171 | assert pytest.approx(x,0.000001) == expdata[0] 172 | assert pytest.approx(y,0.000001) == expdata[1] 173 | assert pytest.approx(h,0.000001) == expdata[2] 174 | assert pytest.approx(l,0.000001) == expdata[3] 175 | # assert False 176 | def test_polyparam(): 177 | poly = pyodrx.ParamPoly3(1,2,3,4,5,6,7,8) 178 | 179 | p = poly.get_element() 180 | pyodrx.prettyprint(p) 181 | 182 | @pytest.mark.parametrize("data, expdata",[\ 183 | ([1, 0,0,0], [1,1,np.pi/4]), 184 | ]) 185 | # data: length, x, y, h 186 | # expdata: new x, new y, new h 187 | def test_arc_calc(data,expdata): 188 | arc = pyodrx.ParamPoly3(0,1,0,0,0,1,0,0,'arcLength',data[0]) 189 | x,y,h,l = arc.get_end_data(data[1],data[2],data[3]) 190 | assert pytest.approx(x,0.000001) == expdata[0] 191 | assert pytest.approx(y,0.000001) == expdata[1] 192 | assert pytest.approx(h,0.000001) == expdata[2] 193 | assert pytest.approx(l,0.000001) == data[0] 194 | 195 | 196 | def test_geometry(): 197 | geom = pyodrx.geometry._Geometry(1,2,3,4,pyodrx.Line(1)) 198 | p = geom.get_element() 199 | pyodrx.prettyprint(p) 200 | 201 | @pytest.mark.parametrize("data",[\ 202 | ([100, 0,0,0]), 203 | ([100, 10, 0, 0]), 204 | ([100, -10, 0, 0]), 205 | ([100, 0, 10, 0]), 206 | ([100, 0, -10, 0]), 207 | ([100, 0, 0, np.pi]), 208 | ([100, 0, 0, -np.pi]), 209 | ]) 210 | 211 | def test_inverted_Line(data): 212 | line = pyodrx.Line(data[0]) 213 | 214 | end_x,end_y,end_h,end_l = line.get_end_data(data[1],data[2],data[3]) 215 | 216 | end_h += np.pi 217 | 218 | start_x,start_y,start_h,start_l = line.get_start_data(end_x,end_y,end_h) 219 | 220 | start_h -= np.pi 221 | 222 | assert pytest.approx(start_x, 0.000001) == data[1] 223 | assert pytest.approx(start_y, 0.000001) == data[2] 224 | assert pytest.approx(start_h, 0.1) == data[3] 225 | 226 | @pytest.mark.parametrize("data",[\ 227 | ([1, np.pi, 0,0,0]), 228 | ([1, np.pi/2, 0,0,0]), 229 | ([1, np.pi, 1,0,0]), 230 | ([1, np.pi, -1,0,0]), 231 | ([1, np.pi, 0,1,0]), 232 | ([1, np.pi, 0,-1,0]), 233 | ([1, np.pi, 0,0,1]), 234 | ([1, np.pi, 0,0,-1]), 235 | ([-1, -np.pi, 0,0,0]), 236 | ([-1, -np.pi, 1,0,0]), 237 | ([-1, -np.pi, -1,0,0]), 238 | ([-1, -np.pi, 0,1,0]), 239 | ([-1, -np.pi, 0,-1,0]), 240 | ([-1, -np.pi, 0,0,1]), 241 | ([-1, -np.pi, 0,0,-1]), 242 | ]) 243 | 244 | def test_inverted_Arc(data): 245 | 246 | arc = pyodrx.Arc(data[0],angle=data[1]) 247 | 248 | end_x,end_y,end_h,end_l = arc.get_end_data(data[2],data[3],data[4]) 249 | 250 | end_h += np.pi 251 | 252 | start_x,start_y,start_h,start_l = arc.get_start_data(end_x,end_y,end_h) 253 | 254 | start_h -= np.pi 255 | 256 | assert pytest.approx(start_x, 0.000001) == data[2] 257 | assert pytest.approx(start_y, 0.000001) == data[3] 258 | assert pytest.approx(start_h, 0.00001) == data[4] 259 | 260 | 261 | @pytest.mark.parametrize("data",[\ 262 | ([0.01, 0.05,15, 0, 0, 0]), 263 | ([0.01, -0.05,15, 0, 0, 0]), 264 | ([-0.01, 0.05,15, 0, 0, 0]), 265 | ([-0.01, -0.05,15, 0, 0, 0]), 266 | ([0.01, 0.05,15, 1, 0, 0]), 267 | ([0.01, 0.05,15, 0, 1, 0]), 268 | ([0.01, 0.05,15, 0, 0, 1]), 269 | ([0.01, 0.05,15, -1, 0, 0]), 270 | ([0.01, 0.05,15, 0, -1, 0]), 271 | ([0.01, 0.05,15, 0, 0, -1]), 272 | ([-0.01, -0.05,15, 1, 0, 0]), 273 | ([-0.01, -0.05,15, 0, 1, 0]), 274 | ([-0.01, -0.05,15, 0, 0, 1]), 275 | ([-0.01, -0.05,15, -1, 0, 0]), 276 | ([-0.01, -0.05,15, 0, -1, 0]), 277 | ([-0.01, -0.05,15, 0, 0, -1]), 278 | 279 | ]) 280 | 281 | def test_inverted_Spiral(data): 282 | cloth = pyodrx.Spiral(data[0], data[1], data[2]) 283 | 284 | end_x,end_y,end_h,end_l = cloth.get_end_data(data[3],data[4],data[5]) 285 | 286 | end_h += np.pi 287 | 288 | start_x,start_y,start_h,start_l = cloth.get_start_data(end_x,end_y,end_h) 289 | 290 | start_h -= np.pi 291 | 292 | assert pytest.approx(start_x, 0.000001) == data[3] 293 | assert pytest.approx(start_y, 0.000001) == data[4] 294 | assert pytest.approx(start_h, 0.000001) == data[5] -------------------------------------------------------------------------------- /tests/test_lane.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | import pyodrx 5 | 6 | def test_roadline(): 7 | line = pyodrx.RoadLine() 8 | 9 | pyodrx.prettyprint(line.get_element()) 10 | line = pyodrx.RoadLine(1,2,3,5,1,pyodrx.MarkRule.no_passing,pyodrx.RoadMarkColor.standard) 11 | pyodrx.prettyprint(line.get_element()) 12 | 13 | 14 | def test_roadmark(): 15 | mark = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2) 16 | pyodrx.prettyprint(mark.get_element()) 17 | mark = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,1,1,1,pyodrx.MarkRule.no_passing,pyodrx.RoadMarkColor.standard) 18 | pyodrx.prettyprint(mark.get_element()) 19 | 20 | def test_lane(): 21 | 22 | lane = pyodrx.Lane() 23 | lane._set_lane_id(1) 24 | pyodrx.prettyprint(lane.get_element()) 25 | lane = pyodrx.Lane(pyodrx.LaneType.driving,1,1,1,1,2) 26 | lane._set_lane_id(1) 27 | pyodrx.prettyprint(lane.get_element()) 28 | 29 | def test_lanesection(): 30 | centerlane = pyodrx.Lane() 31 | ls = pyodrx.LaneSection(0,centerlane) 32 | pyodrx.prettyprint(ls.get_element()) 33 | right_lane = pyodrx.Lane() 34 | ls.add_right_lane(right_lane) 35 | pyodrx.prettyprint(ls.get_element()) 36 | left_lane = pyodrx.Lane(a=2) 37 | ls.add_left_lane(left_lane) 38 | pyodrx.prettyprint(ls.get_element()) 39 | 40 | def test_lanes(): 41 | centerlane = pyodrx.Lane() 42 | ls = pyodrx.LaneSection(0,centerlane) 43 | lanes = pyodrx.Lanes() 44 | lanes.add_lanesection(ls) 45 | pyodrx.prettyprint(lanes.get_element()) -------------------------------------------------------------------------------- /tests/test_links.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pyodrx 3 | import numpy as np 4 | 5 | def test_link(): 6 | link = pyodrx.links._Link('successor','1') 7 | 8 | pyodrx.prettyprint(link.get_element()) 9 | 10 | link = pyodrx.links._Link('successor','1',element_type=pyodrx.ElementType.road,contact_point=pyodrx.ContactPoint.start) 11 | 12 | pyodrx.prettyprint(link.get_element()) 13 | 14 | def test_links(): 15 | 16 | links = pyodrx.links._Links() 17 | pyodrx.prettyprint(links.get_element()) 18 | link = pyodrx.links._Link('successor','1') 19 | links.add_link(link) 20 | pyodrx.prettyprint(links.get_element()) 21 | 22 | def test_lanelinker(): 23 | 24 | lane = pyodrx.Lane(a=3) 25 | lane._set_lane_id(1) 26 | lane.add_link('successor','2') 27 | 28 | pyodrx.prettyprint(lane.get_element()) 29 | 30 | def test_connection(): 31 | con = pyodrx.Connection(1,2,pyodrx.ContactPoint.start,5) 32 | 33 | con.add_lanelink(1,-1) 34 | con.add_lanelink(2,-2) 35 | 36 | pyodrx.prettyprint(con.get_element()) 37 | 38 | def test_junction(): 39 | con1 = pyodrx.Connection(1,2,pyodrx.ContactPoint.start) 40 | 41 | con1.add_lanelink(1,-1) 42 | con1.add_lanelink(2,-2) 43 | 44 | con2 = pyodrx.Connection(2,1,pyodrx.ContactPoint.start) 45 | 46 | con2.add_lanelink(1,-1) 47 | con2.add_lanelink(2,-2) 48 | con2.add_lanelink(3,-3) 49 | 50 | junciton = pyodrx.Junction('',1) 51 | 52 | junciton.add_connection(con1) 53 | junciton.add_connection(con2) 54 | 55 | pyodrx.prettyprint(junciton.get_element()) 56 | 57 | 58 | # road - road - road // -> - -> - -> 59 | def test_create_lane_links_normalroad1(): 60 | 61 | planview = [] 62 | lanec = [] 63 | lanel = [] 64 | laner = [] 65 | lanesec = [] 66 | lanes = [] 67 | 68 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 69 | 70 | geom= [] 71 | geom.append(pyodrx.Line(50)) 72 | geom.append(pyodrx.Arc(0.01,angle=np.pi/2)) 73 | geom.append(pyodrx.Line(50)) 74 | 75 | # create planviews 76 | for i in range(len(geom)): 77 | planview.append(pyodrx.PlanView()) 78 | planview[i].add_geometry(geom[i]) 79 | # create centerlanes 80 | for i in range(len(geom)): 81 | lanec.append(pyodrx.Lane(a=3)) 82 | lanel.append(pyodrx.Lane(a=3)) 83 | laner.append(pyodrx.Lane(a=3)) 84 | #add roadmarks 85 | for i in range(len(geom)): 86 | lanec[i].add_roadmark(rm) 87 | lanel[i].add_roadmark(rm) 88 | laner[i].add_roadmark(rm) 89 | # create lanesections 90 | for i in range(len(geom)): 91 | lanesec.append(pyodrx.LaneSection(0,lanec[i])) 92 | lanesec[i].add_right_lane(lanel[i]) 93 | lanesec[i].add_left_lane(laner[i]) 94 | #create lanes 95 | for i in range(len(geom)): 96 | lanes.append(pyodrx.Lanes()) 97 | lanes[i].add_lanesection(lanesec[i]) 98 | 99 | #create roads 100 | road1 = pyodrx.Road(1,planview[0],lanes[0]) 101 | road1.add_successor(pyodrx.ElementType.road,2, pyodrx.ContactPoint.start) 102 | 103 | road2 = pyodrx.Road(2,planview[1],lanes[1]) 104 | road2.add_predecessor(pyodrx.ElementType.road,1, pyodrx.ContactPoint.end) 105 | road2.add_successor(pyodrx.ElementType.road,3, pyodrx.ContactPoint.start) 106 | 107 | road3 = pyodrx.Road(3,planview[2],lanes[2]) 108 | road3.add_predecessor(pyodrx.ElementType.road,2, pyodrx.ContactPoint.end) 109 | 110 | # create the opendrive and add roads 111 | odr = pyodrx.OpenDrive('myroad') 112 | odr.add_road(road1) 113 | odr.add_road(road2) 114 | odr.add_road(road3) 115 | 116 | odr.adjust_roads_and_lanes() 117 | 118 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 119 | assert int(road1.lanes.lanesections[0].rightlanes[0].links.get_successor_id() ) == -1 120 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 121 | assert int(road1.lanes.lanesections[0].leftlanes[0].links.get_successor_id() ) == 1 122 | 123 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() ) == -1 124 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_successor_id() ) == -1 125 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() ) == 1 126 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_successor_id() ) == 1 127 | 128 | assert int(road3.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() ) == -1 129 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 130 | assert int(road3.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() ) == 1 131 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 132 | 133 | 134 | # road - junction - road // -> - -> - -> 135 | def test_create_lane_links_junction1(): 136 | 137 | planview = [] 138 | lanec = [] 139 | lanel = [] 140 | laner = [] 141 | lanesec = [] 142 | lanes = [] 143 | 144 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 145 | 146 | geom= [] 147 | geom.append(pyodrx.Line(50)) 148 | geom.append(pyodrx.Arc(0.01,angle=np.pi/2)) 149 | geom.append(pyodrx.Line(50)) 150 | 151 | # create planviews 152 | for i in range(len(geom)): 153 | planview.append(pyodrx.PlanView()) 154 | planview[i].add_geometry(geom[i]) 155 | # create centerlanes 156 | for i in range(len(geom)): 157 | lanec.append(pyodrx.Lane(a=3)) 158 | lanel.append(pyodrx.Lane(a=3)) 159 | laner.append(pyodrx.Lane(a=3)) 160 | #add roadmarks 161 | for i in range(len(geom)): 162 | lanec[i].add_roadmark(rm) 163 | lanel[i].add_roadmark(rm) 164 | laner[i].add_roadmark(rm) 165 | # create lanesections 166 | for i in range(len(geom)): 167 | lanesec.append(pyodrx.LaneSection(0,lanec[i])) 168 | lanesec[i].add_right_lane(lanel[i]) 169 | lanesec[i].add_left_lane(laner[i]) 170 | #create lanes 171 | for i in range(len(geom)): 172 | lanes.append(pyodrx.Lanes()) 173 | lanes[i].add_lanesection(lanesec[i]) 174 | 175 | #create roads 176 | road1 = pyodrx.Road(1,planview[0],lanes[0]) 177 | road1.add_successor(pyodrx.ElementType.junction,1) 178 | 179 | road2 = pyodrx.Road(2,planview[1],lanes[1],road_type=1) 180 | road2.add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.end) 181 | road2.add_successor(pyodrx.ElementType.road,3,pyodrx.ContactPoint.start) 182 | 183 | road3 = pyodrx.Road(3,planview[2],lanes[2]) 184 | road3.add_predecessor(pyodrx.ElementType.junction,1) 185 | 186 | # create the opendrive and add roads 187 | odr = pyodrx.OpenDrive('myroad') 188 | odr.add_road(road1) 189 | odr.add_road(road2) 190 | odr.add_road(road3) 191 | 192 | odr.adjust_roads_and_lanes() 193 | 194 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() ) == -1 195 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_successor_id() ) == -1 196 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() ) == 1 197 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_successor_id() ) == 1 198 | 199 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 200 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 201 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 202 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 203 | 204 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 205 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 206 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 207 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 208 | 209 | # road - junction - road // <- - -> - <- 210 | def test_create_lane_links_junction2(): 211 | 212 | planview = [] 213 | lanec = [] 214 | lanel = [] 215 | laner = [] 216 | lanesec = [] 217 | lanes = [] 218 | 219 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 220 | 221 | geom= [] 222 | geom.append(pyodrx.Line(50)) 223 | geom.append(pyodrx.Arc(0.01,angle=np.pi/2)) 224 | geom.append(pyodrx.Line(50)) 225 | 226 | # create planviews 227 | for i in range(len(geom)): 228 | planview.append(pyodrx.PlanView()) 229 | planview[i].add_geometry(geom[i]) 230 | # create centerlanes 231 | lanec.append(pyodrx.Lane(a=3)) 232 | lanel.append(pyodrx.Lane(a=3)) 233 | laner.append(pyodrx.Lane(a=3)) 234 | #add roadmarks 235 | lanec[i].add_roadmark(rm) 236 | lanel[i].add_roadmark(rm) 237 | laner[i].add_roadmark(rm) 238 | # create lanesections 239 | lanesec.append(pyodrx.LaneSection(0,lanec[i])) 240 | lanesec[i].add_right_lane(lanel[i]) 241 | lanesec[i].add_left_lane(laner[i]) 242 | #create lanes 243 | lanes.append(pyodrx.Lanes()) 244 | lanes[i].add_lanesection(lanesec[i]) 245 | 246 | #create roads 247 | road1 = pyodrx.Road(1,planview[0],lanes[0]) 248 | road1.add_predecessor(pyodrx.ElementType.junction,1) 249 | 250 | road2 = pyodrx.Road(2,planview[1],lanes[1],road_type=1) 251 | road2.add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.start) 252 | road2.add_successor(pyodrx.ElementType.road,3,pyodrx.ContactPoint.end) 253 | 254 | road3 = pyodrx.Road(3,planview[2],lanes[2]) 255 | road3.add_successor(pyodrx.ElementType.junction,1) 256 | 257 | # create the opendrive and add roads 258 | odr = pyodrx.OpenDrive('myroad') 259 | odr.add_road(road1) 260 | odr.add_road(road2) 261 | odr.add_road(road3) 262 | 263 | odr.adjust_roads_and_lanes() 264 | 265 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() ) == 1 266 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_successor_id() ) == 1 267 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() ) == -1 268 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_successor_id() ) == -1 269 | 270 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 271 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 272 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 273 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 274 | 275 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 276 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 277 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 278 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 279 | 280 | # road - junction - road // <- - -> - -> 281 | def test_create_lane_links_junction3(): 282 | 283 | planview = [] 284 | lanec = [] 285 | lanel = [] 286 | laner = [] 287 | lanesec = [] 288 | lanes = [] 289 | 290 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 291 | 292 | geom= [] 293 | geom.append(pyodrx.Line(50)) 294 | geom.append(pyodrx.Arc(0.01,angle=np.pi/2)) 295 | geom.append(pyodrx.Line(50)) 296 | 297 | # create planviews 298 | for i in range(len(geom)): 299 | planview.append(pyodrx.PlanView()) 300 | planview[i].add_geometry(geom[i]) 301 | # create centerlanes 302 | lanec.append(pyodrx.Lane(a=3)) 303 | lanel.append(pyodrx.Lane(a=3)) 304 | laner.append(pyodrx.Lane(a=3)) 305 | #add roadmarks 306 | lanec[i].add_roadmark(rm) 307 | lanel[i].add_roadmark(rm) 308 | laner[i].add_roadmark(rm) 309 | # create lanesections 310 | lanesec.append(pyodrx.LaneSection(0,lanec[i])) 311 | lanesec[i].add_right_lane(lanel[i]) 312 | lanesec[i].add_left_lane(laner[i]) 313 | #create lanes 314 | lanes.append(pyodrx.Lanes()) 315 | lanes[i].add_lanesection(lanesec[i]) 316 | 317 | #create roads 318 | road1 = pyodrx.Road(1,planview[0],lanes[0]) 319 | road1.add_predecessor(pyodrx.ElementType.junction,1) 320 | 321 | road2 = pyodrx.Road(2,planview[1],lanes[1],road_type=1) 322 | road2.add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.start) 323 | road2.add_successor(pyodrx.ElementType.road,3,pyodrx.ContactPoint.start) 324 | 325 | road3 = pyodrx.Road(3,planview[2],lanes[2]) 326 | road3.add_predecessor(pyodrx.ElementType.junction,1) 327 | 328 | # create the opendrive and add roads 329 | odr = pyodrx.OpenDrive('myroad') 330 | odr.add_road(road1) 331 | odr.add_road(road2) 332 | odr.add_road(road3) 333 | 334 | odr.adjust_roads_and_lanes() 335 | 336 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() ) == 1 337 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_successor_id() ) == -1 338 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() ) == -1 339 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_successor_id() ) == 1 340 | 341 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 342 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 343 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 344 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 345 | 346 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 347 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 348 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 349 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 350 | 351 | # road - junction - road // -> - -> - <- 352 | def test_create_lane_links_junction4(): 353 | 354 | planview = [] 355 | lanec = [] 356 | lanel = [] 357 | laner = [] 358 | lanesec = [] 359 | lanes = [] 360 | 361 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 362 | 363 | geom= [] 364 | geom.append(pyodrx.Line(50)) 365 | geom.append(pyodrx.Arc(0.01,angle=np.pi/2)) 366 | geom.append(pyodrx.Line(50)) 367 | 368 | # create planviews 369 | for i in range(len(geom)): 370 | planview.append(pyodrx.PlanView()) 371 | planview[i].add_geometry(geom[i]) 372 | # create centerlanes 373 | lanec.append(pyodrx.Lane(a=3)) 374 | lanel.append(pyodrx.Lane(a=3)) 375 | laner.append(pyodrx.Lane(a=3)) 376 | #add roadmarks 377 | lanec[i].add_roadmark(rm) 378 | lanel[i].add_roadmark(rm) 379 | laner[i].add_roadmark(rm) 380 | # create lanesections 381 | lanesec.append(pyodrx.LaneSection(0,lanec[i])) 382 | lanesec[i].add_right_lane(lanel[i]) 383 | lanesec[i].add_left_lane(laner[i]) 384 | #create lanes 385 | lanes.append(pyodrx.Lanes()) 386 | lanes[i].add_lanesection(lanesec[i]) 387 | 388 | #create roads 389 | road1 = pyodrx.Road(1,planview[0],lanes[0]) 390 | road1.add_successor(pyodrx.ElementType.junction,1) 391 | 392 | road2 = pyodrx.Road(2,planview[1],lanes[1],road_type=1) 393 | road2.add_predecessor(pyodrx.ElementType.road,1,pyodrx.ContactPoint.end) 394 | road2.add_successor(pyodrx.ElementType.road,3,pyodrx.ContactPoint.end) 395 | 396 | road3 = pyodrx.Road(3,planview[2],lanes[2]) 397 | road3.add_successor(pyodrx.ElementType.junction,1) 398 | 399 | # create the opendrive and add roads 400 | odr = pyodrx.OpenDrive('myroad') 401 | odr.add_road(road1) 402 | odr.add_road(road2) 403 | odr.add_road(road3) 404 | 405 | odr.adjust_roads_and_lanes() 406 | 407 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() ) == -1 408 | assert int(road2.lanes.lanesections[0].rightlanes[0].links.get_successor_id() ) == 1 409 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() ) == 1 410 | assert int(road2.lanes.lanesections[0].leftlanes[0].links.get_successor_id() ) == -1 411 | 412 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 413 | assert road1.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 414 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 415 | assert road1.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 416 | 417 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_predecessor_id() == None 418 | assert road3.lanes.lanesections[0].rightlanes[0].links.get_successor_id() == None 419 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_predecessor_id() == None 420 | assert road3.lanes.lanesections[0].leftlanes[0].links.get_successor_id() == None 421 | 422 | 423 | 424 | 425 | -------------------------------------------------------------------------------- /tests/test_opendrive.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pyodrx 3 | 4 | 5 | def test_simple_road(): 6 | line1 = pyodrx.Line(100) 7 | planview = pyodrx.PlanView() 8 | planview.add_geometry(line1) 9 | 10 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 11 | 12 | 13 | lane1 = pyodrx.Lane(a=2) 14 | lane1.add_roadmark(rm) 15 | lanesec = pyodrx.LaneSection(0,lane1) 16 | 17 | lanes = pyodrx.Lanes() 18 | lanes.add_lanesection(lanesec) 19 | 20 | road = pyodrx.Road(1,planview,lanes) 21 | 22 | pyodrx.prettyprint(road.get_element()) 23 | 24 | 25 | 26 | def test_link_road(): 27 | line1 = pyodrx.Line(100) 28 | planview = pyodrx.PlanView() 29 | planview.add_geometry(line1) 30 | 31 | rm = pyodrx.RoadMark(pyodrx.RoadMarkType.solid,0.2,rule=pyodrx.MarkRule.no_passing) 32 | 33 | 34 | lane1 = pyodrx.Lane(a=2) 35 | lane1.add_roadmark(rm) 36 | lanesec = pyodrx.LaneSection(0,lane1) 37 | 38 | lanes = pyodrx.Lanes() 39 | lanes.add_lanesection(lanesec) 40 | 41 | road = pyodrx.Road(1,planview,lanes) 42 | road.add_predecessor(pyodrx.ElementType.road,'1',pyodrx.ContactPoint.start) 43 | pyodrx.prettyprint(road.get_element()) 44 | 45 | 46 | @pytest.mark.parametrize("data",[\ 47 | ([10, 100,-1,1, 3]), 48 | ([10, 50,-1,1, 3]), 49 | ([10, 100,1,1, 3]), 50 | ([10, 100,-1,2, 3]), 51 | ([10, 100,-1,10, 3]), 52 | ([10, 100,-1,10, 5]), 53 | ]) 54 | 55 | def test_create_straight_road(data): 56 | 57 | road = pyodrx.generators.create_straight_road(data[0], length=data[1], junction=data[2], n_lanes=data[3], lane_offset=data[4]) 58 | odr = pyodrx.OpenDrive('myroad') 59 | odr.add_road(road) 60 | odr.adjust_roads_and_lanes() 61 | 62 | redict = road.get_attributes() 63 | 64 | assert int(redict['id']) == data[0] 65 | assert int(redict['length']) == data[1] 66 | assert int(redict['junction']) == data[2] 67 | assert len(road.lanes.lanesections[0].leftlanes) == data[3] 68 | assert len(road.lanes.lanesections[0].rightlanes) == data[3] 69 | assert road.lanes.lanesections[0].leftlanes[0].a == data[4] 70 | assert road.lanes.lanesections[0].leftlanes[0].b == 0 71 | assert road.lanes.lanesections[0].leftlanes[0].c == 0 72 | assert road.lanes.lanesections[0].leftlanes[0].d == 0 73 | -------------------------------------------------------------------------------- /tests/test_signals_objects.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test script to create a straight road with a signal at an arbitrary s-coordinate. 3 | """ 4 | import pyodrx 5 | 6 | 7 | def test_signal(): 8 | signal1 = pyodrx.Signal(s=10.0, t=-2, dynamic=pyodrx.Dynamic.no, orientation=pyodrx.Orientation.positive, zOffset=0.00, country="US", Type="R1", 9 | subtype="1") 10 | 11 | signal2 = pyodrx.Signal(s=20.0, t=-2, dynamic=pyodrx.Dynamic.no, orientation=pyodrx.Orientation.positive, country="DEU", Type="274", 12 | subtype="120", value=120, unit="km/h") 13 | 14 | road = pyodrx.create_straight_road(0) 15 | road.add_signal(signal1) 16 | road.add_signal(signal2) 17 | pyodrx.prettyprint(road.get_element()) 18 | 19 | 20 | def test_object(): 21 | object1 = pyodrx.Object(s=10.0, t=-2, dynamic=pyodrx.Dynamic.no, orientation=pyodrx.Orientation.positive, zOffset=0.00, id="1", height=1.0, Type=pyodrx.ObjectType.pole) 22 | 23 | #same chosen ID should cause warning and be resolved automatically 24 | object2 = pyodrx.Object(s=20.0, t=-2, dynamic=pyodrx.Dynamic.no, orientation=pyodrx.Orientation.positive, zOffset=0.00, height=10, id="1", Type=pyodrx.ObjectType.streetLamp) 25 | 26 | road = pyodrx.create_straight_road(0) 27 | road.add_object([object1, object2]) 28 | pyodrx.prettyprint(road.get_element()) 29 | 30 | def test_repeated_object(): 31 | object1 = pyodrx.Object(s=10.0, t=-2, dynamic=pyodrx.Dynamic.no, orientation=pyodrx.Orientation.positive, zOffset=0.00, height=1.0, Type=pyodrx.ObjectType.pole) 32 | object1.repeat(100,50) 33 | road = pyodrx.create_straight_road(0) 34 | road.add_object(object1) 35 | pyodrx.prettyprint(road.get_element()) 36 | 37 | def test_object_roadside(): 38 | road = pyodrx.create_straight_road(0) 39 | odr = pyodrx.OpenDrive('myroad') 40 | odr.add_road(road) 41 | odr.adjust_roads_and_lanes() 42 | object1 = pyodrx.Object(s=0, t=0, dynamic=pyodrx.Dynamic.no, orientation=pyodrx.Orientation.positive, zOffset=0.00, height=1.0, Type=pyodrx.ObjectType.pole) 43 | road.add_object_roadside(object1,50,tOffset=0.85) 44 | pyodrx.prettyprint(road.get_element()) -------------------------------------------------------------------------------- /xodr_coverage.txt: -------------------------------------------------------------------------------- 1 | COVERAGE OPEN DRIVE 1.5: 2 | 3 | 4 | OpenDRIVE YES 5 | |-header YES 6 | | |-geoReference NO 7 | | |-offset NO 8 | |-road YES 9 | | |-link YES 10 | | | |-predecessor YES 11 | | | |-successor YES 12 | | | |-neighbor YES 13 | | |-type NO 14 | | | |-speed NO 15 | | |-planView YES 16 | | | |-geometry YES 17 | | | | |-line YES 18 | | | | |-spiral YES 19 | | | | |-arc YES 20 | | | | |-poly3 YES 21 | | | | |-paramPoly3 YES 22 | | |-elevationProfile NO 23 | | | |-elevation NO 24 | | |-lateralProfile NO 25 | | | |-superelevation NO 26 | | | |-crossfall NO 27 | | | |-shape NO 28 | | |-lanes YES 29 | | | |-laneOffset YES 30 | | | |-laneSection YES 31 | | | | |-left YES 32 | | | | | |-lane YES 33 | | | | | | |-link YES 34 | | | | | | | |-predecessor YES 35 | | | | | | | |-successor YES 36 | | | | | | |-width YES 37 | | | | | | |-border NO 38 | | | | | | |-roadMark YES 39 | | | | | | | | -sway NO 40 | | | | | | | | -type YES 41 | | | | | | | | | -line YES 42 | | | | | | | | -explicit NO 43 | | | | | | | | | -line NO 44 | | | | | | |-material NO 45 | | | | | | |-visibility NO 46 | | | | | | |-speed NO 47 | | | | | | |-access NO 48 | | | | | | |-height NO 49 | | | | | | |-rule NO 50 | | | | |-center YES 51 | | | | | |-lane YES 52 | | | | | | |-link YES 53 | | | | | | | |-predecessor YES 54 | | | | | | | |-successor YES 55 | | | | | | |-roadMark YES 56 | | | | | | | | -sway NO 57 | | | | | | | | -type YES 58 | | | | | | | | | -line YES 59 | | | | | | | | -explicit NO 60 | | | | | | | | | -line NO 61 | | | | |-right YES 62 | | | | | |-lane YES 63 | | | | | | |-link YES 64 | | | | | | | |-predecessor YES 65 | | | | | | | |-successor YES 66 | | | | | | |-width YES 67 | | | | | | |-border NO 68 | | | | | | |-roadMark YES 69 | | | | | | | | -sway NO 70 | | | | | | | | -type YES 71 | | | | | | | | | -line YES 72 | | | | | | | | -explicit NO 73 | | | | | | | | | -line NO 74 | | | | | | |-material NO 75 | | | | | | |-visibility NO 76 | | | | | | |-speed NO 77 | | | | | | |-access NO 78 | | | | | | |-height NO 79 | | | | | | |-rule NO 80 | | |-objects YES 81 | | | |-object YES 82 | | | | |-repeat YES 83 | | | | |-outlines NO 84 | | | | | |-outline NO 85 | | | | | | |-cornerRoad NO 86 | | | | | | |-cornerLocal NO 87 | | | | |-material NO 88 | | | | |-validity NO 89 | | | | |-parkingSpace NO 90 | | | | |-markings NO 91 | | | | | |-marking NO 92 | | | | | | |-cornerReference NO 93 | | | | |-borders NO 94 | | | | | |-border NO 95 | | | | | | |-cornerReference NO 96 | | | |-objectReference NO 97 | | | | |-validity NO 98 | | | |-tunnel NO 99 | | | | |-validity NO 100 | | | |-bridge NO 101 | | | | |-validity NO 102 | | |-signals YES 103 | | | |-signal YES 104 | | | | |-validity NO 105 | | | | |-dependency NO 106 | | | | |-reference NO 107 | | | | |-positionRoad NO 108 | | | | |-positionInertial NO 109 | | | |-signalReference NO 110 | | | | |-validity NO 111 | | |-surface NO 112 | | | |-CRG NO 113 | | |-railroad NO 114 | | | |-switch NO 115 | | | | |-mainTrack NO 116 | | | | |-sideTrack NO 117 | | | | |-partner NO 118 | |-controller NO 119 | | |-control NO 120 | |-junction YES 121 | | |-connection YES 122 | | | |-predecessor YES 123 | | | |-successor YES 124 | | | |-laneLink YES 125 | | |-priority NO 126 | | |-controller NO 127 | | |-surface NO 128 | | | |-CRG NO 129 | |-junctionGroup NO 130 | | |-junctionReference NO 131 | |-station NO 132 | | |-platform NO 133 | | | |-segment NO --------------------------------------------------------------------------------