Spaces:
Running
Running
GitHub Actions commited on
Commit ·
cdf3344
0
Parent(s):
Deploy from GitHub Actions
Browse files- .dockerignore +5 -0
- .github/workflows/collect.yml +42 -0
- .github/workflows/deploy-hf.yml +41 -0
- .github/workflows/monitor.yml +51 -0
- .gitignore +24 -0
- Dockerfile +28 -0
- LICENSE +21 -0
- README.md +86 -0
- data/gtfs-static/agency.txt +2 -0
- data/gtfs-static/calendar.txt +1 -0
- data/gtfs-static/calendar_dates.txt +0 -0
- data/gtfs-static/routes.txt +31 -0
- data/gtfs-static/shapes.txt +0 -0
- data/gtfs-static/stop_times.txt +0 -0
- data/gtfs-static/stops.txt +460 -0
- data/gtfs-static/trips.txt +0 -0
- hf_space_metadata.yml +7 -0
- pulsetransit-worker/.editorconfig +12 -0
- pulsetransit-worker/.gitignore +170 -0
- pulsetransit-worker/.prettierrc +6 -0
- pulsetransit-worker/AGENTS.md +34 -0
- pulsetransit-worker/package-lock.json +1504 -0
- pulsetransit-worker/package.json +13 -0
- pulsetransit-worker/schema.sql +36 -0
- pulsetransit-worker/src/index.js +158 -0
- pulsetransit-worker/wrangler.jsonc +22 -0
- pyproject.toml +20 -0
- src/pulsetransit/__init__.py +0 -0
- src/pulsetransit/cfg/config.py +49 -0
- src/pulsetransit/collector.py +89 -0
- src/pulsetransit/dashboard/app.py +201 -0
- src/pulsetransit/dashboard/map.py +261 -0
- src/pulsetransit/dashboard/schedules.py +98 -0
- src/pulsetransit/db.py +45 -0
- src/pulsetransit/validate.py +35 -0
.dockerignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.github
|
| 3 |
+
pulsetransit-worker/
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.pyc
|
.github/workflows/collect.yml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: TUS Collector
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
workflow_dispatch:
|
| 5 |
+
|
| 6 |
+
permissions:
|
| 7 |
+
contents: write
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
collect:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- uses: actions/checkout@v4
|
| 14 |
+
|
| 15 |
+
- uses: actions/setup-python@v5
|
| 16 |
+
with:
|
| 17 |
+
python-version: "3.11"
|
| 18 |
+
|
| 19 |
+
- name: Collect estimaciones
|
| 20 |
+
run: PYTHONPATH=src python src/pulsetransit/collector.py estimaciones
|
| 21 |
+
|
| 22 |
+
- name: Collect posiciones every 20 min
|
| 23 |
+
run: |
|
| 24 |
+
MINUTE=$(date -u +%M)
|
| 25 |
+
if [ $((MINUTE % 20)) -eq 0 ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
| 26 |
+
PYTHONPATH=src python src/pulsetransit/collector.py posiciones
|
| 27 |
+
else
|
| 28 |
+
echo "Skipping posiciones this run (minute=$MINUTE)"
|
| 29 |
+
fi
|
| 30 |
+
|
| 31 |
+
- name: Validate
|
| 32 |
+
run: PYTHONPATH=src python src/pulsetransit/validate.py
|
| 33 |
+
|
| 34 |
+
- name: Commit DB update
|
| 35 |
+
run: |
|
| 36 |
+
git config user.name "tus-bot"
|
| 37 |
+
git config user.email "bot@tus"
|
| 38 |
+
git add -f data/tus.db
|
| 39 |
+
git diff --cached --quiet || (
|
| 40 |
+
git commit -m "data: collect $(date -u +%H:%M)" &&
|
| 41 |
+
git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/pmatorras/pulsetransit.git
|
| 42 |
+
)
|
.github/workflows/deploy-hf.yml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Deploy Dashboard to HF Space
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
workflow_dispatch:
|
| 5 |
+
|
| 6 |
+
jobs:
|
| 7 |
+
sync-to-hub:
|
| 8 |
+
runs-on: ubuntu-latest
|
| 9 |
+
steps:
|
| 10 |
+
- uses: actions/checkout@v4
|
| 11 |
+
|
| 12 |
+
- name: Deploy to Hugging Face
|
| 13 |
+
env:
|
| 14 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 15 |
+
run: |
|
| 16 |
+
# 1. Create a clean temporary directory
|
| 17 |
+
mkdir /tmp/hf_deploy
|
| 18 |
+
|
| 19 |
+
# 2. Copy all files EXCEPT the .git folder and the DB files
|
| 20 |
+
rsync -av --exclude='.git' --exclude='data/*.db' ./ /tmp/hf_deploy/
|
| 21 |
+
|
| 22 |
+
# 3. Go into the clean directory
|
| 23 |
+
cd /tmp/hf_deploy
|
| 24 |
+
|
| 25 |
+
# 4. Merge the README with the HF metadata
|
| 26 |
+
cat hf_space_metadata.yml > hf_readme.md
|
| 27 |
+
echo "" >> hf_readme.md
|
| 28 |
+
cat README.md >> hf_readme.md
|
| 29 |
+
mv hf_readme.md README.md
|
| 30 |
+
|
| 31 |
+
# 5. Initialize a brand new git repo (NO HISTORY)
|
| 32 |
+
git init
|
| 33 |
+
git config user.email "github-actions@github.com"
|
| 34 |
+
git config user.name "GitHub Actions"
|
| 35 |
+
|
| 36 |
+
# 6. Commit the current state
|
| 37 |
+
git add .
|
| 38 |
+
git commit -m "Deploy from GitHub Actions"
|
| 39 |
+
|
| 40 |
+
# 7. Force push to the 'main' branch on Hugging Face
|
| 41 |
+
git push -f https://pmatorras:$HF_TOKEN@huggingface.co/spaces/pmatorras/pulsetransit HEAD:main
|
.github/workflows/monitor.yml
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Monitor Worker
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
schedule:
|
| 5 |
+
- cron: '0 8 * * *'
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
health-check:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
steps:
|
| 12 |
+
- name: Check worker health
|
| 13 |
+
run: |
|
| 14 |
+
RESPONSE=$(curl -s https://pulsetransit-worker.pablo-matorras.workers.dev/health)
|
| 15 |
+
echo "Worker response: $RESPONSE"
|
| 16 |
+
|
| 17 |
+
LAST_EST=$(echo $RESPONSE | jq -r '.last_estimaciones')
|
| 18 |
+
LAST_POS=$(echo $RESPONSE | jq -r '.last_posiciones')
|
| 19 |
+
|
| 20 |
+
# Calculate age
|
| 21 |
+
EST_AGE=$(($(date +%s) - $(date -d "$LAST_EST" +%s)))
|
| 22 |
+
POS_AGE=$(($(date +%s) - $(date -d "$LAST_POS" +%s)))
|
| 23 |
+
|
| 24 |
+
# Check current hour (UTC - adjust if needed)
|
| 25 |
+
CURRENT_HOUR=$(date +%H)
|
| 26 |
+
|
| 27 |
+
# Service hours: 5am-11pm UTC (6am-midnight CET)
|
| 28 |
+
if [ $CURRENT_HOUR -ge 5 ] && [ $CURRENT_HOUR -lt 23 ]; then
|
| 29 |
+
# During service hours - strict checks
|
| 30 |
+
if [ $EST_AGE -gt 1800 ]; then # 30 min
|
| 31 |
+
echo "⛔ ERROR: Estimaciones stale (${EST_AGE}s) during service hours"
|
| 32 |
+
exit 1
|
| 33 |
+
elif [ $EST_AGE -gt 600 ]; then # 10 min
|
| 34 |
+
echo "⚠️ WARNING: Estimaciones possibly stale (${EST_AGE}s old)"
|
| 35 |
+
# Don't exit - just warn
|
| 36 |
+
fi
|
| 37 |
+
|
| 38 |
+
if [ $POS_AGE -gt 7200 ]; then
|
| 39 |
+
echo "⛔ ERROR: Posiciones stale (${POS_AGE}s) during service hours"
|
| 40 |
+
exit 1
|
| 41 |
+
fi
|
| 42 |
+
else
|
| 43 |
+
# Outside service hours - lenient
|
| 44 |
+
echo "💤 Outside service hours - data age: est=${EST_AGE}s, pos=${POS_AGE}s"
|
| 45 |
+
if [ $EST_AGE -gt 43200 ]; then # 12 hours
|
| 46 |
+
echo "⚠️ WARNING: Data very stale, but outside service hours"
|
| 47 |
+
fi
|
| 48 |
+
# Don't fail outside service hours
|
| 49 |
+
fi
|
| 50 |
+
|
| 51 |
+
echo "✅ Worker is healthy"
|
.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data — only commit static info
|
| 2 |
+
*.db
|
| 3 |
+
*.csv
|
| 4 |
+
!data/gtfs-static/
|
| 5 |
+
!data/gtfs-static/*.txt
|
| 6 |
+
|
| 7 |
+
# Python
|
| 8 |
+
__pycache__/
|
| 9 |
+
*.pyc
|
| 10 |
+
*.pyo
|
| 11 |
+
.env
|
| 12 |
+
.venv/
|
| 13 |
+
*.egg-info/
|
| 14 |
+
dist/
|
| 15 |
+
build/
|
| 16 |
+
|
| 17 |
+
# Notebooks
|
| 18 |
+
.ipynb_checkpoints/
|
| 19 |
+
|
| 20 |
+
# OS
|
| 21 |
+
.DS_Store
|
| 22 |
+
Thumbs.db
|
| 23 |
+
|
| 24 |
+
.wrangler
|
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use a lightweight Python base image
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# Hugging Face Spaces strongly recommends running as a non-root user (User ID 1000)
|
| 5 |
+
RUN useradd -m -u 1000 user
|
| 6 |
+
USER user
|
| 7 |
+
|
| 8 |
+
# Set environment variables for the user and Streamlit
|
| 9 |
+
ENV HOME=/home/user \
|
| 10 |
+
PATH=/home/user/.local/bin:$PATH \
|
| 11 |
+
STREAMLIT_SERVER_PORT=7860 \
|
| 12 |
+
STREAMLIT_SERVER_ADDRESS=0.0.0.0
|
| 13 |
+
|
| 14 |
+
# Set the working directory
|
| 15 |
+
WORKDIR $HOME/app
|
| 16 |
+
|
| 17 |
+
# Copy all project files into the container with the correct permissions
|
| 18 |
+
COPY --chown=user:user . $HOME/app
|
| 19 |
+
|
| 20 |
+
# Install your package using your existing pyproject.toml
|
| 21 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 22 |
+
pip install --no-cache-dir .
|
| 23 |
+
|
| 24 |
+
# Expose the default Hugging Face Spaces port
|
| 25 |
+
EXPOSE 7860
|
| 26 |
+
|
| 27 |
+
# Run the Streamlit dashboard
|
| 28 |
+
CMD ["streamlit", "run", "src/pulsetransit/dashboard/app.py"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Pablo Matorras
|
| 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.
|
README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: PulseTransit
|
| 3 |
+
emoji: 🚌
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
# PulseTransit
|
| 10 |
+

|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
Real-time data pipeline for TUS (Transportes Urbanos de Santander) bus network.
|
| 14 |
+
Collects live vehicle positions and stop-level ETA predictions to build a
|
| 15 |
+
historical dataset for delay analysis and ML-based prediction.
|
| 16 |
+
|
| 17 |
+
## Data Sources
|
| 18 |
+
|
| 19 |
+
### Real-time Data (datos.santander.es API)
|
| 20 |
+
|
| 21 |
+
- **`posiciones`**: GPS positions of buses (lat/lon, timestamp, line, vehicle ID)
|
| 22 |
+
- **`estimaciones_parada`**: Real-time ETAs for each bus-stop pair
|
| 23 |
+
- ~~**`pasos_parada`**: Historical passages (stale since June 2025, not used)~~
|
| 24 |
+
|
| 25 |
+
### Static Data (NAP - National Access Point)
|
| 26 |
+
|
| 27 |
+
GTFS static files from [nap.transportes.gob.es](https://nap.transportes.gob.es/Files/Detail/1391):
|
| 28 |
+
|
| 29 |
+
- **`stops.txt`**: Stop coordinates and metadata (for proximity calculation)
|
| 30 |
+
- **`shapes.txt`**: Detailed route geometries (for GPS map-matching and visualization)
|
| 31 |
+
- **`routes.txt`**: Route names, colors, and metadata
|
| 32 |
+
- **`trips.txt`**: Trip patterns and service IDs
|
| 33 |
+
- **`stop_times.txt`**: Stop sequences and route structure
|
| 34 |
+
- **`calendar_dates.txt`**: Service exceptions (holidays, special schedules)
|
| 35 |
+
|
| 36 |
+
**Note**: GTFS files are stored in `data/gtfs-static/` (not tracked in git due to size).
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
Source: [datos.santander.es](http://datos.santander.es)
|
| 40 |
+
|
| 41 |
+
## Architecture
|
| 42 |
+
|
| 43 |
+
**Data Collection:**
|
| 44 |
+
- **Cloudflare Worker** (`pulsetransit-worker/`): Scheduled collection every 2 minutes (estimaciones) and hourly (posiciones), storing in Cloudflare D1 database
|
| 45 |
+
- **GitHub Actions (Legacy)** (`.github/workflows/collect.yml`): Legacy collector, writes to `data/tus.db` for development/testing
|
| 46 |
+
|
| 47 |
+
**Database Schema:**
|
| 48 |
+
- `estimaciones`: Predictions with `UNIQUE(parada_id, linea, fech_actual)` to deduplicate
|
| 49 |
+
- `posiciones`: GPS breadcrumbs with `UNIQUE(vehiculo, instante)` to deduplicate overlapping route histories
|
| 50 |
+
|
| 51 |
+
## Project Structure
|
| 52 |
+
|
| 53 |
+
```
|
| 54 |
+
src/pulsetransit/ # Legacy Python collector (backup/testing)
|
| 55 |
+
├── collector.py # API fetching and DB insertion
|
| 56 |
+
└── db.py # Schema and connection management
|
| 57 |
+
|
| 58 |
+
pulsetransit-worker/ # Cloudflare Worker (production collector)
|
| 59 |
+
├── src/index.js # Scheduled tasks, API fetching, health endpoint
|
| 60 |
+
├── schema.sql # D1 database schema
|
| 61 |
+
└── wrangler.jsonc # Cloudflare config and cron triggers
|
| 62 |
+
|
| 63 |
+
.github/workflows/
|
| 64 |
+
├── collect.yml # Manual backup collector
|
| 65 |
+
└── monitor.yml # Hourly worker health check
|
| 66 |
+
|
| 67 |
+
data/
|
| 68 |
+
└── tus.db # SQLite database (GitHub Actions/local dev)
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
## Roadmap
|
| 73 |
+
|
| 74 |
+
- [x] Data collection pipeline (GPS + ETA)
|
| 75 |
+
- [ ] GTFS static feed integration (stop geometries, scheduled timetables)
|
| 76 |
+
- [ ] Delay computation (predicted vs actual arrival)
|
| 77 |
+
- [ ] Weather feature enrichment (via meteomat)
|
| 78 |
+
- [ ] ML delay prediction model
|
| 79 |
+
- [ ] Live dashboard
|
| 80 |
+
|
| 81 |
+
## Setup
|
| 82 |
+
|
| 83 |
+
```bash
|
| 84 |
+
pip install -e .
|
| 85 |
+
python src/pulsetransit/collector.py both
|
| 86 |
+
```
|
data/gtfs-static/agency.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,agency_fare_url
|
| 2 |
+
1,S.M.T.U. SANTANDER (TUS),https://tus.santander.es,Europe/Madrid,es,,https://tus.santander.es
|
data/gtfs-static/calendar.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
|
data/gtfs-static/calendar_dates.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/gtfs-static/routes.txt
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
|
| 2 |
+
1,1,1,G.TREVILLA 14-PCTCAN/ADARZO,,3,,FF0000,FFFFFF
|
| 3 |
+
2,1,2,CORBAN-CONSUELO BERGES,,3,,EBBCC2,000000
|
| 4 |
+
3,1,3,OJAIZ-SARDINERO/UNI/P.PEREDA,,3,,FFD014,000000
|
| 5 |
+
4,1,4,B.PESQUERO-INT.SARDINERO/UNI,,3,,3AF2E5,000000
|
| 6 |
+
11,1,11,AVDA.VALDECILLA-PZA.ITALIA,,3,,BA3309,FFFFFF
|
| 7 |
+
12,1,12,CARREFOUR,,3,,B2DB74,000000
|
| 8 |
+
13,1,13,LLUJA-CUETO,,3,,D4B0D3,000000
|
| 9 |
+
14,1,14,ESTACIONES,,3,,3EB9E6,000000
|
| 10 |
+
15,1,15,ESTACIONES-CAMPING,,3,,F5F53D,000000
|
| 11 |
+
16,1,16,PLAZA DE LOS REMEDIOS,,3,,D444B2,000000
|
| 12 |
+
17,1,17,PZA.ESTACIONES-CORBAN/CIRIEGO,,3,,FFD2C7,000000
|
| 13 |
+
18,1,18,PUERTOCHICO-CORBANERA/CASTILLO,,3,,C9F3FF,000000
|
| 14 |
+
31,1,E31,ALISAL-INT.SARDINERO,,3,,BED171,000000
|
| 15 |
+
41,1,E1,SE INT.VALDECILLA- INT.SARDINERO,,3,,2A99B5,FFFFFF
|
| 16 |
+
42,1,E2,S.E. INTERMODAL,,3,,C9CCC0,000000
|
| 17 |
+
43,1,E3,SE MIRANDA-INSTITUTOS,,3,,D692D4,000000
|
| 18 |
+
44,1,E4,SE SAN MARTIN-INSTITUTOS,,3,,E8B780,000000
|
| 19 |
+
51,1,5C1,MIRANDA/PLZ. ITALIA C1,,3,,CFCFCF,000000
|
| 20 |
+
52,1,5C2,MIRANDA/PLZ. ITALIA C2,,3,,ADADAD,000000
|
| 21 |
+
61,1,6C1,COMPLEJO C1,,3,,0EC758,FFFFFF
|
| 22 |
+
62,1,6C2,COMPLEJO RUTH BEITIA C2,,3,,00C200,FFFFFF
|
| 23 |
+
71,1,7C1,LUIS QUINTANILLA C1,,3,,FF9D1C,000000
|
| 24 |
+
72,1,7C2,LUIS QUINTANILLA C2,,3,,FF9D1C,000000
|
| 25 |
+
99,1,99,LANZADERA,,3,,FF9D1C,000000
|
| 26 |
+
100,1,LC,INT.VALDECILLA-INT SARDINERO,,3,,1717FF,000000
|
| 27 |
+
101,1,N1,CORBAN-G. ATECA por Valdenoja,,3,,ADADAD,000000
|
| 28 |
+
102,1,N2,CORBAN-COMPLEJO por Paseo Altamira,,3,,696969,FFFFFF
|
| 29 |
+
103,1,N3,PEÑACASTILLO-PLAZA DE ITALIA ,,3,,ABABAB,000000
|
| 30 |
+
241,1,24C1,PCTCAN-SAN MARTIN-PCTCAN,,3,,FF6622,000000
|
| 31 |
+
242,1,24C2,PCTCAN-SAN MARTIN-PCTCAN,,3,,FF6622,000000
|
data/gtfs-static/shapes.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/gtfs-static/stop_times.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/gtfs-static/stops.txt
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,stop_timezone
|
| 2 |
+
1,PA1,LOS CIRUELOS 47,Parada,43.4576488249693,-3.85555763126247,,,,,Europe/Madrid
|
| 3 |
+
10,PA10,CUATRO CAMINOS,Parada,43.4587638498046,-3.82538532259084,,,,,Europe/Madrid
|
| 4 |
+
99,PA100,LOS CASTROS 39,Parada,43.4724960314191,-3.79209293900641,,,,,Europe/Madrid
|
| 5 |
+
100,PA101,LOS CASTROS 23,Parada,43.4732880183678,-3.78791271217829,,,,,Europe/Madrid
|
| 6 |
+
101,PA103,LAS ESTACIONES,Parada,43.4591167695794,-3.81075395497855,,,,,Europe/Madrid
|
| 7 |
+
102,PA104,CALLE CASTILLA 27,Parada,43.4575008706594,-3.81285327700227,,,,,Europe/Madrid
|
| 8 |
+
103,PA105,CALLE CASTILLA 51,Parada,43.4557605431402,-3.817533946692,,,,,Europe/Madrid
|
| 9 |
+
104,PA106,CALLE CASTILLA 71,Parada,43.4545723535417,-3.82078517573242,,,,,Europe/Madrid
|
| 10 |
+
105,PA107,CALLE CASTILLA 64,Parada,43.453146350609,-3.82493768892471,,,,,Europe/Madrid
|
| 11 |
+
106,PA108,PUENTE LA MARGA,Parada,43.452694764383,-3.82774505856727,,,,,Europe/Madrid
|
| 12 |
+
107,PA109,JERONIMO SAINZ DE LA MAZA 9,Parada,43.4560471857845,-3.82704379820745,,,,,Europe/Madrid
|
| 13 |
+
11,PA11,SAN FERNANDO,Parada,43.4602549664282,-3.8199534076077,,,,,Europe/Madrid
|
| 14 |
+
109,PA111,MENENDEZ PELAYO 97,Parada,43.4673013619228,-3.79216289556649,,,,,Europe/Madrid
|
| 15 |
+
110,PA112,MENENDEZ PELAYO 61,Parada,43.4667016896395,-3.79595983788604,,,,,Europe/Madrid
|
| 16 |
+
111,PA113,VALLICIERGO 7,Parada,43.4647215973614,-3.80033541399032,,,,,Europe/Madrid
|
| 17 |
+
112,PA114,SANTA LUCIA 1,Parada,43.4639724054467,-3.80384447007856,,,,,Europe/Madrid
|
| 18 |
+
113,PA115,GUEVARA 21,Parada,43.4637344370763,-3.80742315320999,,,,,Europe/Madrid
|
| 19 |
+
114,PA116,PLAZA DE LOS REMEDIOS,Parada,43.4627399847095,-3.80867161085099,,,,,Europe/Madrid
|
| 20 |
+
115,PA118,JOSE HIERRO 22,Parada,43.4606156156359,-3.82542496460283,,,,,Europe/Madrid
|
| 21 |
+
116,PA119,JOSE HIERRO 32,Parada,43.4624208941179,-3.82369373013492,,,,,Europe/Madrid
|
| 22 |
+
12,PA12,JESUS DE MONASTERIO 21,Parada,43.4617290137147,-3.81243950472629,,,,,Europe/Madrid
|
| 23 |
+
117,PA120,LAS MERCEDARIAS,Parada,43.4644229373817,-3.81974712118845,,,,,Europe/Madrid
|
| 24 |
+
118,PA121,PASEO DE ALTAMIRA 89,Parada,43.4652773202967,-3.81775393456929,,,,,Europe/Madrid
|
| 25 |
+
119,PA122,PASEO DE ALTAMIRA 87,Parada,43.4671905802941,-3.81475214229526,,,,,Europe/Madrid
|
| 26 |
+
120,PA123,LOS SALESIANOS,Parada,43.4673992465001,-3.80870448748227,,,,,Europe/Madrid
|
| 27 |
+
121,PA124,PRADO SAN ROQUE,Parada,43.4672659976073,-3.80625175486188,,,,,Europe/Madrid
|
| 28 |
+
122,PA125,PASEO DE ALTAMIRA 41,Parada,43.4677650748204,-3.80111657271067,,,,,Europe/Madrid
|
| 29 |
+
123,PA126,SANTA CLOTILDE,Parada,43.4685213555613,-3.79765062764561,,,,,Europe/Madrid
|
| 30 |
+
124,PA127,BAJADA DE LA ENCINA 1,Parada,43.468870608137,-3.7916241731006,,,,,Europe/Madrid
|
| 31 |
+
125,PA128,FERNANDO CALDERÓN RUEDA 6,Parada,43.470468715894,-3.78918220049349,,,,,Europe/Madrid
|
| 32 |
+
126,PA129,PLAZA DE ITALIA CASINO,Parada,43.4720669820009,-3.78226709203205,,,,,Europe/Madrid
|
| 33 |
+
13,PA13,AYUNTAMIENTO,Parada,43.4614716076811,-3.81004714351273,,,,,Europe/Madrid
|
| 34 |
+
127,PA130,REINA VICTORIA 117,Parada,43.4698843300937,-3.77750007123078,,,,,Europe/Madrid
|
| 35 |
+
128,PA131,LAS ESCLAVAS,Parada,43.4675248095255,-3.7794741268824,,,,,Europe/Madrid
|
| 36 |
+
129,PA132,PEREZ GALDÓS 17,Parada,43.4664497093828,-3.78491352890353,,,,,Europe/Madrid
|
| 37 |
+
130,PA133,JOSE HIERRO 10,Parada,43.4594847354251,-3.82642371027838,,,,,Europe/Madrid
|
| 38 |
+
131,PA134,LOS PINARES,Parada,43.471390090196,-3.7877614643787,,,,,Europe/Madrid
|
| 39 |
+
133,PA136,PASEO DE ALTAMIRA 28,Parada,43.4686154673231,-3.79287101284989,,,,,Europe/Madrid
|
| 40 |
+
134,PA137,PASEO DE ALTAMIRA 58,Parada,43.4685837762071,-3.7977379893668,,,,,Europe/Madrid
|
| 41 |
+
135,PA138,PASEO DE ALTAMIRA 84,Parada,43.4676743729047,-3.8019555747684,,,,,Europe/Madrid
|
| 42 |
+
136,PA139,PASEO DE ALTAMIRA 122,Parada,43.4674532440932,-3.80559355392471,,,,,Europe/Madrid
|
| 43 |
+
14,PA14,CORREOS,Parada,43.4614823957,-3.80653011145617,,,,,Europe/Madrid
|
| 44 |
+
137,PA140,LOS SALESIANOS,Parada,43.4675874471235,-3.80932910373885,,,,,Europe/Madrid
|
| 45 |
+
138,PA141,PASEO DE ALTAMIRA 224,Parada,43.4673384590671,-3.81473682082279,,,,,Europe/Madrid
|
| 46 |
+
139,PA142,PASEO DE ALTAMIRA 256,Parada,43.4654361606394,-3.81772813699138,,,,,Europe/Madrid
|
| 47 |
+
140,PA143,PASEO DE ALTAMIRA 266,Parada,43.4643419018453,-3.82032421432836,,,,,Europe/Madrid
|
| 48 |
+
141,PA144,COLEGIO LA SALLE,Parada,43.4624561585847,-3.8238619575228,,,,,Europe/Madrid
|
| 49 |
+
142,PA145,JOSE HIERRO 19,Parada,43.4604061435979,-3.82581594595342,,,,,Europe/Madrid
|
| 50 |
+
143,PA146,CATEDRAL,Parada,43.4614250526855,-3.80724262100708,,,,,Europe/Madrid
|
| 51 |
+
146,PA149,MENENDEZ PELAYO 14,Parada,43.4657017223253,-3.79808139223909,,,,,Europe/Madrid
|
| 52 |
+
15,PA15,JARDINES DE PEREDA,Parada,43.4617267613344,-3.80353646845979,,,,,Europe/Madrid
|
| 53 |
+
147,PA150,MENENDEZ PELAYO 46,Parada,43.4667049715542,-3.79555165572053,,,,,Europe/Madrid
|
| 54 |
+
148,PA151,MENENDEZ PELAYO 76,Parada,43.4671475465413,-3.79249220675055,,,,,Europe/Madrid
|
| 55 |
+
149,PA152,BAJADA DE LA ENCINA S/N,Parada,43.4707630715103,-3.78912928257658,,,,,Europe/Madrid
|
| 56 |
+
150,PA153,COMPLEJO DEPORTIVO,Parada,43.4602087594312,-3.85267179932964,,,,,Europe/Madrid
|
| 57 |
+
151,PA154,AVENIDA DEL DEPORTE 11,Parada,43.4613667190941,-3.84717137239985,,,,,Europe/Madrid
|
| 58 |
+
152,PA155,LAVAPIES 1,Parada,43.4626994625506,-3.83889751016233,,,,,Europe/Madrid
|
| 59 |
+
153,PA156,CALLE REPUENTE 43,Parada,43.4640869279286,-3.83493860027926,,,,,Europe/Madrid
|
| 60 |
+
154,PA157,CALLE REPUENTE 26,Parada,43.4671122353559,-3.83196315715773,,,,,Europe/Madrid
|
| 61 |
+
155,PA158,CALLE REPUENTE 15,Parada,43.46918793866,-3.8282166937927,,,,,Europe/Madrid
|
| 62 |
+
156,PA159,BARRIO LA TORRE 95,Parada,43.4714991606583,-3.82114062648601,,,,,Europe/Madrid
|
| 63 |
+
16,PA16,PUERTO CHICO,Parada,43.4621794921801,-3.79741106347385,,,,,Europe/Madrid
|
| 64 |
+
157,PA160,BARRIO LA TORRE 1,Parada,43.4738398841223,-3.81528526705468,,,,,Europe/Madrid
|
| 65 |
+
158,PA161,JORGE SEPULVEDA 11,Parada,43.4742986025426,-3.81156862881098,,,,,Europe/Madrid
|
| 66 |
+
159,PA162,AVENIDA CANTABRIA 43,Parada,43.4751237424502,-3.80905605470708,,,,,Europe/Madrid
|
| 67 |
+
160,PA163,PADRE MENNI,Parada,43.4763196471126,-3.80303363302839,,,,,Europe/Madrid
|
| 68 |
+
161,PA164,AVENIDA CANTABRIA 11,Parada,43.4775564670184,-3.79755458610791,,,,,Europe/Madrid
|
| 69 |
+
163,PA167,MANUEL GONZALEZ HOYOS 39,Parada,43.4807082871033,-3.79577564210222,,,,,Europe/Madrid
|
| 70 |
+
164,PA168,VALDENOJA 25,Parada,43.4808930736711,-3.79274105426077,,,,,Europe/Madrid
|
| 71 |
+
165,PA169,VALDENOJA 3,Parada,43.4811720017571,-3.78993570707354,,,,,Europe/Madrid
|
| 72 |
+
17,PA17,CASTELAR,Parada,43.4627488279462,-3.79292037646895,,,,,Europe/Madrid
|
| 73 |
+
167,PA171,ALCALDE VEGA LAMERA 1,Parada,43.4731789406836,-3.79343630829962,,,,,Europe/Madrid
|
| 74 |
+
168,PA172,JOSE HIERRO 9,Parada,43.4595818856838,-3.82654915173831,,,,,Europe/Madrid
|
| 75 |
+
169,PA173,GRUPO SAN FRANCISCO,Parada,43.4630464130455,-3.82487302573295,,,,,Europe/Madrid
|
| 76 |
+
170,PA174,PASEO DE ALTAMIRA 314,Parada,43.4617601311828,-3.82944341397276,,,,,Europe/Madrid
|
| 77 |
+
171,PA175,FACULTAD DE MEDICINA,Parada,43.4596742222992,-3.83457235466751,,,,,Europe/Madrid
|
| 78 |
+
172,PA176,CARDENAL HERRERA ORIA 26,Parada,43.4588974095354,-3.83894435969918,,,,,Europe/Madrid
|
| 79 |
+
173,PA177,CARDENAL HERRERA ORIA 42,Parada,43.4586036798906,-3.84340454913237,,,,,Europe/Madrid
|
| 80 |
+
174,PA178,CARDENAL HERRERA ORIA 76,Parada,43.4577348267658,-3.84651538042628,,,,,Europe/Madrid
|
| 81 |
+
175,PA179,RESIDENCIA SANTA LUCIA,Parada,43.4562303034807,-3.85151063298984,,,,,Europe/Madrid
|
| 82 |
+
18,PA18,REINA VICTORIA 18,Parada,43.4634398241484,-3.78832006473095,,,,,Europe/Madrid
|
| 83 |
+
176,PA180,CARDENAL HERRERA ORIA 130,Parada,43.4550472743475,-3.85543210604103,,,,,Europe/Madrid
|
| 84 |
+
177,PA181,AVENIDA DEL DEPORTE 15,Parada,43.4595958490693,-3.85577727708927,,,,,Europe/Madrid
|
| 85 |
+
178,PA182,COMPLEJO RUTH BEITIA,Parada,43.4602479564986,-3.85357312820778,,,,,Europe/Madrid
|
| 86 |
+
179,PA183,POLIDEPORTIVO,Parada,43.4596711662686,-3.85604844292869,,,,,Europe/Madrid
|
| 87 |
+
180,PA184,CRUCE VICENTE TRUEBA,Parada,43.4558879811215,-3.85638830302217,,,,,Europe/Madrid
|
| 88 |
+
181,PA185,CARDENAL HERRERA ORIA 95,Parada,43.4562809722381,-3.85070356319127,,,,,Europe/Madrid
|
| 89 |
+
182,PA186,CARDENAL HERRERA ORIA 51,Parada,43.4578134690333,-3.84598754771547,,,,,Europe/Madrid
|
| 90 |
+
183,PA187,CARDENAL HERRERA ORIA 31,Parada,43.4585234411853,-3.8432704302976,,,,,Europe/Madrid
|
| 91 |
+
184,PA188,CARDENAL HERRERA ORIA 23,Parada,43.4588157358959,-3.83880874294457,,,,,Europe/Madrid
|
| 92 |
+
185,PA189,CARDENAL HERRERA ORIA 17,Parada,43.4590374667873,-3.83607204901029,,,,,Europe/Madrid
|
| 93 |
+
19,PA19,AVENIDA REINA VICTORIA,Parada,43.4645272222598,-3.78281785697395,,,,,Europe/Madrid
|
| 94 |
+
186,PA190,CARDENAL HERRERA ORIA,Parada,43.4597797627929,-3.83399096458054,,,,,Europe/Madrid
|
| 95 |
+
187,PA191,PASEO DE ALTAMIRA 125,Parada,43.4618085537798,-3.82889990640637,,,,,Europe/Madrid
|
| 96 |
+
188,PA192,PASEO DE ALTAMIRA 117,Parada,43.4629113926715,-3.82495426126731,,,,,Europe/Madrid
|
| 97 |
+
190,PA194,INSTITUTO LAS LLAMAS,Parada,43.4732789396835,-3.79329914055971,,,,,Europe/Madrid
|
| 98 |
+
191,PA195,PALACIO DE EXPOSICIONES,Parada,43.4757106160868,-3.79166813367462,,,,,Europe/Madrid
|
| 99 |
+
193,PA197,VALDENOJA 32,Parada,43.4813288301357,-3.78974398185534,,,,,Europe/Madrid
|
| 100 |
+
194,PA198,VALDENOJA 50,Parada,43.4809760396086,-3.79319151389859,,,,,Europe/Madrid
|
| 101 |
+
195,PA199,MANUEL GONZALEZ HOYOS 7,Parada,43.4807313562557,-3.79588945976864,,,,,Europe/Madrid
|
| 102 |
+
2,PA2,LOS CIRUELOS 27,Parada,43.4582276304313,-3.8524726489669,,,,,Europe/Madrid
|
| 103 |
+
20,PA20,AVENIDA REINA VICTORIA 38,Parada,43.4663164713884,-3.77941016312866,,,,,Europe/Madrid
|
| 104 |
+
196,PA200,CONSUELO BERGÉS 16,Parada,43.4795447472921,-3.79817673088218,,,,,Europe/Madrid
|
| 105 |
+
197,PA201,CONCHA ESPINA 18,Parada,43.479168866962,-3.79721209973858,,,,,Europe/Madrid
|
| 106 |
+
198,PA203,PADRE MENNI,Parada,43.4764827807206,-3.80260104220408,,,,,Europe/Madrid
|
| 107 |
+
199,PA205,AVENIDA CANTABRIA 100,Parada,43.4753447240692,-3.80883865789799,,,,,Europe/Madrid
|
| 108 |
+
200,PA206,BARRIO LA TORRE 2,Parada,43.4738108897242,-3.8156514833982,,,,,Europe/Madrid
|
| 109 |
+
201,PA207,BARRIO LA TORRE 60,Parada,43.4714960901312,-3.82138007956667,,,,,Europe/Madrid
|
| 110 |
+
202,PA208,CALLE REPUENTE 16,Parada,43.4691434023217,-3.82844802950164,,,,,Europe/Madrid
|
| 111 |
+
203,PA209,CALLE REPUENTE 19,Parada,43.4673029484697,-3.83216209280771,,,,,Europe/Madrid
|
| 112 |
+
21,PA21,LA MAGDALENA,Parada,43.4687262484862,-3.77644957676557,,,,,Europe/Madrid
|
| 113 |
+
204,PA210,CALLE REPUENTE 36,Parada,43.4642951675794,-3.83473304144922,,,,,Europe/Madrid
|
| 114 |
+
205,PA211,LAVAPIES 34,Parada,43.4627196233941,-3.84043265161535,,,,,Europe/Madrid
|
| 115 |
+
206,PA212,AVENIDA DEL DEPORTE 6,Parada,43.4612905163792,-3.84794142993128,,,,,Europe/Madrid
|
| 116 |
+
207,PA213,AVENIDA CANTABRIA 10,Parada,43.4790364609833,-3.79315281013775,,,,,Europe/Madrid
|
| 117 |
+
208,PA214,GUTIERREZ SOLANA 13,Parada,43.4598357026417,-3.83870762546173,,,,,Europe/Madrid
|
| 118 |
+
209,PA215,AVENIDA LOS CASTROS,Parada,43.4620337879232,-3.83406333648598,,,,,Europe/Madrid
|
| 119 |
+
210,PA216,AVENIDA LOS CASTROS 155,Parada,43.4644267251748,-3.82914605910189,,,,,Europe/Madrid
|
| 120 |
+
211,PA217,AVENIDA LOS CASTROS 139,Parada,43.46599281024,-3.82355773446237,,,,,Europe/Madrid
|
| 121 |
+
212,PA218,AVENIDA LOS CASTROS 119,Parada,43.4679615896012,-3.81738668620196,,,,,Europe/Madrid
|
| 122 |
+
213,PA219,AVENIDA LOS CASTROS 83,Parada,43.4697964813046,-3.80767872749989,,,,,Europe/Madrid
|
| 123 |
+
22,PA22,LUIS MARTINEZ,Parada,43.4710929658344,-3.77858290562545,,,,,Europe/Madrid
|
| 124 |
+
214,PA220,TORRES QUEVEDO 12,Parada,43.4569237399302,-3.83547954047646,,,,,Europe/Madrid
|
| 125 |
+
215,PA221,AVENIDA CANTABRIA 12,Parada,43.4790276429021,-3.79372545892564,,,,,Europe/Madrid
|
| 126 |
+
216,PA223,GERARDO DIEGO,Parada,43.4558164385288,-3.84591447880765,,,,,Europe/Madrid
|
| 127 |
+
217,PA224,JOAQUIN BUSTAMANTE 10,Parada,43.4565499751617,-3.83979696615997,,,,,Europe/Madrid
|
| 128 |
+
218,PA225,JOAQUIN BUSTAMANTE 5,Parada,43.4565424914924,-3.83584993960173,,,,,Europe/Madrid
|
| 129 |
+
219,PA226,LOS CASTROS 62,Parada,43.4698129315964,-3.8087666879077,,,,,Europe/Madrid
|
| 130 |
+
220,PA227,AVENIDA LOS CASTROS 115,Parada,43.4680764595134,-3.81746574938437,,,,,Europe/Madrid
|
| 131 |
+
221,PA228,BAJADA DE SAN JUAN,Parada,43.4660004819531,-3.82395518000018,,,,,Europe/Madrid
|
| 132 |
+
222,PA229,BAJADA DEL CALERUCO,Parada,43.464276875972,-3.83010529972449,,,,,Europe/Madrid
|
| 133 |
+
23,PA23,PLAZA DE ITALIA 3,Parada,43.4721308522484,-3.78214427885009,,,,,Europe/Madrid
|
| 134 |
+
223,PA230,LOS CASTROS 136,Parada,43.4621227824407,-3.83419596437341,,,,,Europe/Madrid
|
| 135 |
+
224,PA231,GUTIERREZ SOLANA 34,Parada,43.4599601458889,-3.83891701408624,,,,,Europe/Madrid
|
| 136 |
+
226,PA234,BARRIO HOSPITALILLO 40,Parada,43.4540841536187,-3.86025586724053,,,,,Europe/Madrid
|
| 137 |
+
227,PA235,ADARZO,Parada,43.4531679620636,-3.86556096605062,,,,,Europe/Madrid
|
| 138 |
+
228,PA236,RUCANDIAL 28,Parada,43.4575465905675,-3.86911004913654,,,,,Europe/Madrid
|
| 139 |
+
231,PA239,PLAZA AMADOR TOCA,Parada,43.4529252290187,-3.86560545992211,,,,,Europe/Madrid
|
| 140 |
+
24,PA24,PARQUE PIQUIO,Parada,43.4742918403958,-3.78511458124276,,,,,Europe/Madrid
|
| 141 |
+
232,PA240,BARRIO HOSPITALILLO 97,Parada,43.4540015223164,-3.86018028870079,,,,,Europe/Madrid
|
| 142 |
+
233,PA241,CARDENAL HERRERA ORIA 119,Parada,43.4549907793605,-3.85502786044527,,,,,Europe/Madrid
|
| 143 |
+
234,PA244,INÉS DIEGO DEL NOVAL 50,Parada,43.4841265599562,-3.79889327741975,,,,,Europe/Madrid
|
| 144 |
+
235,PA245,INÉS DIEGO DEL NOVAL 108,Parada,43.4821203689433,-3.80267337271866,,,,,Europe/Madrid
|
| 145 |
+
236,PA246,INÉS DIEGO DEL NOVAL 152,Parada,43.4808946645051,-3.80542969729909,,,,,Europe/Madrid
|
| 146 |
+
237,PA247,HERMANOS TONETTI 8,Parada,43.4828555112216,-3.80637340814649,,,,,Europe/Madrid
|
| 147 |
+
238,PA248,HERMANOS TONETTI 16,Parada,43.4850837454485,-3.8071174393841,,,,,Europe/Madrid
|
| 148 |
+
239,PA249,CALLE ARRIBA 85,Parada,43.4789257492991,-3.80939695722868,,,,,Europe/Madrid
|
| 149 |
+
25,PA25,DOCTOR FLEMING,Parada,43.4756430358505,-3.78820689598774,,,,,Europe/Madrid
|
| 150 |
+
240,PA250,CALLE ARRIBA 31,Parada,43.478070806657,-3.81221260798722,,,,,Europe/Madrid
|
| 151 |
+
241,PA251,RESIDENCIA MAYORES DE CUETO,Parada,43.476856643692,-3.8120462496972,,,,,Europe/Madrid
|
| 152 |
+
242,PA252,CRUCE DE POLIO,Parada,43.4750953688282,-3.8105081478848,,,,,Europe/Madrid
|
| 153 |
+
244,PA254,INÉS DIEGO DEL NOVAL 61,Parada,43.4808973644216,-3.80518698629473,,,,,Europe/Madrid
|
| 154 |
+
245,PA255,INÉS DIEGO DEL NOVAL 55,Parada,43.4820632064633,-3.80263279825868,,,,,Europe/Madrid
|
| 155 |
+
246,PA256,INÉS DIEGO DEL NOVAL 25,Parada,43.484130977366,-3.79869918711736,,,,,Europe/Madrid
|
| 156 |
+
26,PA26,LOS CASTROS 95,Parada,43.4690556461798,-3.8129867136293,,,,,Europe/Madrid
|
| 157 |
+
248,PA261,CASIMIRO SAINZ 6,Parada,43.4641854337078,-3.7966350946193,,,,,Europe/Madrid
|
| 158 |
+
249,PA262,ESCOLAPIOS,Parada,43.4644887369753,-3.79485444163084,,,,,Europe/Madrid
|
| 159 |
+
250,PA263,CANALEJAS 26,Parada,43.4648568628653,-3.79125400966089,,,,,Europe/Madrid
|
| 160 |
+
251,PA264,CANALEJAS 42,Parada,43.4650664069891,-3.78965642343758,,,,,Europe/Madrid
|
| 161 |
+
252,PA265,PEREZ GALDÓS 4,Parada,43.4667426293496,-3.7863863798326,,,,,Europe/Madrid
|
| 162 |
+
253,PA266,PEREZ GALDÓS 18,Parada,43.4664414104567,-3.78392737275812,,,,,Europe/Madrid
|
| 163 |
+
254,PA267,PEREZ GALDÓS 36,Parada,43.4673104323219,-3.78005416302407,,,,,Europe/Madrid
|
| 164 |
+
255,PA268,MERCADO MEJICO,Parada,43.4581230263184,-3.82521876975588,,,,,Europe/Madrid
|
| 165 |
+
256,PA269,CALLE ALTA 109,Parada,43.45839110343,-3.82175040089812,,,,,Europe/Madrid
|
| 166 |
+
257,PA270,CALLE ALTA 81,Parada,43.4588215638876,-3.81779667398707,,,,,Europe/Madrid
|
| 167 |
+
258,PA271,CALLE ALTA 45,Parada,43.4596593548805,-3.81524542469063,,,,,Europe/Madrid
|
| 168 |
+
259,PA272,MONTE CALOCA,Parada,43.4608342730004,-3.81235843529524,,,,,Europe/Madrid
|
| 169 |
+
260,PA273,ESTACIONES,Parada,43.4593350508244,-3.81063857613249,,,,,Europe/Madrid
|
| 170 |
+
261,PA274,AVENIDA EL FARO 20,Parada,43.4812610782138,-3.78896455104923,,,,,Europe/Madrid
|
| 171 |
+
265,PA280,MUTUA MONTAÑESA,Parada,43.4817699900265,-3.78850918008642,,,,,Europe/Madrid
|
| 172 |
+
266,PA281,PASEO DE ALTAMIRA 15,Parada,43.468463995661,-3.79163272509664,,,,,Europe/Madrid
|
| 173 |
+
29,PA29,PLAZA DOCTOR FLEMING,Parada,43.4757194052145,-3.78891266137255,,,,,Europe/Madrid
|
| 174 |
+
268,PA290,AVENIDA CANTABRIA 28,Parada,43.4779835779547,-3.7965817329675,,,,,Europe/Madrid
|
| 175 |
+
270,PA293,AVENIDA PARAYAS 14,Parada,43.4492551181044,-3.83090134980203,,,,,Europe/Madrid
|
| 176 |
+
271,PA294,AVENIDA PARAYAS 28,Parada,43.4469699003364,-3.83259229565344,,,,,Europe/Madrid
|
| 177 |
+
272,PA295,AVENIDA PARAYAS 32,Parada,43.4449163393099,-3.83409919683719,,,,,Europe/Madrid
|
| 178 |
+
273,PA296,ABILIO GARCIA BARON 1,Parada,43.4428706674346,-3.83752925302995,,,,,Europe/Madrid
|
| 179 |
+
274,PA297,AUTONOMIA 8,Parada,43.4814493565375,-3.79541216296859,,,,,Europe/Madrid
|
| 180 |
+
275,PA298,FRANCISCO TOMAS Y VALIENTE 7,Parada,43.4394001454439,-3.84098609006235,,,,,Europe/Madrid
|
| 181 |
+
276,PA299,BARTOLOMÉ DARNIS,Parada,43.4400181110233,-3.84272801196556,,,,,Europe/Madrid
|
| 182 |
+
3,PA3,JOSE MARIA COSSIO 54,Parada,43.4589971199811,-3.84849236072325,,,,,Europe/Madrid
|
| 183 |
+
30,PA30,PIQUIO,Parada,43.4736352493327,-3.78459742536888,,,,,Europe/Madrid
|
| 184 |
+
277,PA300,COLEGIO NUEVA MONTAÑA,Parada,43.4393427548805,-3.84475785406979,,,,,Europe/Madrid
|
| 185 |
+
278,PA301,SANTIAGO EL MAYOR 10,Parada,43.4397253940847,-3.84727590566982,,,,,Europe/Madrid
|
| 186 |
+
279,PA302,SAN MARTÍN DEL PINO 24,Parada,43.4445708027919,-3.85205100694995,,,,,Europe/Madrid
|
| 187 |
+
280,PA303,SAN MARTÍN DEL PINO 23,Parada,43.4456647117685,-3.84832589998866,,,,,Europe/Madrid
|
| 188 |
+
281,PA304,INSTITUTOS,Parada,43.4468305849545,-3.85142223753078,,,,,Europe/Madrid
|
| 189 |
+
282,PA305,CARREFOUR PEÑACASTILLO,Parada,43.4452004003529,-3.85354853804297,,,,,Europe/Madrid
|
| 190 |
+
283,PA306,FRANCISCO RIVAS MORENO,Parada,43.4468083944068,-3.85104054199511,,,,,Europe/Madrid
|
| 191 |
+
284,PA307,NUEVO PARQUE,Parada,43.4458709785244,-3.8483774744796,,,,,Europe/Madrid
|
| 192 |
+
285,PA308,SAN MARTÍN DEL PINO 13,Parada,43.4437590986191,-3.85249069018376,,,,,Europe/Madrid
|
| 193 |
+
286,PA309,SANTIAGO EL MAYOR,Parada,43.439806514729,-3.8476670403417,,,,,Europe/Madrid
|
| 194 |
+
31,PA31,PLAZA DE ITALIA,Parada,43.4718887035753,-3.78200021029465,,,,,Europe/Madrid
|
| 195 |
+
287,PA310,FRANCISCO TOMÁS Y VALIENTE 23,Parada,43.439095278854,-3.84422514984006,,,,,Europe/Madrid
|
| 196 |
+
288,PA311,FRANCISCO TOMÁS Y VALIENTE 11,Parada,43.4392458250639,-3.84223326928483,,,,,Europe/Madrid
|
| 197 |
+
289,PA312,HERMANOS TONETTI 6,Parada,43.4827260399488,-3.80647408770083,,,,,Europe/Madrid
|
| 198 |
+
32,PA32,REINA VICTORIA 129,Parada,43.4709936475781,-3.77866236409666,,,,,Europe/Madrid
|
| 199 |
+
292,PA321,MARQUES DE LA HERMIDA 15,Parada,43.4548331609319,-3.81547606679072,,,,,Europe/Madrid
|
| 200 |
+
295,PA324,PLAZA DE SAN MARTIN,Parada,43.4637532111919,-3.78780516823318,,,,,Europe/Madrid
|
| 201 |
+
296,PA326,JOSE MARIA GONZALEZ TREVILLA 4,Parada,43.481871974146,-3.79617264526541,,,,,Europe/Madrid
|
| 202 |
+
298,PA328,ARSENIO ODRIOZOLA 16,Parada,43.483701326269,-3.79289597102978,,,,,Europe/Madrid
|
| 203 |
+
33,PA33,LA MAGDALENA,Parada,43.4690235105157,-3.77701911622299,,,,,Europe/Madrid
|
| 204 |
+
299,PA330,DOCTOR DIEGO MADRAZO,Parada,43.4825944704407,-3.79591760373235,,,,,Europe/Madrid
|
| 205 |
+
300,PA331,CORBAN,Parada,43.4648061891114,-3.86742808576968,,,,,Europe/Madrid
|
| 206 |
+
301,PA332,CRUCE CON RUCANDIAL,Parada,43.4628644592016,-3.86597969104836,,,,,Europe/Madrid
|
| 207 |
+
302,PA333,CONSUELO BERGÉS 22,Parada,43.4785422440693,-3.80141450437114,,,,,Europe/Madrid
|
| 208 |
+
303,PA334,JULIO JAURENA ,Parada,43.4629858608501,-3.86592900988451,,,,,Europe/Madrid
|
| 209 |
+
304,PA335,AUTONOMIA 9,Parada,43.4825958600313,-3.79209727138814,,,,,Europe/Madrid
|
| 210 |
+
305,PA336,AUTONOMIA,Parada,43.4825520302991,-3.79264253867287,,,,,Europe/Madrid
|
| 211 |
+
306,PA337,JOSE MARIA GONZALEZ TREVILLA,Parada,43.4838186253368,-3.79679857207913,,,,,Europe/Madrid
|
| 212 |
+
307,PA338,GLORIETA DE ADARZO,Parada,43.4545356646387,-3.86554894351014,,,,,Europe/Madrid
|
| 213 |
+
308,PA339,BARRIO LA TORRE 123,Parada,43.4699971631456,-3.82427795435541,,,,,Europe/Madrid
|
| 214 |
+
34,PA34,GONZALEZ DE RIANCHO,Parada,43.4659393691515,-3.78028442332358,,,,,Europe/Madrid
|
| 215 |
+
309,PA340,BARRIO LA TORRE 76,Parada,43.4698401969132,-3.82475416679008,,,,,Europe/Madrid
|
| 216 |
+
310,PA341,DECATHLON,Parada,43.455250403206,-3.86404248638163,,,,,Europe/Madrid
|
| 217 |
+
311,PA342,CARREFOUR ALISAL,Parada,43.4562769744994,-3.8613263575244,,,,,Europe/Madrid
|
| 218 |
+
312,PA343,JOAQUIN RODRIGO,Parada,43.4562109963297,-3.86113196767049,,,,,Europe/Madrid
|
| 219 |
+
313,PA345,CUESTA LA ATALAYA 32,Parada,43.4654420929829,-3.80766184020256,,,,,Europe/Madrid
|
| 220 |
+
314,PA346,MARIA CRISTINA,Parada,43.4662176026056,-3.80853644011791,,,,,Europe/Madrid
|
| 221 |
+
315,PA347,VIA CORNELIA,Parada,43.4663555156704,-3.81299073912499,,,,,Europe/Madrid
|
| 222 |
+
316,PA348,JUAN 23 NUMERO 2,Parada,43.4660025858023,-3.81198165891434,,,,,Europe/Madrid
|
| 223 |
+
35,PA35,REINA VICTORIA 79,Parada,43.4643871707779,-3.78374694236764,,,,,Europe/Madrid
|
| 224 |
+
318,PA350,CALLE EL MONTE 30,Parada,43.4647453372469,-3.81656702685798,,,,,Europe/Madrid
|
| 225 |
+
319,PA351,FRANCISCO PALAZUELOS 13,Parada,43.4661862753964,-3.79935691278352,,,,,Europe/Madrid
|
| 226 |
+
320,PA352,CAMARREAL 135,Parada,43.4444367277555,-3.8752822022717,,,,,Europe/Madrid
|
| 227 |
+
321,PA353,CAMARREAL 136,Parada,43.4442654651638,-3.87562329791023,,,,,Europe/Madrid
|
| 228 |
+
322,PA354,FRANCISCO CACERES,Parada,43.477861272355,-3.79717997070447,,,,,Europe/Madrid
|
| 229 |
+
323,PA355,OJAIZ,Parada,43.4417726780897,-3.88047216501227,,,,,Europe/Madrid
|
| 230 |
+
324,PA356,PLAZA DE LAS ESTACIONES,Parada,43.4591675760181,-3.81090321376792,,,,,Europe/Madrid
|
| 231 |
+
327,PA359,PRIMERO DE MAYO 34,Parada,43.4434358462366,-3.85727049009976,,,,,Europe/Madrid
|
| 232 |
+
36,PA36,SAN MARTIN,Parada,43.4635786630664,-3.78825585603695,,,,,Europe/Madrid
|
| 233 |
+
329,PA361,RICARDO LOPEZ ARANDA 22,Parada,43.4410256857036,-3.86279867455001,,,,,Europe/Madrid
|
| 234 |
+
330,PA362,PRIMERO DE MAYO 9,Parada,43.4434422791554,-3.85695284839424,,,,,Europe/Madrid
|
| 235 |
+
332,PA364,PEDRO SAN MARTIN 12,Parada,43.4605170909995,-3.82926399210909,,,,,Europe/Madrid
|
| 236 |
+
333,PA365,CALERUCO,Parada,43.462094425948,-3.8305051649941,,,,,Europe/Madrid
|
| 237 |
+
335,PA367,GRUPO ATECA,Parada,43.4697654062749,-3.8266346330138,,,,,Europe/Madrid
|
| 238 |
+
336,PA368,IGLESIA,Parada,43.4716736099347,-3.83046992930636,,,,,Europe/Madrid
|
| 239 |
+
337,PA369,SAN PEDRO DEL MAR 64,Parada,43.4735647658029,-3.83112390520699,,,,,Europe/Madrid
|
| 240 |
+
37,PA37,CASTELAR 29,Parada,43.4629523625136,-3.79305914045617,,,,,Europe/Madrid
|
| 241 |
+
338,PA370,CORBANERA 95,Parada,43.4764066075481,-3.83160461715894,,,,,Europe/Madrid
|
| 242 |
+
339,PA371,EL CASTILLO,Parada,43.477406477731,-3.83465839437778,,,,,Europe/Madrid
|
| 243 |
+
340,PA372,CORBANERA 162,Parada,43.4766034771749,-3.83604841166022,,,,,Europe/Madrid
|
| 244 |
+
341,PA373,EL PARQUE,Parada,43.473335289669,-3.83320883186837,,,,,Europe/Madrid
|
| 245 |
+
342,PA374,SAN PEDRO DEL MAR 91,Parada,43.4735245497666,-3.83136706042645,,,,,Europe/Madrid
|
| 246 |
+
343,PA375,BARRIO BOLADO 23,Parada,43.4734575677743,-3.8287088169145,,,,,Europe/Madrid
|
| 247 |
+
344,PA376,BARRIO BOLADO 37,Parada,43.4739377046032,-3.82673967501052,,,,,Europe/Madrid
|
| 248 |
+
345,PA377,BARRIO BOLADO 58,Parada,43.4745673217105,-3.82546457119698,,,,,Europe/Madrid
|
| 249 |
+
346,PA378,CRUCE AVICHE,Parada,43.4755034754695,-3.82214329576031,,,,,Europe/Madrid
|
| 250 |
+
347,PA379,AVICHE 37,Parada,43.47282372707,-3.82307065328326,,,,,Europe/Madrid
|
| 251 |
+
38,PA38,CASTELAR 1,Parada,43.4631695135272,-3.79625114489291,,,,,Europe/Madrid
|
| 252 |
+
348,PA380,CANTEROS DE TRASMIERA 2,Parada,43.471165809806,-3.82504740500348,,,,,Europe/Madrid
|
| 253 |
+
349,PA381,CALLE REPUENTE,Parada,43.4679107059954,-3.83038308652781,,,,,Europe/Madrid
|
| 254 |
+
350,PA382,CALERUCO 3,Parada,43.4623709961287,-3.83082207310368,,,,,Europe/Madrid
|
| 255 |
+
351,PA383,CORBANERA 53,Parada,43.4783137279772,-3.82950047716986,,,,,Europe/Madrid
|
| 256 |
+
352,PA384,CORBANERA,Parada,43.479492303054,-3.82701628444093,,,,,Europe/Madrid
|
| 257 |
+
353,PA385,CORBANERA 57,Parada,43.478437110576,-3.82941029332153,,,,,Europe/Madrid
|
| 258 |
+
354,PA386,CORBANERA 93,Parada,43.4763120050059,-3.83178583282105,,,,,Europe/Madrid
|
| 259 |
+
355,PA387,SAN PEDRO DEL MAR 51,Parada,43.4713845481523,-3.83031080862172,,,,,Europe/Madrid
|
| 260 |
+
356,PA388,VIRGEN DEL MAR 18,Parada,43.4683072184823,-3.8702605865064,,,,,Europe/Madrid
|
| 261 |
+
357,PA389,VIRGEN DEL MAR 37,Parada,43.4691711239441,-3.87058972428273,,,,,Europe/Madrid
|
| 262 |
+
39,PA39,PASEO DE PEREDA,Parada,43.4620618834338,-3.80162765789247,,,,,Europe/Madrid
|
| 263 |
+
358,PA390,LA ALBERICIA 18,Parada,43.4619663482906,-3.83549489880529,,,,,Europe/Madrid
|
| 264 |
+
360,PA393,AVENIDA DEL DEPORTE 2,Parada,43.4620263487846,-3.84447257017362,,,,,Europe/Madrid
|
| 265 |
+
361,PA394,INSTITUTO ALBERICIA,Parada,43.4611095835658,-3.84878370701347,,,,,Europe/Madrid
|
| 266 |
+
362,PA395,CASA DEL DEPORTE,Parada,43.4606634368548,-3.8509015662423,,,,,Europe/Madrid
|
| 267 |
+
363,PA396,CORCEÑO,Parada,43.4626014783748,-3.85690491208748,,,,,Europe/Madrid
|
| 268 |
+
364,PA397,BARRIO EL SOMO,Parada,43.4656044028786,-3.85401659911379,,,,,Europe/Madrid
|
| 269 |
+
365,PA398,AMBULATORIO,Parada,43.4661101291899,-3.85659863480881,,,,,Europe/Madrid
|
| 270 |
+
366,PA399,BARRIO EL SOMO 82,Parada,43.4665377009843,-3.85906587107027,,,,,Europe/Madrid
|
| 271 |
+
4,PA4,JOSE MARIA COSSIO 33,Parada,43.4594039079168,-3.84621045531486,,,,,Europe/Madrid
|
| 272 |
+
40,PA40,CORREOS,Parada,43.461586284304,-3.8070180554901,,,,,Europe/Madrid
|
| 273 |
+
367,PA400,BARRIO EL SOMO 118,Parada,43.4659477306817,-3.86386786040264,,,,,Europe/Madrid
|
| 274 |
+
368,PA401,CORBAN 2,Parada,43.4658147043635,-3.86766453612096,,,,,Europe/Madrid
|
| 275 |
+
369,PA402,CIRIEGO,Parada,43.4717418305777,-3.8697485132745,,,,,Europe/Madrid
|
| 276 |
+
370,PA403,CORBAN,Parada,43.4651662033829,-3.86768740207848,,,,,Europe/Madrid
|
| 277 |
+
371,PA404,EL MAZO 30,Parada,43.4679707941334,-3.86551981516366,,,,,Europe/Madrid
|
| 278 |
+
372,PA405,EL MAZO 2,Parada,43.4692314976257,-3.85997387982827,,,,,Europe/Madrid
|
| 279 |
+
373,PA406,BARRIO EL SOMO 55,Parada,43.4664769308924,-3.85913658950547,,,,,Europe/Madrid
|
| 280 |
+
374,PA407,BARRIO EL SOMO 37,Parada,43.4660687872555,-3.85666035776502,,,,,Europe/Madrid
|
| 281 |
+
375,PA408,EL SOMO,Parada,43.4655283284939,-3.85390649220254,,,,,Europe/Madrid
|
| 282 |
+
376,PA409,CORCEÑO 69,Parada,43.4622106975426,-3.85707110832907,,,,,Europe/Madrid
|
| 283 |
+
41,PA41,PLAZA AYUNTAMIENTO,Parada,43.461676344705,-3.81017720129389,,,,,Europe/Madrid
|
| 284 |
+
377,PA410,AVENIDA DEL DEPORTE 9,Parada,43.4607778179876,-3.85013538058959,,,,,Europe/Madrid
|
| 285 |
+
378,PA411,AVENIDA DEL DEPORTE 3,Parada,43.4619169934569,-3.84464906753743,,,,,Europe/Madrid
|
| 286 |
+
379,PA412,LA CAVADUCA,Parada,43.462543661705,-3.8421984864839,,,,,Europe/Madrid
|
| 287 |
+
380,PA413,LA ALBERICIA 1,Parada,43.4618212447229,-3.83546397437353,,,,,Europe/Madrid
|
| 288 |
+
381,PA414,LA GLORIA 8,Parada,43.4630810515708,-3.83928217198746,,,,,Europe/Madrid
|
| 289 |
+
382,PA415,LA GLORIA 60,Parada,43.4643542452569,-3.84129975321356,,,,,Europe/Madrid
|
| 290 |
+
383,PA416,LA GLORIA 120,Parada,43.4655500824443,-3.84407034857136,,,,,Europe/Madrid
|
| 291 |
+
384,PA417,LA GLORIA 144,Parada,43.4654106599076,-3.84709725163737,,,,,Europe/Madrid
|
| 292 |
+
385,PA418,LA GLORIA 234,Parada,43.4654611121416,-3.8499836334989,,,,,Europe/Madrid
|
| 293 |
+
42,PA42,JESUS DE MONASTERIO 12,Parada,43.4618409362494,-3.81282101148866,,,,,Europe/Madrid
|
| 294 |
+
386,PA420,LA GLORIA 179,Parada,43.4654139429291,-3.85108460378396,,,,,Europe/Madrid
|
| 295 |
+
387,PA421,LA GLORIA 157,Parada,43.4653296295831,-3.84709612008427,,,,,Europe/Madrid
|
| 296 |
+
388,PA422,LA GLORIA 123,Parada,43.4654687783052,-3.84410630424926,,,,,Europe/Madrid
|
| 297 |
+
389,PA423,LA GLORIA 43,Parada,43.4638673779052,-3.8408205252234,,,,,Europe/Madrid
|
| 298 |
+
390,PA424,LA GLORIA 1,Parada,43.4630739670259,-3.83949902438931,,,,,Europe/Madrid
|
| 299 |
+
394,PA428,INSTITUTOS PEÑACASTILLO,Parada,43.4465876612644,-3.85242358740531,,,,,Europe/Madrid
|
| 300 |
+
395,PA429,CALLE ALTA 46,Parada,43.4596932782695,-3.81546456183005,,,,,Europe/Madrid
|
| 301 |
+
43,PA43,SAN FERNANDO 22,Parada,43.4604212625301,-3.81986739529346,,,,,Europe/Madrid
|
| 302 |
+
396,PA430,CALLE ALTA 56,Parada,43.4590074277368,-3.81751758270069,,,,,Europe/Madrid
|
| 303 |
+
397,PA431,CALLE ALTA 80,Parada,43.4584920717696,-3.82167105156775,,,,,Europe/Madrid
|
| 304 |
+
398,PA432,CALLE ARGENTINA 7,Parada,43.4572892645479,-3.82355996322701,,,,,Europe/Madrid
|
| 305 |
+
399,PA433,PLAZA DE TOROS,Parada,43.4567534235799,-3.82669235968599,,,,,Europe/Madrid
|
| 306 |
+
400,PA434,CALLE ALTA 28,Parada,43.460311331633,-3.81240249785165,,,,,Europe/Madrid
|
| 307 |
+
402,PA436,PEDRO SAN MARTIN 8,Parada,43.4588514581089,-3.82799251980776,,,,,Europe/Madrid
|
| 308 |
+
403,PA437,EMILIO DIAZ CANEJA 2,Parada,43.4608502825807,-3.83293143783027,,,,,Europe/Madrid
|
| 309 |
+
404,PA438,CUESTA LA ATALAYA 2,Parada,43.4639046627338,-3.80780061859014,,,,,Europe/Madrid
|
| 310 |
+
44,PA44,SAN FERNANDO 66,Parada,43.4592421279873,-3.82426744137884,,,,,Europe/Madrid
|
| 311 |
+
406,PA440,AVENIDA VICENTE TRUEBA 8,Parada,43.4558475256162,-3.85622346273512,,,,,Europe/Madrid
|
| 312 |
+
407,PA441,EMILIO DIAZ CANEJA,Parada,43.460868133141,-3.83323861642279,,,,,Europe/Madrid
|
| 313 |
+
408,PA442,PASEO DE ALTAMIRA 208,Parada,43.467652445223,-3.81271027786179,,,,,Europe/Madrid
|
| 314 |
+
409,PA443,EL MAZO,Parada,43.4666841504528,-3.86817815177243,,,,,Europe/Madrid
|
| 315 |
+
410,PA444,PASEO DE PEREDA 35,Parada,43.4624092988485,-3.79809399093298,,,,,Europe/Madrid
|
| 316 |
+
411,PA445,CEMENTERIO DE LLUJA,Parada,43.4506305958069,-3.87271290141561,,,,,Europe/Madrid
|
| 317 |
+
412,PA446,OJAIZ 89,Parada,43.4407974733323,-3.88554429982084,,,,,Europe/Madrid
|
| 318 |
+
413,PA447,OJAIZ 166,Parada,43.4408128886208,-3.88578612232193,,,,,Europe/Madrid
|
| 319 |
+
414,PA448,VALDECILLA SUR,Parada,43.4547201945284,-3.82769058964239,,,,,Europe/Madrid
|
| 320 |
+
415,PA449,VALDECILLA SUR,Parada,43.4546510006132,-3.82748347885879,,,,,Europe/Madrid
|
| 321 |
+
45,PA45,AVENIDA DE VALDECILLA,Parada,43.4575563357455,-3.83041565699705,,,,,Europe/Madrid
|
| 322 |
+
420,PA454,PCTCAN 1,Parada,43.4527992548983,-3.87004191278329,,,,,Europe/Madrid
|
| 323 |
+
421,PA455,JOAQUIN RODRIGO 10,Parada,43.4546095856968,-3.86575171499463,,,,,Europe/Madrid
|
| 324 |
+
422,PA456,CALLE CERVANTES 29,Parada,43.4638600403187,-3.81115675576739,,,,,Europe/Madrid
|
| 325 |
+
423,PA457,CALLE DEL MONTE 12,Parada,43.463922606637,-3.81426497264765,,,,,Europe/Madrid
|
| 326 |
+
425,PA459,VERIDIANO ROJO,Parada,43.4778276729954,-3.81022004304701,,,,,Europe/Madrid
|
| 327 |
+
46,PA46,TORRES QUEVEDO 22,Parada,43.4573284352537,-3.83720105898256,,,,,Europe/Madrid
|
| 328 |
+
427,PA461,MENENDEZ PELAYO 25,Parada,43.4658259655421,-3.79808500484664,,,,,Europe/Madrid
|
| 329 |
+
428,PA462,JORGE SEPULVEDA 2,Parada,43.4744784946856,-3.81127998359722,,,,,Europe/Madrid
|
| 330 |
+
429,PA463,CALLE LA PEREDA 14,Parada,43.4780858614423,-3.8046645991396,,,,,Europe/Madrid
|
| 331 |
+
430,PA464,CALLE LA PEREDA,Parada,43.4780475234356,-3.80448257354547,,,,,Europe/Madrid
|
| 332 |
+
431,PA465,AVENIDA DEL DEPORTE 9,Parada,43.4610186418197,-3.8488441182331,,,,,Europe/Madrid
|
| 333 |
+
432,PA466,BARRIO LA SIERRA,Parada,43.4609242100335,-3.85865737041405,,,,,Europe/Madrid
|
| 334 |
+
433,PA467,VICENTE TRUEBA 19,Parada,43.4600710070074,-3.85682444881087,,,,,Europe/Madrid
|
| 335 |
+
434,PA468,VICENTE TRUEBA,Parada,43.459986994748,-3.85666849995585,,,,,Europe/Madrid
|
| 336 |
+
435,PA469,BARRIO LA SIERRA 10,Parada,43.4613344431689,-3.85796324425597,,,,,Europe/Madrid
|
| 337 |
+
47,PA47,PLAZA MANUEL LLANO,Parada,43.4582239387637,-3.84071370115077,,,,,Europe/Madrid
|
| 338 |
+
436,PA470,PASEO DE ALTAMIRA 77,Parada,43.467533179491,-3.8119300608435,,,,,Europe/Madrid
|
| 339 |
+
437,PA471,CALLE ARRIBA 64,Parada,43.4790204226531,-3.80818096318293,,,,,Europe/Madrid
|
| 340 |
+
439,PA473,RICARDO LEON,Parada,43.4557152673055,-3.84884130859439,,,,,Europe/Madrid
|
| 341 |
+
443,PA477,JOSE ORTEGA Y GASSET 2,Parada,43.4462019313202,-3.86296969164083,,,,,Europe/Madrid
|
| 342 |
+
444,PA478,CAMARREAL,Parada,43.4471914170464,-3.86634191402969,,,,,Europe/Madrid
|
| 343 |
+
445,PA479,MARQUES DE HAZAS 5,Parada,43.4767660359588,-3.80712441511871,,,,,Europe/Madrid
|
| 344 |
+
48,PA48,JOSE MARIA COSSIO 12,Parada,43.4598511204018,-3.84150152934565,,,,,Europe/Madrid
|
| 345 |
+
446,PA480,MARQUES DE HAZAS,Parada,43.4768960647198,-3.80711069007873,,,,,Europe/Madrid
|
| 346 |
+
447,PA481,PLAZA DE MEJICO,Parada,43.457936768905,-3.82631142841492,,,,,Europe/Madrid
|
| 347 |
+
448,PA482,LUIS QUINTANILLA ISASI,Parada,43.4546966693559,-3.84986113410367,,,,,Europe/Madrid
|
| 348 |
+
449,PA483,GERARDO DIEGO 1,Parada,43.4557216939061,-3.84560056484003,,,,,Europe/Madrid
|
| 349 |
+
452,PA486,PCTCAN 2,Parada,43.4529102297186,-3.87144353206348,,,,,Europe/Madrid
|
| 350 |
+
453,PA487,PCTCAN 3,Parada,43.4503880060865,-3.87446954423473,,,,,Europe/Madrid
|
| 351 |
+
454,PA488,PCTCAN-UNEATLANTICO,Parada,43.451337425151,-3.8768700067606,,,,,Europe/Madrid
|
| 352 |
+
455,PA489,ALBERT EINSTEIN 14,Parada,43.4530227581068,-3.87003980235665,,,,,Europe/Madrid
|
| 353 |
+
49,PA49,JOSE MARIA COSSIO 24,Parada,43.4598340579658,-3.8435263392934,,,,,Europe/Madrid
|
| 354 |
+
456,PA490,CALLE ADARZO 48,Parada,43.4532986472827,-3.86303956647519,,,,,Europe/Madrid
|
| 355 |
+
457,PA491,CALLE ADARZO 117,Parada,43.4532805807024,-3.86288498448966,,,,,Europe/Madrid
|
| 356 |
+
458,PA492,CENTRO DE SALUD NUEVA MONTAÑA,Parada,43.4398902843286,-3.8554051774051,,,,,Europe/Madrid
|
| 357 |
+
459,PA493,GERTRUDIS GOMEZ DE AVELLANEDA,Parada,43.4386652911293,-3.85355042737659,,,,,Europe/Madrid
|
| 358 |
+
460,PA494,EUSEBIO SANTAMARIA 1,Parada,43.4412335639345,-3.85132058303889,,,,,Europe/Madrid
|
| 359 |
+
461,PA495,EUSEBIO SANTAMARIA 2,Parada,43.4412772511232,-3.85152399301006,,,,,Europe/Madrid
|
| 360 |
+
462,PA496,EUSEBIO SANTAMARIA,Parada,43.4388248168292,-3.85324212857421,,,,,Europe/Madrid
|
| 361 |
+
463,PA497,CARMEN BRAVO VILLASANTE,Parada,43.4396761585398,-3.85536941435556,,,,,Europe/Madrid
|
| 362 |
+
464,PA498,CARMEN BRAVO VILLASANTE 1,Parada,43.4425574491711,-3.8533336995566,,,,,Europe/Madrid
|
| 363 |
+
465,PA499,CAMARREAL 45,Parada,43.4471534447214,-3.86651674057648,,,,,Europe/Madrid
|
| 364 |
+
5,PA5,JOSE MARIA COSSIO 17,Parada,43.4596836342094,-3.84310483395947,,,,,Europe/Madrid
|
| 365 |
+
50,PA50,JOSE MARIA COSSIO 44,Parada,43.4594752137699,-3.84663852625277,,,,,Europe/Madrid
|
| 366 |
+
466,PA500,ORTEGA Y GASSET 28,Parada,43.4438028762276,-3.85718583107456,,,,,Europe/Madrid
|
| 367 |
+
467,PA502,BARRIO CAMINO 27,Parada,43.4664401514431,-3.7883590187461,,,,,Europe/Madrid
|
| 368 |
+
468,PA503,CENTRO DE SALUD,Parada,43.4663203052662,-3.79058631172042,,,,,Europe/Madrid
|
| 369 |
+
469,PA504,TETUAN 41,Parada,43.4660219475815,-3.79356555793385,,,,,Europe/Madrid
|
| 370 |
+
470,PA505,AVENIDA CANTABRIA 35,Parada,43.4759532537233,-3.80622222893083,,,,,Europe/Madrid
|
| 371 |
+
471,PA506,AVENIDA CANTABRIA 76,Parada,43.4759360310165,-3.80663424389743,,,,,Europe/Madrid
|
| 372 |
+
607,PA508,CALLE RUCANDIAL,Parada,43.454691161505,-3.86660797761512,,,,,Europe/Madrid
|
| 373 |
+
622,PA509,INTERCAMBIADOR DE VALDECILLA,Parada,43.4567167237491,-3.83137413617495,,,,,Europe/Madrid
|
| 374 |
+
51,PA51,JOSE MARIA COSSIO 52,Parada,43.4591619177878,-3.84858687875844,,,,,Europe/Madrid
|
| 375 |
+
623,PA510,JESÚS DE MONASTERIO 7,Parada,43.4616528357724,-3.81075121789887,,,,,Europe/Madrid
|
| 376 |
+
624,PA511,INTERCAMBIADOR DEL SARDINERO 1,Parada,43.4775020790382,-3.79085673855073,,,,,Europe/Madrid
|
| 377 |
+
625,PA512,INTERCAMBIADOR AVENIDA VALDECILLA,Parada,43.4568340994357,-3.83165336292031,,,,,Europe/Madrid
|
| 378 |
+
626,PA513,ESTACION DE AUTOBUSES 1,Parada,43.4596150793176,-3.81014389134529,,,,,Europe/Madrid
|
| 379 |
+
628,PA515,INTERCAMBIADOR DEL SARDINERO 2,Parada,43.4776577444221,-3.79095187603918,,,,,Europe/Madrid
|
| 380 |
+
629,PA516,INTERCAMBIADOR SARDINERO,Parada,43.477729285163,-3.79134984436265,,,,,Europe/Madrid
|
| 381 |
+
630,PA517,LOS AGUSTINOS,Parada,43.4775628908506,-3.7906697102616,,,,,Europe/Madrid
|
| 382 |
+
680,PA518,CANALEJAS 90,Parada,43.467740734326,-3.78749776759895,,,,,Europe/Madrid
|
| 383 |
+
681,PA519,CANALEJAS 93,Parada,43.4674317296456,-3.78742773876638,,,,,Europe/Madrid
|
| 384 |
+
52,PA52,LOS CIRUELOS 26,Parada,43.4584374654372,-3.85244506567506,,,,,Europe/Madrid
|
| 385 |
+
697,PA521,ESTACION,Parada,43.4588100795261,-3.81090549402236,,,,,Europe/Madrid
|
| 386 |
+
703,PA522,MIRANDA,Parada,43.4681888025525,-3.78879552016277,,,,,Europe/Madrid
|
| 387 |
+
704,PA523,MIRANDA,Parada,43.4687332287924,-3.78783702457795,,,,,Europe/Madrid
|
| 388 |
+
705,PA524,AVENIDA LOS INFANTES,Parada,43.46841880731,-3.78770374779538,,,,,Europe/Madrid
|
| 389 |
+
1021,PA525,CAMARREAL 40,Parada,43.447348842043,-3.86734881936896,,,,,Europe/Madrid
|
| 390 |
+
1022,PA526,CAMARREAL 51,Parada,43.4472039357739,-3.86745797622715,,,,,Europe/Madrid
|
| 391 |
+
1082,PA528,JULIO JAURENA 3,Parada,43.4573438255219,-3.85783097073306,,,,,Europe/Madrid
|
| 392 |
+
1083,PA529,ERNEST LLUCH 25,Parada,43.4729155192166,-3.81388770481565,,,,,Europe/Madrid
|
| 393 |
+
53,PA53,INSTITUTO ALISAL,Parada,43.4578006520384,-3.85572417030395,,,,,Europe/Madrid
|
| 394 |
+
1084,PA530,ERNEST LLUCH 17,Parada,43.4738693062942,-3.8104068323338,,,,,Europe/Madrid
|
| 395 |
+
1085,PA531,ERNEST LLUCH 9,Parada,43.4751160991395,-3.80624758578527,,,,,Europe/Madrid
|
| 396 |
+
1086,PA532,ERNEST LLUCH 3,Parada,43.4757722764766,-3.80368592827066,,,,,Europe/Madrid
|
| 397 |
+
1087,PA533,PRIMERO MAYO 64,Parada,43.4408314170724,-3.85974308912486,,,,,Europe/Madrid
|
| 398 |
+
1088,PA534,RICARDO LOPEZ ARANDA 23,Parada,43.4409313492861,-3.86275902617791,,,,,Europe/Madrid
|
| 399 |
+
1089,PA535,RICARDO LOPEZ ARANDA 17,Parada,43.4428265595752,-3.86069013994948,,,,,Europe/Madrid
|
| 400 |
+
1090,PA536,JOSE ORTEGA Y GASSET 32,Parada,43.4449743486346,-3.85974288868983,,,,,Europe/Madrid
|
| 401 |
+
1091,PA537,JOSE ORTEGA Y GASSET 39,Parada,43.4458527491669,-3.86250350205888,,,,,Europe/Madrid
|
| 402 |
+
1092,PA538,JOSE ORTEGA Y GASSET 21,Parada,43.4448639008703,-3.85984476200943,,,,,Europe/Madrid
|
| 403 |
+
1093,PA539,RICARDO LOPEZ ARANDA 18,Parada,43.4428687603334,-3.86079095941926,,,,,Europe/Madrid
|
| 404 |
+
54,PA54,BARRIO DE OJAIZ 7,Parada,43.4414597543452,-3.88173603114804,,,,,Europe/Madrid
|
| 405 |
+
1094,PA540,ERNEST LLUCH 2,Parada,43.4760319870018,-3.80287464911648,,,,,Europe/Madrid
|
| 406 |
+
1095,PA541,ERNEST LLUCH 10,Parada,43.4751750356589,-3.80633009753582,,,,,Europe/Madrid
|
| 407 |
+
1096,PA542,ERNEST LLUCH 18,Parada,43.4738339428105,-3.81069567841201,,,,,Europe/Madrid
|
| 408 |
+
1097,PA543,ERNEST LLUCH 26,Parada,43.47299145066,-3.813922106319,,,,,Europe/Madrid
|
| 409 |
+
1098,PA544,JULIO JAURENA 4,Parada,43.4574681640917,-3.85788044089065,,,,,Europe/Madrid
|
| 410 |
+
1113,PA545,JOSE MARIA GONZALEZ TREVILLA 14,Parada,43.483344837503,-3.79644584982088,,,,,Europe/Madrid
|
| 411 |
+
55,PA55,LOS CASTROS 76,Parada,43.4692541279202,-3.8126789239879,,,,,Europe/Madrid
|
| 412 |
+
1152,PA551,MARIA GUERRERO,Parada,43.4426417985347,-3.85508428813021,,,,,Europe/Madrid
|
| 413 |
+
2123,PA552,PIQUIO-PZA ITALIA,Parada,43.4735840958775,-3.78450163494693,,,,,Europe/Madrid
|
| 414 |
+
56,PA56,CAMARREAL 109,Parada,43.4463066400746,-3.87060899664979,,,,,Europe/Madrid
|
| 415 |
+
57,PA57,IGLESIA LA PEÑA,Parada,43.4478230465854,-3.86418178584658,,,,,Europe/Madrid
|
| 416 |
+
58,PA58,PEÑACASTILLO ESCUELAS,Parada,43.4475649145202,-3.86058523138619,,,,,Europe/Madrid
|
| 417 |
+
59,PA59,LOS LLANOS,Parada,43.4480318230699,-3.85798296254346,,,,,Europe/Madrid
|
| 418 |
+
6,PA6,JOSE MARIA COSSIO 1,Parada,43.4597071637227,-3.84088653041633,,,,,Europe/Madrid
|
| 419 |
+
60,PA60,EL EMPALME,Parada,43.4488092590174,-3.85344896511681,,,,,Europe/Madrid
|
| 420 |
+
1023,PA600,CAMARREAL 40 (Cocheras),Parada,43.4480891030846,-3.86709983819633,,,,,Europe/Madrid
|
| 421 |
+
61,PA61,CAMPOGIRO 23,Parada,43.4510329397632,-3.84918942439161,,,,,Europe/Madrid
|
| 422 |
+
62,PA62,CAMPOGIRO,Parada,43.4536539776165,-3.84617396507445,,,,,Europe/Madrid
|
| 423 |
+
63,PA63,CAMPOGIRO 5,Parada,43.4545529680249,-3.84246577375812,,,,,Europe/Madrid
|
| 424 |
+
64,PA64,CAJO 17,Parada,43.4541833616203,-3.83835269834877,,,,,Europe/Madrid
|
| 425 |
+
65,PA65,CAJO 5,Parada,43.4549304071569,-3.83384909059824,,,,,Europe/Madrid
|
| 426 |
+
66,PA66,PLAZA BRISAS,Parada,43.4738646764307,-3.7853099792147,,,,,Europe/Madrid
|
| 427 |
+
67,PA67,LOS CASTROS 20,Parada,43.4733741380285,-3.78803907794824,,,,,Europe/Madrid
|
| 428 |
+
68,PA68,LOS CASTROS 38,Parada,43.4726961366342,-3.79163228038217,,,,,Europe/Madrid
|
| 429 |
+
69,PA69,UIMP,Parada,43.4720195637191,-3.79571836097396,,,,,Europe/Madrid
|
| 430 |
+
7,PA7,MANUEL LLANO,Parada,43.4579542340806,-3.84117016926005,,,,,Europe/Madrid
|
| 431 |
+
70,PA70,ESCUELA DE CAMINOS,Parada,43.4712521263596,-3.79950189008281,,,,,Europe/Madrid
|
| 432 |
+
71,PA71,INTERFACULTATIVO,Parada,43.4704921227331,-3.80354109622813,,,,,Europe/Madrid
|
| 433 |
+
72,PA72,RECTORADO,Parada,43.4702037397336,-3.80582911104401,,,,,Europe/Madrid
|
| 434 |
+
73,PA73,LOS CASTROS 63,Parada,43.4703601042066,-3.80354330341222,,,,,Europe/Madrid
|
| 435 |
+
74,PA74,PARQUE LA TEJA,Parada,43.4712010216925,-3.79906984115093,,,,,Europe/Madrid
|
| 436 |
+
75,PA75,LOS CASTROS 53,Parada,43.4718671838563,-3.79561745250289,,,,,Europe/Madrid
|
| 437 |
+
76,PA76,CASIMIRO SAINZ 9,Parada,43.4642164612191,-3.79682266407217,,,,,Europe/Madrid
|
| 438 |
+
77,PA77,CALVO SOTELO 1,Parada,43.4614927538632,-3.80933576022168,,,,,Europe/Madrid
|
| 439 |
+
78,PA78,CAJO 2,Parada,43.4553595638565,-3.83289753604669,,,,,Europe/Madrid
|
| 440 |
+
79,PA79,CAJO 10,Parada,43.4546850231196,-3.83501241696265,,,,,Europe/Madrid
|
| 441 |
+
8,PA8,INSTITUTO TORRES QUEVEDO,Parada,43.4570117954802,-3.8364805039506,,,,,Europe/Madrid
|
| 442 |
+
80,PA80,PARQUE DOCTOR MORALES,Parada,43.4542903979677,-3.83840646010009,,,,,Europe/Madrid
|
| 443 |
+
81,PA81,LAS CALIFORNAS,Parada,43.4545546310208,-3.84297317938012,,,,,Europe/Madrid
|
| 444 |
+
82,PA82,CAMPOGIRO 90,Parada,43.4531283081487,-3.84702369002419,,,,,Europe/Madrid
|
| 445 |
+
83,PA83,ALTO DE LA PEÑA,Parada,43.4511947070305,-3.84923123801319,,,,,Europe/Madrid
|
| 446 |
+
84,PA84,EL EMPALME 6,Parada,43.449023828337,-3.85296797976471,,,,,Europe/Madrid
|
| 447 |
+
85,PA85,LOS LLANOS,Parada,43.4481216795872,-3.85799584997868,,,,,Europe/Madrid
|
| 448 |
+
86,PA86,ESCUELAS PEÑACASTILLO,Parada,43.4476401391254,-3.8606997507865,,,,,Europe/Madrid
|
| 449 |
+
87,PA87,CALLE LA PEÑA,Parada,43.4478805623503,-3.86376758563544,,,,,Europe/Madrid
|
| 450 |
+
88,PA88,CAMARREAL 68,Parada,43.4465008165512,-3.87026029455905,,,,,Europe/Madrid
|
| 451 |
+
89,PA89,DEPOSITO MUNICIPAL,Parada,43.441454158967,-3.87791097747283,,,,,Europe/Madrid
|
| 452 |
+
9,PA9,VALDECILLA,Parada,43.4573874930483,-3.83008121452806,,,,,Europe/Madrid
|
| 453 |
+
90,PA90,BARRIO LAS TEJERAS,Parada,43.4389535091726,-3.87833096279877,,,,,Europe/Madrid
|
| 454 |
+
92,PA92,JERONIMO SAINZ DE LA MAZA,Parada,43.4563261084019,-3.82709914298288,,,,,Europe/Madrid
|
| 455 |
+
93,PA93,CANDINA,Parada,43.4531465599797,-3.82832522356584,,,,,Europe/Madrid
|
| 456 |
+
94,PA94,LA LONJA,Parada,43.4532909066148,-3.82111332664809,,,,,Europe/Madrid
|
| 457 |
+
95,PA95,MARQUES DE LA HERMIDA 36,Parada,43.4540308940062,-3.81842261568624,,,,,Europe/Madrid
|
| 458 |
+
96,PA96,BARRIO PESQUERO,Parada,43.4521640063865,-3.81877146629328,,,,,Europe/Madrid
|
| 459 |
+
97,PA97,PARQUE VARADERO,Parada,43.4540444185371,-3.81811811145799,,,,,Europe/Madrid
|
| 460 |
+
98,PA98,MARQUES DE LA HERMIDA 1,Parada,43.4556979608165,-3.81231000356183,,,,,Europe/Madrid
|
data/gtfs-static/trips.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
hf_space_metadata.yml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: PulseTransit
|
| 3 |
+
emoji: 🚌
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
---
|
pulsetransit-worker/.editorconfig
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# http://editorconfig.org
|
| 2 |
+
root = true
|
| 3 |
+
|
| 4 |
+
[*]
|
| 5 |
+
indent_style = tab
|
| 6 |
+
end_of_line = lf
|
| 7 |
+
charset = utf-8
|
| 8 |
+
trim_trailing_whitespace = true
|
| 9 |
+
insert_final_newline = true
|
| 10 |
+
|
| 11 |
+
[*.yml]
|
| 12 |
+
indent_style = space
|
pulsetransit-worker/.gitignore
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
|
| 3 |
+
logs
|
| 4 |
+
_.log
|
| 5 |
+
npm-debug.log_
|
| 6 |
+
yarn-debug.log*
|
| 7 |
+
yarn-error.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
.pnpm-debug.log*
|
| 10 |
+
|
| 11 |
+
# Diagnostic reports (https://nodejs.org/api/report.html)
|
| 12 |
+
|
| 13 |
+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
| 14 |
+
|
| 15 |
+
# Runtime data
|
| 16 |
+
|
| 17 |
+
pids
|
| 18 |
+
_.pid
|
| 19 |
+
_.seed
|
| 20 |
+
\*.pid.lock
|
| 21 |
+
|
| 22 |
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
| 23 |
+
|
| 24 |
+
lib-cov
|
| 25 |
+
|
| 26 |
+
# Coverage directory used by tools like istanbul
|
| 27 |
+
|
| 28 |
+
coverage
|
| 29 |
+
\*.lcov
|
| 30 |
+
|
| 31 |
+
# nyc test coverage
|
| 32 |
+
|
| 33 |
+
.nyc_output
|
| 34 |
+
|
| 35 |
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
| 36 |
+
|
| 37 |
+
.grunt
|
| 38 |
+
|
| 39 |
+
# Bower dependency directory (https://bower.io/)
|
| 40 |
+
|
| 41 |
+
bower_components
|
| 42 |
+
|
| 43 |
+
# node-waf configuration
|
| 44 |
+
|
| 45 |
+
.lock-wscript
|
| 46 |
+
|
| 47 |
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
| 48 |
+
|
| 49 |
+
build/Release
|
| 50 |
+
|
| 51 |
+
# Dependency directories
|
| 52 |
+
|
| 53 |
+
node_modules/
|
| 54 |
+
jspm_packages/
|
| 55 |
+
|
| 56 |
+
# Snowpack dependency directory (https://snowpack.dev/)
|
| 57 |
+
|
| 58 |
+
web_modules/
|
| 59 |
+
|
| 60 |
+
# TypeScript cache
|
| 61 |
+
|
| 62 |
+
\*.tsbuildinfo
|
| 63 |
+
|
| 64 |
+
# Optional npm cache directory
|
| 65 |
+
|
| 66 |
+
.npm
|
| 67 |
+
|
| 68 |
+
# Optional eslint cache
|
| 69 |
+
|
| 70 |
+
.eslintcache
|
| 71 |
+
|
| 72 |
+
# Optional stylelint cache
|
| 73 |
+
|
| 74 |
+
.stylelintcache
|
| 75 |
+
|
| 76 |
+
# Microbundle cache
|
| 77 |
+
|
| 78 |
+
.rpt2_cache/
|
| 79 |
+
.rts2_cache_cjs/
|
| 80 |
+
.rts2_cache_es/
|
| 81 |
+
.rts2_cache_umd/
|
| 82 |
+
|
| 83 |
+
# Optional REPL history
|
| 84 |
+
|
| 85 |
+
.node_repl_history
|
| 86 |
+
|
| 87 |
+
# Output of 'npm pack'
|
| 88 |
+
|
| 89 |
+
\*.tgz
|
| 90 |
+
|
| 91 |
+
# Yarn Integrity file
|
| 92 |
+
|
| 93 |
+
.yarn-integrity
|
| 94 |
+
|
| 95 |
+
# parcel-bundler cache (https://parceljs.org/)
|
| 96 |
+
|
| 97 |
+
.cache
|
| 98 |
+
.parcel-cache
|
| 99 |
+
|
| 100 |
+
# Next.js build output
|
| 101 |
+
|
| 102 |
+
.next
|
| 103 |
+
out
|
| 104 |
+
|
| 105 |
+
# Nuxt.js build / generate output
|
| 106 |
+
|
| 107 |
+
.nuxt
|
| 108 |
+
dist
|
| 109 |
+
|
| 110 |
+
# Gatsby files
|
| 111 |
+
|
| 112 |
+
.cache/
|
| 113 |
+
|
| 114 |
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
| 115 |
+
|
| 116 |
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
| 117 |
+
|
| 118 |
+
# public
|
| 119 |
+
|
| 120 |
+
# vuepress build output
|
| 121 |
+
|
| 122 |
+
.vuepress/dist
|
| 123 |
+
|
| 124 |
+
# vuepress v2.x temp and cache directory
|
| 125 |
+
|
| 126 |
+
.temp
|
| 127 |
+
.cache
|
| 128 |
+
|
| 129 |
+
# Docusaurus cache and generated files
|
| 130 |
+
|
| 131 |
+
.docusaurus
|
| 132 |
+
|
| 133 |
+
# Serverless directories
|
| 134 |
+
|
| 135 |
+
.serverless/
|
| 136 |
+
|
| 137 |
+
# FuseBox cache
|
| 138 |
+
|
| 139 |
+
.fusebox/
|
| 140 |
+
|
| 141 |
+
# DynamoDB Local files
|
| 142 |
+
|
| 143 |
+
.dynamodb/
|
| 144 |
+
|
| 145 |
+
# TernJS port file
|
| 146 |
+
|
| 147 |
+
.tern-port
|
| 148 |
+
|
| 149 |
+
# Stores VSCode versions used for testing VSCode extensions
|
| 150 |
+
|
| 151 |
+
.vscode-test
|
| 152 |
+
|
| 153 |
+
# yarn v2
|
| 154 |
+
|
| 155 |
+
.yarn/cache
|
| 156 |
+
.yarn/unplugged
|
| 157 |
+
.yarn/build-state.yml
|
| 158 |
+
.yarn/install-state.gz
|
| 159 |
+
.pnp.\*
|
| 160 |
+
|
| 161 |
+
# wrangler project
|
| 162 |
+
|
| 163 |
+
.dev.vars*
|
| 164 |
+
!.dev.vars.example
|
| 165 |
+
.env*
|
| 166 |
+
!.env.example
|
| 167 |
+
.wrangler/
|
| 168 |
+
|
| 169 |
+
#vscode
|
| 170 |
+
.vscode
|
pulsetransit-worker/.prettierrc
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"printWidth": 140,
|
| 3 |
+
"singleQuote": true,
|
| 4 |
+
"semi": true,
|
| 5 |
+
"useTabs": true
|
| 6 |
+
}
|
pulsetransit-worker/AGENTS.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Cloudflare Workers
|
| 2 |
+
|
| 3 |
+
STOP. Your knowledge of Cloudflare Workers APIs and limits may be outdated. Always retrieve current documentation before any Workers, KV, R2, D1, Durable Objects, Queues, Vectorize, AI, or Agents SDK task.
|
| 4 |
+
|
| 5 |
+
## Docs
|
| 6 |
+
|
| 7 |
+
- https://developers.cloudflare.com/workers/
|
| 8 |
+
- MCP: `https://docs.mcp.cloudflare.com/mcp`
|
| 9 |
+
|
| 10 |
+
For all limits and quotas, retrieve from the product's `/platform/limits/` page. eg. `/workers/platform/limits`
|
| 11 |
+
|
| 12 |
+
## Commands
|
| 13 |
+
|
| 14 |
+
| Command | Purpose |
|
| 15 |
+
|---------|---------|
|
| 16 |
+
| `npx wrangler dev` | Local development |
|
| 17 |
+
| `npx wrangler deploy` | Deploy to Cloudflare |
|
| 18 |
+
| `npx wrangler types` | Generate TypeScript types |
|
| 19 |
+
|
| 20 |
+
Run `wrangler types` after changing bindings in wrangler.jsonc.
|
| 21 |
+
|
| 22 |
+
## Node.js Compatibility
|
| 23 |
+
|
| 24 |
+
https://developers.cloudflare.com/workers/runtime-apis/nodejs/
|
| 25 |
+
|
| 26 |
+
## Errors
|
| 27 |
+
|
| 28 |
+
- **Error 1102** (CPU/Memory exceeded): Retrieve limits from `/workers/platform/limits/`
|
| 29 |
+
- **All errors**: https://developers.cloudflare.com/workers/observability/errors/
|
| 30 |
+
|
| 31 |
+
## Product Docs
|
| 32 |
+
|
| 33 |
+
Retrieve API references and limits from:
|
| 34 |
+
`/kv/` · `/r2/` · `/d1/` · `/durable-objects/` · `/queues/` · `/vectorize/` · `/workers-ai/` · `/agents/`
|
pulsetransit-worker/package-lock.json
ADDED
|
@@ -0,0 +1,1504 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "pulsetransit-worker",
|
| 3 |
+
"version": "0.2.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "pulsetransit-worker",
|
| 9 |
+
"version": "0.2.0",
|
| 10 |
+
"devDependencies": {
|
| 11 |
+
"wrangler": "^4.68.0"
|
| 12 |
+
}
|
| 13 |
+
},
|
| 14 |
+
"node_modules/@cloudflare/kv-asset-handler": {
|
| 15 |
+
"version": "0.4.2",
|
| 16 |
+
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz",
|
| 17 |
+
"integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==",
|
| 18 |
+
"dev": true,
|
| 19 |
+
"license": "MIT OR Apache-2.0",
|
| 20 |
+
"engines": {
|
| 21 |
+
"node": ">=18.0.0"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"node_modules/@cloudflare/unenv-preset": {
|
| 25 |
+
"version": "2.14.0",
|
| 26 |
+
"resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.14.0.tgz",
|
| 27 |
+
"integrity": "sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg==",
|
| 28 |
+
"dev": true,
|
| 29 |
+
"license": "MIT OR Apache-2.0",
|
| 30 |
+
"peerDependencies": {
|
| 31 |
+
"unenv": "2.0.0-rc.24",
|
| 32 |
+
"workerd": "^1.20260218.0"
|
| 33 |
+
},
|
| 34 |
+
"peerDependenciesMeta": {
|
| 35 |
+
"workerd": {
|
| 36 |
+
"optional": true
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"node_modules/@cloudflare/workerd-darwin-64": {
|
| 41 |
+
"version": "1.20260302.0",
|
| 42 |
+
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260302.0.tgz",
|
| 43 |
+
"integrity": "sha512-cGtxPByeVrgoqxbmd8qs631wuGwf8yTm/FY44dEW4HdoXrb5jhlE4oWYHFafedkQCvGjY1Vbs3puAiKnuMxTXQ==",
|
| 44 |
+
"cpu": [
|
| 45 |
+
"x64"
|
| 46 |
+
],
|
| 47 |
+
"dev": true,
|
| 48 |
+
"license": "Apache-2.0",
|
| 49 |
+
"optional": true,
|
| 50 |
+
"os": [
|
| 51 |
+
"darwin"
|
| 52 |
+
],
|
| 53 |
+
"engines": {
|
| 54 |
+
"node": ">=16"
|
| 55 |
+
}
|
| 56 |
+
},
|
| 57 |
+
"node_modules/@cloudflare/workerd-darwin-arm64": {
|
| 58 |
+
"version": "1.20260302.0",
|
| 59 |
+
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260302.0.tgz",
|
| 60 |
+
"integrity": "sha512-WRGqV6RNXM3xoQblJJw1EHKwx9exyhB18cdnToSCUFPObFhk3fzMLoQh7S+nUHUpto6aUrXPVj6R/4G3UPjCxw==",
|
| 61 |
+
"cpu": [
|
| 62 |
+
"arm64"
|
| 63 |
+
],
|
| 64 |
+
"dev": true,
|
| 65 |
+
"license": "Apache-2.0",
|
| 66 |
+
"optional": true,
|
| 67 |
+
"os": [
|
| 68 |
+
"darwin"
|
| 69 |
+
],
|
| 70 |
+
"engines": {
|
| 71 |
+
"node": ">=16"
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"node_modules/@cloudflare/workerd-linux-64": {
|
| 75 |
+
"version": "1.20260302.0",
|
| 76 |
+
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260302.0.tgz",
|
| 77 |
+
"integrity": "sha512-gG423mtUjrmlQT+W2+KisLc6qcGcBLR+QcK5x1gje3bu/dF3oNiYuqY7o58A+sQk6IB849UC4UyNclo1RhP2xw==",
|
| 78 |
+
"cpu": [
|
| 79 |
+
"x64"
|
| 80 |
+
],
|
| 81 |
+
"dev": true,
|
| 82 |
+
"license": "Apache-2.0",
|
| 83 |
+
"optional": true,
|
| 84 |
+
"os": [
|
| 85 |
+
"linux"
|
| 86 |
+
],
|
| 87 |
+
"engines": {
|
| 88 |
+
"node": ">=16"
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
"node_modules/@cloudflare/workerd-linux-arm64": {
|
| 92 |
+
"version": "1.20260302.0",
|
| 93 |
+
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260302.0.tgz",
|
| 94 |
+
"integrity": "sha512-7M25noGI4WlSBOhrIaY8xZrnn87OQKtJg9YWAO2EFqGjF1Su5QXGaLlQVF4fAKbqTywbHnI8BAuIsIlUSNkhCg==",
|
| 95 |
+
"cpu": [
|
| 96 |
+
"arm64"
|
| 97 |
+
],
|
| 98 |
+
"dev": true,
|
| 99 |
+
"license": "Apache-2.0",
|
| 100 |
+
"optional": true,
|
| 101 |
+
"os": [
|
| 102 |
+
"linux"
|
| 103 |
+
],
|
| 104 |
+
"engines": {
|
| 105 |
+
"node": ">=16"
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"node_modules/@cloudflare/workerd-windows-64": {
|
| 109 |
+
"version": "1.20260302.0",
|
| 110 |
+
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260302.0.tgz",
|
| 111 |
+
"integrity": "sha512-jK1L3ADkiWxFzlqZTq2iHW1Bd2Nzu1fmMWCGZw4sMZ2W1B2WCm2wHwO2SX/py4BgylyEN3wuF+5zagbkNKht9A==",
|
| 112 |
+
"cpu": [
|
| 113 |
+
"x64"
|
| 114 |
+
],
|
| 115 |
+
"dev": true,
|
| 116 |
+
"license": "Apache-2.0",
|
| 117 |
+
"optional": true,
|
| 118 |
+
"os": [
|
| 119 |
+
"win32"
|
| 120 |
+
],
|
| 121 |
+
"engines": {
|
| 122 |
+
"node": ">=16"
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"node_modules/@cspotcode/source-map-support": {
|
| 126 |
+
"version": "0.8.1",
|
| 127 |
+
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
| 128 |
+
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
| 129 |
+
"dev": true,
|
| 130 |
+
"license": "MIT",
|
| 131 |
+
"dependencies": {
|
| 132 |
+
"@jridgewell/trace-mapping": "0.3.9"
|
| 133 |
+
},
|
| 134 |
+
"engines": {
|
| 135 |
+
"node": ">=12"
|
| 136 |
+
}
|
| 137 |
+
},
|
| 138 |
+
"node_modules/@emnapi/runtime": {
|
| 139 |
+
"version": "1.8.1",
|
| 140 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
|
| 141 |
+
"integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
|
| 142 |
+
"dev": true,
|
| 143 |
+
"license": "MIT",
|
| 144 |
+
"optional": true,
|
| 145 |
+
"dependencies": {
|
| 146 |
+
"tslib": "^2.4.0"
|
| 147 |
+
}
|
| 148 |
+
},
|
| 149 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 150 |
+
"version": "0.27.3",
|
| 151 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
|
| 152 |
+
"integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
|
| 153 |
+
"cpu": [
|
| 154 |
+
"ppc64"
|
| 155 |
+
],
|
| 156 |
+
"dev": true,
|
| 157 |
+
"license": "MIT",
|
| 158 |
+
"optional": true,
|
| 159 |
+
"os": [
|
| 160 |
+
"aix"
|
| 161 |
+
],
|
| 162 |
+
"engines": {
|
| 163 |
+
"node": ">=18"
|
| 164 |
+
}
|
| 165 |
+
},
|
| 166 |
+
"node_modules/@esbuild/android-arm": {
|
| 167 |
+
"version": "0.27.3",
|
| 168 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
|
| 169 |
+
"integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
|
| 170 |
+
"cpu": [
|
| 171 |
+
"arm"
|
| 172 |
+
],
|
| 173 |
+
"dev": true,
|
| 174 |
+
"license": "MIT",
|
| 175 |
+
"optional": true,
|
| 176 |
+
"os": [
|
| 177 |
+
"android"
|
| 178 |
+
],
|
| 179 |
+
"engines": {
|
| 180 |
+
"node": ">=18"
|
| 181 |
+
}
|
| 182 |
+
},
|
| 183 |
+
"node_modules/@esbuild/android-arm64": {
|
| 184 |
+
"version": "0.27.3",
|
| 185 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
|
| 186 |
+
"integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
|
| 187 |
+
"cpu": [
|
| 188 |
+
"arm64"
|
| 189 |
+
],
|
| 190 |
+
"dev": true,
|
| 191 |
+
"license": "MIT",
|
| 192 |
+
"optional": true,
|
| 193 |
+
"os": [
|
| 194 |
+
"android"
|
| 195 |
+
],
|
| 196 |
+
"engines": {
|
| 197 |
+
"node": ">=18"
|
| 198 |
+
}
|
| 199 |
+
},
|
| 200 |
+
"node_modules/@esbuild/android-x64": {
|
| 201 |
+
"version": "0.27.3",
|
| 202 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
|
| 203 |
+
"integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
|
| 204 |
+
"cpu": [
|
| 205 |
+
"x64"
|
| 206 |
+
],
|
| 207 |
+
"dev": true,
|
| 208 |
+
"license": "MIT",
|
| 209 |
+
"optional": true,
|
| 210 |
+
"os": [
|
| 211 |
+
"android"
|
| 212 |
+
],
|
| 213 |
+
"engines": {
|
| 214 |
+
"node": ">=18"
|
| 215 |
+
}
|
| 216 |
+
},
|
| 217 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 218 |
+
"version": "0.27.3",
|
| 219 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
|
| 220 |
+
"integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
|
| 221 |
+
"cpu": [
|
| 222 |
+
"arm64"
|
| 223 |
+
],
|
| 224 |
+
"dev": true,
|
| 225 |
+
"license": "MIT",
|
| 226 |
+
"optional": true,
|
| 227 |
+
"os": [
|
| 228 |
+
"darwin"
|
| 229 |
+
],
|
| 230 |
+
"engines": {
|
| 231 |
+
"node": ">=18"
|
| 232 |
+
}
|
| 233 |
+
},
|
| 234 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 235 |
+
"version": "0.27.3",
|
| 236 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
|
| 237 |
+
"integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
|
| 238 |
+
"cpu": [
|
| 239 |
+
"x64"
|
| 240 |
+
],
|
| 241 |
+
"dev": true,
|
| 242 |
+
"license": "MIT",
|
| 243 |
+
"optional": true,
|
| 244 |
+
"os": [
|
| 245 |
+
"darwin"
|
| 246 |
+
],
|
| 247 |
+
"engines": {
|
| 248 |
+
"node": ">=18"
|
| 249 |
+
}
|
| 250 |
+
},
|
| 251 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 252 |
+
"version": "0.27.3",
|
| 253 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
|
| 254 |
+
"integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
|
| 255 |
+
"cpu": [
|
| 256 |
+
"arm64"
|
| 257 |
+
],
|
| 258 |
+
"dev": true,
|
| 259 |
+
"license": "MIT",
|
| 260 |
+
"optional": true,
|
| 261 |
+
"os": [
|
| 262 |
+
"freebsd"
|
| 263 |
+
],
|
| 264 |
+
"engines": {
|
| 265 |
+
"node": ">=18"
|
| 266 |
+
}
|
| 267 |
+
},
|
| 268 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 269 |
+
"version": "0.27.3",
|
| 270 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
|
| 271 |
+
"integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
|
| 272 |
+
"cpu": [
|
| 273 |
+
"x64"
|
| 274 |
+
],
|
| 275 |
+
"dev": true,
|
| 276 |
+
"license": "MIT",
|
| 277 |
+
"optional": true,
|
| 278 |
+
"os": [
|
| 279 |
+
"freebsd"
|
| 280 |
+
],
|
| 281 |
+
"engines": {
|
| 282 |
+
"node": ">=18"
|
| 283 |
+
}
|
| 284 |
+
},
|
| 285 |
+
"node_modules/@esbuild/linux-arm": {
|
| 286 |
+
"version": "0.27.3",
|
| 287 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
|
| 288 |
+
"integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
|
| 289 |
+
"cpu": [
|
| 290 |
+
"arm"
|
| 291 |
+
],
|
| 292 |
+
"dev": true,
|
| 293 |
+
"license": "MIT",
|
| 294 |
+
"optional": true,
|
| 295 |
+
"os": [
|
| 296 |
+
"linux"
|
| 297 |
+
],
|
| 298 |
+
"engines": {
|
| 299 |
+
"node": ">=18"
|
| 300 |
+
}
|
| 301 |
+
},
|
| 302 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 303 |
+
"version": "0.27.3",
|
| 304 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
|
| 305 |
+
"integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
|
| 306 |
+
"cpu": [
|
| 307 |
+
"arm64"
|
| 308 |
+
],
|
| 309 |
+
"dev": true,
|
| 310 |
+
"license": "MIT",
|
| 311 |
+
"optional": true,
|
| 312 |
+
"os": [
|
| 313 |
+
"linux"
|
| 314 |
+
],
|
| 315 |
+
"engines": {
|
| 316 |
+
"node": ">=18"
|
| 317 |
+
}
|
| 318 |
+
},
|
| 319 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 320 |
+
"version": "0.27.3",
|
| 321 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
|
| 322 |
+
"integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
|
| 323 |
+
"cpu": [
|
| 324 |
+
"ia32"
|
| 325 |
+
],
|
| 326 |
+
"dev": true,
|
| 327 |
+
"license": "MIT",
|
| 328 |
+
"optional": true,
|
| 329 |
+
"os": [
|
| 330 |
+
"linux"
|
| 331 |
+
],
|
| 332 |
+
"engines": {
|
| 333 |
+
"node": ">=18"
|
| 334 |
+
}
|
| 335 |
+
},
|
| 336 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 337 |
+
"version": "0.27.3",
|
| 338 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
|
| 339 |
+
"integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
|
| 340 |
+
"cpu": [
|
| 341 |
+
"loong64"
|
| 342 |
+
],
|
| 343 |
+
"dev": true,
|
| 344 |
+
"license": "MIT",
|
| 345 |
+
"optional": true,
|
| 346 |
+
"os": [
|
| 347 |
+
"linux"
|
| 348 |
+
],
|
| 349 |
+
"engines": {
|
| 350 |
+
"node": ">=18"
|
| 351 |
+
}
|
| 352 |
+
},
|
| 353 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 354 |
+
"version": "0.27.3",
|
| 355 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
|
| 356 |
+
"integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
|
| 357 |
+
"cpu": [
|
| 358 |
+
"mips64el"
|
| 359 |
+
],
|
| 360 |
+
"dev": true,
|
| 361 |
+
"license": "MIT",
|
| 362 |
+
"optional": true,
|
| 363 |
+
"os": [
|
| 364 |
+
"linux"
|
| 365 |
+
],
|
| 366 |
+
"engines": {
|
| 367 |
+
"node": ">=18"
|
| 368 |
+
}
|
| 369 |
+
},
|
| 370 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 371 |
+
"version": "0.27.3",
|
| 372 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
|
| 373 |
+
"integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
|
| 374 |
+
"cpu": [
|
| 375 |
+
"ppc64"
|
| 376 |
+
],
|
| 377 |
+
"dev": true,
|
| 378 |
+
"license": "MIT",
|
| 379 |
+
"optional": true,
|
| 380 |
+
"os": [
|
| 381 |
+
"linux"
|
| 382 |
+
],
|
| 383 |
+
"engines": {
|
| 384 |
+
"node": ">=18"
|
| 385 |
+
}
|
| 386 |
+
},
|
| 387 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 388 |
+
"version": "0.27.3",
|
| 389 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
|
| 390 |
+
"integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
|
| 391 |
+
"cpu": [
|
| 392 |
+
"riscv64"
|
| 393 |
+
],
|
| 394 |
+
"dev": true,
|
| 395 |
+
"license": "MIT",
|
| 396 |
+
"optional": true,
|
| 397 |
+
"os": [
|
| 398 |
+
"linux"
|
| 399 |
+
],
|
| 400 |
+
"engines": {
|
| 401 |
+
"node": ">=18"
|
| 402 |
+
}
|
| 403 |
+
},
|
| 404 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 405 |
+
"version": "0.27.3",
|
| 406 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
|
| 407 |
+
"integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
|
| 408 |
+
"cpu": [
|
| 409 |
+
"s390x"
|
| 410 |
+
],
|
| 411 |
+
"dev": true,
|
| 412 |
+
"license": "MIT",
|
| 413 |
+
"optional": true,
|
| 414 |
+
"os": [
|
| 415 |
+
"linux"
|
| 416 |
+
],
|
| 417 |
+
"engines": {
|
| 418 |
+
"node": ">=18"
|
| 419 |
+
}
|
| 420 |
+
},
|
| 421 |
+
"node_modules/@esbuild/linux-x64": {
|
| 422 |
+
"version": "0.27.3",
|
| 423 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
|
| 424 |
+
"integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
|
| 425 |
+
"cpu": [
|
| 426 |
+
"x64"
|
| 427 |
+
],
|
| 428 |
+
"dev": true,
|
| 429 |
+
"license": "MIT",
|
| 430 |
+
"optional": true,
|
| 431 |
+
"os": [
|
| 432 |
+
"linux"
|
| 433 |
+
],
|
| 434 |
+
"engines": {
|
| 435 |
+
"node": ">=18"
|
| 436 |
+
}
|
| 437 |
+
},
|
| 438 |
+
"node_modules/@esbuild/netbsd-arm64": {
|
| 439 |
+
"version": "0.27.3",
|
| 440 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
|
| 441 |
+
"integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
|
| 442 |
+
"cpu": [
|
| 443 |
+
"arm64"
|
| 444 |
+
],
|
| 445 |
+
"dev": true,
|
| 446 |
+
"license": "MIT",
|
| 447 |
+
"optional": true,
|
| 448 |
+
"os": [
|
| 449 |
+
"netbsd"
|
| 450 |
+
],
|
| 451 |
+
"engines": {
|
| 452 |
+
"node": ">=18"
|
| 453 |
+
}
|
| 454 |
+
},
|
| 455 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 456 |
+
"version": "0.27.3",
|
| 457 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
|
| 458 |
+
"integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
|
| 459 |
+
"cpu": [
|
| 460 |
+
"x64"
|
| 461 |
+
],
|
| 462 |
+
"dev": true,
|
| 463 |
+
"license": "MIT",
|
| 464 |
+
"optional": true,
|
| 465 |
+
"os": [
|
| 466 |
+
"netbsd"
|
| 467 |
+
],
|
| 468 |
+
"engines": {
|
| 469 |
+
"node": ">=18"
|
| 470 |
+
}
|
| 471 |
+
},
|
| 472 |
+
"node_modules/@esbuild/openbsd-arm64": {
|
| 473 |
+
"version": "0.27.3",
|
| 474 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
|
| 475 |
+
"integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
|
| 476 |
+
"cpu": [
|
| 477 |
+
"arm64"
|
| 478 |
+
],
|
| 479 |
+
"dev": true,
|
| 480 |
+
"license": "MIT",
|
| 481 |
+
"optional": true,
|
| 482 |
+
"os": [
|
| 483 |
+
"openbsd"
|
| 484 |
+
],
|
| 485 |
+
"engines": {
|
| 486 |
+
"node": ">=18"
|
| 487 |
+
}
|
| 488 |
+
},
|
| 489 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 490 |
+
"version": "0.27.3",
|
| 491 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
|
| 492 |
+
"integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
|
| 493 |
+
"cpu": [
|
| 494 |
+
"x64"
|
| 495 |
+
],
|
| 496 |
+
"dev": true,
|
| 497 |
+
"license": "MIT",
|
| 498 |
+
"optional": true,
|
| 499 |
+
"os": [
|
| 500 |
+
"openbsd"
|
| 501 |
+
],
|
| 502 |
+
"engines": {
|
| 503 |
+
"node": ">=18"
|
| 504 |
+
}
|
| 505 |
+
},
|
| 506 |
+
"node_modules/@esbuild/openharmony-arm64": {
|
| 507 |
+
"version": "0.27.3",
|
| 508 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
|
| 509 |
+
"integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
|
| 510 |
+
"cpu": [
|
| 511 |
+
"arm64"
|
| 512 |
+
],
|
| 513 |
+
"dev": true,
|
| 514 |
+
"license": "MIT",
|
| 515 |
+
"optional": true,
|
| 516 |
+
"os": [
|
| 517 |
+
"openharmony"
|
| 518 |
+
],
|
| 519 |
+
"engines": {
|
| 520 |
+
"node": ">=18"
|
| 521 |
+
}
|
| 522 |
+
},
|
| 523 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 524 |
+
"version": "0.27.3",
|
| 525 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
|
| 526 |
+
"integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
|
| 527 |
+
"cpu": [
|
| 528 |
+
"x64"
|
| 529 |
+
],
|
| 530 |
+
"dev": true,
|
| 531 |
+
"license": "MIT",
|
| 532 |
+
"optional": true,
|
| 533 |
+
"os": [
|
| 534 |
+
"sunos"
|
| 535 |
+
],
|
| 536 |
+
"engines": {
|
| 537 |
+
"node": ">=18"
|
| 538 |
+
}
|
| 539 |
+
},
|
| 540 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 541 |
+
"version": "0.27.3",
|
| 542 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
|
| 543 |
+
"integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
|
| 544 |
+
"cpu": [
|
| 545 |
+
"arm64"
|
| 546 |
+
],
|
| 547 |
+
"dev": true,
|
| 548 |
+
"license": "MIT",
|
| 549 |
+
"optional": true,
|
| 550 |
+
"os": [
|
| 551 |
+
"win32"
|
| 552 |
+
],
|
| 553 |
+
"engines": {
|
| 554 |
+
"node": ">=18"
|
| 555 |
+
}
|
| 556 |
+
},
|
| 557 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 558 |
+
"version": "0.27.3",
|
| 559 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
|
| 560 |
+
"integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
|
| 561 |
+
"cpu": [
|
| 562 |
+
"ia32"
|
| 563 |
+
],
|
| 564 |
+
"dev": true,
|
| 565 |
+
"license": "MIT",
|
| 566 |
+
"optional": true,
|
| 567 |
+
"os": [
|
| 568 |
+
"win32"
|
| 569 |
+
],
|
| 570 |
+
"engines": {
|
| 571 |
+
"node": ">=18"
|
| 572 |
+
}
|
| 573 |
+
},
|
| 574 |
+
"node_modules/@esbuild/win32-x64": {
|
| 575 |
+
"version": "0.27.3",
|
| 576 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
|
| 577 |
+
"integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
|
| 578 |
+
"cpu": [
|
| 579 |
+
"x64"
|
| 580 |
+
],
|
| 581 |
+
"dev": true,
|
| 582 |
+
"license": "MIT",
|
| 583 |
+
"optional": true,
|
| 584 |
+
"os": [
|
| 585 |
+
"win32"
|
| 586 |
+
],
|
| 587 |
+
"engines": {
|
| 588 |
+
"node": ">=18"
|
| 589 |
+
}
|
| 590 |
+
},
|
| 591 |
+
"node_modules/@img/colour": {
|
| 592 |
+
"version": "1.0.0",
|
| 593 |
+
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
|
| 594 |
+
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
|
| 595 |
+
"dev": true,
|
| 596 |
+
"license": "MIT",
|
| 597 |
+
"engines": {
|
| 598 |
+
"node": ">=18"
|
| 599 |
+
}
|
| 600 |
+
},
|
| 601 |
+
"node_modules/@img/sharp-darwin-arm64": {
|
| 602 |
+
"version": "0.34.5",
|
| 603 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
| 604 |
+
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
| 605 |
+
"cpu": [
|
| 606 |
+
"arm64"
|
| 607 |
+
],
|
| 608 |
+
"dev": true,
|
| 609 |
+
"license": "Apache-2.0",
|
| 610 |
+
"optional": true,
|
| 611 |
+
"os": [
|
| 612 |
+
"darwin"
|
| 613 |
+
],
|
| 614 |
+
"engines": {
|
| 615 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 616 |
+
},
|
| 617 |
+
"funding": {
|
| 618 |
+
"url": "https://opencollective.com/libvips"
|
| 619 |
+
},
|
| 620 |
+
"optionalDependencies": {
|
| 621 |
+
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
| 622 |
+
}
|
| 623 |
+
},
|
| 624 |
+
"node_modules/@img/sharp-darwin-x64": {
|
| 625 |
+
"version": "0.34.5",
|
| 626 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
| 627 |
+
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
| 628 |
+
"cpu": [
|
| 629 |
+
"x64"
|
| 630 |
+
],
|
| 631 |
+
"dev": true,
|
| 632 |
+
"license": "Apache-2.0",
|
| 633 |
+
"optional": true,
|
| 634 |
+
"os": [
|
| 635 |
+
"darwin"
|
| 636 |
+
],
|
| 637 |
+
"engines": {
|
| 638 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 639 |
+
},
|
| 640 |
+
"funding": {
|
| 641 |
+
"url": "https://opencollective.com/libvips"
|
| 642 |
+
},
|
| 643 |
+
"optionalDependencies": {
|
| 644 |
+
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
| 645 |
+
}
|
| 646 |
+
},
|
| 647 |
+
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
| 648 |
+
"version": "1.2.4",
|
| 649 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
| 650 |
+
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
| 651 |
+
"cpu": [
|
| 652 |
+
"arm64"
|
| 653 |
+
],
|
| 654 |
+
"dev": true,
|
| 655 |
+
"license": "LGPL-3.0-or-later",
|
| 656 |
+
"optional": true,
|
| 657 |
+
"os": [
|
| 658 |
+
"darwin"
|
| 659 |
+
],
|
| 660 |
+
"funding": {
|
| 661 |
+
"url": "https://opencollective.com/libvips"
|
| 662 |
+
}
|
| 663 |
+
},
|
| 664 |
+
"node_modules/@img/sharp-libvips-darwin-x64": {
|
| 665 |
+
"version": "1.2.4",
|
| 666 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
| 667 |
+
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
| 668 |
+
"cpu": [
|
| 669 |
+
"x64"
|
| 670 |
+
],
|
| 671 |
+
"dev": true,
|
| 672 |
+
"license": "LGPL-3.0-or-later",
|
| 673 |
+
"optional": true,
|
| 674 |
+
"os": [
|
| 675 |
+
"darwin"
|
| 676 |
+
],
|
| 677 |
+
"funding": {
|
| 678 |
+
"url": "https://opencollective.com/libvips"
|
| 679 |
+
}
|
| 680 |
+
},
|
| 681 |
+
"node_modules/@img/sharp-libvips-linux-arm": {
|
| 682 |
+
"version": "1.2.4",
|
| 683 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
| 684 |
+
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
| 685 |
+
"cpu": [
|
| 686 |
+
"arm"
|
| 687 |
+
],
|
| 688 |
+
"dev": true,
|
| 689 |
+
"license": "LGPL-3.0-or-later",
|
| 690 |
+
"optional": true,
|
| 691 |
+
"os": [
|
| 692 |
+
"linux"
|
| 693 |
+
],
|
| 694 |
+
"funding": {
|
| 695 |
+
"url": "https://opencollective.com/libvips"
|
| 696 |
+
}
|
| 697 |
+
},
|
| 698 |
+
"node_modules/@img/sharp-libvips-linux-arm64": {
|
| 699 |
+
"version": "1.2.4",
|
| 700 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
| 701 |
+
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
| 702 |
+
"cpu": [
|
| 703 |
+
"arm64"
|
| 704 |
+
],
|
| 705 |
+
"dev": true,
|
| 706 |
+
"license": "LGPL-3.0-or-later",
|
| 707 |
+
"optional": true,
|
| 708 |
+
"os": [
|
| 709 |
+
"linux"
|
| 710 |
+
],
|
| 711 |
+
"funding": {
|
| 712 |
+
"url": "https://opencollective.com/libvips"
|
| 713 |
+
}
|
| 714 |
+
},
|
| 715 |
+
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
| 716 |
+
"version": "1.2.4",
|
| 717 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
| 718 |
+
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
| 719 |
+
"cpu": [
|
| 720 |
+
"ppc64"
|
| 721 |
+
],
|
| 722 |
+
"dev": true,
|
| 723 |
+
"license": "LGPL-3.0-or-later",
|
| 724 |
+
"optional": true,
|
| 725 |
+
"os": [
|
| 726 |
+
"linux"
|
| 727 |
+
],
|
| 728 |
+
"funding": {
|
| 729 |
+
"url": "https://opencollective.com/libvips"
|
| 730 |
+
}
|
| 731 |
+
},
|
| 732 |
+
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
| 733 |
+
"version": "1.2.4",
|
| 734 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
| 735 |
+
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
| 736 |
+
"cpu": [
|
| 737 |
+
"riscv64"
|
| 738 |
+
],
|
| 739 |
+
"dev": true,
|
| 740 |
+
"license": "LGPL-3.0-or-later",
|
| 741 |
+
"optional": true,
|
| 742 |
+
"os": [
|
| 743 |
+
"linux"
|
| 744 |
+
],
|
| 745 |
+
"funding": {
|
| 746 |
+
"url": "https://opencollective.com/libvips"
|
| 747 |
+
}
|
| 748 |
+
},
|
| 749 |
+
"node_modules/@img/sharp-libvips-linux-s390x": {
|
| 750 |
+
"version": "1.2.4",
|
| 751 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
| 752 |
+
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
| 753 |
+
"cpu": [
|
| 754 |
+
"s390x"
|
| 755 |
+
],
|
| 756 |
+
"dev": true,
|
| 757 |
+
"license": "LGPL-3.0-or-later",
|
| 758 |
+
"optional": true,
|
| 759 |
+
"os": [
|
| 760 |
+
"linux"
|
| 761 |
+
],
|
| 762 |
+
"funding": {
|
| 763 |
+
"url": "https://opencollective.com/libvips"
|
| 764 |
+
}
|
| 765 |
+
},
|
| 766 |
+
"node_modules/@img/sharp-libvips-linux-x64": {
|
| 767 |
+
"version": "1.2.4",
|
| 768 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
| 769 |
+
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
| 770 |
+
"cpu": [
|
| 771 |
+
"x64"
|
| 772 |
+
],
|
| 773 |
+
"dev": true,
|
| 774 |
+
"license": "LGPL-3.0-or-later",
|
| 775 |
+
"optional": true,
|
| 776 |
+
"os": [
|
| 777 |
+
"linux"
|
| 778 |
+
],
|
| 779 |
+
"funding": {
|
| 780 |
+
"url": "https://opencollective.com/libvips"
|
| 781 |
+
}
|
| 782 |
+
},
|
| 783 |
+
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
| 784 |
+
"version": "1.2.4",
|
| 785 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
| 786 |
+
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
| 787 |
+
"cpu": [
|
| 788 |
+
"arm64"
|
| 789 |
+
],
|
| 790 |
+
"dev": true,
|
| 791 |
+
"license": "LGPL-3.0-or-later",
|
| 792 |
+
"optional": true,
|
| 793 |
+
"os": [
|
| 794 |
+
"linux"
|
| 795 |
+
],
|
| 796 |
+
"funding": {
|
| 797 |
+
"url": "https://opencollective.com/libvips"
|
| 798 |
+
}
|
| 799 |
+
},
|
| 800 |
+
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
| 801 |
+
"version": "1.2.4",
|
| 802 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
| 803 |
+
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
| 804 |
+
"cpu": [
|
| 805 |
+
"x64"
|
| 806 |
+
],
|
| 807 |
+
"dev": true,
|
| 808 |
+
"license": "LGPL-3.0-or-later",
|
| 809 |
+
"optional": true,
|
| 810 |
+
"os": [
|
| 811 |
+
"linux"
|
| 812 |
+
],
|
| 813 |
+
"funding": {
|
| 814 |
+
"url": "https://opencollective.com/libvips"
|
| 815 |
+
}
|
| 816 |
+
},
|
| 817 |
+
"node_modules/@img/sharp-linux-arm": {
|
| 818 |
+
"version": "0.34.5",
|
| 819 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
| 820 |
+
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
| 821 |
+
"cpu": [
|
| 822 |
+
"arm"
|
| 823 |
+
],
|
| 824 |
+
"dev": true,
|
| 825 |
+
"license": "Apache-2.0",
|
| 826 |
+
"optional": true,
|
| 827 |
+
"os": [
|
| 828 |
+
"linux"
|
| 829 |
+
],
|
| 830 |
+
"engines": {
|
| 831 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 832 |
+
},
|
| 833 |
+
"funding": {
|
| 834 |
+
"url": "https://opencollective.com/libvips"
|
| 835 |
+
},
|
| 836 |
+
"optionalDependencies": {
|
| 837 |
+
"@img/sharp-libvips-linux-arm": "1.2.4"
|
| 838 |
+
}
|
| 839 |
+
},
|
| 840 |
+
"node_modules/@img/sharp-linux-arm64": {
|
| 841 |
+
"version": "0.34.5",
|
| 842 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
| 843 |
+
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
| 844 |
+
"cpu": [
|
| 845 |
+
"arm64"
|
| 846 |
+
],
|
| 847 |
+
"dev": true,
|
| 848 |
+
"license": "Apache-2.0",
|
| 849 |
+
"optional": true,
|
| 850 |
+
"os": [
|
| 851 |
+
"linux"
|
| 852 |
+
],
|
| 853 |
+
"engines": {
|
| 854 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 855 |
+
},
|
| 856 |
+
"funding": {
|
| 857 |
+
"url": "https://opencollective.com/libvips"
|
| 858 |
+
},
|
| 859 |
+
"optionalDependencies": {
|
| 860 |
+
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
| 861 |
+
}
|
| 862 |
+
},
|
| 863 |
+
"node_modules/@img/sharp-linux-ppc64": {
|
| 864 |
+
"version": "0.34.5",
|
| 865 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
| 866 |
+
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
| 867 |
+
"cpu": [
|
| 868 |
+
"ppc64"
|
| 869 |
+
],
|
| 870 |
+
"dev": true,
|
| 871 |
+
"license": "Apache-2.0",
|
| 872 |
+
"optional": true,
|
| 873 |
+
"os": [
|
| 874 |
+
"linux"
|
| 875 |
+
],
|
| 876 |
+
"engines": {
|
| 877 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 878 |
+
},
|
| 879 |
+
"funding": {
|
| 880 |
+
"url": "https://opencollective.com/libvips"
|
| 881 |
+
},
|
| 882 |
+
"optionalDependencies": {
|
| 883 |
+
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
| 884 |
+
}
|
| 885 |
+
},
|
| 886 |
+
"node_modules/@img/sharp-linux-riscv64": {
|
| 887 |
+
"version": "0.34.5",
|
| 888 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
| 889 |
+
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
| 890 |
+
"cpu": [
|
| 891 |
+
"riscv64"
|
| 892 |
+
],
|
| 893 |
+
"dev": true,
|
| 894 |
+
"license": "Apache-2.0",
|
| 895 |
+
"optional": true,
|
| 896 |
+
"os": [
|
| 897 |
+
"linux"
|
| 898 |
+
],
|
| 899 |
+
"engines": {
|
| 900 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 901 |
+
},
|
| 902 |
+
"funding": {
|
| 903 |
+
"url": "https://opencollective.com/libvips"
|
| 904 |
+
},
|
| 905 |
+
"optionalDependencies": {
|
| 906 |
+
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
| 907 |
+
}
|
| 908 |
+
},
|
| 909 |
+
"node_modules/@img/sharp-linux-s390x": {
|
| 910 |
+
"version": "0.34.5",
|
| 911 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
| 912 |
+
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
| 913 |
+
"cpu": [
|
| 914 |
+
"s390x"
|
| 915 |
+
],
|
| 916 |
+
"dev": true,
|
| 917 |
+
"license": "Apache-2.0",
|
| 918 |
+
"optional": true,
|
| 919 |
+
"os": [
|
| 920 |
+
"linux"
|
| 921 |
+
],
|
| 922 |
+
"engines": {
|
| 923 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 924 |
+
},
|
| 925 |
+
"funding": {
|
| 926 |
+
"url": "https://opencollective.com/libvips"
|
| 927 |
+
},
|
| 928 |
+
"optionalDependencies": {
|
| 929 |
+
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
| 930 |
+
}
|
| 931 |
+
},
|
| 932 |
+
"node_modules/@img/sharp-linux-x64": {
|
| 933 |
+
"version": "0.34.5",
|
| 934 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
| 935 |
+
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
| 936 |
+
"cpu": [
|
| 937 |
+
"x64"
|
| 938 |
+
],
|
| 939 |
+
"dev": true,
|
| 940 |
+
"license": "Apache-2.0",
|
| 941 |
+
"optional": true,
|
| 942 |
+
"os": [
|
| 943 |
+
"linux"
|
| 944 |
+
],
|
| 945 |
+
"engines": {
|
| 946 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 947 |
+
},
|
| 948 |
+
"funding": {
|
| 949 |
+
"url": "https://opencollective.com/libvips"
|
| 950 |
+
},
|
| 951 |
+
"optionalDependencies": {
|
| 952 |
+
"@img/sharp-libvips-linux-x64": "1.2.4"
|
| 953 |
+
}
|
| 954 |
+
},
|
| 955 |
+
"node_modules/@img/sharp-linuxmusl-arm64": {
|
| 956 |
+
"version": "0.34.5",
|
| 957 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
| 958 |
+
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
| 959 |
+
"cpu": [
|
| 960 |
+
"arm64"
|
| 961 |
+
],
|
| 962 |
+
"dev": true,
|
| 963 |
+
"license": "Apache-2.0",
|
| 964 |
+
"optional": true,
|
| 965 |
+
"os": [
|
| 966 |
+
"linux"
|
| 967 |
+
],
|
| 968 |
+
"engines": {
|
| 969 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 970 |
+
},
|
| 971 |
+
"funding": {
|
| 972 |
+
"url": "https://opencollective.com/libvips"
|
| 973 |
+
},
|
| 974 |
+
"optionalDependencies": {
|
| 975 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
| 976 |
+
}
|
| 977 |
+
},
|
| 978 |
+
"node_modules/@img/sharp-linuxmusl-x64": {
|
| 979 |
+
"version": "0.34.5",
|
| 980 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
| 981 |
+
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
| 982 |
+
"cpu": [
|
| 983 |
+
"x64"
|
| 984 |
+
],
|
| 985 |
+
"dev": true,
|
| 986 |
+
"license": "Apache-2.0",
|
| 987 |
+
"optional": true,
|
| 988 |
+
"os": [
|
| 989 |
+
"linux"
|
| 990 |
+
],
|
| 991 |
+
"engines": {
|
| 992 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 993 |
+
},
|
| 994 |
+
"funding": {
|
| 995 |
+
"url": "https://opencollective.com/libvips"
|
| 996 |
+
},
|
| 997 |
+
"optionalDependencies": {
|
| 998 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
| 999 |
+
}
|
| 1000 |
+
},
|
| 1001 |
+
"node_modules/@img/sharp-wasm32": {
|
| 1002 |
+
"version": "0.34.5",
|
| 1003 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
| 1004 |
+
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
| 1005 |
+
"cpu": [
|
| 1006 |
+
"wasm32"
|
| 1007 |
+
],
|
| 1008 |
+
"dev": true,
|
| 1009 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
| 1010 |
+
"optional": true,
|
| 1011 |
+
"dependencies": {
|
| 1012 |
+
"@emnapi/runtime": "^1.7.0"
|
| 1013 |
+
},
|
| 1014 |
+
"engines": {
|
| 1015 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 1016 |
+
},
|
| 1017 |
+
"funding": {
|
| 1018 |
+
"url": "https://opencollective.com/libvips"
|
| 1019 |
+
}
|
| 1020 |
+
},
|
| 1021 |
+
"node_modules/@img/sharp-win32-arm64": {
|
| 1022 |
+
"version": "0.34.5",
|
| 1023 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
| 1024 |
+
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
| 1025 |
+
"cpu": [
|
| 1026 |
+
"arm64"
|
| 1027 |
+
],
|
| 1028 |
+
"dev": true,
|
| 1029 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 1030 |
+
"optional": true,
|
| 1031 |
+
"os": [
|
| 1032 |
+
"win32"
|
| 1033 |
+
],
|
| 1034 |
+
"engines": {
|
| 1035 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 1036 |
+
},
|
| 1037 |
+
"funding": {
|
| 1038 |
+
"url": "https://opencollective.com/libvips"
|
| 1039 |
+
}
|
| 1040 |
+
},
|
| 1041 |
+
"node_modules/@img/sharp-win32-ia32": {
|
| 1042 |
+
"version": "0.34.5",
|
| 1043 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
| 1044 |
+
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
| 1045 |
+
"cpu": [
|
| 1046 |
+
"ia32"
|
| 1047 |
+
],
|
| 1048 |
+
"dev": true,
|
| 1049 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 1050 |
+
"optional": true,
|
| 1051 |
+
"os": [
|
| 1052 |
+
"win32"
|
| 1053 |
+
],
|
| 1054 |
+
"engines": {
|
| 1055 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 1056 |
+
},
|
| 1057 |
+
"funding": {
|
| 1058 |
+
"url": "https://opencollective.com/libvips"
|
| 1059 |
+
}
|
| 1060 |
+
},
|
| 1061 |
+
"node_modules/@img/sharp-win32-x64": {
|
| 1062 |
+
"version": "0.34.5",
|
| 1063 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
| 1064 |
+
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
| 1065 |
+
"cpu": [
|
| 1066 |
+
"x64"
|
| 1067 |
+
],
|
| 1068 |
+
"dev": true,
|
| 1069 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 1070 |
+
"optional": true,
|
| 1071 |
+
"os": [
|
| 1072 |
+
"win32"
|
| 1073 |
+
],
|
| 1074 |
+
"engines": {
|
| 1075 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 1076 |
+
},
|
| 1077 |
+
"funding": {
|
| 1078 |
+
"url": "https://opencollective.com/libvips"
|
| 1079 |
+
}
|
| 1080 |
+
},
|
| 1081 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 1082 |
+
"version": "3.1.2",
|
| 1083 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 1084 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 1085 |
+
"dev": true,
|
| 1086 |
+
"license": "MIT",
|
| 1087 |
+
"engines": {
|
| 1088 |
+
"node": ">=6.0.0"
|
| 1089 |
+
}
|
| 1090 |
+
},
|
| 1091 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 1092 |
+
"version": "1.5.5",
|
| 1093 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 1094 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 1095 |
+
"dev": true,
|
| 1096 |
+
"license": "MIT"
|
| 1097 |
+
},
|
| 1098 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 1099 |
+
"version": "0.3.9",
|
| 1100 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
| 1101 |
+
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
| 1102 |
+
"dev": true,
|
| 1103 |
+
"license": "MIT",
|
| 1104 |
+
"dependencies": {
|
| 1105 |
+
"@jridgewell/resolve-uri": "^3.0.3",
|
| 1106 |
+
"@jridgewell/sourcemap-codec": "^1.4.10"
|
| 1107 |
+
}
|
| 1108 |
+
},
|
| 1109 |
+
"node_modules/@poppinss/colors": {
|
| 1110 |
+
"version": "4.1.6",
|
| 1111 |
+
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
|
| 1112 |
+
"integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==",
|
| 1113 |
+
"dev": true,
|
| 1114 |
+
"license": "MIT",
|
| 1115 |
+
"dependencies": {
|
| 1116 |
+
"kleur": "^4.1.5"
|
| 1117 |
+
}
|
| 1118 |
+
},
|
| 1119 |
+
"node_modules/@poppinss/dumper": {
|
| 1120 |
+
"version": "0.6.5",
|
| 1121 |
+
"resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz",
|
| 1122 |
+
"integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==",
|
| 1123 |
+
"dev": true,
|
| 1124 |
+
"license": "MIT",
|
| 1125 |
+
"dependencies": {
|
| 1126 |
+
"@poppinss/colors": "^4.1.5",
|
| 1127 |
+
"@sindresorhus/is": "^7.0.2",
|
| 1128 |
+
"supports-color": "^10.0.0"
|
| 1129 |
+
}
|
| 1130 |
+
},
|
| 1131 |
+
"node_modules/@poppinss/exception": {
|
| 1132 |
+
"version": "1.2.3",
|
| 1133 |
+
"resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz",
|
| 1134 |
+
"integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==",
|
| 1135 |
+
"dev": true,
|
| 1136 |
+
"license": "MIT"
|
| 1137 |
+
},
|
| 1138 |
+
"node_modules/@sindresorhus/is": {
|
| 1139 |
+
"version": "7.2.0",
|
| 1140 |
+
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz",
|
| 1141 |
+
"integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==",
|
| 1142 |
+
"dev": true,
|
| 1143 |
+
"license": "MIT",
|
| 1144 |
+
"engines": {
|
| 1145 |
+
"node": ">=18"
|
| 1146 |
+
},
|
| 1147 |
+
"funding": {
|
| 1148 |
+
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
| 1149 |
+
}
|
| 1150 |
+
},
|
| 1151 |
+
"node_modules/@speed-highlight/core": {
|
| 1152 |
+
"version": "1.2.14",
|
| 1153 |
+
"resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz",
|
| 1154 |
+
"integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==",
|
| 1155 |
+
"dev": true,
|
| 1156 |
+
"license": "CC0-1.0"
|
| 1157 |
+
},
|
| 1158 |
+
"node_modules/blake3-wasm": {
|
| 1159 |
+
"version": "2.1.5",
|
| 1160 |
+
"resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
|
| 1161 |
+
"integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
|
| 1162 |
+
"dev": true,
|
| 1163 |
+
"license": "MIT"
|
| 1164 |
+
},
|
| 1165 |
+
"node_modules/cookie": {
|
| 1166 |
+
"version": "1.1.1",
|
| 1167 |
+
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
| 1168 |
+
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
| 1169 |
+
"dev": true,
|
| 1170 |
+
"license": "MIT",
|
| 1171 |
+
"engines": {
|
| 1172 |
+
"node": ">=18"
|
| 1173 |
+
},
|
| 1174 |
+
"funding": {
|
| 1175 |
+
"type": "opencollective",
|
| 1176 |
+
"url": "https://opencollective.com/express"
|
| 1177 |
+
}
|
| 1178 |
+
},
|
| 1179 |
+
"node_modules/detect-libc": {
|
| 1180 |
+
"version": "2.1.2",
|
| 1181 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1182 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1183 |
+
"dev": true,
|
| 1184 |
+
"license": "Apache-2.0",
|
| 1185 |
+
"engines": {
|
| 1186 |
+
"node": ">=8"
|
| 1187 |
+
}
|
| 1188 |
+
},
|
| 1189 |
+
"node_modules/error-stack-parser-es": {
|
| 1190 |
+
"version": "1.0.5",
|
| 1191 |
+
"resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
|
| 1192 |
+
"integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
|
| 1193 |
+
"dev": true,
|
| 1194 |
+
"license": "MIT",
|
| 1195 |
+
"funding": {
|
| 1196 |
+
"url": "https://github.com/sponsors/antfu"
|
| 1197 |
+
}
|
| 1198 |
+
},
|
| 1199 |
+
"node_modules/esbuild": {
|
| 1200 |
+
"version": "0.27.3",
|
| 1201 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
| 1202 |
+
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
|
| 1203 |
+
"dev": true,
|
| 1204 |
+
"hasInstallScript": true,
|
| 1205 |
+
"license": "MIT",
|
| 1206 |
+
"bin": {
|
| 1207 |
+
"esbuild": "bin/esbuild"
|
| 1208 |
+
},
|
| 1209 |
+
"engines": {
|
| 1210 |
+
"node": ">=18"
|
| 1211 |
+
},
|
| 1212 |
+
"optionalDependencies": {
|
| 1213 |
+
"@esbuild/aix-ppc64": "0.27.3",
|
| 1214 |
+
"@esbuild/android-arm": "0.27.3",
|
| 1215 |
+
"@esbuild/android-arm64": "0.27.3",
|
| 1216 |
+
"@esbuild/android-x64": "0.27.3",
|
| 1217 |
+
"@esbuild/darwin-arm64": "0.27.3",
|
| 1218 |
+
"@esbuild/darwin-x64": "0.27.3",
|
| 1219 |
+
"@esbuild/freebsd-arm64": "0.27.3",
|
| 1220 |
+
"@esbuild/freebsd-x64": "0.27.3",
|
| 1221 |
+
"@esbuild/linux-arm": "0.27.3",
|
| 1222 |
+
"@esbuild/linux-arm64": "0.27.3",
|
| 1223 |
+
"@esbuild/linux-ia32": "0.27.3",
|
| 1224 |
+
"@esbuild/linux-loong64": "0.27.3",
|
| 1225 |
+
"@esbuild/linux-mips64el": "0.27.3",
|
| 1226 |
+
"@esbuild/linux-ppc64": "0.27.3",
|
| 1227 |
+
"@esbuild/linux-riscv64": "0.27.3",
|
| 1228 |
+
"@esbuild/linux-s390x": "0.27.3",
|
| 1229 |
+
"@esbuild/linux-x64": "0.27.3",
|
| 1230 |
+
"@esbuild/netbsd-arm64": "0.27.3",
|
| 1231 |
+
"@esbuild/netbsd-x64": "0.27.3",
|
| 1232 |
+
"@esbuild/openbsd-arm64": "0.27.3",
|
| 1233 |
+
"@esbuild/openbsd-x64": "0.27.3",
|
| 1234 |
+
"@esbuild/openharmony-arm64": "0.27.3",
|
| 1235 |
+
"@esbuild/sunos-x64": "0.27.3",
|
| 1236 |
+
"@esbuild/win32-arm64": "0.27.3",
|
| 1237 |
+
"@esbuild/win32-ia32": "0.27.3",
|
| 1238 |
+
"@esbuild/win32-x64": "0.27.3"
|
| 1239 |
+
}
|
| 1240 |
+
},
|
| 1241 |
+
"node_modules/fsevents": {
|
| 1242 |
+
"version": "2.3.3",
|
| 1243 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1244 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1245 |
+
"dev": true,
|
| 1246 |
+
"hasInstallScript": true,
|
| 1247 |
+
"license": "MIT",
|
| 1248 |
+
"optional": true,
|
| 1249 |
+
"os": [
|
| 1250 |
+
"darwin"
|
| 1251 |
+
],
|
| 1252 |
+
"engines": {
|
| 1253 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1254 |
+
}
|
| 1255 |
+
},
|
| 1256 |
+
"node_modules/kleur": {
|
| 1257 |
+
"version": "4.1.5",
|
| 1258 |
+
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
| 1259 |
+
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
|
| 1260 |
+
"dev": true,
|
| 1261 |
+
"license": "MIT",
|
| 1262 |
+
"engines": {
|
| 1263 |
+
"node": ">=6"
|
| 1264 |
+
}
|
| 1265 |
+
},
|
| 1266 |
+
"node_modules/miniflare": {
|
| 1267 |
+
"version": "4.20260302.0",
|
| 1268 |
+
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260302.0.tgz",
|
| 1269 |
+
"integrity": "sha512-joGFywlo7HdfHXXGOkc6tDCVkwjEncM0mwEsMOLWcl+vDVJPj9HRV7JtEa0+lCpNOLdYw7mZNHYe12xz9KtJOw==",
|
| 1270 |
+
"dev": true,
|
| 1271 |
+
"license": "MIT",
|
| 1272 |
+
"dependencies": {
|
| 1273 |
+
"@cspotcode/source-map-support": "0.8.1",
|
| 1274 |
+
"sharp": "^0.34.5",
|
| 1275 |
+
"undici": "7.18.2",
|
| 1276 |
+
"workerd": "1.20260302.0",
|
| 1277 |
+
"ws": "8.18.0",
|
| 1278 |
+
"youch": "4.1.0-beta.10"
|
| 1279 |
+
},
|
| 1280 |
+
"bin": {
|
| 1281 |
+
"miniflare": "bootstrap.js"
|
| 1282 |
+
},
|
| 1283 |
+
"engines": {
|
| 1284 |
+
"node": ">=18.0.0"
|
| 1285 |
+
}
|
| 1286 |
+
},
|
| 1287 |
+
"node_modules/path-to-regexp": {
|
| 1288 |
+
"version": "6.3.0",
|
| 1289 |
+
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
|
| 1290 |
+
"integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
|
| 1291 |
+
"dev": true,
|
| 1292 |
+
"license": "MIT"
|
| 1293 |
+
},
|
| 1294 |
+
"node_modules/pathe": {
|
| 1295 |
+
"version": "2.0.3",
|
| 1296 |
+
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
| 1297 |
+
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
| 1298 |
+
"dev": true,
|
| 1299 |
+
"license": "MIT"
|
| 1300 |
+
},
|
| 1301 |
+
"node_modules/semver": {
|
| 1302 |
+
"version": "7.7.4",
|
| 1303 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
| 1304 |
+
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
| 1305 |
+
"dev": true,
|
| 1306 |
+
"license": "ISC",
|
| 1307 |
+
"bin": {
|
| 1308 |
+
"semver": "bin/semver.js"
|
| 1309 |
+
},
|
| 1310 |
+
"engines": {
|
| 1311 |
+
"node": ">=10"
|
| 1312 |
+
}
|
| 1313 |
+
},
|
| 1314 |
+
"node_modules/sharp": {
|
| 1315 |
+
"version": "0.34.5",
|
| 1316 |
+
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
| 1317 |
+
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
| 1318 |
+
"dev": true,
|
| 1319 |
+
"hasInstallScript": true,
|
| 1320 |
+
"license": "Apache-2.0",
|
| 1321 |
+
"dependencies": {
|
| 1322 |
+
"@img/colour": "^1.0.0",
|
| 1323 |
+
"detect-libc": "^2.1.2",
|
| 1324 |
+
"semver": "^7.7.3"
|
| 1325 |
+
},
|
| 1326 |
+
"engines": {
|
| 1327 |
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
| 1328 |
+
},
|
| 1329 |
+
"funding": {
|
| 1330 |
+
"url": "https://opencollective.com/libvips"
|
| 1331 |
+
},
|
| 1332 |
+
"optionalDependencies": {
|
| 1333 |
+
"@img/sharp-darwin-arm64": "0.34.5",
|
| 1334 |
+
"@img/sharp-darwin-x64": "0.34.5",
|
| 1335 |
+
"@img/sharp-libvips-darwin-arm64": "1.2.4",
|
| 1336 |
+
"@img/sharp-libvips-darwin-x64": "1.2.4",
|
| 1337 |
+
"@img/sharp-libvips-linux-arm": "1.2.4",
|
| 1338 |
+
"@img/sharp-libvips-linux-arm64": "1.2.4",
|
| 1339 |
+
"@img/sharp-libvips-linux-ppc64": "1.2.4",
|
| 1340 |
+
"@img/sharp-libvips-linux-riscv64": "1.2.4",
|
| 1341 |
+
"@img/sharp-libvips-linux-s390x": "1.2.4",
|
| 1342 |
+
"@img/sharp-libvips-linux-x64": "1.2.4",
|
| 1343 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
|
| 1344 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
|
| 1345 |
+
"@img/sharp-linux-arm": "0.34.5",
|
| 1346 |
+
"@img/sharp-linux-arm64": "0.34.5",
|
| 1347 |
+
"@img/sharp-linux-ppc64": "0.34.5",
|
| 1348 |
+
"@img/sharp-linux-riscv64": "0.34.5",
|
| 1349 |
+
"@img/sharp-linux-s390x": "0.34.5",
|
| 1350 |
+
"@img/sharp-linux-x64": "0.34.5",
|
| 1351 |
+
"@img/sharp-linuxmusl-arm64": "0.34.5",
|
| 1352 |
+
"@img/sharp-linuxmusl-x64": "0.34.5",
|
| 1353 |
+
"@img/sharp-wasm32": "0.34.5",
|
| 1354 |
+
"@img/sharp-win32-arm64": "0.34.5",
|
| 1355 |
+
"@img/sharp-win32-ia32": "0.34.5",
|
| 1356 |
+
"@img/sharp-win32-x64": "0.34.5"
|
| 1357 |
+
}
|
| 1358 |
+
},
|
| 1359 |
+
"node_modules/supports-color": {
|
| 1360 |
+
"version": "10.2.2",
|
| 1361 |
+
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
|
| 1362 |
+
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
|
| 1363 |
+
"dev": true,
|
| 1364 |
+
"license": "MIT",
|
| 1365 |
+
"engines": {
|
| 1366 |
+
"node": ">=18"
|
| 1367 |
+
},
|
| 1368 |
+
"funding": {
|
| 1369 |
+
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
| 1370 |
+
}
|
| 1371 |
+
},
|
| 1372 |
+
"node_modules/tslib": {
|
| 1373 |
+
"version": "2.8.1",
|
| 1374 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 1375 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 1376 |
+
"dev": true,
|
| 1377 |
+
"license": "0BSD",
|
| 1378 |
+
"optional": true
|
| 1379 |
+
},
|
| 1380 |
+
"node_modules/undici": {
|
| 1381 |
+
"version": "7.18.2",
|
| 1382 |
+
"resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz",
|
| 1383 |
+
"integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==",
|
| 1384 |
+
"dev": true,
|
| 1385 |
+
"license": "MIT",
|
| 1386 |
+
"engines": {
|
| 1387 |
+
"node": ">=20.18.1"
|
| 1388 |
+
}
|
| 1389 |
+
},
|
| 1390 |
+
"node_modules/unenv": {
|
| 1391 |
+
"version": "2.0.0-rc.24",
|
| 1392 |
+
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
|
| 1393 |
+
"integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
|
| 1394 |
+
"dev": true,
|
| 1395 |
+
"license": "MIT",
|
| 1396 |
+
"dependencies": {
|
| 1397 |
+
"pathe": "^2.0.3"
|
| 1398 |
+
}
|
| 1399 |
+
},
|
| 1400 |
+
"node_modules/workerd": {
|
| 1401 |
+
"version": "1.20260302.0",
|
| 1402 |
+
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260302.0.tgz",
|
| 1403 |
+
"integrity": "sha512-FhNdC8cenMDllI6bTktFgxP5Bn5ZEnGtofgKipY6pW9jtq708D1DeGI6vGad78KQLBGaDwFy1eThjCoLYgFfog==",
|
| 1404 |
+
"dev": true,
|
| 1405 |
+
"hasInstallScript": true,
|
| 1406 |
+
"license": "Apache-2.0",
|
| 1407 |
+
"bin": {
|
| 1408 |
+
"workerd": "bin/workerd"
|
| 1409 |
+
},
|
| 1410 |
+
"engines": {
|
| 1411 |
+
"node": ">=16"
|
| 1412 |
+
},
|
| 1413 |
+
"optionalDependencies": {
|
| 1414 |
+
"@cloudflare/workerd-darwin-64": "1.20260302.0",
|
| 1415 |
+
"@cloudflare/workerd-darwin-arm64": "1.20260302.0",
|
| 1416 |
+
"@cloudflare/workerd-linux-64": "1.20260302.0",
|
| 1417 |
+
"@cloudflare/workerd-linux-arm64": "1.20260302.0",
|
| 1418 |
+
"@cloudflare/workerd-windows-64": "1.20260302.0"
|
| 1419 |
+
}
|
| 1420 |
+
},
|
| 1421 |
+
"node_modules/wrangler": {
|
| 1422 |
+
"version": "4.68.0",
|
| 1423 |
+
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.68.0.tgz",
|
| 1424 |
+
"integrity": "sha512-DCjl2ZfjwWV10iH4Zn+97isitPkb7BYxpbt4E/Okd/QKLFTp9xdwoa999UN9lugToqPm5Zz/UsRu6hpKZuT8BA==",
|
| 1425 |
+
"dev": true,
|
| 1426 |
+
"license": "MIT OR Apache-2.0",
|
| 1427 |
+
"dependencies": {
|
| 1428 |
+
"@cloudflare/kv-asset-handler": "0.4.2",
|
| 1429 |
+
"@cloudflare/unenv-preset": "2.14.0",
|
| 1430 |
+
"blake3-wasm": "2.1.5",
|
| 1431 |
+
"esbuild": "0.27.3",
|
| 1432 |
+
"miniflare": "4.20260302.0",
|
| 1433 |
+
"path-to-regexp": "6.3.0",
|
| 1434 |
+
"unenv": "2.0.0-rc.24",
|
| 1435 |
+
"workerd": "1.20260302.0"
|
| 1436 |
+
},
|
| 1437 |
+
"bin": {
|
| 1438 |
+
"wrangler": "bin/wrangler.js",
|
| 1439 |
+
"wrangler2": "bin/wrangler.js"
|
| 1440 |
+
},
|
| 1441 |
+
"engines": {
|
| 1442 |
+
"node": ">=20.0.0"
|
| 1443 |
+
},
|
| 1444 |
+
"optionalDependencies": {
|
| 1445 |
+
"fsevents": "~2.3.2"
|
| 1446 |
+
},
|
| 1447 |
+
"peerDependencies": {
|
| 1448 |
+
"@cloudflare/workers-types": "^4.20260302.0"
|
| 1449 |
+
},
|
| 1450 |
+
"peerDependenciesMeta": {
|
| 1451 |
+
"@cloudflare/workers-types": {
|
| 1452 |
+
"optional": true
|
| 1453 |
+
}
|
| 1454 |
+
}
|
| 1455 |
+
},
|
| 1456 |
+
"node_modules/ws": {
|
| 1457 |
+
"version": "8.18.0",
|
| 1458 |
+
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
| 1459 |
+
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
| 1460 |
+
"dev": true,
|
| 1461 |
+
"license": "MIT",
|
| 1462 |
+
"engines": {
|
| 1463 |
+
"node": ">=10.0.0"
|
| 1464 |
+
},
|
| 1465 |
+
"peerDependencies": {
|
| 1466 |
+
"bufferutil": "^4.0.1",
|
| 1467 |
+
"utf-8-validate": ">=5.0.2"
|
| 1468 |
+
},
|
| 1469 |
+
"peerDependenciesMeta": {
|
| 1470 |
+
"bufferutil": {
|
| 1471 |
+
"optional": true
|
| 1472 |
+
},
|
| 1473 |
+
"utf-8-validate": {
|
| 1474 |
+
"optional": true
|
| 1475 |
+
}
|
| 1476 |
+
}
|
| 1477 |
+
},
|
| 1478 |
+
"node_modules/youch": {
|
| 1479 |
+
"version": "4.1.0-beta.10",
|
| 1480 |
+
"resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz",
|
| 1481 |
+
"integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==",
|
| 1482 |
+
"dev": true,
|
| 1483 |
+
"license": "MIT",
|
| 1484 |
+
"dependencies": {
|
| 1485 |
+
"@poppinss/colors": "^4.1.5",
|
| 1486 |
+
"@poppinss/dumper": "^0.6.4",
|
| 1487 |
+
"@speed-highlight/core": "^1.2.7",
|
| 1488 |
+
"cookie": "^1.0.2",
|
| 1489 |
+
"youch-core": "^0.3.3"
|
| 1490 |
+
}
|
| 1491 |
+
},
|
| 1492 |
+
"node_modules/youch-core": {
|
| 1493 |
+
"version": "0.3.3",
|
| 1494 |
+
"resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz",
|
| 1495 |
+
"integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==",
|
| 1496 |
+
"dev": true,
|
| 1497 |
+
"license": "MIT",
|
| 1498 |
+
"dependencies": {
|
| 1499 |
+
"@poppinss/exception": "^1.2.2",
|
| 1500 |
+
"error-stack-parser-es": "^1.0.5"
|
| 1501 |
+
}
|
| 1502 |
+
}
|
| 1503 |
+
}
|
| 1504 |
+
}
|
pulsetransit-worker/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "pulsetransit-worker",
|
| 3 |
+
"version": "0.2.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"deploy": "wrangler deploy",
|
| 7 |
+
"dev": "wrangler dev --test-scheduled",
|
| 8 |
+
"start": "wrangler dev --test-scheduled"
|
| 9 |
+
},
|
| 10 |
+
"devDependencies": {
|
| 11 |
+
"wrangler": "^4.68.0"
|
| 12 |
+
}
|
| 13 |
+
}
|
pulsetransit-worker/schema.sql
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CREATE TABLE IF NOT EXISTS estimaciones (
|
| 2 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 3 |
+
collected_at TEXT NOT NULL,
|
| 4 |
+
parada_id INTEGER,
|
| 5 |
+
linea TEXT,
|
| 6 |
+
fech_actual TEXT,
|
| 7 |
+
tiempo1 INTEGER,
|
| 8 |
+
tiempo2 INTEGER,
|
| 9 |
+
distancia1 INTEGER,
|
| 10 |
+
distancia2 INTEGER,
|
| 11 |
+
destino1 TEXT,
|
| 12 |
+
destino2 TEXT,
|
| 13 |
+
predicted_arrival TEXT,
|
| 14 |
+
UNIQUE(parada_id, linea, fech_actual)
|
| 15 |
+
);
|
| 16 |
+
|
| 17 |
+
CREATE INDEX IF NOT EXISTS idx_est_parada ON estimaciones(parada_id);
|
| 18 |
+
CREATE INDEX IF NOT EXISTS idx_est_linea ON estimaciones(linea);
|
| 19 |
+
CREATE INDEX IF NOT EXISTS idx_est_arrival ON estimaciones(predicted_arrival);
|
| 20 |
+
|
| 21 |
+
CREATE TABLE IF NOT EXISTS posiciones (
|
| 22 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 23 |
+
collected_at TEXT NOT NULL,
|
| 24 |
+
instante TEXT NOT NULL,
|
| 25 |
+
vehiculo INTEGER,
|
| 26 |
+
linea INTEGER,
|
| 27 |
+
lat REAL,
|
| 28 |
+
lon REAL,
|
| 29 |
+
velocidad INTEGER,
|
| 30 |
+
estado INTEGER,
|
| 31 |
+
UNIQUE(vehiculo, instante)
|
| 32 |
+
);
|
| 33 |
+
|
| 34 |
+
CREATE INDEX IF NOT EXISTS idx_pos_instant ON posiciones(instante);
|
| 35 |
+
CREATE INDEX IF NOT EXISTS idx_pos_linea ON posiciones(linea);
|
| 36 |
+
CREATE INDEX IF NOT EXISTS idx_pos_vehiculo ON posiciones(vehiculo);
|
pulsetransit-worker/src/index.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default {
|
| 2 |
+
async fetch(req, env, ctx) {
|
| 3 |
+
const url = new URL(req.url);
|
| 4 |
+
|
| 5 |
+
if (url.pathname === "/trigger") {
|
| 6 |
+
await collectEstimaciones(env);
|
| 7 |
+
await new Promise(r => setTimeout(r, 1000));
|
| 8 |
+
await collectPosiciones(env);
|
| 9 |
+
return new Response("Triggered estimaciones+posiciones");
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
if (url.pathname === "/health") {
|
| 13 |
+
const lastEst = await env.DB.prepare(
|
| 14 |
+
"SELECT MAX(collected_at) as last FROM estimaciones"
|
| 15 |
+
).first();
|
| 16 |
+
const lastPos = await env.DB.prepare(
|
| 17 |
+
"SELECT MAX(collected_at) as last FROM posiciones"
|
| 18 |
+
).first();
|
| 19 |
+
|
| 20 |
+
return Response.json({
|
| 21 |
+
status: "ok",
|
| 22 |
+
last_estimaciones: lastEst?.last,
|
| 23 |
+
last_posiciones: lastPos?.last
|
| 24 |
+
});
|
| 25 |
+
}
|
| 26 |
+
if (url.pathname === "/badge") {
|
| 27 |
+
const lastEst = await env.DB.prepare(
|
| 28 |
+
"SELECT MAX(collected_at) as last FROM estimaciones"
|
| 29 |
+
).first();
|
| 30 |
+
|
| 31 |
+
const now = new Date();
|
| 32 |
+
const lastTime = lastEst?.last ? new Date(lastEst.last) : null;
|
| 33 |
+
const ageSeconds = lastTime ? Math.floor((now - lastTime) / 1000) : Infinity;
|
| 34 |
+
const hourUTC = now.getUTCHours();
|
| 35 |
+
const inServiceHours = hourUTC >= 5 && hourUTC < 23; // 6am-midnight CET
|
| 36 |
+
|
| 37 |
+
let message, color;
|
| 38 |
+
|
| 39 |
+
if (!lastTime) {
|
| 40 |
+
message = "no data";
|
| 41 |
+
color = "lightgrey";
|
| 42 |
+
} else if (inServiceHours && ageSeconds > 1800) {
|
| 43 |
+
message = `stale ${Math.floor(ageSeconds / 60)}m`;
|
| 44 |
+
color = "red";
|
| 45 |
+
} else if (inServiceHours && ageSeconds > 600) {
|
| 46 |
+
message = `stale ${Math.floor(ageSeconds / 60)}m`;
|
| 47 |
+
color = "yellow";
|
| 48 |
+
} else if (!inServiceHours) {
|
| 49 |
+
message = "off hours";
|
| 50 |
+
color = "blue";
|
| 51 |
+
} else {
|
| 52 |
+
message = "live";
|
| 53 |
+
color = "brightgreen";
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
return Response.json({
|
| 57 |
+
schemaVersion: 1,
|
| 58 |
+
label: "TUS worker",
|
| 59 |
+
message,
|
| 60 |
+
color
|
| 61 |
+
}, {
|
| 62 |
+
headers: { "Cache-Control": "no-cache, max-age=0" }
|
| 63 |
+
});
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
return new Response("pulsetransit-worker running");
|
| 68 |
+
},
|
| 69 |
+
|
| 70 |
+
async scheduled(event, env, ctx) {
|
| 71 |
+
if (event.cron === "0 * * * *") {
|
| 72 |
+
await collectPosiciones(env);
|
| 73 |
+
} else if (event.cron === "*/2 * * * *") {
|
| 74 |
+
await collectEstimaciones(env);
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
async function collectEstimaciones(env) {
|
| 81 |
+
const url = "https://datos.santander.es/api/rest/datasets/control_flotas_estimaciones.json?rows=5000";
|
| 82 |
+
const resp = await fetch(url, { signal: AbortSignal.timeout(25000) });
|
| 83 |
+
if (!resp.ok) throw new Error(`API fetch failed: ${resp.status}`);
|
| 84 |
+
|
| 85 |
+
const json = await resp.json();
|
| 86 |
+
const rows = json.resources ?? [];
|
| 87 |
+
const collectedAt = new Date().toISOString();
|
| 88 |
+
|
| 89 |
+
let inserted = 0;
|
| 90 |
+
for (const item of rows) {
|
| 91 |
+
const fechActual = item["ayto:fechActual"] ?? null;
|
| 92 |
+
const tiempo1 = item["ayto:tiempo1"] ?? null;
|
| 93 |
+
|
| 94 |
+
let predictedArrival = null;
|
| 95 |
+
if (fechActual && tiempo1 !== null) {
|
| 96 |
+
try {
|
| 97 |
+
const t = new Date(fechActual);
|
| 98 |
+
t.setSeconds(t.getSeconds() + Number(tiempo1));
|
| 99 |
+
predictedArrival = t.toISOString();
|
| 100 |
+
} catch (_) { }
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
const result = await env.DB.prepare(`
|
| 104 |
+
INSERT OR IGNORE INTO estimaciones
|
| 105 |
+
(collected_at, parada_id, linea, fech_actual, tiempo1, tiempo2,
|
| 106 |
+
distancia1, distancia2, destino1, destino2, predicted_arrival)
|
| 107 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 108 |
+
`).bind(
|
| 109 |
+
collectedAt,
|
| 110 |
+
item["ayto:paradaId"] ?? null,
|
| 111 |
+
item["ayto:etiqLinea"] ?? null,
|
| 112 |
+
fechActual,
|
| 113 |
+
tiempo1,
|
| 114 |
+
item["ayto:tiempo2"] ?? null,
|
| 115 |
+
item["ayto:distancia1"] ?? null,
|
| 116 |
+
item["ayto:distancia2"] ?? null,
|
| 117 |
+
item["ayto:destino1"] ?? null,
|
| 118 |
+
item["ayto:destino2"] ?? null,
|
| 119 |
+
predictedArrival,
|
| 120 |
+
).run();
|
| 121 |
+
|
| 122 |
+
if (result.meta.changes > 0) inserted++;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
console.log(`[${collectedAt}] estimaciones: ${inserted} new rows from ${rows.length} fetched`);
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
async function collectPosiciones(env) {
|
| 129 |
+
const url = "https://datos.santander.es/api/rest/datasets/control_flotas_posiciones.json?rows=5000";
|
| 130 |
+
const resp = await fetch(url, { signal: AbortSignal.timeout(25000) });
|
| 131 |
+
if (!resp.ok) throw new Error(`API fetch failed: ${resp.status}`);
|
| 132 |
+
|
| 133 |
+
const json = await resp.json();
|
| 134 |
+
const rows = json.resources ?? [];
|
| 135 |
+
const collectedAt = new Date().toISOString();
|
| 136 |
+
|
| 137 |
+
let inserted = 0;
|
| 138 |
+
for (const item of rows) {
|
| 139 |
+
const result = await env.DB.prepare(`
|
| 140 |
+
INSERT OR IGNORE INTO posiciones
|
| 141 |
+
(collected_at, instante, vehiculo, linea, lat, lon, velocidad, estado)
|
| 142 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
| 143 |
+
`).bind(
|
| 144 |
+
collectedAt,
|
| 145 |
+
item["ayto:instante"] ?? null,
|
| 146 |
+
item["ayto:vehiculo"] ?? null,
|
| 147 |
+
item["ayto:linea"] ?? null,
|
| 148 |
+
item["wgs84_pos:lat"] ?? null,
|
| 149 |
+
item["wgs84_pos:long"] ?? null,
|
| 150 |
+
item["ayto:velocidad"] ?? null,
|
| 151 |
+
item["ayto:estado"] ?? null,
|
| 152 |
+
).run();
|
| 153 |
+
|
| 154 |
+
if (result.meta.changes > 0) inserted++;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
console.log(`[${collectedAt}] posiciones: ${inserted} new rows from ${rows.length} fetched`);
|
| 158 |
+
}
|
pulsetransit-worker/wrangler.jsonc
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* For more details on how to configure Wrangler, refer to:
|
| 3 |
+
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
| 4 |
+
*/
|
| 5 |
+
{
|
| 6 |
+
"$schema": "node_modules/wrangler/config-schema.json",
|
| 7 |
+
"name": "pulsetransit-worker",
|
| 8 |
+
"main": "src/index.js",
|
| 9 |
+
"compatibility_date": "2026-02-24",
|
| 10 |
+
"observability": { "enabled": true },
|
| 11 |
+
"compatibility_flags": ["nodejs_compat"],
|
| 12 |
+
"triggers": {
|
| 13 |
+
"crons": ["*/2 * * * *", "0 * * * *"] // estimaciones every 5min, posiciones every hour
|
| 14 |
+
},
|
| 15 |
+
"d1_databases": [
|
| 16 |
+
{
|
| 17 |
+
"binding": "DB",
|
| 18 |
+
"database_name": "pulsetransit-db",
|
| 19 |
+
"database_id": "14eda04e-aa18-4fb4-a6cb-b3ca5c333cb9"
|
| 20 |
+
}
|
| 21 |
+
]
|
| 22 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=61.0", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "pulsetransit"
|
| 7 |
+
version = "0.4.0"
|
| 8 |
+
description = "TUS Santander bus data pipeline and delay prediction"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"pandas",
|
| 12 |
+
"plotly",
|
| 13 |
+
"streamlit",
|
| 14 |
+
"streamlit_js_eval",
|
| 15 |
+
]
|
| 16 |
+
requires-python = ">=3.10"
|
| 17 |
+
license = { text = "MIT" }
|
| 18 |
+
|
| 19 |
+
[tool.setuptools.packages.find]
|
| 20 |
+
where = ["src"]
|
src/pulsetransit/__init__.py
ADDED
|
File without changes
|
src/pulsetransit/cfg/config.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
LANG = {
|
| 2 |
+
"en": {
|
| 3 |
+
"title": "TUS Santander Tracker",
|
| 4 |
+
"subtitle": "Public transport network visualization",
|
| 5 |
+
"browse_tab": "Browse",
|
| 6 |
+
"plan_tab": "Plan",
|
| 7 |
+
"search_stop": "Search stop",
|
| 8 |
+
"search_placeholder": "Type stop ID or name to search...",
|
| 9 |
+
"click_info": "Click a stop on the map or use the search bar",
|
| 10 |
+
"scheduled_departures": "Scheduled Departures",
|
| 11 |
+
"no_departures": "No upcoming departures found for this stop.",
|
| 12 |
+
"line": "Line",
|
| 13 |
+
"destination": "Destination",
|
| 14 |
+
"time": "Time",
|
| 15 |
+
"in": "In",
|
| 16 |
+
"min": "min",
|
| 17 |
+
"now": "Now",
|
| 18 |
+
"plan_trip": "Plan Your Trip",
|
| 19 |
+
"query_time": "Query time",
|
| 20 |
+
"query_time_help": "Show schedules for this time of day",
|
| 21 |
+
"coming_soon": " COMING SOON: \n - 📍 Live bus positions, \n - ⏱️ Real-time arrival predictions (estimaciones), and \n3) 💻 ML-enhanced predictions (currently collecting training data)",
|
| 22 |
+
"stops":"Stops",
|
| 23 |
+
"selected_stop": "Selected Stop"
|
| 24 |
+
},
|
| 25 |
+
"es": {
|
| 26 |
+
"title": "TUS Santander Tracker",
|
| 27 |
+
"subtitle": "Visualización de la red de transporte público",
|
| 28 |
+
"browse_tab": "Explorar",
|
| 29 |
+
"plan_tab": "Planificar",
|
| 30 |
+
"search_stop": "Buscar parada",
|
| 31 |
+
"search_placeholder": "Escribe el nombre o el número de parada...",
|
| 32 |
+
"click_info": "Pulsa en una parada del mapa o usa el buscador",
|
| 33 |
+
"scheduled_departures": "Próximas Salidas",
|
| 34 |
+
"no_departures": "No se encontraron salidas próximas para esta parada.",
|
| 35 |
+
"line": "Línea",
|
| 36 |
+
"destination": "Destino",
|
| 37 |
+
"time": "Hora",
|
| 38 |
+
"in": "En",
|
| 39 |
+
"min": "min",
|
| 40 |
+
"now": "Ahora",
|
| 41 |
+
"plan_trip": "Planifica tu Viaje",
|
| 42 |
+
"query_time": "Hora de consulta",
|
| 43 |
+
"query_time_help": "Mostrar horarios para esta hora del día",
|
| 44 |
+
"coming_soon": " PRÓXIMAMENTE:\n - 📍 Posiciones en vivo de autobuses, \n - ⏱️ Predicciones de llegada en tiempo real (estimaciones), \n - 💻 Predicciones mejoradas con ML (actualmente recopilando datos de entrenamiento)",
|
| 45 |
+
"stops":"Paradas",
|
| 46 |
+
"selected_stop": "Parada Seleccionada"
|
| 47 |
+
|
| 48 |
+
}
|
| 49 |
+
}
|
src/pulsetransit/collector.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# collector.py
|
| 2 |
+
import urllib.request
|
| 3 |
+
import json
|
| 4 |
+
from datetime import datetime, timezone
|
| 5 |
+
from pulsetransit.db import get_connection, init_db
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def fetch_json(dataset, rows=5000):
|
| 9 |
+
url = f"http://datos.santander.es/api/rest/datasets/{dataset}.json?rows={rows}"
|
| 10 |
+
with urllib.request.urlopen(url, timeout=30) as r:
|
| 11 |
+
return json.loads(r.read()).get("resources", [])
|
| 12 |
+
|
| 13 |
+
def collect_estimaciones(conn):
|
| 14 |
+
collected_at = datetime.now(timezone.utc).isoformat()
|
| 15 |
+
rows = fetch_json("control_flotas_estimaciones")
|
| 16 |
+
inserted = 0
|
| 17 |
+
for item in rows:
|
| 18 |
+
fech_actual = item.get("ayto:fechActual")
|
| 19 |
+
tiempo1 = item.get("ayto:tiempo1")
|
| 20 |
+
# Compute predicted arrival = fechActual + tiempo1 seconds
|
| 21 |
+
predicted_arrival = None
|
| 22 |
+
if fech_actual and tiempo1 is not None:
|
| 23 |
+
try:
|
| 24 |
+
from datetime import timedelta
|
| 25 |
+
t = datetime.fromisoformat(fech_actual.replace("Z", "+00:00"))
|
| 26 |
+
predicted_arrival = (t + timedelta(seconds=int(tiempo1))).isoformat()
|
| 27 |
+
except Exception:
|
| 28 |
+
pass
|
| 29 |
+
try:
|
| 30 |
+
conn.execute("""
|
| 31 |
+
INSERT OR IGNORE INTO estimaciones
|
| 32 |
+
(collected_at, parada_id, linea, fech_actual, tiempo1, tiempo2,
|
| 33 |
+
distancia1, distancia2, destino1, destino2, predicted_arrival)
|
| 34 |
+
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
| 35 |
+
""", (
|
| 36 |
+
collected_at,
|
| 37 |
+
item.get("ayto:paradaId"),
|
| 38 |
+
item.get("ayto:etiqLinea"),
|
| 39 |
+
fech_actual,
|
| 40 |
+
tiempo1,
|
| 41 |
+
item.get("ayto:tiempo2"),
|
| 42 |
+
item.get("ayto:distancia1"),
|
| 43 |
+
item.get("ayto:distancia2"),
|
| 44 |
+
item.get("ayto:destino1"),
|
| 45 |
+
item.get("ayto:destino2"),
|
| 46 |
+
predicted_arrival
|
| 47 |
+
))
|
| 48 |
+
inserted += conn.execute("SELECT changes()").fetchone()[0]
|
| 49 |
+
except Exception as e:
|
| 50 |
+
print(f" estimaciones insert error: {e}")
|
| 51 |
+
conn.commit()
|
| 52 |
+
print(f"[{collected_at}] estimaciones: {inserted} new rows from {len(rows)} fetched")
|
| 53 |
+
|
| 54 |
+
def collect_posiciones(conn):
|
| 55 |
+
collected_at = datetime.now(timezone.utc).isoformat()
|
| 56 |
+
rows = fetch_json("control_flotas_posiciones")
|
| 57 |
+
inserted = 0
|
| 58 |
+
for item in rows:
|
| 59 |
+
try:
|
| 60 |
+
conn.execute("""
|
| 61 |
+
INSERT OR IGNORE INTO posiciones
|
| 62 |
+
(collected_at, instante, vehiculo, linea, lat, lon, velocidad, estado)
|
| 63 |
+
VALUES (?,?,?,?,?,?,?,?)
|
| 64 |
+
""", (
|
| 65 |
+
collected_at,
|
| 66 |
+
item.get("ayto:instante"),
|
| 67 |
+
item.get("ayto:vehiculo"),
|
| 68 |
+
item.get("ayto:linea"),
|
| 69 |
+
item.get("wgs84_pos:lat"),
|
| 70 |
+
item.get("wgs84_pos:long"),
|
| 71 |
+
item.get("ayto:velocidad"),
|
| 72 |
+
item.get("ayto:estado"),
|
| 73 |
+
))
|
| 74 |
+
inserted += conn.execute("SELECT changes()").fetchone()[0]
|
| 75 |
+
except Exception as e:
|
| 76 |
+
print(f" posiciones insert error: {e}")
|
| 77 |
+
conn.commit()
|
| 78 |
+
print(f"[{collected_at}] posiciones: {inserted} new rows from {len(rows)} fetched")
|
| 79 |
+
|
| 80 |
+
if __name__ == "__main__":
|
| 81 |
+
import sys
|
| 82 |
+
conn = get_connection()
|
| 83 |
+
init_db(conn)
|
| 84 |
+
mode = sys.argv[1] if len(sys.argv) > 1 else "both"
|
| 85 |
+
if mode in ("estimaciones", "both"):
|
| 86 |
+
collect_estimaciones(conn)
|
| 87 |
+
if mode in ("posiciones", "both"):
|
| 88 |
+
collect_posiciones(conn)
|
| 89 |
+
conn.close()
|
src/pulsetransit/dashboard/app.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
import streamlit as st
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from pulsetransit.dashboard.map import (
|
| 8 |
+
build_map,
|
| 9 |
+
load_stops,
|
| 10 |
+
load_shapes,
|
| 11 |
+
load_trips,
|
| 12 |
+
load_routes,
|
| 13 |
+
)
|
| 14 |
+
from pulsetransit.dashboard.schedules import get_next_departures
|
| 15 |
+
from pulsetransit.cfg.config import LANG
|
| 16 |
+
|
| 17 |
+
def render_interactive_map(stops, shapes, trips, routes, highlight_stop_id=None, lang_code='es'):
|
| 18 |
+
"""Render map and handle click interactions"""
|
| 19 |
+
fig = build_map(
|
| 20 |
+
stops=stops,
|
| 21 |
+
shapes=shapes,
|
| 22 |
+
trips=trips,
|
| 23 |
+
routes=routes,
|
| 24 |
+
highlight_stop_id=highlight_stop_id,
|
| 25 |
+
lang_code=lang_code
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
selected_point = st.plotly_chart(
|
| 29 |
+
fig,
|
| 30 |
+
width='stretch',
|
| 31 |
+
on_select="rerun",
|
| 32 |
+
selection_mode="points",
|
| 33 |
+
key="transit_map",
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
if selected_point and "selection" in selected_point:
|
| 37 |
+
points = selected_point["selection"].get("points", [])
|
| 38 |
+
if points:
|
| 39 |
+
point = points[0]
|
| 40 |
+
clicked_lat = point["lat"]
|
| 41 |
+
clicked_lon = point["lon"]
|
| 42 |
+
|
| 43 |
+
stops["dist"] = (
|
| 44 |
+
(stops["stop_lat"] - clicked_lat) ** 2 +
|
| 45 |
+
(stops["stop_lon"] - clicked_lon) ** 2
|
| 46 |
+
)
|
| 47 |
+
nearest_stop = stops.loc[stops["dist"].idxmin()]
|
| 48 |
+
new_stop_id = int(nearest_stop["stop_id"])
|
| 49 |
+
|
| 50 |
+
if new_stop_id != st.session_state.clicked_stop_id:
|
| 51 |
+
st.session_state.clicked_stop_id = new_stop_id
|
| 52 |
+
st.rerun()
|
| 53 |
+
|
| 54 |
+
def display_stop_schedule(active_stop_id, stops, t):
|
| 55 |
+
"""Display schedule for a given stop"""
|
| 56 |
+
st.subheader(t["scheduled_departures"])
|
| 57 |
+
stop_info = stops[stops["stop_id"] == active_stop_id].iloc[0]
|
| 58 |
+
stop_name = stop_info["stop_name"]
|
| 59 |
+
|
| 60 |
+
st.markdown(f"**{active_stop_id} - {stop_name}**")
|
| 61 |
+
|
| 62 |
+
reference_dt = datetime.now()
|
| 63 |
+
departures = get_next_departures(active_stop_id, reference_dt, limit=10)
|
| 64 |
+
|
| 65 |
+
if not departures.empty:
|
| 66 |
+
departures["In"] = departures["minutes_until"].apply(
|
| 67 |
+
lambda m: f"{m} min" if m > 0 else "Now"
|
| 68 |
+
)
|
| 69 |
+
display = departures[[
|
| 70 |
+
"route_short_name",
|
| 71 |
+
"trip_headsign",
|
| 72 |
+
"departure_time",
|
| 73 |
+
"In"
|
| 74 |
+
]]
|
| 75 |
+
display.columns = [t["line"], t["destination"], t["time"], t["in"]]
|
| 76 |
+
|
| 77 |
+
st.dataframe(display, width='stretch', hide_index=True)
|
| 78 |
+
else:
|
| 79 |
+
st.info("No upcoming departures found for this stop.")
|
| 80 |
+
|
| 81 |
+
# Get language from query params
|
| 82 |
+
query_params = st.query_params
|
| 83 |
+
default_lang = query_params.get("lang", "es") # Default to Spanish
|
| 84 |
+
if default_lang not in ["en", "es"]:
|
| 85 |
+
default_lang = "es"
|
| 86 |
+
|
| 87 |
+
#Page config and language selector
|
| 88 |
+
st.set_page_config(page_title="PulseTransit - Santander TUS", layout="wide", page_icon="🚌")
|
| 89 |
+
|
| 90 |
+
st.markdown("""
|
| 91 |
+
<style>
|
| 92 |
+
|
| 93 |
+
/* Ensure the markdown (subtitle) doesn't have its own bottom margin */
|
| 94 |
+
.stMarkdown div p { margin-bottom: 0.5rem !important; }
|
| 95 |
+
|
| 96 |
+
/* Optional: fine-tune tab list position */
|
| 97 |
+
.stTabs [data-baseweb="tab-list"] { margin-top: -2.0rem !important; }
|
| 98 |
+
</style>
|
| 99 |
+
""", unsafe_allow_html=True)
|
| 100 |
+
|
| 101 |
+
# Language selector in header (Meteomat style)
|
| 102 |
+
col1, col2 = st.columns([6, 1], vertical_alignment="top")
|
| 103 |
+
with col2:
|
| 104 |
+
default_idx = 0 if default_lang == "en" else 1
|
| 105 |
+
lang = st.selectbox("🌐", ["🇬🇧 EN", "🇪🇸 ES"], index=default_idx, label_visibility="collapsed", key="lang_selector")
|
| 106 |
+
lang_code = "en" if "EN" in lang else "es"
|
| 107 |
+
|
| 108 |
+
# Update URL when language changes
|
| 109 |
+
if lang_code != default_lang:
|
| 110 |
+
st.query_params["lang"] = lang_code
|
| 111 |
+
|
| 112 |
+
# Get translations for current language
|
| 113 |
+
t = LANG[lang_code]
|
| 114 |
+
|
| 115 |
+
with col1:
|
| 116 |
+
st.title(f"🚌 {t['title']}")
|
| 117 |
+
st.markdown(f"**{t['subtitle']}** · Santander, España", unsafe_allow_html=True)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# Load GTFS data
|
| 121 |
+
stops = load_stops()
|
| 122 |
+
shapes = load_shapes()
|
| 123 |
+
trips = load_trips()
|
| 124 |
+
routes = load_routes()
|
| 125 |
+
|
| 126 |
+
# Initialize session state
|
| 127 |
+
if "clicked_stop_id" not in st.session_state:
|
| 128 |
+
st.session_state.clicked_stop_id = None
|
| 129 |
+
|
| 130 |
+
# MOBILE DETECTION
|
| 131 |
+
try:
|
| 132 |
+
from streamlit_js_eval import streamlit_js_eval
|
| 133 |
+
screen_width = streamlit_js_eval(js_expressions='window.innerWidth', key='WIDTH')
|
| 134 |
+
is_mobile = screen_width and screen_width < 768
|
| 135 |
+
except:
|
| 136 |
+
is_mobile = False
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
# TABS: Browse vs Plan
|
| 141 |
+
tab_browse, tab_plan = st.tabs([f"📅 {t['browse_tab']}", f"🚏 {t['plan_tab']}"])
|
| 142 |
+
|
| 143 |
+
with tab_browse:
|
| 144 |
+
# SEARCH ABOVE MAP
|
| 145 |
+
stops["search_label"] = stops["stop_id"].astype(str) + " - " + stops["stop_name"]
|
| 146 |
+
stop_options = [""] + stops["search_label"].tolist()
|
| 147 |
+
st.info(f"👆 {t['click_info']}")
|
| 148 |
+
|
| 149 |
+
selected_stop_label = st.selectbox(
|
| 150 |
+
t["search_stop"],
|
| 151 |
+
options=stop_options,
|
| 152 |
+
index=None,
|
| 153 |
+
placeholder=t["search_placeholder"],
|
| 154 |
+
label_visibility='collapsed'
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
# Parse selected stop
|
| 158 |
+
selected_stop_id = None
|
| 159 |
+
if selected_stop_label:
|
| 160 |
+
selected_stop_id = int(selected_stop_label.split(" - ")[0])
|
| 161 |
+
|
| 162 |
+
# Determine active stop: search bar > map click
|
| 163 |
+
if selected_stop_id:
|
| 164 |
+
active_stop_id = selected_stop_id
|
| 165 |
+
st.session_state.clicked_stop_id = None
|
| 166 |
+
else:
|
| 167 |
+
active_stop_id = st.session_state.clicked_stop_id
|
| 168 |
+
|
| 169 |
+
# RESPONSIVE LAYOUT
|
| 170 |
+
if is_mobile:
|
| 171 |
+
# Mobile: Schedules FIRST, then map
|
| 172 |
+
if active_stop_id: display_stop_schedule(active_stop_id, stops, t)
|
| 173 |
+
|
| 174 |
+
# Map schedules on mobile
|
| 175 |
+
render_interactive_map(stops, shapes, trips, routes, highlight_stop_id=active_stop_id, lang_code=lang_code)
|
| 176 |
+
|
| 177 |
+
else:
|
| 178 |
+
# Desktop: Full-width map until stop is selected
|
| 179 |
+
if active_stop_id:
|
| 180 |
+
# Stop selected: 2-column layout
|
| 181 |
+
col1, col2 = st.columns([2, 1])
|
| 182 |
+
|
| 183 |
+
with col1:
|
| 184 |
+
render_interactive_map(stops, shapes, trips, routes, highlight_stop_id=active_stop_id, lang_code=lang_code)
|
| 185 |
+
|
| 186 |
+
with col2:
|
| 187 |
+
display_stop_schedule(active_stop_id, stops, t)
|
| 188 |
+
|
| 189 |
+
else:
|
| 190 |
+
render_interactive_map(stops, shapes, trips, routes, highlight_stop_id=None , lang_code=lang_code)
|
| 191 |
+
|
| 192 |
+
with tab_plan:
|
| 193 |
+
st.subheader(t["plan_trip"])
|
| 194 |
+
|
| 195 |
+
# Query time
|
| 196 |
+
query_time = st.time_input(
|
| 197 |
+
t["query_time"],
|
| 198 |
+
value=datetime.now().time(),
|
| 199 |
+
help="Show schedules for this time of day"
|
| 200 |
+
)
|
| 201 |
+
st.info(t["coming_soon"])
|
src/pulsetransit/dashboard/map.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import plotly.graph_objects as go
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from pulsetransit.cfg.config import LANG
|
| 5 |
+
SANTANDER = dict(lat=43.4623, lon=-3.8099)
|
| 6 |
+
GTFS_DIR = Path("data/gtfs-static")
|
| 7 |
+
|
| 8 |
+
def load_stops() -> pd.DataFrame:
|
| 9 |
+
return pd.read_csv(GTFS_DIR / "stops.txt")
|
| 10 |
+
|
| 11 |
+
def load_shapes() -> pd.DataFrame:
|
| 12 |
+
return pd.read_csv(GTFS_DIR / "shapes.txt")
|
| 13 |
+
|
| 14 |
+
def load_routes() -> pd.DataFrame:
|
| 15 |
+
return pd.read_csv(GTFS_DIR / "routes.txt")
|
| 16 |
+
|
| 17 |
+
def load_trips() -> pd.DataFrame:
|
| 18 |
+
return pd.read_csv(GTFS_DIR / "trips.txt")
|
| 19 |
+
|
| 20 |
+
def _build_shape_colors(trips: pd.DataFrame, routes: pd.DataFrame) -> dict:
|
| 21 |
+
"""Map shape_id → (route_short_name, #rrggbb color)."""
|
| 22 |
+
shape_route = trips[["shape_id", "route_id"]].drop_duplicates("shape_id")
|
| 23 |
+
merged = shape_route.merge(
|
| 24 |
+
routes[["route_id", "route_short_name", "route_color"]],
|
| 25 |
+
on="route_id"
|
| 26 |
+
)
|
| 27 |
+
merged["color"] = merged["route_color"].apply(
|
| 28 |
+
lambda c: f"#{c}" if pd.notna(c) and str(c).strip() else "#888888"
|
| 29 |
+
)
|
| 30 |
+
return merged.set_index("shape_id")[["route_short_name", "color"]].to_dict("index")
|
| 31 |
+
|
| 32 |
+
def _shapes_to_lines_colored(
|
| 33 |
+
shapes: pd.DataFrame,
|
| 34 |
+
shape_colors: dict,
|
| 35 |
+
lang_code: str,
|
| 36 |
+
) -> list[dict]:
|
| 37 |
+
"""One trace per route, with None separators within each."""
|
| 38 |
+
route_groups: dict[str, dict] = {}
|
| 39 |
+
t = LANG[lang_code]
|
| 40 |
+
|
| 41 |
+
for shape_id, group in shapes.groupby("shape_id", sort=False):
|
| 42 |
+
info = shape_colors.get(shape_id)
|
| 43 |
+
if info is None:
|
| 44 |
+
continue
|
| 45 |
+
|
| 46 |
+
color = info["color"]
|
| 47 |
+
name = info["route_short_name"]
|
| 48 |
+
if name == "99": continue # remove 99 lanzadera from list
|
| 49 |
+
# Group by route NAME (not color)
|
| 50 |
+
if name not in route_groups:
|
| 51 |
+
route_groups[name] = {
|
| 52 |
+
"name": f"{t['line']} {name}",
|
| 53 |
+
"color": color,
|
| 54 |
+
"lats": [],
|
| 55 |
+
"lons": []
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
pts = group.sort_values("shape_pt_sequence")
|
| 59 |
+
route_groups[name]["lats"].extend(pts["shape_pt_lat"].tolist() + [None])
|
| 60 |
+
route_groups[name]["lons"].extend(pts["shape_pt_lon"].tolist() + [None])
|
| 61 |
+
|
| 62 |
+
# Sort: LC first, then by route number
|
| 63 |
+
def sort_key(item):
|
| 64 |
+
name_key, data = item
|
| 65 |
+
|
| 66 |
+
if name_key == "LC":
|
| 67 |
+
return (0, 0, "")
|
| 68 |
+
|
| 69 |
+
if name_key[0].isdigit():
|
| 70 |
+
num_part = ""
|
| 71 |
+
for char in name_key:
|
| 72 |
+
if char.isdigit():
|
| 73 |
+
num_part += char
|
| 74 |
+
else:
|
| 75 |
+
break
|
| 76 |
+
|
| 77 |
+
if num_part == name_key:
|
| 78 |
+
return (1, int(num_part), "")
|
| 79 |
+
else:
|
| 80 |
+
suffix = name_key[len(num_part):]
|
| 81 |
+
return (1, int(num_part), suffix)
|
| 82 |
+
|
| 83 |
+
return (2, 0, name_key)
|
| 84 |
+
|
| 85 |
+
sorted_groups = sorted(route_groups.items(), key=sort_key)
|
| 86 |
+
|
| 87 |
+
return [{"color": v["color"], "name": v["name"], "lats": v["lats"], "lons": v["lons"]} for k, v in sorted_groups]
|
| 88 |
+
|
| 89 |
+
def _extract_arrow_points(shapes: pd.DataFrame, shape_colors: dict, interval: int = 15) -> list[dict]:
|
| 90 |
+
"""Extract evenly-spaced arrow markers along each shape."""
|
| 91 |
+
arrows = []
|
| 92 |
+
for shape_id, group in shapes.groupby("shape_id", sort=False):
|
| 93 |
+
info = shape_colors.get(shape_id, {"route_short_name": "?", "color": "#888888"})
|
| 94 |
+
pts = group.sort_values("shape_pt_sequence")
|
| 95 |
+
|
| 96 |
+
# Sample every Nth point for arrows
|
| 97 |
+
sampled = pts.iloc[::interval]
|
| 98 |
+
if len(sampled) < 2:
|
| 99 |
+
continue
|
| 100 |
+
|
| 101 |
+
# Calculate bearing from current point to next point
|
| 102 |
+
lats, lons, angles = [], [], []
|
| 103 |
+
for i in range(len(sampled) - 1):
|
| 104 |
+
lat1, lon1 = sampled.iloc[i][["shape_pt_lat", "shape_pt_lon"]]
|
| 105 |
+
lat2, lon2 = sampled.iloc[i+1][["shape_pt_lat", "shape_pt_lon"]]
|
| 106 |
+
|
| 107 |
+
# Simple angle calculation (good enough for small distances)
|
| 108 |
+
import math
|
| 109 |
+
dlat = lat2 - lat1
|
| 110 |
+
dlon = lon2 - lon1
|
| 111 |
+
angle = math.degrees(math.atan2(dlon, dlat))
|
| 112 |
+
|
| 113 |
+
lats.append(lat1)
|
| 114 |
+
lons.append(lon1)
|
| 115 |
+
angles.append(angle)
|
| 116 |
+
|
| 117 |
+
arrows.append({
|
| 118 |
+
"lats": lats,
|
| 119 |
+
"lons": lons,
|
| 120 |
+
"angles": angles,
|
| 121 |
+
"color": info["color"],
|
| 122 |
+
"name": info["route_short_name"],
|
| 123 |
+
})
|
| 124 |
+
return arrows
|
| 125 |
+
|
| 126 |
+
def build_map(
|
| 127 |
+
stops: pd.DataFrame | None = None,
|
| 128 |
+
shapes: pd.DataFrame | None = None,
|
| 129 |
+
trips: pd.DataFrame | None = None,
|
| 130 |
+
routes: pd.DataFrame | None = None,
|
| 131 |
+
highlight_stop_id: int | None = None,
|
| 132 |
+
lang_code: str = 'es',
|
| 133 |
+
) -> go.Figure:
|
| 134 |
+
fig = go.Figure()
|
| 135 |
+
|
| 136 |
+
if shapes is not None and trips is not None and routes is not None:
|
| 137 |
+
shape_colors = _build_shape_colors(trips, routes)
|
| 138 |
+
|
| 139 |
+
# Route lines
|
| 140 |
+
for trace in _shapes_to_lines_colored(shapes, shape_colors, lang_code):
|
| 141 |
+
fig.add_trace(go.Scattermap(
|
| 142 |
+
lat=trace["lats"],
|
| 143 |
+
lon=trace["lons"],
|
| 144 |
+
mode="lines",
|
| 145 |
+
line=dict(width=3, color=trace["color"]),
|
| 146 |
+
hoverinfo="skip",
|
| 147 |
+
name=trace["name"],
|
| 148 |
+
showlegend=True,
|
| 149 |
+
))
|
| 150 |
+
|
| 151 |
+
# Direction arrows
|
| 152 |
+
for arrow in _extract_arrow_points(shapes, shape_colors):
|
| 153 |
+
fig.add_trace(go.Scattermap(
|
| 154 |
+
lat=arrow["lats"],
|
| 155 |
+
lon=arrow["lons"],
|
| 156 |
+
mode="markers",
|
| 157 |
+
marker=dict(
|
| 158 |
+
size=8,
|
| 159 |
+
color=arrow["color"],
|
| 160 |
+
symbol="arrow",
|
| 161 |
+
angle=arrow["angles"],
|
| 162 |
+
),
|
| 163 |
+
hoverinfo="skip",
|
| 164 |
+
showlegend=False,
|
| 165 |
+
))
|
| 166 |
+
|
| 167 |
+
elif shapes is not None:
|
| 168 |
+
# Fallback: no color info
|
| 169 |
+
lats, lons = [], []
|
| 170 |
+
for _, group in shapes.groupby("shape_id", sort=False):
|
| 171 |
+
pts = group.sort_values("shape_pt_sequence")
|
| 172 |
+
lats.extend(pts["shape_pt_lat"].tolist() + [None])
|
| 173 |
+
lons.extend(pts["shape_pt_lon"].tolist() + [None])
|
| 174 |
+
fig.add_trace(go.Scattermap(
|
| 175 |
+
lat=lats, lon=lons, mode="lines",
|
| 176 |
+
line=dict(width=3, color="#888888"),
|
| 177 |
+
hoverinfo="skip", name="Routes",
|
| 178 |
+
))
|
| 179 |
+
t= LANG["es"]
|
| 180 |
+
if stops is not None:
|
| 181 |
+
# Separate highlighted stop from others
|
| 182 |
+
if highlight_stop_id is not None:
|
| 183 |
+
regular_stops = stops[stops["stop_id"] != highlight_stop_id]
|
| 184 |
+
highlighted = stops[stops["stop_id"] == highlight_stop_id]
|
| 185 |
+
else:
|
| 186 |
+
regular_stops = stops
|
| 187 |
+
highlighted = pd.DataFrame()
|
| 188 |
+
|
| 189 |
+
if not regular_stops.empty:
|
| 190 |
+
# Dark border layer
|
| 191 |
+
fig.add_trace(go.Scattermap(
|
| 192 |
+
lat=regular_stops["stop_lat"],
|
| 193 |
+
lon=regular_stops["stop_lon"],
|
| 194 |
+
mode="markers",
|
| 195 |
+
marker=dict(size=10, color="#333333", opacity=0.8),
|
| 196 |
+
hoverinfo="skip",
|
| 197 |
+
showlegend=False,
|
| 198 |
+
))
|
| 199 |
+
# Visible inner circle
|
| 200 |
+
fig.add_trace(go.Scattermap(
|
| 201 |
+
lat=regular_stops["stop_lat"],
|
| 202 |
+
lon=regular_stops["stop_lon"],
|
| 203 |
+
mode="markers",
|
| 204 |
+
marker=dict(size=7, color="#B8B6B6", opacity=1.0),
|
| 205 |
+
text=regular_stops["stop_id"].astype(str) + " - " + regular_stops["stop_name"],
|
| 206 |
+
hovertemplate="<b>%{text}</b><extra></extra>", # ← simplified
|
| 207 |
+
name=t["stops"],
|
| 208 |
+
))
|
| 209 |
+
# Highlighted stop (larger, bright color)
|
| 210 |
+
if not highlighted.empty:
|
| 211 |
+
fig.add_trace(go.Scattermap(
|
| 212 |
+
lat=highlighted["stop_lat"],
|
| 213 |
+
lon=highlighted["stop_lon"],
|
| 214 |
+
mode="markers",
|
| 215 |
+
marker=dict(size=16, color="#FF4444", opacity=0.9),
|
| 216 |
+
hoverinfo="skip",
|
| 217 |
+
showlegend=False,
|
| 218 |
+
))
|
| 219 |
+
fig.add_trace(go.Scattermap(
|
| 220 |
+
lat=highlighted["stop_lat"],
|
| 221 |
+
lon=highlighted["stop_lon"],
|
| 222 |
+
mode="markers",
|
| 223 |
+
marker=dict(size=12, color="white", opacity=1.0),
|
| 224 |
+
text=highlighted["stop_id"].astype(str) + " - " + highlighted["stop_name"],
|
| 225 |
+
hovertemplate="<b>%{text}</b><extra></extra>",
|
| 226 |
+
name=t["selected_stop"],
|
| 227 |
+
))
|
| 228 |
+
|
| 229 |
+
# Zoom to highlighted stop
|
| 230 |
+
center = dict(
|
| 231 |
+
lat=highlighted["stop_lat"].iloc[0],
|
| 232 |
+
lon=highlighted["stop_lon"].iloc[0]
|
| 233 |
+
)
|
| 234 |
+
zoom = 16 # Closer zoom
|
| 235 |
+
else:
|
| 236 |
+
center = SANTANDER
|
| 237 |
+
zoom = 13
|
| 238 |
+
else:
|
| 239 |
+
center = SANTANDER
|
| 240 |
+
zoom = 13
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
fig.update_layout(
|
| 244 |
+
map=dict(
|
| 245 |
+
style="open-street-map",
|
| 246 |
+
center=center,
|
| 247 |
+
zoom=zoom,
|
| 248 |
+
),
|
| 249 |
+
legend=dict(bgcolor="rgba(255,255,255,0.8)", borderwidth=1),
|
| 250 |
+
margin=dict(l=0, r=0, t=0, b=0),
|
| 251 |
+
height=600,
|
| 252 |
+
)
|
| 253 |
+
return fig
|
| 254 |
+
|
| 255 |
+
if __name__ == "__main__":
|
| 256 |
+
build_map(
|
| 257 |
+
stops=load_stops(),
|
| 258 |
+
shapes=load_shapes(),
|
| 259 |
+
trips=load_trips(),
|
| 260 |
+
routes=load_routes(),
|
| 261 |
+
).show()
|
src/pulsetransit/dashboard/schedules.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from datetime import datetime, time, timedelta
|
| 4 |
+
|
| 5 |
+
GTFS_DIR = Path("data/gtfs-static")
|
| 6 |
+
|
| 7 |
+
def load_stop_times() -> pd.DataFrame:
|
| 8 |
+
return pd.read_csv(GTFS_DIR / "stop_times.txt")
|
| 9 |
+
|
| 10 |
+
def load_trips() -> pd.DataFrame:
|
| 11 |
+
return pd.read_csv(GTFS_DIR / "trips.txt")
|
| 12 |
+
|
| 13 |
+
def load_routes() -> pd.DataFrame:
|
| 14 |
+
return pd.read_csv(GTFS_DIR / "routes.txt")
|
| 15 |
+
|
| 16 |
+
def load_calendar_dates() -> pd.DataFrame:
|
| 17 |
+
return pd.read_csv(GTFS_DIR / "calendar_dates.txt")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _parse_gtfs_time(time_str: str) -> int:
|
| 21 |
+
"""Convert GTFS time string (HH:MM:SS, possibly >24h) to minutes since midnight."""
|
| 22 |
+
h, m, s = map(int, time_str.split(":"))
|
| 23 |
+
return h * 60 + m
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def get_next_departures(
|
| 27 |
+
stop_id: int,
|
| 28 |
+
reference_datetime: datetime,
|
| 29 |
+
limit: int = 10
|
| 30 |
+
) -> pd.DataFrame:
|
| 31 |
+
"""
|
| 32 |
+
Get next N scheduled departures for a stop after reference_datetime.
|
| 33 |
+
|
| 34 |
+
Returns DataFrame with columns:
|
| 35 |
+
- route_short_name: Line number/name
|
| 36 |
+
- trip_headsign: Destination
|
| 37 |
+
- departure_time: Original GTFS time string
|
| 38 |
+
- minutes_until: Minutes from now until departure
|
| 39 |
+
"""
|
| 40 |
+
# Load data
|
| 41 |
+
stop_times = load_stop_times()
|
| 42 |
+
trips = load_trips()
|
| 43 |
+
routes = load_routes()
|
| 44 |
+
calendar = load_calendar_dates()
|
| 45 |
+
|
| 46 |
+
# Filter by stop
|
| 47 |
+
stop_schedule = stop_times[stop_times["stop_id"] == stop_id].copy()
|
| 48 |
+
|
| 49 |
+
# Join to get route and service info
|
| 50 |
+
stop_schedule = stop_schedule.merge(trips, on="trip_id")
|
| 51 |
+
stop_schedule = stop_schedule.merge(
|
| 52 |
+
routes[["route_id", "route_short_name", "route_color"]],
|
| 53 |
+
on="route_id"
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
# Filter by service active on reference date
|
| 57 |
+
date_str = reference_datetime.strftime("%Y%m%d")
|
| 58 |
+
active_services = calendar[
|
| 59 |
+
(calendar["date"] == int(date_str)) & (calendar["exception_type"] == 1)
|
| 60 |
+
]["service_id"].unique()
|
| 61 |
+
|
| 62 |
+
stop_schedule = stop_schedule[stop_schedule["service_id"].isin(active_services)]
|
| 63 |
+
|
| 64 |
+
# Convert times to minutes since midnight
|
| 65 |
+
stop_schedule["departure_minutes"] = stop_schedule["departure_time"].apply(_parse_gtfs_time)
|
| 66 |
+
reference_minutes = reference_datetime.hour * 60 + reference_datetime.minute
|
| 67 |
+
|
| 68 |
+
# Handle overnight times: if departure > 24h, it's for "tomorrow" in GTFS terms
|
| 69 |
+
# but we're querying for "today", so skip those unless we're past midnight
|
| 70 |
+
stop_schedule["minutes_until"] = stop_schedule["departure_minutes"] - reference_minutes
|
| 71 |
+
|
| 72 |
+
# Filter future departures only
|
| 73 |
+
upcoming = stop_schedule[stop_schedule["minutes_until"] >= 0].copy()
|
| 74 |
+
|
| 75 |
+
# Sort and limit
|
| 76 |
+
upcoming = upcoming.sort_values("minutes_until").head(limit)
|
| 77 |
+
|
| 78 |
+
return upcoming[[
|
| 79 |
+
"route_short_name",
|
| 80 |
+
"trip_headsign",
|
| 81 |
+
"departure_time",
|
| 82 |
+
"minutes_until"
|
| 83 |
+
]]
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
# Test with a real stop
|
| 88 |
+
stop_times = load_stop_times()
|
| 89 |
+
sample_stop = stop_times["stop_id"].iloc[0]
|
| 90 |
+
|
| 91 |
+
print(f"Testing stop_id: {sample_stop}")
|
| 92 |
+
|
| 93 |
+
# Test at 10:00 AM today
|
| 94 |
+
test_time = datetime.now().replace(hour=10, minute=0, second=0, microsecond=0)
|
| 95 |
+
|
| 96 |
+
departures = get_next_departures(sample_stop, test_time, limit=5)
|
| 97 |
+
print(f"\nNext 5 departures from stop {sample_stop} after {test_time.strftime('%H:%M')}:")
|
| 98 |
+
print(departures.to_string(index=False))
|
src/pulsetransit/db.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/pulsetransit/db.py
|
| 2 |
+
import sqlite3
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
DB_PATH = Path(__file__).parent.parent.parent / "data" / "tus.db"
|
| 6 |
+
|
| 7 |
+
def get_connection():
|
| 8 |
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 9 |
+
conn = sqlite3.connect(DB_PATH)
|
| 10 |
+
conn.row_factory = sqlite3.Row
|
| 11 |
+
return conn
|
| 12 |
+
|
| 13 |
+
def init_db(conn):
|
| 14 |
+
conn.execute("""
|
| 15 |
+
CREATE TABLE IF NOT EXISTS estimaciones (
|
| 16 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 17 |
+
collected_at TEXT NOT NULL,
|
| 18 |
+
parada_id INTEGER,
|
| 19 |
+
linea TEXT,
|
| 20 |
+
fech_actual TEXT,
|
| 21 |
+
tiempo1 INTEGER,
|
| 22 |
+
tiempo2 INTEGER,
|
| 23 |
+
distancia1 INTEGER,
|
| 24 |
+
distancia2 INTEGER,
|
| 25 |
+
destino1 TEXT,
|
| 26 |
+
destino2 TEXT,
|
| 27 |
+
predicted_arrival TEXT,
|
| 28 |
+
UNIQUE(parada_id, linea, fech_actual)
|
| 29 |
+
)
|
| 30 |
+
""")
|
| 31 |
+
conn.execute("""
|
| 32 |
+
CREATE TABLE IF NOT EXISTS posiciones (
|
| 33 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 34 |
+
collected_at TEXT NOT NULL,
|
| 35 |
+
instante TEXT NOT NULL,
|
| 36 |
+
vehiculo INTEGER,
|
| 37 |
+
linea INTEGER,
|
| 38 |
+
lat REAL,
|
| 39 |
+
lon REAL,
|
| 40 |
+
velocidad INTEGER,
|
| 41 |
+
estado INTEGER,
|
| 42 |
+
UNIQUE(vehiculo, instante)
|
| 43 |
+
)
|
| 44 |
+
""")
|
| 45 |
+
conn.commit()
|
src/pulsetransit/validate.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import sys
|
| 3 |
+
from datetime import datetime, timezone, timedelta
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
DB_PATH = Path(__file__).parent.parent.parent / "data" / "tus.db"
|
| 7 |
+
MAX_AGE_HOURS = 2 # fail if no data collected in last 2 hours
|
| 8 |
+
|
| 9 |
+
def check_table(conn, table, time_col):
|
| 10 |
+
row = conn.execute(f"SELECT COUNT(*), MAX({time_col}) FROM {table}").fetchone()
|
| 11 |
+
count, latest = row[0], row[1]
|
| 12 |
+
if not latest:
|
| 13 |
+
print(f" FAIL — {table}: no data at all")
|
| 14 |
+
return False
|
| 15 |
+
# Normalise timestamp
|
| 16 |
+
latest_dt = datetime.fromisoformat(latest.replace("Z", "+00:00"))
|
| 17 |
+
age = datetime.now(timezone.utc) - latest_dt
|
| 18 |
+
ok = age < timedelta(hours=MAX_AGE_HOURS)
|
| 19 |
+
status = "OK" if ok else "FAIL"
|
| 20 |
+
print(f" {status} — {table}: {count} rows, latest {latest_dt.strftime('%H:%M UTC')} ({age.seconds//60} min ago)")
|
| 21 |
+
return ok
|
| 22 |
+
|
| 23 |
+
conn = sqlite3.connect(DB_PATH)
|
| 24 |
+
print(f"Validating at {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}")
|
| 25 |
+
results = [
|
| 26 |
+
check_table(conn, "estimaciones", "collected_at"),
|
| 27 |
+
check_table(conn, "posiciones", "instante"),
|
| 28 |
+
]
|
| 29 |
+
conn.close()
|
| 30 |
+
|
| 31 |
+
if not all(results):
|
| 32 |
+
print("Validation FAILED")
|
| 33 |
+
sys.exit(1)
|
| 34 |
+
|
| 35 |
+
print("Validation PASSED")
|