demandify 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- demandify-0.0.1/LICENSE +21 -0
- demandify-0.0.1/MANIFEST.in +24 -0
- demandify-0.0.1/PKG-INFO +270 -0
- demandify-0.0.1/README.md +226 -0
- demandify-0.0.1/demandify/__init__.py +5 -0
- demandify-0.0.1/demandify/__main__.py +14 -0
- demandify-0.0.1/demandify/app.py +49 -0
- demandify-0.0.1/demandify/cache/__init__.py +0 -0
- demandify-0.0.1/demandify/cache/keys.py +71 -0
- demandify-0.0.1/demandify/cache/manager.py +99 -0
- demandify-0.0.1/demandify/calibration/__init__.py +0 -0
- demandify-0.0.1/demandify/calibration/objective.py +157 -0
- demandify-0.0.1/demandify/calibration/optimizer.py +275 -0
- demandify-0.0.1/demandify/calibration/worker.py +219 -0
- demandify-0.0.1/demandify/cli.py +287 -0
- demandify-0.0.1/demandify/config.py +122 -0
- demandify-0.0.1/demandify/export/__init__.py +0 -0
- demandify-0.0.1/demandify/export/custom_formats.py +221 -0
- demandify-0.0.1/demandify/export/exporter.py +170 -0
- demandify-0.0.1/demandify/export/report.py +332 -0
- demandify-0.0.1/demandify/export/visualize.py +46 -0
- demandify-0.0.1/demandify/pipeline.py +776 -0
- demandify-0.0.1/demandify/providers/__init__.py +0 -0
- demandify-0.0.1/demandify/providers/base.py +45 -0
- demandify-0.0.1/demandify/providers/osm.py +128 -0
- demandify-0.0.1/demandify/providers/tomtom.py +229 -0
- demandify-0.0.1/demandify/static/banner.png +0 -0
- demandify-0.0.1/demandify/static/css/style.css +165 -0
- demandify-0.0.1/demandify/static/icon.png +0 -0
- demandify-0.0.1/demandify/static/js/app.js +472 -0
- demandify-0.0.1/demandify/sumo/__init__.py +0 -0
- demandify-0.0.1/demandify/sumo/demand.py +358 -0
- demandify-0.0.1/demandify/sumo/matching.py +329 -0
- demandify-0.0.1/demandify/sumo/network.py +260 -0
- demandify-0.0.1/demandify/sumo/simulation.py +386 -0
- demandify-0.0.1/demandify/templates/api_key_section.html +23 -0
- demandify-0.0.1/demandify/templates/index.html +522 -0
- demandify-0.0.1/demandify/utils/__init__.py +0 -0
- demandify-0.0.1/demandify/utils/geometry.py +29 -0
- demandify-0.0.1/demandify/utils/logger.py +83 -0
- demandify-0.0.1/demandify/utils/logging.py +31 -0
- demandify-0.0.1/demandify/utils/validation.py +67 -0
- demandify-0.0.1/demandify/utils/visualization.py +48 -0
- demandify-0.0.1/demandify/web/__init__.py +0 -0
- demandify-0.0.1/demandify/web/routes.py +314 -0
- demandify-0.0.1/demandify.egg-info/PKG-INFO +270 -0
- demandify-0.0.1/demandify.egg-info/SOURCES.txt +51 -0
- demandify-0.0.1/demandify.egg-info/dependency_links.txt +1 -0
- demandify-0.0.1/demandify.egg-info/entry_points.txt +2 -0
- demandify-0.0.1/demandify.egg-info/requires.txt +28 -0
- demandify-0.0.1/demandify.egg-info/top_level.txt +2 -0
- demandify-0.0.1/pyproject.toml +90 -0
- demandify-0.0.1/setup.cfg +4 -0
demandify-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Onur Akman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Exclude development and environment files from Source Distribution
|
|
2
|
+
exclude DEVELOPER_GUIDE.md
|
|
3
|
+
exclude pyproject.toml.bak
|
|
4
|
+
exclude test_ready.py
|
|
5
|
+
exclude SPEC.md
|
|
6
|
+
exclude .env
|
|
7
|
+
exclude .env.example
|
|
8
|
+
exclude pipeline.log
|
|
9
|
+
|
|
10
|
+
# Exclude directories
|
|
11
|
+
recursive-exclude .agent *
|
|
12
|
+
recursive-exclude .gemini *
|
|
13
|
+
recursive-exclude demandify_runs *
|
|
14
|
+
recursive-exclude logs *
|
|
15
|
+
recursive-exclude cache *
|
|
16
|
+
recursive-exclude .demandify *
|
|
17
|
+
recursive-exclude venv *
|
|
18
|
+
recursive-exclude tests *
|
|
19
|
+
recursive-exclude scripts *
|
|
20
|
+
recursive-exclude reference *
|
|
21
|
+
|
|
22
|
+
# Ensure operational data is included (matching pyproject.toml)
|
|
23
|
+
recursive-include demandify/templates *.html
|
|
24
|
+
recursive-include demandify/static *
|
demandify-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: demandify
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Calibrate SUMO traffic simulations against real-world congestion data
|
|
5
|
+
Author: demandify contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: fastapi>=0.104.0
|
|
17
|
+
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
18
|
+
Requires-Dist: jinja2>=3.1.0
|
|
19
|
+
Requires-Dist: httpx>=0.25.0
|
|
20
|
+
Requires-Dist: python-multipart>=0.0.6
|
|
21
|
+
Requires-Dist: shapely>=2.0.0
|
|
22
|
+
Requires-Dist: rtree>=1.1.0
|
|
23
|
+
Requires-Dist: protobuf>=4.24.0
|
|
24
|
+
Requires-Dist: numpy>=1.24.0
|
|
25
|
+
Requires-Dist: pandas>=2.0.0
|
|
26
|
+
Requires-Dist: lxml>=4.9.0
|
|
27
|
+
Requires-Dist: xmltodict>=0.13.0
|
|
28
|
+
Requires-Dist: deap>=1.4.0
|
|
29
|
+
Requires-Dist: matplotlib>=3.7.0
|
|
30
|
+
Requires-Dist: plotly>=5.17.0
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
32
|
+
Requires-Dist: pydantic>=2.0.0
|
|
33
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
34
|
+
Requires-Dist: pyproj>=3.4.0
|
|
35
|
+
Requires-Dist: tqdm>=4.65.0
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
40
|
+
Requires-Dist: black>=23.7.0; extra == "dev"
|
|
41
|
+
Requires-Dist: ruff>=0.0.286; extra == "dev"
|
|
42
|
+
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
# Welcome to demandify!
|
|
48
|
+
|
|
49
|
+
> [!CAUTION]
|
|
50
|
+
> **BETA VERSION**: This project is currently in early beta (0.0.1). Expect frequent breaking changes and instability.
|
|
51
|
+
> Use at your own risk.
|
|
52
|
+
|
|
53
|
+
**Turn real-world traffic data into accurate SUMO simulations.**
|
|
54
|
+
|
|
55
|
+
Have you ever wanted to create a realistic traffic simulation but got stuck figuring out where the cars should go? **demandify** solves that.
|
|
56
|
+
|
|
57
|
+
It's simple: Pick a spot on the map and demandify will:
|
|
58
|
+
1. Fetch real-time congestion data from TomTom 🗺️
|
|
59
|
+
2. Build a clean SUMO network 🛣️
|
|
60
|
+
3. Use the Genetic Algorithm to figure out the demand pattern to match that traffic 🧬
|
|
61
|
+
|
|
62
|
+
The result? A ready-to-run SUMO scenario that allows you to test your urban routing policies, even for your CAVs! ([wink](https://github.com/COeXISTENCE-PROJECT/URB) [wink](https://github.com/COeXISTENCE-PROJECT/RouteRL)).
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- 🌍 **Real-world calibration**: Uses TomTom Traffic Flow API for live congestion data
|
|
67
|
+
- 🎯 **Seeded & reproducible**: Same seed = identical results for same congestion and bbox
|
|
68
|
+
- 🚗 **Car-only SUMO networks**: Automatic OSM → SUMO conversion with car filtering
|
|
69
|
+
- 🧬 **Genetic algorithm**: Optimizes demand to match observed speeds
|
|
70
|
+
- 💾 **Smart caching**: Content-addressed caching for fast re-runs
|
|
71
|
+
- 📊 **Beautiful reports**: HTML reports with visualizations and statistics
|
|
72
|
+
- 🖥️ **Clean web UI**: Leaflet map, real-time progress stepper, log console
|
|
73
|
+
|
|
74
|
+
## Quickstart
|
|
75
|
+
|
|
76
|
+
### 1. Install demandify
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Clone the repository
|
|
80
|
+
git clone <repository-url>
|
|
81
|
+
cd demandify
|
|
82
|
+
|
|
83
|
+
# Install in development mode
|
|
84
|
+
pip install -e .
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Install SUMO 🚦
|
|
88
|
+
|
|
89
|
+
**demandify** requires SUMO (Simulation of Urban MObility) to power its simulations.
|
|
90
|
+
|
|
91
|
+
👉 **[Download SUMO from the official website](https://eclipse.dev/sumo/)**
|
|
92
|
+
|
|
93
|
+
Once installed, verify it's working:
|
|
94
|
+
```bash
|
|
95
|
+
demandify doctor
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Get a TomTom API Key
|
|
99
|
+
|
|
100
|
+
1. Sign up at [https://developer.tomtom.com/](https://developer.tomtom.com/)
|
|
101
|
+
2. Create a new app and copy the API key
|
|
102
|
+
3. The free tier includes 2,500 requests/day
|
|
103
|
+
|
|
104
|
+
### 4. Run demandify
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
demandify
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This starts the web server at [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
|
111
|
+
|
|
112
|
+
### 5. Calibrate a scenario
|
|
113
|
+
|
|
114
|
+
1. **Draw a bounding box** on the map
|
|
115
|
+
2. **Configure parameters** (defaults work well):
|
|
116
|
+
- Time window: 15 or 30 minutes
|
|
117
|
+
- Seed: any integer for reproducibility
|
|
118
|
+
- GA population/generations: controls quality vs speed
|
|
119
|
+
3. **Paste your API key** (one-time, stored locally)
|
|
120
|
+
4. **Click "Start Calibration"**
|
|
121
|
+
5. **Watch the progress** through 8 stages
|
|
122
|
+
6. **Download your scenario** with `demand.csv`, SUMO network, and report
|
|
123
|
+
|
|
124
|
+
### 6. Run Headless (Optional) 🤖
|
|
125
|
+
|
|
126
|
+
You can run the full calibration pipeline directly from the command line, ideal for automation or remote servers.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Basic usage (defaults: window=15, pop=50, gen=20)
|
|
130
|
+
demandify run "2.2961,48.8469,2.3071,48.8532" --name Paris_Test_01
|
|
131
|
+
|
|
132
|
+
# Advanced usage with custom parameters
|
|
133
|
+
demandify run "2.2961,48.8469,2.3071,48.8532" \
|
|
134
|
+
--name Paris_Optimization \
|
|
135
|
+
--window 30 \
|
|
136
|
+
--seed 123 \
|
|
137
|
+
--pop 100 \
|
|
138
|
+
--gen 50 \
|
|
139
|
+
--mutation 0.5 \
|
|
140
|
+
--elitism 2
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
> **Note:** The CLI will pause after fetching/matching data to show matching statistics and ask for confirmation before starting the intensive calibration.
|
|
144
|
+
|
|
145
|
+
#### Parameters
|
|
146
|
+
|
|
147
|
+
| Argument | Type | Default | Description |
|
|
148
|
+
|----------|------|---------|-------------|
|
|
149
|
+
| `bbox` | String | (Req) | Bounding box (`west,south,east,north`) |
|
|
150
|
+
| `--name` | String | Auto | Custom Run ID/Name |
|
|
151
|
+
| `--window` | Int | 15 | Simulation duration (min) |
|
|
152
|
+
| `--seed` | Int | 42 | Random seed |
|
|
153
|
+
| `--pop` | Int | 50 | GA Population size |
|
|
154
|
+
| `--gen` | Int | 20 | GA Generations |
|
|
155
|
+
| `--mutation`| Float | 0.5 | Mutation rate (per individual) |
|
|
156
|
+
| `--crossover`| Float| 0.7 | Crossover rate |
|
|
157
|
+
| `--elitism` | Int | 2 | Top individuals to keep |
|
|
158
|
+
| `--sigma` | Int | 20 | Mutation magnitude (step size) |
|
|
159
|
+
| `--indpb` | Float | 0.3 | Mutation probability (per gene) |
|
|
160
|
+
| `--origins` | Int | 10 | Number of origin candidates |
|
|
161
|
+
| `--destinations` | Int | 10 | Number of destination candidates |
|
|
162
|
+
| `--max-ods` | Int | 50 | Max OD pairs to generate |
|
|
163
|
+
| `--bin-size` | Int | 5 | Time bin size in minutes |
|
|
164
|
+
| `--initial-population` | Int | 1000 | Target initial number of vehicles (controls sparse initialization) |
|
|
165
|
+
|
|
166
|
+
## How It Works
|
|
167
|
+
|
|
168
|
+
demandify follows an 8-stage pipeline:
|
|
169
|
+
|
|
170
|
+
1. **Validate inputs** - Check bbox, parameters, API key
|
|
171
|
+
2. **Fetch traffic snapshot** - Get real-time speeds from TomTom
|
|
172
|
+
3. **Fetch OSM extract** - Download road network data
|
|
173
|
+
4. **Build SUMO network** - Convert OSM to car-only SUMO `.net.xml`
|
|
174
|
+
5. **Map matching** - Match traffic segments to SUMO edges
|
|
175
|
+
6. **Initialize demand** - Select origin/destination pairs and time bins
|
|
176
|
+
7. **Calibrate demand** - Run GA to optimize vehicle counts
|
|
177
|
+
8. **Export scenario** - Generate `demand.csv`, routes, config, and report
|
|
178
|
+
|
|
179
|
+
### Variability & Consistency
|
|
180
|
+
|
|
181
|
+
While demandify uses seeding (random seed) for all internal stochastic operations (OD selection, GA evolution), **perfect reproducibility is not guaranteed** due to the inherently chaotic nature of traffic microsimulation (SUMO) and real-time data inputs.
|
|
182
|
+
|
|
183
|
+
Seeding ensures *consistency* (runs look similar), but small timing differences in OS scheduling or dynamic routing decisions can lead to divergent outcomes.
|
|
184
|
+
|
|
185
|
+
### Caching
|
|
186
|
+
|
|
187
|
+
demandify caches:
|
|
188
|
+
- OSM extracts (by bbox)
|
|
189
|
+
- SUMO networks (by bbox + conversion params)
|
|
190
|
+
- Traffic snapshots (by bbox + timestamp bucket)
|
|
191
|
+
- Map matching results
|
|
192
|
+
|
|
193
|
+
Cache location: `~/.demandify/cache/`
|
|
194
|
+
|
|
195
|
+
Clear cache: `demandify cache clear`
|
|
196
|
+
|
|
197
|
+
## CLI Commands
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Start web server (default)
|
|
201
|
+
demandify
|
|
202
|
+
|
|
203
|
+
# Run headless calibration
|
|
204
|
+
demandify run "west,south,east,north"
|
|
205
|
+
|
|
206
|
+
# Check system requirements
|
|
207
|
+
demandify doctor
|
|
208
|
+
|
|
209
|
+
# Clear cache
|
|
210
|
+
demandify cache clear
|
|
211
|
+
|
|
212
|
+
# Show version
|
|
213
|
+
demandify --version
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Output Files
|
|
217
|
+
|
|
218
|
+
Each run creates a folder with:
|
|
219
|
+
|
|
220
|
+
- **`demand.csv`** - Travel demand with exact schema:
|
|
221
|
+
- `ID`, `origin link id`, `destination link id`, `departure timestep`
|
|
222
|
+
- **`trips.xml`** - SUMO trips file
|
|
223
|
+
- **`routes.rou.xml`** - Routed SUMO routes
|
|
224
|
+
- **`network.net.xml`** - SUMO network
|
|
225
|
+
- **`scenario.sumocfg`** - SUMO configuration (ready to run)
|
|
226
|
+
- **`observed_edges.csv`** - Observed traffic speeds
|
|
227
|
+
- **`run_meta.json`** - Complete run metadata
|
|
228
|
+
- **`report.html`** - Calibration report with visualizations
|
|
229
|
+
|
|
230
|
+
Run the scenario:
|
|
231
|
+
```bash
|
|
232
|
+
cd demandify_runs/run_<timestamp>
|
|
233
|
+
sumo-gui -c scenario.sumocfg
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Configuration
|
|
237
|
+
|
|
238
|
+
### API Keys
|
|
239
|
+
|
|
240
|
+
Three ways to provide your TomTom API key:
|
|
241
|
+
|
|
242
|
+
1. **Web UI**: Paste in the form (saved to `~/.demandify/config.json`)
|
|
243
|
+
2. **Environment variable**: `export TOMTOM_API_KEY=your_key`
|
|
244
|
+
3. **`.env` file**: Copy `.env.example` to `.env` and add your key
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Install with dev dependencies
|
|
250
|
+
pip install -e ".[dev]"
|
|
251
|
+
|
|
252
|
+
# Run tests
|
|
253
|
+
pytest
|
|
254
|
+
|
|
255
|
+
# Format code
|
|
256
|
+
black demandify/
|
|
257
|
+
|
|
258
|
+
# Lint
|
|
259
|
+
ruff check demandify/
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT
|
|
265
|
+
|
|
266
|
+
## Acknowledgments
|
|
267
|
+
|
|
268
|
+
- **SUMO**: [Eclipse SUMO](https://eclipse.dev/sumo/)
|
|
269
|
+
- **TomTom**: [Traffic Flow API](https://developer.tomtom.com/traffic-api)
|
|
270
|
+
- **OpenStreetMap**: [© OpenStreetMap contributors](https://www.openstreetmap.org/copyright)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Welcome to demandify!
|
|
4
|
+
|
|
5
|
+
> [!CAUTION]
|
|
6
|
+
> **BETA VERSION**: This project is currently in early beta (0.0.1). Expect frequent breaking changes and instability.
|
|
7
|
+
> Use at your own risk.
|
|
8
|
+
|
|
9
|
+
**Turn real-world traffic data into accurate SUMO simulations.**
|
|
10
|
+
|
|
11
|
+
Have you ever wanted to create a realistic traffic simulation but got stuck figuring out where the cars should go? **demandify** solves that.
|
|
12
|
+
|
|
13
|
+
It's simple: Pick a spot on the map and demandify will:
|
|
14
|
+
1. Fetch real-time congestion data from TomTom 🗺️
|
|
15
|
+
2. Build a clean SUMO network 🛣️
|
|
16
|
+
3. Use the Genetic Algorithm to figure out the demand pattern to match that traffic 🧬
|
|
17
|
+
|
|
18
|
+
The result? A ready-to-run SUMO scenario that allows you to test your urban routing policies, even for your CAVs! ([wink](https://github.com/COeXISTENCE-PROJECT/URB) [wink](https://github.com/COeXISTENCE-PROJECT/RouteRL)).
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- 🌍 **Real-world calibration**: Uses TomTom Traffic Flow API for live congestion data
|
|
23
|
+
- 🎯 **Seeded & reproducible**: Same seed = identical results for same congestion and bbox
|
|
24
|
+
- 🚗 **Car-only SUMO networks**: Automatic OSM → SUMO conversion with car filtering
|
|
25
|
+
- 🧬 **Genetic algorithm**: Optimizes demand to match observed speeds
|
|
26
|
+
- 💾 **Smart caching**: Content-addressed caching for fast re-runs
|
|
27
|
+
- 📊 **Beautiful reports**: HTML reports with visualizations and statistics
|
|
28
|
+
- 🖥️ **Clean web UI**: Leaflet map, real-time progress stepper, log console
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
### 1. Install demandify
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Clone the repository
|
|
36
|
+
git clone <repository-url>
|
|
37
|
+
cd demandify
|
|
38
|
+
|
|
39
|
+
# Install in development mode
|
|
40
|
+
pip install -e .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Install SUMO 🚦
|
|
44
|
+
|
|
45
|
+
**demandify** requires SUMO (Simulation of Urban MObility) to power its simulations.
|
|
46
|
+
|
|
47
|
+
👉 **[Download SUMO from the official website](https://eclipse.dev/sumo/)**
|
|
48
|
+
|
|
49
|
+
Once installed, verify it's working:
|
|
50
|
+
```bash
|
|
51
|
+
demandify doctor
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Get a TomTom API Key
|
|
55
|
+
|
|
56
|
+
1. Sign up at [https://developer.tomtom.com/](https://developer.tomtom.com/)
|
|
57
|
+
2. Create a new app and copy the API key
|
|
58
|
+
3. The free tier includes 2,500 requests/day
|
|
59
|
+
|
|
60
|
+
### 4. Run demandify
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
demandify
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This starts the web server at [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
|
67
|
+
|
|
68
|
+
### 5. Calibrate a scenario
|
|
69
|
+
|
|
70
|
+
1. **Draw a bounding box** on the map
|
|
71
|
+
2. **Configure parameters** (defaults work well):
|
|
72
|
+
- Time window: 15 or 30 minutes
|
|
73
|
+
- Seed: any integer for reproducibility
|
|
74
|
+
- GA population/generations: controls quality vs speed
|
|
75
|
+
3. **Paste your API key** (one-time, stored locally)
|
|
76
|
+
4. **Click "Start Calibration"**
|
|
77
|
+
5. **Watch the progress** through 8 stages
|
|
78
|
+
6. **Download your scenario** with `demand.csv`, SUMO network, and report
|
|
79
|
+
|
|
80
|
+
### 6. Run Headless (Optional) 🤖
|
|
81
|
+
|
|
82
|
+
You can run the full calibration pipeline directly from the command line, ideal for automation or remote servers.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Basic usage (defaults: window=15, pop=50, gen=20)
|
|
86
|
+
demandify run "2.2961,48.8469,2.3071,48.8532" --name Paris_Test_01
|
|
87
|
+
|
|
88
|
+
# Advanced usage with custom parameters
|
|
89
|
+
demandify run "2.2961,48.8469,2.3071,48.8532" \
|
|
90
|
+
--name Paris_Optimization \
|
|
91
|
+
--window 30 \
|
|
92
|
+
--seed 123 \
|
|
93
|
+
--pop 100 \
|
|
94
|
+
--gen 50 \
|
|
95
|
+
--mutation 0.5 \
|
|
96
|
+
--elitism 2
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> **Note:** The CLI will pause after fetching/matching data to show matching statistics and ask for confirmation before starting the intensive calibration.
|
|
100
|
+
|
|
101
|
+
#### Parameters
|
|
102
|
+
|
|
103
|
+
| Argument | Type | Default | Description |
|
|
104
|
+
|----------|------|---------|-------------|
|
|
105
|
+
| `bbox` | String | (Req) | Bounding box (`west,south,east,north`) |
|
|
106
|
+
| `--name` | String | Auto | Custom Run ID/Name |
|
|
107
|
+
| `--window` | Int | 15 | Simulation duration (min) |
|
|
108
|
+
| `--seed` | Int | 42 | Random seed |
|
|
109
|
+
| `--pop` | Int | 50 | GA Population size |
|
|
110
|
+
| `--gen` | Int | 20 | GA Generations |
|
|
111
|
+
| `--mutation`| Float | 0.5 | Mutation rate (per individual) |
|
|
112
|
+
| `--crossover`| Float| 0.7 | Crossover rate |
|
|
113
|
+
| `--elitism` | Int | 2 | Top individuals to keep |
|
|
114
|
+
| `--sigma` | Int | 20 | Mutation magnitude (step size) |
|
|
115
|
+
| `--indpb` | Float | 0.3 | Mutation probability (per gene) |
|
|
116
|
+
| `--origins` | Int | 10 | Number of origin candidates |
|
|
117
|
+
| `--destinations` | Int | 10 | Number of destination candidates |
|
|
118
|
+
| `--max-ods` | Int | 50 | Max OD pairs to generate |
|
|
119
|
+
| `--bin-size` | Int | 5 | Time bin size in minutes |
|
|
120
|
+
| `--initial-population` | Int | 1000 | Target initial number of vehicles (controls sparse initialization) |
|
|
121
|
+
|
|
122
|
+
## How It Works
|
|
123
|
+
|
|
124
|
+
demandify follows an 8-stage pipeline:
|
|
125
|
+
|
|
126
|
+
1. **Validate inputs** - Check bbox, parameters, API key
|
|
127
|
+
2. **Fetch traffic snapshot** - Get real-time speeds from TomTom
|
|
128
|
+
3. **Fetch OSM extract** - Download road network data
|
|
129
|
+
4. **Build SUMO network** - Convert OSM to car-only SUMO `.net.xml`
|
|
130
|
+
5. **Map matching** - Match traffic segments to SUMO edges
|
|
131
|
+
6. **Initialize demand** - Select origin/destination pairs and time bins
|
|
132
|
+
7. **Calibrate demand** - Run GA to optimize vehicle counts
|
|
133
|
+
8. **Export scenario** - Generate `demand.csv`, routes, config, and report
|
|
134
|
+
|
|
135
|
+
### Variability & Consistency
|
|
136
|
+
|
|
137
|
+
While demandify uses seeding (random seed) for all internal stochastic operations (OD selection, GA evolution), **perfect reproducibility is not guaranteed** due to the inherently chaotic nature of traffic microsimulation (SUMO) and real-time data inputs.
|
|
138
|
+
|
|
139
|
+
Seeding ensures *consistency* (runs look similar), but small timing differences in OS scheduling or dynamic routing decisions can lead to divergent outcomes.
|
|
140
|
+
|
|
141
|
+
### Caching
|
|
142
|
+
|
|
143
|
+
demandify caches:
|
|
144
|
+
- OSM extracts (by bbox)
|
|
145
|
+
- SUMO networks (by bbox + conversion params)
|
|
146
|
+
- Traffic snapshots (by bbox + timestamp bucket)
|
|
147
|
+
- Map matching results
|
|
148
|
+
|
|
149
|
+
Cache location: `~/.demandify/cache/`
|
|
150
|
+
|
|
151
|
+
Clear cache: `demandify cache clear`
|
|
152
|
+
|
|
153
|
+
## CLI Commands
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Start web server (default)
|
|
157
|
+
demandify
|
|
158
|
+
|
|
159
|
+
# Run headless calibration
|
|
160
|
+
demandify run "west,south,east,north"
|
|
161
|
+
|
|
162
|
+
# Check system requirements
|
|
163
|
+
demandify doctor
|
|
164
|
+
|
|
165
|
+
# Clear cache
|
|
166
|
+
demandify cache clear
|
|
167
|
+
|
|
168
|
+
# Show version
|
|
169
|
+
demandify --version
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Output Files
|
|
173
|
+
|
|
174
|
+
Each run creates a folder with:
|
|
175
|
+
|
|
176
|
+
- **`demand.csv`** - Travel demand with exact schema:
|
|
177
|
+
- `ID`, `origin link id`, `destination link id`, `departure timestep`
|
|
178
|
+
- **`trips.xml`** - SUMO trips file
|
|
179
|
+
- **`routes.rou.xml`** - Routed SUMO routes
|
|
180
|
+
- **`network.net.xml`** - SUMO network
|
|
181
|
+
- **`scenario.sumocfg`** - SUMO configuration (ready to run)
|
|
182
|
+
- **`observed_edges.csv`** - Observed traffic speeds
|
|
183
|
+
- **`run_meta.json`** - Complete run metadata
|
|
184
|
+
- **`report.html`** - Calibration report with visualizations
|
|
185
|
+
|
|
186
|
+
Run the scenario:
|
|
187
|
+
```bash
|
|
188
|
+
cd demandify_runs/run_<timestamp>
|
|
189
|
+
sumo-gui -c scenario.sumocfg
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Configuration
|
|
193
|
+
|
|
194
|
+
### API Keys
|
|
195
|
+
|
|
196
|
+
Three ways to provide your TomTom API key:
|
|
197
|
+
|
|
198
|
+
1. **Web UI**: Paste in the form (saved to `~/.demandify/config.json`)
|
|
199
|
+
2. **Environment variable**: `export TOMTOM_API_KEY=your_key`
|
|
200
|
+
3. **`.env` file**: Copy `.env.example` to `.env` and add your key
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Install with dev dependencies
|
|
206
|
+
pip install -e ".[dev]"
|
|
207
|
+
|
|
208
|
+
# Run tests
|
|
209
|
+
pytest
|
|
210
|
+
|
|
211
|
+
# Format code
|
|
212
|
+
black demandify/
|
|
213
|
+
|
|
214
|
+
# Lint
|
|
215
|
+
ruff check demandify/
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT
|
|
221
|
+
|
|
222
|
+
## Acknowledgments
|
|
223
|
+
|
|
224
|
+
- **SUMO**: [Eclipse SUMO](https://eclipse.dev/sumo/)
|
|
225
|
+
- **TomTom**: [Traffic Flow API](https://developer.tomtom.com/traffic-api)
|
|
226
|
+
- **OpenStreetMap**: [© OpenStreetMap contributors](https://www.openstreetmap.org/copyright)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI application setup.
|
|
3
|
+
"""
|
|
4
|
+
from fastapi import FastAPI
|
|
5
|
+
from fastapi.staticfiles import StaticFiles
|
|
6
|
+
from fastapi.templating import Jinja2Templates
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from demandify import __version__
|
|
10
|
+
from demandify.web import routes
|
|
11
|
+
from demandify.utils import logging # Setup logging
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Get base directory
|
|
15
|
+
BASE_DIR = Path(__file__).parent
|
|
16
|
+
|
|
17
|
+
# Create FastAPI app
|
|
18
|
+
app = FastAPI(
|
|
19
|
+
title="demandify",
|
|
20
|
+
description="Calibrate SUMO traffic simulations against real-world congestion data",
|
|
21
|
+
version=__version__
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Mount static files
|
|
25
|
+
static_dir = BASE_DIR / "static"
|
|
26
|
+
static_dir.mkdir(exist_ok=True)
|
|
27
|
+
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
28
|
+
|
|
29
|
+
# Setup templates
|
|
30
|
+
templates_dir = BASE_DIR / "templates"
|
|
31
|
+
templates_dir.mkdir(exist_ok=True)
|
|
32
|
+
templates = Jinja2Templates(directory=str(templates_dir))
|
|
33
|
+
|
|
34
|
+
# Include routes
|
|
35
|
+
app.include_router(routes.router)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.on_event("startup")
|
|
39
|
+
async def startup_event():
|
|
40
|
+
"""Initialize on startup."""
|
|
41
|
+
from demandify.config import get_config
|
|
42
|
+
config = get_config()
|
|
43
|
+
print(f"Cache directory: {config.cache_dir}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.get("/health")
|
|
47
|
+
async def health_check():
|
|
48
|
+
"""Health check endpoint."""
|
|
49
|
+
return {"status": "healthy", "version": __version__}
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cache key generation for content-addressed caching.
|
|
3
|
+
"""
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def generate_cache_key(data: Dict[str, Any]) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Generate a deterministic cache key from data.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
data: Dictionary of parameters
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Hex string hash
|
|
18
|
+
"""
|
|
19
|
+
# Sort keys for deterministic serialization
|
|
20
|
+
serialized = json.dumps(data, sort_keys=True, separators=(',', ':'))
|
|
21
|
+
|
|
22
|
+
# SHA256 hash
|
|
23
|
+
hash_obj = hashlib.sha256(serialized.encode('utf-8'))
|
|
24
|
+
|
|
25
|
+
return hash_obj.hexdigest()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def bbox_key(west: float, south: float, east: float, north: float) -> str:
|
|
29
|
+
"""Generate cache key for a bounding box."""
|
|
30
|
+
return generate_cache_key({
|
|
31
|
+
'type': 'bbox',
|
|
32
|
+
'west': round(west, 6),
|
|
33
|
+
'south': round(south, 6),
|
|
34
|
+
'east': round(east, 6),
|
|
35
|
+
'north': round(north, 6)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def osm_key(bbox_key: str) -> str:
|
|
40
|
+
"""Generate cache key for OSM data."""
|
|
41
|
+
return generate_cache_key({
|
|
42
|
+
'type': 'osm',
|
|
43
|
+
'bbox_key': bbox_key
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def network_key(osm_key: str, car_only: bool, seed: int) -> str:
|
|
48
|
+
"""Generate cache key for SUMO network."""
|
|
49
|
+
return generate_cache_key({
|
|
50
|
+
'type': 'network',
|
|
51
|
+
'osm_key': osm_key,
|
|
52
|
+
'car_only': car_only,
|
|
53
|
+
'seed': seed
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def traffic_key(bbox_key: str, provider: str, timestamp_bucket: str) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Generate cache key for traffic data.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
bbox_key: Bbox cache key
|
|
63
|
+
provider: Provider name
|
|
64
|
+
timestamp_bucket: Timestamp rounded to hour (e.g., "2024-01-28T18:00")
|
|
65
|
+
"""
|
|
66
|
+
return generate_cache_key({
|
|
67
|
+
'type': 'traffic',
|
|
68
|
+
'bbox_key': bbox_key,
|
|
69
|
+
'provider': provider,
|
|
70
|
+
'timestamp_bucket': timestamp_bucket
|
|
71
|
+
})
|