├── .gitignore ├── CONTRIBUTORS.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── examples ├── random.grid.example.py ├── random_grid │ ├── .gitignore │ ├── basic.vType.xml │ ├── busStops.add.xml │ ├── parkingArea.add.xml │ ├── parkingAreaRerouters.add.xml │ ├── random.net.xml │ ├── random.sumocfg │ └── rou │ │ ├── buses.flows.xml │ │ ├── commercial.rou.xml │ │ ├── passenger.rou.xml │ │ └── ptw.rou.xml ├── simple.example.py ├── subscriptions.example.py ├── test_scenario │ ├── .gitignore │ ├── ex.pedestrian.rou.xml │ ├── example.net.xml │ ├── parkings.big.add.xml │ ├── parkings.small.add.xml │ ├── sumo.simple.cfg │ ├── sumo.subscriptions.cfg │ └── vType.add.xml └── uncert.example.py ├── pydocs └── pypml.ParkingMonitor.html ├── pypml ├── __init__.py ├── pypml.py └── pypml_tests.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | ## Testing environment: 2 | tests/* 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Environments 88 | .env 89 | .venv 90 | env/ 91 | venv/ 92 | ENV/ 93 | env.bak/ 94 | venv.bak/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | -------------------------------------------------------------------------------- /CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | Contributors 2 | 3 | - Lara CODECA: 4 | - EURECOM: from June 2017 until May 2019 this project was partially funded by the French Government (National Research Agency, ANR) through the 'Investments for the Future', ref. #ANR-11-LABX-0031-01 5 | - Trinity College Dublin: from July 2019 this project was partially funded by the European Union’s Horizon 2020 research and innovation programme under the Marie Skłodowska-Curie grant agreement No. 713567, and ENABLE, which is funded under Science Foundation Ireland (16/SP/3804) and is co-funded under the European Regional Development Fund. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PyPML - Python Parking Monitor Library for SUMO 2 | 3 | Contacts: Lara CODECA [lara.codeca@gmail.com], Jerome HAERRI [haerri@eurecom.fr] 4 | 5 | This program and the accompanying materials are made available under the terms of 6 | the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0. 7 | 8 | This Source Code may also be made available under the following Secondary Licenses when the 9 | conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: 10 | GNU General Public License version 3 . 11 | 12 | If you use PyPML, cite us with: 13 | L. Codeca; J. Erdmann; J. Härri. 14 | "A SUMO-Based Parking Management Framework for Large-Scale Smart Cities Simulations", 15 | VNC 2018, IEEE Vehicular Networking Conference, 16 | December 5-7, 2018, Taipei, Taiwan. 17 | 18 | -------- 19 | Reqirements: 20 | * It requires at least [SUMO 1.0.1](https://github.com/eclipse/sumo/tree/v1_0_1). 21 | 22 | Tested with: 23 | * Eclipse SUMO Version 1.4.0 24 | Build features: Linux-4.19.0-4-amd64 x86_64 GNU 8.3.0 Release Proj GUI GDAL FFmpeg OSG GL2PS SWIG 25 | * Eclipse SUMO Version 1.3.1 26 | Build features: Linux-4.19.0-4-amd64 x86_64 GNU 8.3.0 Release Proj GUI GDAL FFmpeg OSG GL2PS SWIG 27 | * Eclipse SUMO Version 1.3.0 28 | Build features: Linux-4.19.0-4-amd64 x86_64 GNU 8.3.0 Release Proj GUI GDAL FFmpeg OSG GL2PS SWIG 29 | * Eclipse SUMO Version 1.2.0 30 | Build features: Linux-4.19.0-4-amd64 x86_64 GNU 8.3.0 Release Proj GUI GDAL FFmpeg OSG GL2PS SWIG 31 | * Eclipse SUMO Version 1.1.0 32 | Build features: Linux-4.19.0-4-amd64 x86_64 GNU 8.3.0 Release Proj GUI GDAL FFmpeg OSG GL2PS SWIG 33 | * Eclipse SUMO Version 1.0.1 34 | Build features: Linux-4.19.0-4-amd64 Proj GUI GDAL FFmpeg OSG GL2PS SWIG 35 | 36 | -------- 37 | Installation: 38 | * Install: `pip3 install .` from the root directory, or `python3 setup.py install` 39 | * Development install: `pip3 install -e .` or `python3 setup.py develop` 40 | 41 | -------- 42 | Examples: 43 | * Given the ~under development~ status of the project, examples are provided. 44 | * examples/simple.example.py 45 | * examples/subscriptions.example.py (Subscription usage) 46 | * examples/uncert.example.py (Uncertainty usage) 47 | * examples/random.grid.example.py applies the simple.example.py to random_grid, 48 | a more complex scenario than test_grid. 49 | 50 | -------- 51 | Important: 52 | * PyPML behavior in case of multiple TraCI servers is unpredictable due to how the subscriptions are 53 | implemented, to work around this issue we provide functions to retrieve vehicle and simulation 54 | subscriptions from the library: `get_traci_vehicle_subscriptions` and 55 | `get_traci_simulation_subscriptions` 56 | * Due to some changes in the SUMO development version of the TraCI APIs, the master branch is not 57 | compatible with SUMO 1.2.0. Release v0.2 is compatible with SUMO 1.2.0 58 | -------------------------------------------------------------------------------- /examples/random.grid.example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ Example of usage of PyPML. 4 | 5 | Python Parking Monitor Library (PyPML) 6 | 7 | Author: Lara CODECA 8 | 9 | This program and the accompanying materials are made available under the 10 | terms of the Eclipse Public License 2.0 which is available at 11 | http://www.eclipse.org/legal/epl-2.0. 12 | """ 13 | 14 | import argparse 15 | import logging 16 | import os 17 | import pprint 18 | import sys 19 | import traceback 20 | 21 | from pypml import ParkingMonitor 22 | 23 | # """ Import SUMO library """ 24 | if 'SUMO_TOOLS' in os.environ: 25 | sys.path.append(os.environ['SUMO_TOOLS']) 26 | import traci 27 | else: 28 | sys.exit("Please declare environment variable 'SUMO_TOOLS'") 29 | 30 | def _args(): 31 | """ 32 | Argument Parser 33 | ret: parsed arguments. 34 | """ 35 | parser = argparse.ArgumentParser( 36 | prog='{}'.format(sys.argv[0]), 37 | usage='%(prog)s', 38 | description='PyPML Random Grid Scenario Example') 39 | parser.add_argument( 40 | '--profiling', dest='profiling', action='store_true', 41 | help='Enable Python3 cProfile feature.') 42 | parser.add_argument( 43 | '--no-profiling', dest='profiling', action='store_false', 44 | help='Disable Python3 cProfile feature.') 45 | parser.set_defaults(profiling=False) 46 | return parser.parse_args() 47 | 48 | def _main(): 49 | """ Example of parking management in SUMO. """ 50 | 51 | traci.start(['sumo', '-c', 'random_grid/random.sumocfg']) 52 | 53 | parking_monitor_options = { 54 | 'seed': 42, 55 | 'addStepListener': True, 56 | 'logging': { 57 | 'stdout': False, 58 | 'filename': 'random.example.log', 59 | 'level': logging.DEBUG, 60 | }, 61 | 'sumo_parking_file': 'random_grid/parkingArea.add.xml', 62 | 'blacklist': [], 63 | 'vclasses': {'delivery', 'motorcycle', 'passenger'}, 64 | 'generic_conf': [], 65 | 'specific_conf': {}, 66 | 'subscriptions': { 67 | 'only_parkings': True, 68 | }, 69 | } 70 | 71 | monitor = ParkingMonitor(traci, parking_monitor_options) 72 | # parking travel time structure initialized 73 | monitor.compute_parking_travel_time() 74 | 75 | while traci.simulation.getMinExpectedNumber() > 0: 76 | traci.simulationStep() 77 | 78 | ## PARKING OPTIMIZATION 79 | for vehicle in monitor.get_vehicle_iterator(): 80 | if vehicle['arrived']: 81 | ## the vehicle is not in the simulation anymore 82 | continue 83 | if vehicle['stopped']: 84 | ## the vehicle is stopped and it does not require additional 85 | ## parking changes at least for the moment 86 | continue 87 | if not vehicle['edge'] or ':' in vehicle['edge']: 88 | ## the vehicle is on an intersection and the change would not be safe. 89 | continue 90 | if vehicle['stops']: 91 | _, _, stopping_place, stop_flags, _, _ = vehicle['stops'][0] 92 | if monitor.is_parking_area(stop_flags): 93 | ### OPTIMIZE VEHICLE 94 | availability = monitor.get_free_places(stopping_place, 95 | vclass=vehicle['vClass'], 96 | with_projections=False, 97 | with_uncertainty=False) 98 | if availability < 1: 99 | alternatives = monitor.get_closest_parkings(stopping_place, num=25) 100 | for trtime, alt in alternatives: 101 | alt_availability = monitor.get_free_places( 102 | alt, vclass=vehicle['vClass'], 103 | with_projections=False, with_uncertainty=False) 104 | print(trtime, alt, alt_availability) 105 | if alt_availability > 1: 106 | ## reroute vehicle 107 | route = None 108 | try: 109 | edge = monitor.get_parking_access(alt).split('_')[0] 110 | route = traci.simulation.findRoute( 111 | vehicle['edge'], edge, vType=vehicle['vClass']) 112 | except traci.exceptions.TraCIException: 113 | route = None 114 | 115 | if route and len(route.edges) >= 2: 116 | try: 117 | traci.vehicle.rerouteParkingArea(vehicle['id'], alt) 118 | print("""Vehicle {} is going to be rerouted from {} """ 119 | """[{}] to {} [{}].""".format(vehicle['id'], 120 | stopping_place, 121 | availability, alt, 122 | alt_availability)) 123 | except traci.exceptions.TraCIException: 124 | pprint.pprint([vehicle, 125 | monitor.get_parking_access(alt), 126 | route]) 127 | raise 128 | break 129 | 130 | if __name__ == '__main__': 131 | 132 | args = _args() 133 | 134 | ## ======================== PROFILER ======================== ## 135 | if args.profiling: 136 | import cProfile, pstats, io 137 | profiler = cProfile.Profile() 138 | profiler.enable() 139 | ## ======================== PROFILER ======================== ## 140 | 141 | try: 142 | _main() 143 | except traci.exceptions.TraCIException: 144 | exc_type, exc_value, exc_traceback = sys.exc_info() 145 | traceback.print_exception(exc_type, exc_value, exc_traceback, limit=10, file=sys.stdout) 146 | finally: 147 | 148 | ## ==================== PROFILER ======================== ## 149 | if args.profiling: 150 | profiler.disable() 151 | results = io.StringIO() 152 | pstats.Stats(profiler, stream=results).sort_stats('cumulative').print_stats(25) 153 | print(results.getvalue()) 154 | ## ==================== PROFILER ======================== ## 155 | 156 | traci.close() 157 | -------------------------------------------------------------------------------- /examples/random_grid/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | 3 | buildings 4 | complete.taz.weight.csv 5 | complete.taz.xml 6 | duarouter.cfg 7 | gen.script.sh 8 | poly.add.xml 9 | random.activitygen.json 10 | routeFixer.py 11 | sorted.stop.xml 12 | taz.shape.add.xml 13 | *.stop.out.xml 14 | *.summary.xml 15 | *.tripinfo.xml 16 | *.vehroute.xml -------------------------------------------------------------------------------- /examples/random_grid/basic.vType.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/random_grid/busStops.add.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/random_grid/parkingArea.add.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /examples/random_grid/random.sumocfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/random_grid/rou/buses.flows.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/simple.example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ Example of usage of PyPML. 4 | 5 | Python Parking Monitor Library (PyPML) 6 | 7 | Author: Lara CODECA 8 | 9 | This program and the accompanying materials are made available under the 10 | terms of the Eclipse Public License 2.0 which is available at 11 | http://www.eclipse.org/legal/epl-2.0. 12 | """ 13 | 14 | import argparse 15 | import logging 16 | import os 17 | import pprint 18 | import sys 19 | import traceback 20 | 21 | from pypml import ParkingMonitor 22 | 23 | # """ Import SUMO library """ 24 | if 'SUMO_TOOLS' in os.environ: 25 | sys.path.append(os.environ['SUMO_TOOLS']) 26 | import traci 27 | else: 28 | sys.exit("Please declare environment variable 'SUMO_TOOLS'") 29 | 30 | def _args(): 31 | """ 32 | Argument Parser 33 | ret: parsed arguments. 34 | """ 35 | parser = argparse.ArgumentParser( 36 | prog='{}'.format(sys.argv[0]), 37 | usage='%(prog)s', 38 | description='PyPML Simple Example') 39 | parser.add_argument( 40 | '--profiling', dest='profiling', action='store_true', 41 | help='Enable Python3 cProfile feature.') 42 | parser.add_argument( 43 | '--no-profiling', dest='profiling', action='store_false', 44 | help='Disable Python3 cProfile feature.') 45 | parser.set_defaults(profiling=False) 46 | return parser.parse_args() 47 | 48 | def _main(): 49 | """ Example of parking management in SUMO. """ 50 | 51 | traci.start(['sumo', '-c', 'test_scenario/sumo.simple.cfg']) 52 | 53 | parking_monitor_options = { 54 | 'seed': 42, 55 | 'addStepListener': True, 56 | 'logging': { 57 | 'stdout': False, 58 | 'filename': 'simple.example.log', 59 | 'level': logging.DEBUG, 60 | }, 61 | 'sumo_parking_file': 'test_scenario/parkings.small.add.xml', 62 | 'blacklist': [], 63 | 'vclasses': {'truck', 'passenger', 'motorcycle'}, 64 | 'generic_conf': [], 65 | 'specific_conf': {}, 66 | 'subscriptions': { 67 | 'only_parkings': True, 68 | }, 69 | } 70 | 71 | monitor = ParkingMonitor(traci, parking_monitor_options) 72 | # parking travel time structure initialized 73 | monitor.compute_parking_travel_time() 74 | 75 | while traci.simulation.getMinExpectedNumber() > 0: 76 | traci.simulationStep() 77 | 78 | ## PARKING OPTIMIZATION 79 | for vehicle in monitor.get_vehicle_iterator(): 80 | if vehicle['arrived']: 81 | ## the vehicle is not in the simulation anymore 82 | continue 83 | if vehicle['stopped']: 84 | ## the vehicle is stopped and it does not require additional 85 | ## parking changes at least for the moment 86 | continue 87 | if not vehicle['edge'] or ':' in vehicle['edge']: 88 | ## the vehicle is on an intersection and the change would not be safe. 89 | continue 90 | if vehicle['stops']: 91 | _, _, stopping_place, stop_flags, _, _ = vehicle['stops'][0] 92 | if monitor.is_parking_area(stop_flags): 93 | ### OPTIMIZE VEHICLE 94 | availability = monitor.get_free_places(stopping_place, 95 | vclass=vehicle['vClass'], 96 | with_projections=False, 97 | with_uncertainty=False) 98 | if availability < 1: 99 | alternatives = monitor.get_closest_parkings(stopping_place, num=25) 100 | for trtime, alt in alternatives: 101 | alt_availability = monitor.get_free_places( 102 | alt, vclass=vehicle['vClass'], 103 | with_projections=False, with_uncertainty=False) 104 | print(trtime, alt, alt_availability) 105 | if alt_availability > 1: 106 | ## reroute vehicle 107 | route = None 108 | try: 109 | edge = monitor.get_parking_access(alt).split('_')[0] 110 | route = traci.simulation.findRoute( 111 | vehicle['edge'], edge, vType=vehicle['vClass']) 112 | except traci.exceptions.TraCIException: 113 | route = None 114 | 115 | if route and len(route.edges) >= 2: 116 | try: 117 | traci.vehicle.rerouteParkingArea(vehicle['id'], alt) 118 | print("""Vehicle {} is going to be rerouted from {} """ 119 | """[{}] to {} [{}].""".format(vehicle['id'], 120 | stopping_place, 121 | availability, alt, 122 | alt_availability)) 123 | except traci.exceptions.TraCIException: 124 | pprint.pprint([vehicle, 125 | monitor.get_parking_access(alt), 126 | route]) 127 | raise 128 | break 129 | 130 | if __name__ == '__main__': 131 | 132 | args = _args() 133 | 134 | ## ======================== PROFILER ======================== ## 135 | if args.profiling: 136 | import cProfile, pstats, io 137 | profiler = cProfile.Profile() 138 | profiler.enable() 139 | ## ======================== PROFILER ======================== ## 140 | 141 | try: 142 | _main() 143 | except traci.exceptions.TraCIException: 144 | exc_type, exc_value, exc_traceback = sys.exc_info() 145 | traceback.print_exception(exc_type, exc_value, exc_traceback, limit=10, file=sys.stdout) 146 | finally: 147 | 148 | ## ==================== PROFILER ======================== ## 149 | if args.profiling: 150 | profiler.disable() 151 | results = io.StringIO() 152 | pstats.Stats(profiler, stream=results).sort_stats('cumulative').print_stats(25) 153 | print(results.getvalue()) 154 | ## ==================== PROFILER ======================== ## 155 | 156 | traci.close() 157 | -------------------------------------------------------------------------------- /examples/subscriptions.example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ Example of usage of PyPML. 4 | 5 | Python Parking Monitor Library (PyPML) 6 | 7 | Author: Lara CODECA 8 | 9 | This program and the accompanying materials are made available under the 10 | terms of the Eclipse Public License 2.0 which is available at 11 | http://www.eclipse.org/legal/epl-2.0. 12 | """ 13 | 14 | import argparse 15 | import logging 16 | import os 17 | import pprint 18 | import random 19 | import sys 20 | import traceback 21 | 22 | from pypml import ParkingMonitor 23 | 24 | # """ Import SUMO library """ 25 | if 'SUMO_TOOLS' in os.environ: 26 | sys.path.append(os.environ['SUMO_TOOLS']) 27 | import traci 28 | else: 29 | sys.exit("Please declare environment variable 'SUMO_TOOLS'") 30 | 31 | def _args(): 32 | """ 33 | Argument Parser 34 | ret: parsed arguments. 35 | """ 36 | parser = argparse.ArgumentParser( 37 | prog='{}'.format(sys.argv[0]), 38 | usage='%(prog)s', 39 | description='PyPML Subscriptions Example') 40 | parser.add_argument( 41 | '--profiling', dest='profiling', action='store_true', 42 | help='Enable Python3 cProfile feature.') 43 | parser.add_argument( 44 | '--no-profiling', dest='profiling', action='store_false', 45 | help='Disable Python3 cProfile feature.') 46 | parser.set_defaults(profiling=False) 47 | return parser.parse_args() 48 | 49 | def _main(): 50 | """ Example of parking management in SUMO. """ 51 | 52 | traci.start(['sumo', '-c', 'test_scenario/sumo.subscriptions.cfg']) 53 | 54 | parking_monitor_options = { 55 | 'seed': 42, 56 | 'addStepListener': True, 57 | 'logging': { 58 | 'stdout': False, 59 | 'filename': 'subscriptions.example.log', 60 | 'level': logging.DEBUG, 61 | }, 62 | 'sumo_parking_file': 'test_scenario/parkings.big.add.xml', 63 | 'blacklist': [], 64 | 'vclasses': {'truck', 'passenger', 'motorcycle'}, 65 | 'generic_conf': [ 66 | { 67 | 'cond': ['=', 1, 1], 68 | 'set_to': [ 69 | ['capacity_by_class', {'truck': 15, 70 | 'passenger': 50, 71 | 'motorcycle': 35,}], 72 | ['subscriptions_by_class', {'truck': [5, set()], 73 | 'passenger': [15, set()], 74 | 'motorcycle': [10, set()]}], 75 | ], 76 | }, 77 | ], 78 | 'specific_conf': {}, 79 | 'subscriptions': { 80 | 'only_parkings': True, 81 | }, 82 | } 83 | 84 | monitor = ParkingMonitor(traci, parking_monitor_options) 85 | # parking travel time structure initialized 86 | monitor.compute_parking_travel_time() 87 | 88 | vehicles_with_subscriptions = set() 89 | 90 | while traci.simulation.getMinExpectedNumber() > 0: 91 | traci.simulationStep() 92 | 93 | ## PARKING SUBSCRIPTION 94 | for vehicle in monitor.get_vehicle_iterator(): 95 | if vehicle['arrived']: 96 | ## the vehicle is not in the simulation anymore 97 | continue 98 | if vehicle['stopped']: 99 | ## the vehicle is stopped and it does not require additional 100 | ## parking changes at least for the moment 101 | continue 102 | if vehicle['id'] in vehicles_with_subscriptions: 103 | # nothing to be done here 104 | continue 105 | if not vehicle['edge'] or ':' in vehicle['edge']: 106 | # the vehicle is not yet inserted or in an intersection 107 | # and the change would not be safe. 108 | continue 109 | if vehicle['stops']: 110 | _, _, stopping_place, stop_flags, _, _ = vehicle['stops'][0] 111 | if monitor.is_parking_area(stop_flags): 112 | ### TRY TO SUBSCRIBE TO YOUR PARKING 113 | subscriptions = monitor.get_parking_subscriptions(stopping_place) 114 | capacity, vehicles = subscriptions[vehicle['vClass']] 115 | free_spots = capacity - len(vehicles) 116 | if free_spots >= 1: 117 | res = monitor.subscribe_vehicle_to_parking( 118 | stopping_place, vehicle['vClass'], vehicle['id']) 119 | if res: 120 | vehicles_with_subscriptions.add(vehicle['id']) 121 | print(vehicle['id'], 'subscribed to parking', stopping_place) 122 | continue 123 | 124 | ## CENTRALIZED PARKING OPTIMIZATION 125 | for parking in monitor.get_parking_iterator(): 126 | 127 | availability = monitor.get_free_places( 128 | parking['sumo']['id'], with_subscriptions=True, with_projections=True, 129 | with_uncertainty=False) 130 | 131 | for v_type, free_spaces in availability.items(): 132 | if free_spaces >= 0: 133 | continue 134 | 135 | projections = parking['projections_by_class'][v_type] 136 | _, subscriptions = parking['subscriptions_by_class'][v_type] 137 | candidates = projections - subscriptions 138 | 139 | ## redistribute the problem 140 | to_reroute = random.sample(candidates, min(abs(free_spaces), len(candidates))) 141 | 142 | for vid in to_reroute: 143 | is_rerouted = False 144 | 145 | vehicle = monitor.get_vehicle(vid) 146 | if not vehicle['edge'] or ':' in vehicle['edge']: 147 | ## the vehicle is on an intersection and the change would not be safe. 148 | continue 149 | if vehicle['stopped']: 150 | ## the vehicle is stopped and it does not require additional 151 | ## parking changes at least for the moment 152 | continue 153 | _, _, stopping_place, stop_flags, _, _ = vehicle['stops'][0] 154 | if not monitor.is_parking_area(stop_flags): 155 | ## the next stop is not associated to a parking area 156 | continue 157 | 158 | alternatives = monitor.get_closest_parkings(stopping_place, num=25) 159 | for trtime, alt in alternatives: 160 | alt_availability = monitor.get_free_places( 161 | alt, vclass=vehicle['vClass'], 162 | with_subscriptions=True, with_uncertainty=True) 163 | if alt_availability > 0: 164 | ## reroute vehicle 165 | route = None 166 | try: 167 | edge = monitor.get_parking_access(alt).split('_')[0] 168 | route = traci.simulation.findRoute( 169 | vehicle['edge'], edge, vType=vehicle['vClass']) 170 | except traci.exceptions.TraCIException: 171 | route = None 172 | 173 | if route and len(route.edges) >= 2: 174 | try: 175 | traci.vehicle.rerouteParkingArea(vehicle['id'], alt) 176 | is_rerouted = True 177 | print("""Vehicle {} is going to be rerouted from {} """ 178 | """to {} [{}].""".format(vehicle['id'], stopping_place, 179 | alt, alt_availability)) 180 | except traci.exceptions.TraCIException: 181 | pprint.pprint([vehicle, 182 | monitor.get_parking_access(alt), 183 | route]) 184 | raise 185 | break 186 | if not is_rerouted: 187 | print("Vehicle {} failed to reroute".format(vehicle['id'])) 188 | 189 | 190 | if __name__ == '__main__': 191 | 192 | args = _args() 193 | 194 | ## ======================== PROFILER ======================== ## 195 | if args.profiling: 196 | import cProfile, pstats, io 197 | profiler = cProfile.Profile() 198 | profiler.enable() 199 | ## ======================== PROFILER ======================== ## 200 | 201 | try: 202 | _main() 203 | except traci.exceptions.TraCIException: 204 | exc_type, exc_value, exc_traceback = sys.exc_info() 205 | traceback.print_exception(exc_type, exc_value, exc_traceback, limit=10, file=sys.stdout) 206 | finally: 207 | 208 | ## ==================== PROFILER ======================== ## 209 | if args.profiling: 210 | profiler.disable() 211 | results = io.StringIO() 212 | pstats.Stats(profiler, stream=results).sort_stats('cumulative').print_stats(25) 213 | print(results.getvalue()) 214 | ## ==================== PROFILER ======================== ## 215 | 216 | traci.close() 217 | -------------------------------------------------------------------------------- /examples/test_scenario/.gitignore: -------------------------------------------------------------------------------- 1 | duarouter.sumocfg 2 | edges.taz.xml 3 | example.netgenerate.cfg 4 | example.trips.xml 5 | mobilitygen.json 6 | taz.shape.xml 7 | taz.weight.csv 8 | 9 | test.sumocfg 10 | *.lanechanges.out.xml 11 | *.stop.out.xml 12 | *.summary.xml 13 | *.tripinfo.xml 14 | *.vehroute.xml -------------------------------------------------------------------------------- /examples/test_scenario/parkings.big.add.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/test_scenario/parkings.small.add.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/test_scenario/sumo.simple.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/test_scenario/sumo.subscriptions.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/test_scenario/vType.add.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/uncert.example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ Example of usage of PyPML. 4 | 5 | Python Parking Monitor Library (PyPML) 6 | 7 | Author: Lara CODECA 8 | 9 | This program and the accompanying materials are made available under the 10 | terms of the Eclipse Public License 2.0 which is available at 11 | http://www.eclipse.org/legal/epl-2.0. 12 | """ 13 | 14 | import argparse 15 | import logging 16 | import os 17 | import sys 18 | import traceback 19 | from pypml import ParkingMonitor 20 | 21 | # """ Import SUMO library """ 22 | if 'SUMO_TOOLS' in os.environ: 23 | sys.path.append(os.environ['SUMO_TOOLS']) 24 | import traci 25 | else: 26 | sys.exit("Please declare environment variable 'SUMO_TOOLS'") 27 | 28 | def _args(): 29 | """ 30 | Argument Parser 31 | ret: parsed arguments. 32 | """ 33 | parser = argparse.ArgumentParser( 34 | prog='{}'.format(sys.argv[0]), 35 | usage='%(prog)s', 36 | description='PyPML Uncertainty Example') 37 | parser.add_argument( 38 | '--profiling', dest='profiling', action='store_true', 39 | help='Enable Python3 cProfile feature.') 40 | parser.add_argument( 41 | '--no-profiling', dest='profiling', action='store_false', 42 | help='Disable Python3 cProfile feature.') 43 | parser.set_defaults(profiling=False) 44 | return parser.parse_args() 45 | 46 | def _main(): 47 | """ Example of parking management in SUMO. """ 48 | 49 | traci.start(['sumo', '-c', 'test_scenario/sumo.simple.cfg']) 50 | 51 | parking_monitor_options = { 52 | 'seed': 42, 53 | 'addStepListener': True, 54 | 'logging': { 55 | 'stdout': False, 56 | 'filename': 'uncert.example.log', 57 | 'level': logging.DEBUG, 58 | }, 59 | 'sumo_parking_file': 'test_scenario/parkings.small.add.xml', 60 | 'blacklist': [], 61 | 'vclasses': {'truck', 'passenger', 'motorcycle'}, 62 | 'generic_conf': [ 63 | { 64 | 'cond': ['<', 'total_capacity', 25], 65 | 'set_to': [ 66 | ['uncertainty', { 67 | 'mu': 0.0, 68 | 'sigma': ['*', 'total_capacity', 0.10] 69 | } 70 | ], 71 | ], 72 | }, 73 | ], 74 | 'specific_conf': {}, 75 | 'subscriptions': { 76 | 'only_parkings': True, 77 | }, 78 | } 79 | 80 | monitor = ParkingMonitor(traci, parking_monitor_options) 81 | # parking travel time structure initialized 82 | monitor.compute_parking_travel_time() 83 | 84 | while traci.simulation.getMinExpectedNumber() > 0: 85 | traci.simulationStep() 86 | 87 | ## PARKING OPTIMIZATION 88 | for vehicle in monitor.get_vehicle_iterator(): 89 | if vehicle['arrived']: 90 | ## the vehicle is not in the simulation anymore 91 | continue 92 | if vehicle['stopped']: 93 | ## the vehicle is stopped and it does not require additional 94 | ## parking changes at least for the moment 95 | continue 96 | if not vehicle['edge'] or ':' in vehicle['edge']: 97 | ## the vehicle is on an intersection and the change would not be safe. 98 | continue 99 | if vehicle['stops']: 100 | _, _, stopping_place, stop_flags, _, _ = vehicle['stops'][0] 101 | if monitor.is_parking_area(stop_flags): 102 | ### OPTIMIZE VEHICLE 103 | availability = monitor.get_free_places(stopping_place, 104 | vclass=vehicle['vClass'], 105 | with_projections=False, 106 | with_uncertainty=True) 107 | if availability < 1: 108 | alternatives = monitor.get_closest_parkings(stopping_place, num=25) 109 | for trtime, alt in alternatives: 110 | alt_availability = monitor.get_free_places( 111 | alt, vclass=vehicle['vClass'], 112 | with_projections=False, with_uncertainty=True) 113 | print(trtime, alt, alt_availability) 114 | if alt_availability > 1: 115 | ## reroute vehicle 116 | route = None 117 | try: 118 | edge = monitor.get_parking_access(alt).split('_')[0] 119 | route = traci.simulation.findRoute( 120 | vehicle['edge'], edge, vType=vehicle['vClass']) 121 | except traci.exceptions.TraCIException: 122 | route = None 123 | 124 | if route and len(route.edges) >= 2: 125 | try: 126 | traci.vehicle.rerouteParkingArea(vehicle['id'], alt) 127 | print("""Vehicle {} is going to be rerouted from {} """ 128 | """[{}] to {} [{}].""".format(vehicle['id'], 129 | stopping_place, 130 | availability, alt, 131 | alt_availability)) 132 | except traci.exceptions.TraCIException: 133 | print([monitor.get_parking_access(alt), route]) 134 | raise 135 | break 136 | 137 | if __name__ == '__main__': 138 | 139 | args = _args() 140 | 141 | ## ======================== PROFILER ======================== ## 142 | if args.profiling: 143 | import cProfile, pstats, io 144 | profiler = cProfile.Profile() 145 | profiler.enable() 146 | ## ======================== PROFILER ======================== ## 147 | 148 | try: 149 | _main() 150 | except traci.exceptions.TraCIException: 151 | exc_type, exc_value, exc_traceback = sys.exc_info() 152 | traceback.print_exception(exc_type, exc_value, exc_traceback, limit=10, file=sys.stdout) 153 | finally: 154 | 155 | ## ==================== PROFILER ======================== ## 156 | if args.profiling: 157 | profiler.disable() 158 | results = io.StringIO() 159 | pstats.Stats(profiler, stream=results).sort_stats('cumulative').print_stats(25) 160 | print(results.getvalue()) 161 | ## ==================== PROFILER ======================== ## 162 | 163 | traci.close() 164 | -------------------------------------------------------------------------------- /pydocs/pypml.ParkingMonitor.html: -------------------------------------------------------------------------------- 1 | 2 | Python: class ParkingMonitor 3 | 4 | 5 |

6 | 7 | 8 | 10 | 11 | 12 | 15 | 16 |
 
9 | pypml.ParkingMonitor = class ParkingMonitor(traci.StepListener)
   pypml.ParkingMonitor(traci_handler, options)
13 |  
14 | SUMO StepListener class for the parking monitoring.
 
 
Method resolution order:
17 |
ParkingMonitor
18 |
traci.StepListener
19 |
builtins.object
20 |
21 |
22 | Methods defined here:
23 |
__init__(self, traci_handler, options)
Initialize the knowlegde base for the parking monitor.
24 |  
25 | traci_handler: already initialized TraCI socket that is going to be used by PyPML
26 | options:       in order to reduce the number of parameters and increase the flexibility,
27 |                the complete initialization is done using a dict()
28 |  
29 | Options format:
30 | {
31 |     'seed': Integer. Initialization seed for numpy.random.RandomState.
32 |     'addStepListener': Boolean. Ff True, pypml is added as step listener in SUMO.
33 |                        In case it's False the function step() must be called by hand every
34 |                        simulation step.
35 |     'logging': {
36 |         'stdout': Boolean. If True, the logging will be both file and stdout.
37 |         'filename': String. Name of the logging file.
38 |         'level': Logging level as defined in the logging library. E.g.: logging.DEBUG
39 |     },
40 |     'sumo_parking_file': String. Path and file name of the SUMO additional file defining the
41 |                          parkings.
42 |     'blacklist': List of strings. List of the parking IDS ans defined in sumo that must be
43 |                  excluded from the monitoring.
44 |     'vclasses': Set of strings. Set of vTypes, as defined in SUMO, comprehensive of all the
45 |                 vehicles used in the simulation.
46 |     'generic_conf': List. List of generic configuration applied to all the parking areas.
47 |     'specific_conf': Dictionary. Dictionary of configuration applied to specific parking
48 |                      areas.
49 |     'subscriptions': {
50 |         'only_parkings': Boolean. If True, PyPML subscribes only to the vehicles that have
51 |                          a <stop> define at the beginning of the simulation.
52 |     },
53 | }
54 |  
55 | Example of generic_conf:
56 | 'cond' is an expression (prefix notation) that is going to be evaluate agains all the
57 |        parking areas.
58 | 'set_to' is a list of 'parameter' -> {value/expression} that are going to be applied if
59 |          'cond' is true.
60 | [
61 |     {
62 |         'cond': ['>', 'total_capacity', 50],
63 |         'set_to': [
64 |             ['uncertainty', {
65 |                 'mu': 0.0,
66 |                 'sigma': ['*', 'total_capacity', 0.20]
67 |                 }
68 |             ],
69 |         ],
70 |     },
71 | ]
72 | In this example, every parkign area with more than 50 places will have an uncertainty with
73 |     sigma equal to 20% of the capacity.
74 |  
75 | Example of specific_conf:
76 | Each parking area needs a specific entry in the dictionary.
77 | The values that can be set are:
78 |     'capacity_by_class'
79 |     'subscriptions_by_class'
80 |     'uncertainty'
81 | and the values are assigned, so they must be complete.
82 |  
83 | {
84 |     'parking_id': {
85 |         'capacity_by_class': {
86 |             'vType': number,
87 |             ...
88 |             'vType': number,
89 |         },
90 |     },
91 | }
92 | 93 |
compute_parking_travel_time(self)
For each parking, saves the parkings reachable by 'passenger' vClass where the weight
94 | is the travel time at the current stage of the simulation.
95 | Each call of the funcion destroy the previous state.
96 | 97 |
get_closest_parkings(self, parking, num=None)
Return the 'num' closest parkings by travel time from the requested parking.
98 | It requires the travel time structure initialization using
99 | compute_parking_travel_time(), it raises an Exception if the structure is not yet
100 | initialized.
101 |  
102 | parking: String. Parking area ID as defined in SUMO.
103 | num:     Int. Number of element to be returned.
104 | 105 |
get_free_places(self, parking, with_uncertainty=False, vclass=None, with_projections=False, with_subscriptions=False)
Returns the free places in a given parking area.
106 | Raises an Exception if the requested parking area does not exist.
107 |  
108 | parking:            String. Parking area ID as defined in SUMO.
109 | with_uncertainty:   Boolean. If True, uncertainty is applied.
110 | vclass:             String. If set, returns values only for the specified vType.
111 | with_projections:   Boolean. If True, projections are taken into account.
112 | with_subscriptions: Boolean. If True, subscriptions are taken into account.
113 | 114 |
get_parking(self, parking)
Return the parking area with the given ID or None if not existent.
115 |  
116 | parking: String. Parking area ID as defined in SUMO.
117 | 118 |
get_parking_access(self, parking)
Given a parking ID, returns the lane information.
119 | Raise an Exception in canse the requested parking id does not exist.
120 |  
121 | parking: String. Parking area ID as defined in SUMO.
122 | 123 |
get_parking_capacity_vclass(self, parking)
Given a parking ID, returns the capacity by vclass information.
124 | Raises an Exception if the requested parking area does not exist.
125 |  
126 | parking: String. Parking area ID as defined in SUMO.
127 | 128 |
get_parking_iterator(self)
Return the parking iterator.
129 | 130 |
get_parking_projections(self, parking)
Given a parking ID, returns the projections information.
131 | Raises an Exception if the requested parking area does not exist.
132 |  
133 | parking: String. Parking area ID as defined in SUMO.
134 | 135 |
get_parking_subscriptions(self, parking)
Given a parking ID, returns the subscriptions information.
136 | Raises an Exception if the requested parking area does not exist.
137 |  
138 | parking: String. Parking area ID as defined in SUMO.
139 | 140 |
get_passenger_iterator(self)
Return the passenger iterator.
141 | 142 |
get_rerouter_iterator(self, step)
Return the rerouter info for the given step.
143 |  
144 | step: Float. Simulation time in seconds.
145 | 146 |
get_traci_simulation_subscriptions(self)
Return TraCI simulation subscriptions for the last simulation-step.
147 | 148 |
get_traci_vehicle_subscriptions(self)
Return TraCI vehicle subscriptions for the last simulation-step.
149 | 150 |
get_vehicle(self, vehicle)
Return the vehicle with the given ID or None if not existent.
151 |  
152 | vehicle: String. Vehicle ID as defined in SUMO.
153 | 154 |
get_vehicle_iterator(self)
Return the vehicle info.
155 | 156 |
remove_subscribed_vehicle(self, parking, vclass, vehicle)
Remove the vehicles from the subscriptions of the given parking id.
157 | Raises an Exception if the requested parking area does not exist or
158 | if the subscriptions are not initialized.
159 |  
160 | parking: String. Parking area ID as defined in SUMO.
161 | vclass:  String. vType as defined in SUMO.
162 | vehicle: String. Vehicle ID as defined in SUMO.
163 | 164 |
set_parking_capacity_vclass(self, parking, capacities)
Set the given capacity by vclass to the parking id.
165 | Raises an Exception if the requested parking area does not exist or
166 | if the capacities are not in a valid format.
167 |  
168 | parking:    String. Parking area ID as defined in SUMO.
169 | capacities: Dict. { 'vType': int, .., 'vType': int }
170 | 171 |
set_parking_subscriptions(self, parking, subscriptions)
Set the given subsctiption to the parking id.
172 | Raises an Exception if the requested parking area does not exist or
173 | if the subscriptions are not in a valid format.
174 |  
175 | parking:       String. Parking area ID as defined in SUMO.
176 | subscriptions: Dict. { 'vType': int, .., 'vType': int }
177 | 178 |
set_vehicle_param(self, vehicle, param, value)
Set the param=value in the vehicle with the given ID.
179 | Returns False if the vehicle does not exist.
180 |  
181 | vehicle: String. Vehicle ID as defined in SUMO.
182 | param:   String. Parameters name.
183 | value:   Any. Value for the parameter.
184 | 185 |
step(self, t=0)
TraCI StepListener caller.
186 |  
187 | In order to be independent from the call, t is not used and the time is directly
188 | retrieved using simulation.getTime().
189 | 190 |
subscribe_vehicle_to_parking(self, parking, vclass, vehicle)
Add the vehicle to the subscription list of the parking area.
191 | Returns False iif the number of already subscribed vehicles is equal to the number
192 | of spots available for that specific vclass.
193 | Raises an Exception if the requested parking area does not exist or
194 | if the subscriptions are not initialized.
195 |  
196 | parking: String. Parking area ID as defined in SUMO.
197 | vclass:  String. vType as defined in SUMO.
198 | vehicle: String. Vehicle ID as defined in SUMO.
199 | 200 |
201 | Static methods defined here:
202 |
is_parking_area(flags)
isStoppedParking(string) -> bool
203 | Return whether the vehicle is parking (implies stopped)
204 |  
205 | The flags integer is defined as
206 |    1 * stopped +
207 |    2 * parking +
208 |    4 * personTriggered +
209 |    8 * containerTriggered +
210 |   16 * isBusStop +
211 |   32 * isContainerStop +
212 |   64 * chargingStation +
213 |  128 * parkingarea
214 | with each of these flags defined as 0 or 1
215 | 216 |
217 | Methods inherited from traci.StepListener:
218 |
cleanUp(self)
cleanUp() -> None
219 |  
220 | This method is called at removal of the stepListener, allowing to schedule some final actions
221 | 222 |
getID(self)
223 | 224 |
setID(self, ID)
225 | 226 |
227 | Data descriptors inherited from traci.StepListener:
228 |
__dict__
229 |
dictionary for instance variables (if defined)
230 |
231 |
__weakref__
232 |
list of weak references to the object (if defined)
233 |
234 |
235 | Data and other attributes inherited from traci.StepListener:
236 |
__metaclass__ = <class 'abc.ABCMeta'>
Metaclass for defining Abstract Base Classes (ABCs).
237 |  
238 | Use this metaclass to create an ABC.  An ABC can be subclassed
239 | directly, and then acts as a mix-in class.  You can also register
240 | unrelated concrete classes (even built-in classes) and unrelated
241 | ABCs as 'virtual subclasses' -- these and their descendants will
242 | be considered subclasses of the registering ABC by the built-in
243 | issubclass() function, but the registering ABC won't show up in
244 | their MRO (Method Resolution Order) nor will method
245 | implementations defined by the registering ABC be callable (not
246 | even via super()).
247 | 248 |
249 | -------------------------------------------------------------------------------- /pypml/__init__.py: -------------------------------------------------------------------------------- 1 | """ Init of the parking monitor for SUMO simulations. 2 | 3 | Python Parking Monitor Library (PyPML) 4 | 5 | Author: Lara CODECA 6 | 7 | This program and the accompanying materials are made available under the 8 | terms of the Eclipse Public License 2.0 which is available at 9 | http://www.eclipse.org/legal/epl-2.0. 10 | """ 11 | 12 | from .pypml import ParkingMonitor 13 | -------------------------------------------------------------------------------- /pypml/pypml.py: -------------------------------------------------------------------------------- 1 | """ Python Parking Monitor Library (PyPML) 2 | 3 | Author: Lara CODECA 4 | 5 | This program and the accompanying materials are made available under the 6 | terms of the Eclipse Public License 2.0 which is available at 7 | http://www.eclipse.org/legal/epl-2.0. 8 | """ 9 | 10 | import collections 11 | import copy 12 | import logging 13 | import operator 14 | import os 15 | import sys 16 | import xml.etree.ElementTree 17 | 18 | from numpy.random import RandomState 19 | 20 | # """ Import TraCI library """ 21 | if 'SUMO_TOOLS' in os.environ: 22 | sys.path.append(os.environ['SUMO_TOOLS']) 23 | import traci 24 | import traci.constants as tc 25 | else: 26 | sys.exit("""The PyPML package uses SUMO TraCI API. 27 | Please declare the environment variable 'SUMO_TOOLS'""") 28 | 29 | class ParkingMonitorGenericError(Exception): 30 | """ Parking Monitor Exception Class """ 31 | message = None 32 | 33 | def __init__(self, message=None): 34 | """ Init the error message. """ 35 | super().__init__() 36 | self.message = message 37 | 38 | def __str__(self): 39 | return self.message 40 | 41 | class ParkingMonitor(traci.StepListener): 42 | """ SUMO StepListener class for the parking monitoring. """ 43 | 44 | _logger = None 45 | _options = None 46 | _random = None 47 | 48 | _parking_db = dict() 49 | _routers_db = dict() 50 | _vehicles_db = dict() 51 | _passengers_db = set() 52 | 53 | _edges_routers_mapping = collections.defaultdict(list) 54 | 55 | _blacklisted_edges_pairs = collections.defaultdict(list) 56 | _static_parking_travel_time = collections.defaultdict(list) 57 | 58 | _traci_handler = None 59 | _traci_arrived_list = None 60 | _traci_departed_list = None 61 | _traci_vehicle_subscription = None 62 | _traci_simulation_subscriptions = None 63 | _traci_starting_stop_subscriptions = None 64 | _traci_ending_stop_subscriptions = None 65 | 66 | ## =============================== INITIALIZATION ================================ ## 67 | 68 | def _logs(self): 69 | """ Log init. """ 70 | self._logger = logging.getLogger('parkingmonitor.ParkingMonitor') 71 | self._logger.setLevel(self._options['logging']['level']) 72 | 73 | handlers = [] 74 | formatter = logging.Formatter('[%(asctime)s][%(name)s][%(levelname)s] %(message)s') 75 | if self._options['logging']['filename']: 76 | # create file handler which logs even debug messages 77 | file_handler = logging.FileHandler(filename=self._options['logging']['filename'], 78 | mode='w') 79 | file_handler.setLevel(self._options['logging']['level']) 80 | file_handler.setFormatter(formatter) 81 | handlers.append(file_handler) 82 | 83 | if self._options['logging']['stdout']: 84 | # create console handler with a higher log level 85 | console_handler = logging.StreamHandler() 86 | console_handler.setLevel(self._options['logging']['level']) 87 | console_handler.setFormatter(formatter) 88 | handlers.append(console_handler) 89 | 90 | if handlers: 91 | # add the handlers to the logger 92 | for handler in handlers: 93 | self._logger.addHandler(handler) 94 | else: 95 | self._logger = None 96 | 97 | def __init__(self, traci_handler, options): 98 | """ Initialize the knowlegde base for the parking monitor. 99 | 100 | traci_handler: already initialized TraCI socket that is going to be used by PyPML 101 | options: in order to reduce the number of parameters and increase the flexibility, 102 | the complete initialization is done using a dict() 103 | 104 | Options format: 105 | { 106 | 'seed': Integer. Initialization seed for numpy.random.RandomState. 107 | 'addStepListener': Boolean. Ff True, pypml is added as step listener in SUMO. 108 | In case it's False the function step() must be called by hand every 109 | simulation step. 110 | 'logging': { 111 | 'stdout': Boolean. If True, the logging will be both file and stdout. 112 | 'filename': String. Name of the logging file. 113 | 'level': Logging level as defined in the logging library. E.g.: logging.DEBUG 114 | }, 115 | 'sumo_parking_file': String. Path and file name of the SUMO additional file defining the 116 | parkings. 117 | 'blacklist': List of strings. List of the parking IDS ans defined in sumo that must be 118 | excluded from the monitoring. 119 | 'vclasses': Set of strings. Set of vTypes, as defined in SUMO, comprehensive of all the 120 | vehicles used in the simulation. 121 | 'generic_conf': List. List of generic configuration applied to all the parking areas. 122 | 'specific_conf': Dictionary. Dictionary of configuration applied to specific parking 123 | areas. 124 | 'subscriptions': { 125 | 'only_parkings': Boolean. If True, PyPML subscribes only to the vehicles that have 126 | a define at the beginning of the simulation. 127 | }, 128 | } 129 | 130 | Example of generic_conf: 131 | 'cond' is an expression (prefix notation) that is going to be evaluate agains all the 132 | parking areas. 133 | 'set_to' is a list of 'parameter' -> {value/expression} that are going to be applied if 134 | 'cond' is true. 135 | [ 136 | { 137 | 'cond': ['>', 'total_capacity', 50], 138 | 'set_to': [ 139 | ['uncertainty', { 140 | 'mu': 0.0, 141 | 'sigma': ['*', 'total_capacity', 0.20] 142 | } 143 | ], 144 | ], 145 | }, 146 | ] 147 | In this example, every parkign area with more than 50 places will have an uncertainty with 148 | sigma equal to 20% of the capacity. 149 | 150 | Example of specific_conf: 151 | Each parking area needs a specific entry in the dictionary. 152 | The values that can be set are: 153 | 'capacity_by_class' 154 | 'subscriptions_by_class' 155 | 'uncertainty' 156 | and the values are assigned, so they must be complete. 157 | 158 | { 159 | 'parking_id': { 160 | 'capacity_by_class': { 161 | 'vType': number, 162 | ... 163 | 'vType': number, 164 | }, 165 | }, 166 | } 167 | """ 168 | 169 | self._options = options 170 | 171 | ## Random generator initialization 172 | if 'seed' in options: 173 | self._random = RandomState(seed=options['seed']) 174 | else: 175 | self._random = RandomState() 176 | 177 | ## Logs initialization 178 | self._logs() 179 | 180 | ## TraCI initialization 181 | self._traci_handler = traci_handler 182 | time = self._traci_handler.simulation.getTime() 183 | 184 | ## Read parkings and routers from SUMO add.xml 185 | self._load_parkings_and_routers() 186 | 187 | ## Populate the parking configurations 188 | total = 0 189 | for pid, parking in self._parking_db.items(): 190 | capacity = int( 191 | self._traci_handler.simulation.getParameter(pid, 'parkingArea.capacity')) 192 | occupancy = int( 193 | self._traci_handler.simulation.getParameter(pid, 'parkingArea.occupancy')) 194 | parking['total_capacity'] = capacity # TraCI value 195 | parking['total_occupancy'] = occupancy # TraCI value 196 | parking['occupancy_series'] = [(occupancy, time)] 197 | parking['occupancy_by_class'] = dict() 198 | parking['projections_by_class'] = dict() 199 | for vclass in self._options['vclasses']: 200 | parking['occupancy_by_class'][vclass] = set() 201 | parking['projections_by_class'][vclass] = set() 202 | 203 | ## Set DEFAULT values. 204 | parking['capacity_by_class'] = dict() 205 | parking['subscriptions_by_class'] = dict() 206 | parking['uncertainty'] = { 207 | 'mu': 0.0, 208 | 'sigma': 0.0, 209 | } 210 | 211 | ## Apply GENERAL CONFIGURATIONS 212 | for gopt in options['generic_conf']: 213 | if self._parse_generic_condition(gopt['cond'], parking): 214 | for key, value in gopt['set_to']: 215 | if key == 'uncertainty': 216 | parking['uncertainty'] = { 217 | 'mu': self._eval_expression(value['mu'], parking), 218 | 'sigma': self._eval_expression(value['sigma'], parking), 219 | } 220 | else: 221 | ## not sure what this can be 222 | parking[key] = copy.deepcopy(value) 223 | 224 | ## Apply SPECIFIC VALUES 225 | if pid in options['specific_conf'].keys(): 226 | if 'capacity_by_class' in options['specific_conf'][pid]: 227 | parking['capacity_by_class'] = copy.deepcopy( 228 | options['specific_conf'][pid]['capacity_by_class']) 229 | if 'subscriptions_by_class' in options['specific_conf'][pid]: 230 | parking['subscriptions_by_class'] = copy.deepcopy( 231 | options['specific_conf'][pid]['subscriptions_by_class']) 232 | if 'uncertainty' in options['specific_conf'][pid]: 233 | parking['uncertainty'] = { 234 | 'mu': self._eval_expression( 235 | options['specific_conf'][pid]['uncertainty']['mu'], parking), 236 | 'sigma': self._eval_expression( 237 | options['specific_conf'][pid]['uncertainty']['sigma'], parking), 238 | } 239 | 240 | total += capacity 241 | 242 | if self._logger: 243 | self._logger.info('Monitoring %s parkings with a total capacity of %d.', 244 | len(self._parking_db), total) 245 | 246 | ## Parkings subscriptions 247 | self._traci_handler.simulation.subscribe(varIDs=(tc.VAR_PARKING_STARTING_VEHICLES_IDS, 248 | tc.VAR_PARKING_ENDING_VEHICLES_IDS)) 249 | 250 | ## StepListener registration 251 | if self._options['addStepListener']: 252 | self._traci_handler.addStepListener(self) 253 | 254 | def _load_parkings_and_routers(self): 255 | """ Load the parking and routers definition from SUMO add.xml and apply restrictions. """ 256 | 257 | filename = self._options['sumo_parking_file'] 258 | xml_tree = xml.etree.ElementTree.parse(filename).getroot() 259 | 260 | for child in xml_tree: 261 | if child.tag == 'parkingArea' and child.attrib['id'] not in self._options['blacklist']: 262 | self._parking_db[child.attrib['id']] = { 263 | 'sumo': child.attrib, 264 | } 265 | ## rerouters 266 | elif child.tag == 'rerouter': 267 | self._routers_db[child.attrib['id']] = { 268 | 'id': child.attrib['id'], 269 | 'edges': child.attrib['edges'].strip(' ').split(' '), 270 | 'intervals': list(), 271 | } 272 | 273 | ## given and edge, retrieve the list of routers associated. 274 | for edge in self._routers_db[child.attrib['id']]['edges']: 275 | self._edges_routers_mapping[edge].append(child.attrib['id']) 276 | 277 | for interval in child: 278 | _end = float(interval.attrib['end']) * 1000 # interval in milliseconds 279 | parkings = list() 280 | for area in interval: 281 | if 'visible' in area.attrib and area.attrib['visible'] == 'true': 282 | parkings.append((area.attrib['id'], True)) 283 | else: 284 | parkings.append((area.attrib['id'], False)) 285 | self._routers_db[child.attrib['id']]['intervals'].append((_end, parkings)) 286 | 287 | if self._routers_db: 288 | if self._logger: 289 | self._logger.warning("""The parking rerouters that are define in SUMO additional 290 | files have priority over any TraCI API 291 | (e.g. traci.vehicle.rerouteParkingArea).""") 292 | 293 | 294 | @staticmethod 295 | def _get_operator(oper): 296 | """ Return the function associated to the operator. """ 297 | return { 298 | 'and': operator.and_, 299 | 'or': operator.or_, 300 | '>': operator.gt, 301 | '>=': operator.ge, 302 | '=': operator.eq, 303 | '<=': operator.le, 304 | '<': operator.lt, 305 | '+': operator.add, 306 | '-': operator.sub, 307 | '*': operator.mul, 308 | '/': operator.truediv, 309 | '**': operator.pow, 310 | }[oper] 311 | 312 | def _parse_generic_condition(self, condition, environment): 313 | """ Parse the generic condition and returns True or False accordingly. """ 314 | oper, operand_1, operand_2 = condition 315 | 316 | if isinstance(operand_1, list): 317 | operand_1 = self._parse_generic_condition(operand_1, environment) 318 | if isinstance(operand_2, list): 319 | operand_2 = self._parse_generic_condition(operand_2, environment) 320 | 321 | if operand_1 in environment: 322 | operand_1 = environment[operand_1] 323 | if operand_2 in environment: 324 | operand_2 = environment[operand_2] 325 | 326 | return self._get_operator(oper)(operand_1, operand_2) 327 | 328 | def _eval_expression(self, expr, environment): 329 | """ Evaluate an expression in the enviroment. """ 330 | ## usual case with simple evaluation 331 | if not isinstance(expr, list): 332 | if expr in environment: 333 | return environment[expr] 334 | return expr 335 | 336 | ## complex expression 337 | oper, operand_1, operand_2 = expr 338 | 339 | if isinstance(operand_1, list): 340 | operand_1 = self._eval_expression(operand_1, environment) 341 | if isinstance(operand_2, list): 342 | operand_2 = self._eval_expression(operand_2, environment) 343 | 344 | if operand_1 in environment: 345 | operand_1 = environment[operand_1] 346 | if operand_2 in environment: 347 | operand_2 = environment[operand_2] 348 | 349 | return self._get_operator(oper)(operand_1, operand_2) 350 | 351 | ## =============================== OVERLOADS =============================== ## 352 | 353 | def step(self, t=0): 354 | """ TraCI StepListener caller. 355 | 356 | In order to be independent from the call, t is not used and the time is directly 357 | retrieved using simulation.getTime(). 358 | """ 359 | time = self._traci_handler.simulation.getTime() 360 | self._monitor_vehicles(time) 361 | self._update_vehicles_db(time) 362 | self._update_parking_db(time) 363 | return True 364 | 365 | ## =============================== UTILITIES =============================== ## 366 | 367 | @staticmethod 368 | def is_parking_area(flags): 369 | """ isStoppedParking(string) -> bool 370 | Return whether the vehicle is parking (implies stopped) 371 | 372 | The flags integer is defined as 373 | 1 * stopped + 374 | 2 * parking + 375 | 4 * personTriggered + 376 | 8 * containerTriggered + 377 | 16 * isBusStop + 378 | 32 * isContainerStop + 379 | 64 * chargingStation + 380 | 128 * parkingarea 381 | with each of these flags defined as 0 or 1 382 | """ 383 | return (flags & 128) == 128 384 | 385 | def get_parking_access(self, parking): 386 | """ Given a parking ID, returns the lane information. 387 | Raise an ParkingMonitorGenericError in canse the requested parking id does not exist. 388 | 389 | parking: String. Parking area ID as defined in SUMO. 390 | """ 391 | if parking in self._parking_db.keys(): 392 | return self._parking_db[parking]['sumo']['lane'] 393 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 394 | 395 | ## =============================== MONITORING =============================== ## 396 | 397 | def _monitor_vehicles(self, step): 398 | """ Create subscriptions for the vehicles with planned stops in parking areas. """ 399 | self._traci_departed_list = self._traci_handler.simulation.getDepartedIDList() 400 | for vehicle in self._traci_departed_list: 401 | v_class = self._traci_handler.vehicle.getVehicleClass(vehicle) 402 | if self._options['subscriptions']['only_parkings'] and v_class in ['bus', 'rail']: 403 | continue 404 | 405 | current_stops = self._traci_handler.vehicle.getNextStops(vehicle) 406 | _parking_stops = set() 407 | for stop in current_stops: 408 | _, _, stopping_place, stop_flags, _, _ = stop 409 | if self.is_parking_area(stop_flags): 410 | _parking_stops.add(stopping_place) 411 | 412 | if self._options['subscriptions']['only_parkings'] and not _parking_stops: 413 | continue 414 | 415 | passengers = self._traci_handler.vehicle.getPersonIDList(vehicle) 416 | for passenger in passengers: 417 | self._passengers_db.add(passenger) 418 | 419 | if self._logger: 420 | self._logger.debug('[%.2f] Vehicle %s added to subscriptions.', step, vehicle) 421 | self._traci_handler.vehicle.subscribe( 422 | vehicle, varIDs=(tc.VAR_ROAD_ID, tc.VAR_NEXT_STOPS, tc.LAST_STEP_PERSON_ID_LIST)) 423 | 424 | self._vehicles_db[vehicle] = { 425 | 'id': vehicle, 426 | 'departure': step, 427 | 'edge': '', 428 | 'stops': current_stops, 429 | 'history': [], 430 | 'vClass': v_class, 431 | 'passengers': passengers, 432 | 'arrived': None, 433 | 'stopped': False, 434 | 'roadside': False, 435 | 'current_parking_area': None, 436 | } 437 | 438 | ## update parking projections 439 | for area in _parking_stops: 440 | self._parking_db[area]['projections_by_class'][v_class].add(vehicle) 441 | if self._logger: 442 | self._logger.debug('[%.2f] Vehicle %s added to the projections of %s.', 443 | step, vehicle, area) 444 | 445 | self._traci_arrived_list = self._traci_handler.simulation.getArrivedIDList() 446 | for vehicle in self._traci_arrived_list: 447 | if vehicle in self._vehicles_db: 448 | self._vehicles_db[vehicle]['arrived'] = step 449 | if self._logger: 450 | self._logger.debug('[%.2f] Vehicle %s has arrived.', step, vehicle) 451 | 452 | ## TODO: cleanup the parking areas --> Ask Jakob. 453 | parking_area = self._get_parking_area_from_vehicle(vehicle) 454 | if parking_area: 455 | v_class = self._vehicles_db[vehicle]['vClass'] 456 | if vehicle in self._parking_db[parking_area]['projections_by_class'][v_class]: 457 | if self._logger: 458 | self._logger.debug( 459 | '[%.2f] Vehicle %s removed from the projections of %s.', 460 | step, vehicle, parking_area) 461 | if vehicle in self._parking_db[parking_area]['occupancy_by_class'][v_class]: 462 | if self._logger: 463 | self._logger.debug( 464 | '[%.2f] Vehicle %s removed from parking area %s.', 465 | step, vehicle, parking_area) 466 | 467 | @staticmethod 468 | def _is_same_destinations(a_stops, b_stops): 469 | """ Return true iff the series of stops ids is the same. """ 470 | if len(a_stops) != len(b_stops): 471 | return False 472 | nmr_stops = len(a_stops) 473 | for pos in range(nmr_stops): 474 | if a_stops[pos][2] != b_stops[pos][2]: 475 | return False 476 | return True 477 | 478 | def _update_vehicles_db(self, step): 479 | """ Update subscriptions and vechiles database. """ 480 | self._traci_vehicle_subscription = self._traci_handler.vehicle.getAllSubscriptionResults() 481 | for vehicle, data in self._traci_vehicle_subscription.items(): 482 | ## always to update 483 | self._vehicles_db[vehicle]['edge'] = data[tc.VAR_ROAD_ID] 484 | self._vehicles_db[vehicle]['passengers'] = data[tc.LAST_STEP_PERSON_ID_LIST] 485 | for passenger in data[tc.LAST_STEP_PERSON_ID_LIST]: 486 | self._passengers_db.add(passenger) 487 | 488 | ## stop check 489 | current_stops = data[tc.VAR_NEXT_STOPS] 490 | _new_stops = set() 491 | for stop in current_stops: 492 | _, _, stopping_place, stop_flags, _, _ = stop 493 | if self.is_parking_area(stop_flags): 494 | _new_stops.add(stopping_place) 495 | 496 | if self._is_same_destinations(self._vehicles_db[vehicle]['stops'], current_stops): 497 | ## nothing changed 498 | continue 499 | 500 | if self._logger: 501 | self._logger.debug('[%.2f] Stop change for %s.', step, vehicle) 502 | 503 | ## update parking projections 504 | _old_stops = set() 505 | for _, _, _stop, _flags, _, _ in self._vehicles_db[vehicle]['stops']: 506 | if self.is_parking_area(_flags): 507 | _old_stops.add(_stop) 508 | v_class = self._vehicles_db[vehicle]['vClass'] 509 | for area in _old_stops - _new_stops: 510 | ## the vehicle may have already been removed (when added to occupancy_by_vclass) 511 | ## if the change in stops is due to a vehilce leaving the parking 512 | if vehicle in self._parking_db[area]['projections_by_class'][v_class]: 513 | self._parking_db[area]['projections_by_class'][v_class].remove(vehicle) 514 | if self._logger: 515 | self._logger.debug('[%.2f] Vehicle %s removed from the projections of %s.', 516 | step, vehicle, area) 517 | for area in _new_stops - _old_stops: 518 | self._parking_db[area]['projections_by_class'][v_class].add(vehicle) 519 | if self._logger: 520 | self._logger.debug('[%.2f] Vehicle %s added to the projections of %s.', 521 | step, vehicle, area) 522 | 523 | ## update stops 524 | self._vehicles_db[vehicle]['history'].append( 525 | self._vehicles_db[vehicle]['stops']) 526 | self._vehicles_db[vehicle]['stops'] = current_stops 527 | 528 | if self._options['subscriptions']['only_parkings'] and not current_stops: 529 | if self._logger: 530 | self._logger.debug('[%.2f] Unsubscribing from vehicle %s, no additional stops.', 531 | step, vehicle) 532 | try: 533 | self._traci_handler.vehicle.unsubscribe(vehicle) 534 | except traci.exceptions.TraCIException: 535 | if self._logger: 536 | self._logger.critical('[%.2f] Unsubscription failed.', step) 537 | 538 | def _check_occupancy(self, step): 539 | """ Gather parking current occupancy. """ 540 | for parking in self._parking_db: 541 | occupancy = int(self._traci_handler.simulation.getParameter(parking, 542 | 'parkingArea.occupancy')) 543 | if self._parking_db[parking]['total_occupancy'] != occupancy: 544 | self._parking_db[parking]['occupancy_series'].append((occupancy, step)) 545 | self._parking_db[parking]['total_occupancy'] = occupancy 546 | 547 | def _get_parking_area_from_vehicle(self, vehicle): 548 | """ Return the parking area ID of the 'current' stop. """ 549 | if self._vehicles_db[vehicle]['stops']: 550 | _, _, _stop, _flags, _, _ = self._vehicles_db[vehicle]['stops'][0] 551 | if self.is_parking_area(_flags): 552 | return _stop 553 | return None 554 | 555 | def _update_parking_db(self, step): 556 | """ Update subscriptions and parking database. """ 557 | 558 | self._check_occupancy(step) 559 | 560 | _to_validate = set() 561 | 562 | ## VAR_PARKING_ENDING_VEHICLES_IDS are delayed 1 TS. 563 | if self._traci_ending_stop_subscriptions: 564 | ## Update parking capacity by vClass 565 | for vehicle in self._traci_ending_stop_subscriptions: 566 | if vehicle not in self._vehicles_db: 567 | if self._logger: 568 | self._logger.warning( 569 | '[%.2f] Vehicle %s stop has ended but it\'s but not in the DB.', 570 | step, vehicle) 571 | continue 572 | self._vehicles_db[vehicle]['stopped'] = False 573 | if self._logger: 574 | self._logger.debug('[%.2f] Vehicle %s is not stopped anymore.', 575 | step, vehicle) 576 | 577 | parking_area = self._vehicles_db[vehicle]['current_parking_area'] 578 | if not parking_area and self._vehicles_db[vehicle]['roadside']: 579 | self._vehicles_db[vehicle]['roadside'] = False 580 | else: 581 | if parking_area in self._parking_db: 582 | v_class = self._vehicles_db[vehicle]['vClass'] 583 | try: 584 | self._parking_db[parking_area]['occupancy_by_class'][v_class].remove( 585 | vehicle) 586 | if self._logger: 587 | self._logger.debug('[%.2f] Vehicle %s removed from %s.', 588 | step, vehicle, parking_area) 589 | except KeyError: 590 | if self._logger: 591 | self._logger.critical( 592 | '[%.2f] Vehicle %s cannot be removed from area %s', 593 | step, vehicle, parking_area) 594 | raise ParkingMonitorGenericError( 595 | '[{}] Vehicle {} cannot be removed from area {}.'.format( 596 | step, vehicle, parking_area)) 597 | _to_validate.add(parking_area) 598 | else: 599 | if self._logger: 600 | self._logger.debug('[%.2f] Parking area %s not monitored.', 601 | step, parking_area) 602 | 603 | self._traci_simulation_subscriptions = ( 604 | self._traci_handler.simulation.getAllSubscriptionResults()) 605 | self._traci_starting_stop_subscriptions = ( 606 | self._traci_simulation_subscriptions[''][tc.VAR_PARKING_STARTING_VEHICLES_IDS]) 607 | self._traci_ending_stop_subscriptions = ( 608 | self._traci_simulation_subscriptions[''][tc.VAR_PARKING_ENDING_VEHICLES_IDS]) 609 | 610 | if self._traci_starting_stop_subscriptions: 611 | ## Update parking capacity by vClass 612 | for vehicle in self._traci_starting_stop_subscriptions: 613 | if vehicle not in self._vehicles_db: 614 | if self._logger: 615 | self._logger.warning('[%.2f] Vehicle %s has stopped but not in the DB.', 616 | step, vehicle) 617 | continue 618 | self._vehicles_db[vehicle]['stopped'] = True 619 | if self._logger: 620 | self._logger.debug('[%.2f] Vehicle %s is stopping.', step, vehicle) 621 | 622 | parking_area = self._get_parking_area_from_vehicle(vehicle) 623 | if parking_area: 624 | if parking_area in self._parking_db: 625 | parking_edge = self._parking_db[parking_area]['sumo']['lane'].split('_')[0] 626 | if self._vehicles_db[vehicle]['edge'] == parking_edge: 627 | v_class = self._vehicles_db[vehicle]['vClass'] 628 | self._vehicles_db[vehicle]['current_parking_area'] = parking_area 629 | self._parking_db[parking_area]['projections_by_class'][v_class].remove( 630 | vehicle) 631 | if self._logger: 632 | self._logger.debug( 633 | '[%.2f] Vehicle %s removed from the projections of %s.', 634 | step, vehicle, parking_area) 635 | self._parking_db[parking_area]['occupancy_by_class'][v_class].add( 636 | vehicle) 637 | if self._logger: 638 | self._logger.debug('[%.2f] Vehicle %s added to %s.', 639 | step, vehicle, parking_area) 640 | _to_validate.add(parking_area) 641 | else: 642 | import pprint 643 | pprint.pprint(self._vehicles_db[vehicle]) 644 | raise ParkingMonitorGenericError( 645 | 'Vehicle {} [{}] cannot park in {} [{}]: location error.'.format( 646 | vehicle, self._vehicles_db[vehicle]['edge'], 647 | parking_area, parking_edge)) 648 | else: 649 | if self._logger: 650 | self._logger.debug('[%.2f] Parking area %s not monitored.', 651 | step, parking_area) 652 | else: 653 | self._vehicles_db[vehicle]['roadside'] = True 654 | self._vehicles_db[vehicle]['current_parking_area'] = None 655 | if self._logger: 656 | self._logger.debug( 657 | '[%.2f] Vehicle %s is stopping outside a parking area.', step, vehicle) 658 | 659 | for pid in _to_validate: 660 | try: 661 | self._validate_parking_occupancy(pid) 662 | except ParkingMonitorGenericError as excpt: 663 | if self._logger: 664 | self._logger.critical('%s', str(excpt)) 665 | 666 | ## =============================== TRACI SUBSCRIPTIONS ============================== ## 667 | 668 | def get_traci_vehicle_subscriptions(self): 669 | """ Return TraCI vehicle subscriptions for the last simulation-step. """ 670 | return copy.deepcopy(self._traci_vehicle_subscription) 671 | 672 | def get_traci_simulation_subscriptions(self): 673 | """ Return TraCI simulation subscriptions for the last simulation-step. """ 674 | return copy.deepcopy(self._traci_simulation_subscriptions) 675 | 676 | ## =============================== REROUTERS ============================== ## 677 | 678 | def get_rerouter_iterator(self, step): 679 | """ Return the rerouter info for the given step. 680 | 681 | step: Float. Simulation time in seconds. 682 | """ 683 | for value in self._routers_db.values(): 684 | current = None 685 | for end, parkings in value['intervals']: 686 | if not current: 687 | current = parkings 688 | if step <= end: 689 | current = parkings 690 | else: 691 | break 692 | yield { 693 | 'id': value['id'], 694 | 'edges': value['edges'], 695 | 'info': current, 696 | } 697 | 698 | ## =============================== PASSENGERS =============================== ## 699 | 700 | def get_passenger_iterator(self): 701 | """ Return the passenger iterator. """ 702 | for value in self._passengers_db: 703 | yield value 704 | 705 | ## =============================== VEHICLES =============================== ## 706 | 707 | def get_vehicle_iterator(self): 708 | """ Return the vehicle info. """ 709 | for value in self._vehicles_db.values(): 710 | yield value 711 | 712 | def get_vehicle(self, vehicle): 713 | """ Return the vehicle with the given ID or None if not existent. 714 | 715 | vehicle: String. Vehicle ID as defined in SUMO. 716 | """ 717 | if vehicle in self._vehicles_db: 718 | return copy.deepcopy(self._vehicles_db[vehicle]) 719 | return None 720 | 721 | def set_vehicle_param(self, vehicle, param, value): 722 | """ Set the param=value in the vehicle with the given ID. 723 | Returns False if the vehicle does not exist. 724 | 725 | vehicle: String. Vehicle ID as defined in SUMO. 726 | param: String. Parameters name. 727 | value: Any. Value for the parameter. 728 | """ 729 | if vehicle in self._vehicles_db: 730 | self._vehicles_db[vehicle][param] = copy.deepcopy(value) 731 | return True 732 | return False 733 | 734 | ## =============================== PARKINGS =============================== ## 735 | 736 | def get_parking_iterator(self): 737 | """ Return the parking iterator. """ 738 | for value in self._parking_db.values(): 739 | yield copy.deepcopy(value) 740 | 741 | def get_parking(self, parking): 742 | """ Return the parking area with the given ID or None if not existent. 743 | 744 | parking: String. Parking area ID as defined in SUMO. 745 | """ 746 | if parking in self._parking_db: 747 | return copy.deepcopy(self._parking_db[parking]) 748 | return None 749 | 750 | def compute_parking_travel_time(self): 751 | """ For each parking, saves the parkings reachable by 'passenger' vClass where the weight 752 | is the travel time at the current stage of the simulation. 753 | Each call of the funcion destroy the previous state. 754 | """ 755 | 756 | self._static_parking_travel_time = collections.defaultdict(list) 757 | 758 | parkings = [] 759 | for parking in self._parking_db.values(): 760 | pid = parking['sumo']['id'] 761 | edge = parking['sumo']['lane'].split('_')[0] 762 | end_pos = float(parking['sumo']['endPos']) 763 | parkings.append((pid, edge, end_pos)) 764 | 765 | for from_pid, from_edge, from_end_pos in parkings: 766 | for to_pid, to_edge, to_end_pos in parkings: 767 | if from_pid == to_pid: 768 | continue 769 | if from_edge == to_edge and to_end_pos <= from_end_pos: 770 | ## parking not reachable 771 | continue 772 | if to_edge in self._blacklisted_edges_pairs[from_edge]: 773 | ## route not available 774 | continue 775 | 776 | route = None 777 | try: 778 | route = self._traci_handler.simulation.findRoute(from_edge, to_edge, 779 | vType='passenger') 780 | except traci.exceptions.TraCIException: 781 | route = None 782 | self._blacklisted_edges_pairs[from_edge].append(to_edge) 783 | 784 | cost = None 785 | if route and route.edges: 786 | cost = route.travelTime 787 | 788 | if cost: 789 | self._static_parking_travel_time[from_pid].append((cost, to_pid)) 790 | 791 | for distances in self._static_parking_travel_time.values(): 792 | distances.sort() 793 | 794 | def get_closest_parkings(self, parking, num=None): 795 | """ Return the 'num' closest parkings by travel time from the requested parking. 796 | It requires the travel time structure initialization using 797 | compute_parking_travel_time(), it raises an ParkingMonitorGenericError 798 | if the structure is not yet initialized. 799 | 800 | parking: String. Parking area ID as defined in SUMO. 801 | num: Int. Number of element to be returned. 802 | """ 803 | 804 | if not self._static_parking_travel_time: 805 | raise ParkingMonitorGenericError( 806 | 'Estimated travel time structure for parkings is not initialized.') 807 | 808 | parkings = [] 809 | if parking in self._static_parking_travel_time: 810 | for item in self._static_parking_travel_time[parking]: 811 | if num and len(parkings) == num: 812 | break 813 | parkings.append(item) 814 | return parkings 815 | 816 | ## ============================ PARKING SUBSCRIPTIONS ============================= ## 817 | 818 | def get_parking_subscriptions(self, parking): 819 | """ Given a parking ID, returns the subscriptions information. 820 | Raises an ParkingMonitorGenericError if the requested parking area does not exist. 821 | 822 | parking: String. Parking area ID as defined in SUMO. 823 | """ 824 | if parking in self._parking_db.keys(): 825 | return copy.deepcopy(self._parking_db[parking]['subscriptions_by_class']) 826 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 827 | 828 | def set_parking_subscriptions(self, parking, subscriptions): 829 | """ Set the given subsctiption to the parking id. 830 | Raises an ParkingMonitorGenericError if the requested parking area does not exist or 831 | if the subscriptions are not in a valid format. 832 | 833 | parking: String. Parking area ID as defined in SUMO. 834 | subscriptions: Dict. { 'vType': int, .., 'vType': int } 835 | """ 836 | if parking in self._parking_db: 837 | self._parking_db[parking]['subscriptions_by_class'] = copy.deepcopy(subscriptions) 838 | self._validate_parking_subscriptions(parking) 839 | else: 840 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 841 | 842 | def subscribe_vehicle_to_parking(self, parking, vclass, vehicle): 843 | """ Add the vehicle to the subscription list of the parking area. 844 | Returns False iif the number of already subscribed vehicles is equal to the number 845 | of spots available for that specific vclass. 846 | Raises an ParkingMonitorGenericError if the requested parking area does not exist or 847 | if the subscriptions are not initialized. 848 | 849 | parking: String. Parking area ID as defined in SUMO. 850 | vclass: String. vType as defined in SUMO. 851 | vehicle: String. Vehicle ID as defined in SUMO. 852 | """ 853 | if parking in self._parking_db: 854 | if vclass in self._parking_db[parking]['subscriptions_by_class']: 855 | _capacity, vehicles = self._parking_db[parking]['subscriptions_by_class'][vclass] 856 | if vehicle in vehicles: 857 | return False 858 | if len(vehicles) < _capacity: 859 | vehicles.add(vehicle) 860 | return True 861 | # subscription full 862 | return False 863 | else: 864 | raise ParkingMonitorGenericError( 865 | 'vClass "{}" not initialized in parking {}.'.format(vclass, parking)) 866 | else: 867 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 868 | 869 | def remove_subscribed_vehicle(self, parking, vclass, vehicle): 870 | """ Remove the vehicles from the subscriptions of the given parking id. 871 | Raises an ParkingMonitorGenericError if the requested parking area does not exist or 872 | if the subscriptions are not initialized. 873 | 874 | parking: String. Parking area ID as defined in SUMO. 875 | vclass: String. vType as defined in SUMO. 876 | vehicle: String. Vehicle ID as defined in SUMO. 877 | """ 878 | if parking in self._parking_db: 879 | if vclass in self._parking_db[parking]['subscriptions_by_class']: 880 | _capacity, vehicles = self._parking_db[parking]['subscriptions_by_class'][vclass] 881 | if vehicle in vehicles: 882 | vehicles.remove(vehicle) 883 | return True 884 | # vehicle not found 885 | return False 886 | else: 887 | raise ParkingMonitorGenericError( 888 | 'vClass "{}" not initialized in parking {}.'.format(vclass, parking)) 889 | else: 890 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 891 | 892 | ## ============================ PARKING PROJECTIONS ============================= ## 893 | 894 | def get_parking_projections(self, parking): 895 | """ Given a parking ID, returns the projections information. 896 | Raises an ParkingMonitorGenericError if the requested parking area does not exist. 897 | 898 | parking: String. Parking area ID as defined in SUMO. 899 | """ 900 | if parking in self._parking_db.keys(): 901 | return copy.deepcopy(self._parking_db[parking]['projection_by_class']) 902 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 903 | 904 | ## ============================ PARKING CAPACITY - OCCUPANCY ============================= ## 905 | 906 | def get_free_places(self, parking, with_uncertainty=False, 907 | vclass=None, with_projections=False, with_subscriptions=False): 908 | """ Returns the free places in a given parking area. 909 | Raises an ParkingMonitorGenericError if the requested parking area does not exist. 910 | 911 | parking: String. Parking area ID as defined in SUMO. 912 | with_uncertainty: Boolean. If True, uncertainty is applied. 913 | vclass: String. If set, returns values only for the specified vType. 914 | with_projections: Boolean. If True, projections are taken into account. 915 | with_subscriptions: Boolean. If True, subscriptions are taken into account. 916 | """ 917 | 918 | if parking not in self._parking_db.keys(): 919 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 920 | 921 | error = 0 922 | if with_uncertainty: 923 | error = round(self._random.normal(self._parking_db[parking]['uncertainty']['mu'], 924 | self._parking_db[parking]['uncertainty']['sigma'])) 925 | 926 | current_capacity = dict() 927 | for key, capacity in self._parking_db[parking]['capacity_by_class'].items(): 928 | current_capacity[key] = capacity 929 | 930 | total_occupancy = self._parking_db[parking]['total_occupancy'] 931 | occupancy = dict() 932 | for key, values in self._parking_db[parking]['occupancy_by_class'].items(): 933 | occupancy[key] = set(values) 934 | 935 | total_projections = 0 936 | if with_projections: 937 | for key, vehicles in self._parking_db[parking]['projections_by_class'].items(): 938 | total_projections += len(vehicles) 939 | occupancy[key] = occupancy[key] | vehicles 940 | 941 | total_subscriptions = 0 942 | partial_subscriptions = 0 943 | subscriptions = dict() 944 | if with_subscriptions: 945 | for key, (num, veh) in self._parking_db[parking]['subscriptions_by_class'].items(): 946 | subscriptions[key] = num - len(veh) 947 | occupancy[key] = occupancy[key] | veh 948 | total_subscriptions += num 949 | partial_subscriptions += subscriptions[key] 950 | 951 | if current_capacity: 952 | for key, vehicles in occupancy.items(): 953 | current_capacity[key] += error 954 | current_capacity[key] -= len(vehicles) 955 | if with_subscriptions and subscriptions: 956 | current_capacity[key] -= subscriptions[key] 957 | if vclass in current_capacity: 958 | return current_capacity[vclass] 959 | return current_capacity 960 | 961 | return (self._parking_db[parking]['total_capacity'] - total_occupancy - 962 | total_projections - total_subscriptions + error) 963 | 964 | def get_parking_capacity_vclass(self, parking): 965 | """ Given a parking ID, returns the capacity by vclass information. 966 | Raises an ParkingMonitorGenericError if the requested parking area does not exist. 967 | 968 | parking: String. Parking area ID as defined in SUMO. 969 | """ 970 | if parking in self._parking_db.keys(): 971 | return copy.deepcopy(self._parking_db[parking]['capacity_by_class']) 972 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 973 | 974 | def set_parking_capacity_vclass(self, parking, capacities): 975 | """ Set the given capacity by vclass to the parking id. 976 | Raises an ParkingMonitorGenericError if the requested parking area does not exist or 977 | if the capacities are not in a valid format. 978 | 979 | parking: String. Parking area ID as defined in SUMO. 980 | capacities: Dict. { 'vType': int, .., 'vType': int } 981 | """ 982 | if parking in self._parking_db: 983 | self._parking_db[parking]['capacity_by_class'] = copy.deepcopy(capacities) 984 | self._validate_parking_capacity(parking) 985 | else: 986 | raise ParkingMonitorGenericError('Parking {} does not exist.'.format(parking)) 987 | 988 | ## ============================ PARKING VALIDATION ============================= ## 989 | 990 | def _validate_parking_capacity(self, parking): 991 | """ Checks if the sum of all the 'capacity_by_class' matches the 'total_capacity. """ 992 | 993 | if set(self._parking_db[parking]['capacity_by_class'].keys()) != self._options['vclasses']: 994 | raise ParkingMonitorGenericError( 995 | """The vClasses in "capacity_by_class" of {} must be all and """ 996 | """only {} [see parameter "vclasses"].""".format( 997 | parking, self._options['vclasses'])) 998 | 999 | total = 0 1000 | for value in self._parking_db[parking]['capacity_by_class'].values(): 1001 | total += value 1002 | if total != self._parking_db[parking]['total_capacity']: 1003 | raise ParkingMonitorGenericError( 1004 | """The total capacity for parking area {} is {} but it must be """ 1005 | """equal to the one defined in SUMO: {}.""".format( 1006 | parking, total, self._parking_db[parking]['total_capacity'])) 1007 | 1008 | def _validate_parking_occupancy(self, parking): 1009 | """ Checks if the sum of all the 'occupancy_by_class' matches the 'total_occupancy. """ 1010 | 1011 | if set(self._parking_db[parking]['occupancy_by_class'].keys()) != self._options['vclasses']: 1012 | raise ParkingMonitorGenericError( 1013 | """The vClasses in "occupancy_by_class" of {} must be all and """ 1014 | """only {} [see parameter "vclasses"].""".format( 1015 | parking, self._options['vclasses'])) 1016 | 1017 | total = 0 1018 | for v_class, value in self._parking_db[parking]['occupancy_by_class'].items(): 1019 | if self._parking_db[parking]['capacity_by_class']: 1020 | info = ( 1021 | "The occupancy in parking area {} for vType {} is {} of {}.".format( 1022 | parking, v_class, len(value), 1023 | self._parking_db[parking]['capacity_by_class'][v_class])) 1024 | if self._logger: 1025 | self._logger.debug(info) 1026 | if len(value) > self._parking_db[parking]['capacity_by_class'][v_class]: 1027 | raise ParkingMonitorGenericError(info) 1028 | total += len(value) 1029 | if total != self._parking_db[parking]['total_occupancy']: 1030 | raise ParkingMonitorGenericError( 1031 | """The total occupancy for parking area {} is {} but it must be """ 1032 | """equal to the one retrieved from SUMO: {}.""".format( 1033 | parking, total, self._parking_db[parking]['total_occupancy'])) 1034 | 1035 | def _validate_parking_subscriptions(self, parking): 1036 | """ Checks if the sum of all the 'subscriptions_by_class' matches the 'total_occupancy. """ 1037 | 1038 | if not self._parking_db[parking]['capacity_by_class']: 1039 | raise ParkingMonitorGenericError( 1040 | """Parking subscriptions for parking {} cannot be set without """ 1041 | """setting 'capacity_by_class' in advance.""".format(parking)) 1042 | 1043 | if (set(self._parking_db[parking]['subscriptions_by_class'].keys()) 1044 | != self._options['vclasses']): 1045 | raise ParkingMonitorGenericError( 1046 | """The vClasses in "subscriptions_by_class" of {} must be all and """ 1047 | """only {} [see parameter "vclasses"].""".format( 1048 | parking, self._options['vclasses'])) 1049 | 1050 | for key, value in self._parking_db[parking]['subscriptions_by_class'].items(): 1051 | if value[0] > self._parking_db[parking]['capacity_by_class'][key]: 1052 | raise ParkingMonitorGenericError( 1053 | "In parking {}, subscription for {} exceed the capacity [{}/{}].".format( 1054 | parking, key, value, self._parking_db[parking]['capacity_by_class'][key])) 1055 | 1056 | ## ========================================================================================= ## 1057 | -------------------------------------------------------------------------------- /pypml/pypml_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ pypml.py Tests 4 | 5 | Python Parking Monitor Library (PyPML) 6 | 7 | Author: Lara CODECA 8 | 9 | This program and the accompanying materials are made available under the 10 | terms of the Eclipse Public License 2.0 which is available at 11 | http://www.eclipse.org/legal/epl-2.0. 12 | """ 13 | 14 | from unittest import TestCase 15 | 16 | from pypml import ParkingMonitor 17 | 18 | class TestParkingMonitor(TestCase): 19 | """ Test class for the ParkingMonitor """ 20 | 21 | def test_init_parking_monitor(self): 22 | """ Test __init__ """ 23 | pass -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ PYTHON3 - Setup for pypml. """ 4 | 5 | from setuptools import setup 6 | 7 | def readme(): 8 | """ README """ 9 | with open('README.rst') as freader: 10 | return freader.read() 11 | 12 | setup(name='pypml', 13 | version='0.2', 14 | description='Parking Monitor Library for SUMO via TraCI.', 15 | url='http://github.com/lcodeca/pypml', 16 | classifiers=[ 17 | 'Development Status :: 3 - Alpha', 18 | 'License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)', 19 | 'Programming Language :: Python :: 3 :: Only', 20 | 'Intended Audience :: Science/Research', 21 | 'Topic :: Scientific/Engineering', 22 | ], 23 | author='Lara Codeca', 24 | author_email='lara.codeca@gmail.com', 25 | license='EPL-2.0', 26 | packages=['pypml'], 27 | install_requires=[], 28 | include_package_data=True, 29 | zip_safe=False, 30 | test_suite='nose.collector', 31 | tests_require=['nose']) 32 | --------------------------------------------------------------------------------