A bike sensor data visualization tool that processes GPS and wheel rotation data to display bike trips with speed-colored routes on an interactive map.
This project takes raw CSV files from bike sensors and transforms them into:
- Individual trip visualizations with speed-colored segments
- Interactive web visualization powered by MapLibre GL JS
Reflector-Ride-Maps/
├── csv_data/ # Raw CSV files from sensors (you create this)
├── sensor_data/ # Cleaned GeoJSON files (generated)
├── processed_sensor_data/ # Speed-calculated trips (generated)
├── trips.pmtiles # Compressed trip data for map
├── csv_to_geojson_converter.py # Step 1: Convert CSVs to GeoJSON
├── combined_processor.py # Step 2: Calculate speeds from sensor data
├── build_pmtiles.py # Step 3: Build PMTiles for web
├── index.html # Main visualization page
├── app.js # Map logic and interactions
├── config.js # Configuration (uses .env)
└── styles.css # Styling
- Python 3.x for data processing
- Tippecanoe for PMTiles generation:
brew install tippecanoe # macOS
Place your CSV files in a csv_data/ folder, then run:
python csv_to_geojson_converter.pyWhat it does:
- Reads CSV files with GPS coordinates and sensor data
- Converts to GeoJSON format with LineString geometries
- Organizes by sensor ID (e.g.,
602B3,604F0) - Extracts metadata from CSV footers
- Output:
sensor_data/{sensor_id}/{sensor_id}_Trip{N}_clean.geojson
Input CSV format:
latitude,longitude,HH:mm:ss,SSS,marker,HRot Count,Samples,Speed
52.3644,4.9130,14:23:45,123,2,100,1000,
52.3645,4.9131,14:23:46,123,3,102,1050,234
...
Bike: Trek 820
Distance: 5.2 km
python integrated_processor.pyWhat it does:
- Reads cleaned GeoJSON files from
sensor_data/ - Calculates speed using wheel rotation (HRot) data:
- Uses 711mm wheel diameter
- Formula:
speed = (wheel_rotations × circumference) / time
- Creates line segments only where the wheel actually moved
- Filters out stopped periods and anomalies
- Output:
processed_sensor_data/{sensor_id}_Trip{N}_processed.geojson
Key calculations:
- Wheel circumference: ~2.073 meters
- Sample rate: 50 Hz (0.02 seconds per sample)
- Speed cap: 50 km/h (to filter unrealistic values)
Properties added:
Speed: km/h calculated from wheel rotationhrot_diff: Wheel rotation differencetime_diff_s: Time between pointsgps_distance_m: GPS distance (for validation)
python build_pmtiles.pyWhat it does:
- Uses Tippecanoe to compress processed GeoJSON into PMTiles format
- PMTiles = efficient vector tiles for web maps
- Preserves
Speed,marker, andtrip_idproperties - Output:
trips.pmtiles
Why PMTiles?
- Efficient: ~90% smaller than GeoJSON
- Fast: Only loads visible tiles
- Standard: Works with MapLibre/Mapbox
Visit https://tomvanarman.github.io/Reflector-Ride-Maps/
Individual Trips:
- ✅ View all trips or filter by specific trip
- 🎨 Color segments by speed (gradient or categories)
- 🖱️ Click segments to see speed details
Speed Legend:
- 🟦 Gray: Stopped (0-2 km/h)
- 🔴 Red: Very Slow (2-5 km/h)
- 🟠 Orange: Slow (5-10 km/h)
- 🟡 Yellow: Moderate (10-15 km/h)
- 🟢 Green: Fast (15-20 km/h)
- 🟢 Dark Green: Very Fast (20-25 km/h)
- 🟢 Darkest Green: Extreme (25-30 km/h)
- 🟩 Super Fast (30+ km/h)
csv_to_geojson_converter.py: Converts raw sensor CSVs to GeoJSON LineStringscombined_processor.py: Calculates speeds from wheel rotation data (HRot)build_pmtiles.py: Compresses GeoJSON into PMTiles using Tippecanoe
index.html: Main webpage with map container and controlsapp.js: Map initialization, data loading, speed coloring, click handlersconfig.js: Configuration (Mapbox token, file paths, map style)styles.css: UI styling for controls, legend, and stats panel
WHEEL_DIAMETER_MM = 711
WHEEL_CIRCUMFERENCE_M = (660 / 1000) * math.pi # ~2.073mMAP_CENTER: [4.9, 52.37], // Amsterdam area [lon, lat]
MAP_ZOOM: 11, // Initial zoom level
MAP_STYLE: 'https://...' // CartoDB Dark Matter styleModify the getSpeedColorExpression() function to adjust color thresholds.
- Check that
Speedproperty exists in your processed GeoJSON - Verify wheel diameter is correct for your bike
- Run:
python build_pmtiles.pyto rebuild with-y Speedflag
- Check browser console for errors
- Ensure
trips.pmtilesexists - Check file paths in
config.js - Verify trips have coordinates in Amsterdam area
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Commit changes:
git commit -am 'Add feature' - Push to branch:
git push origin feature-name - Submit a Pull Request
ISC License - See package.json for details
- MapLibre GL JS: Open-source mapping library
- Tippecanoe: PMTiles generation by Mapbox
- CartoDB: Free basemap styles
For questions or issues, please open a GitHub issue or contact the maintainers.
Happy mapping! 🚴♀️🗺️