361 lines
8.3 KiB
Markdown
361 lines
8.3 KiB
Markdown
# CLAUDE.md - Mapbox Globe Iteration 8
|
|
|
|
## Project Context
|
|
|
|
This is iteration 8 in a progressive web-enhanced learning series for Mapbox GL JS globe visualizations. This iteration focuses on **advanced point clustering** for large datasets, demonstrating educational infrastructure distribution globally.
|
|
|
|
## Running the Application
|
|
|
|
### Local Development Server
|
|
|
|
```bash
|
|
# From the mapbox_globe_8/ directory
|
|
|
|
# Python 3
|
|
python3 -m http.server 8000
|
|
|
|
# Python 2
|
|
python -m SimpleHTTPServer 8000
|
|
|
|
# Node.js
|
|
npx http-server -p 8000
|
|
|
|
# PHP
|
|
php -S localhost:8000
|
|
```
|
|
|
|
Then open: `http://localhost:8000`
|
|
|
|
## Code Architecture
|
|
|
|
### File Structure
|
|
```
|
|
mapbox_globe_8/
|
|
├── index.html # UI, overlays, styling
|
|
├── src/
|
|
│ ├── index.js # Map initialization, clustering logic
|
|
│ └── data/
|
|
│ └── data.js # 650-school GeoJSON dataset
|
|
├── README.md
|
|
└── CLAUDE.md # This file
|
|
```
|
|
|
|
### Key Components
|
|
|
|
1. **Clustering Configuration** (`src/index.js`)
|
|
- Source setup with cluster parameters
|
|
- `clusterMaxZoom: 14` - stops clustering at zoom 14
|
|
- `clusterRadius: 50` - 50px aggregation radius
|
|
|
|
2. **Layer System**
|
|
- `clusters` - Aggregated cluster circles
|
|
- `cluster-count` - Point count labels
|
|
- `unclustered-point` - Individual schools
|
|
- `resource-rings` - Resource level indicators
|
|
|
|
3. **Data Structure** (`src/data/data.js`)
|
|
- GeoJSON FeatureCollection
|
|
- 311 educational facilities
|
|
- Properties: name, type, students, teachers, ratio, resources, internet
|
|
|
|
## Web Research Integration
|
|
|
|
### Source URL
|
|
https://docs.mapbox.com/mapbox-gl-js/example/cluster/
|
|
|
|
### Techniques Learned & Applied
|
|
|
|
1. **Cluster Source Configuration**
|
|
```javascript
|
|
{
|
|
cluster: true,
|
|
clusterMaxZoom: 14,
|
|
clusterRadius: 50,
|
|
generateId: true
|
|
}
|
|
```
|
|
|
|
2. **Step-Based Cluster Styling**
|
|
```javascript
|
|
'circle-color': [
|
|
'step',
|
|
['get', 'point_count'],
|
|
'#51bbd6', 50,
|
|
'#f1f075', 150,
|
|
'#f28cb1'
|
|
]
|
|
```
|
|
|
|
3. **Cluster Expansion Interaction**
|
|
```javascript
|
|
map.getSource('schools').getClusterExpansionZoom(
|
|
clusterId,
|
|
(err, zoom) => {
|
|
map.easeTo({ center: coords, zoom: zoom + 0.5 });
|
|
}
|
|
);
|
|
```
|
|
|
|
## Mapbox Best Practices Applied
|
|
|
|
### Performance Optimization
|
|
- **Clustering**: Reduces 311 points to manageable clusters
|
|
- **Layer Filtering**: Separate clustered/unclustered layers
|
|
- **Generate ID**: Enables efficient feature queries
|
|
- **Zoom-based Styling**: Adapts circle sizes to zoom level
|
|
|
|
### Visual Hierarchy
|
|
- Cluster size indicates density
|
|
- Color indicates cluster magnitude
|
|
- Individual schools show type and resource level
|
|
- Progressive disclosure through zoom
|
|
|
|
### Interactivity
|
|
- Click clusters to expand (learned from web research)
|
|
- Click schools for detailed popups
|
|
- Hover cursor changes
|
|
- Keyboard navigation support
|
|
|
|
## Code Patterns
|
|
|
|
### Mapbox Expression Syntax
|
|
|
|
**Step Expression** (thresholds):
|
|
```javascript
|
|
['step', ['get', 'property'],
|
|
value1, threshold1,
|
|
value2, threshold2,
|
|
value3
|
|
]
|
|
```
|
|
|
|
**Match Expression** (categories):
|
|
```javascript
|
|
['match', ['get', 'type'],
|
|
'Primary', '#667eea',
|
|
'Secondary', '#764ba2',
|
|
'University', '#f093fb',
|
|
'#667eea' // default
|
|
]
|
|
```
|
|
|
|
**Interpolate Expression** (continuous):
|
|
```javascript
|
|
['interpolate', ['linear'], ['zoom'],
|
|
1, 3, // at zoom 1, value 3
|
|
10, 8 // at zoom 10, value 8
|
|
]
|
|
```
|
|
|
|
### Layer Filtering
|
|
|
|
**Show only clusters**:
|
|
```javascript
|
|
filter: ['has', 'point_count']
|
|
```
|
|
|
|
**Show only unclustered points**:
|
|
```javascript
|
|
filter: ['!', ['has', 'point_count']]
|
|
```
|
|
|
|
## Data Guidelines
|
|
|
|
### GeoJSON Structure
|
|
```javascript
|
|
{
|
|
"type": "Feature",
|
|
"geometry": {
|
|
"type": "Point",
|
|
"coordinates": [longitude, latitude] // Note: lng first!
|
|
},
|
|
"properties": {
|
|
"name": "School Name",
|
|
"type": "Primary|Secondary|University",
|
|
"students": 500,
|
|
"teachers": 30,
|
|
"ratio": 16.7,
|
|
"resources": 75, // percentage
|
|
"internet": true,
|
|
"country": "Country",
|
|
"city": "City"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Data Quality Standards
|
|
- Accurate geographic coordinates (longitude, latitude order)
|
|
- Realistic enrollment and ratio figures
|
|
- Resource percentages (0-100)
|
|
- Boolean internet access flag
|
|
- Comprehensive location metadata
|
|
|
|
## Styling Guidelines
|
|
|
|
### Color Palette
|
|
|
|
**Clusters** (by density):
|
|
- `#51bbd6` - Blue (small clusters)
|
|
- `#f1f075` - Yellow (medium clusters)
|
|
- `#f28cb1` - Pink (large clusters)
|
|
|
|
**School Types**:
|
|
- `#667eea` - Primary (purple)
|
|
- `#764ba2` - Secondary (violet)
|
|
- `#f093fb` - University (pink)
|
|
|
|
**Resource Levels**:
|
|
- `#48bb78` - Well resourced (green)
|
|
- `#ecc94b` - Moderate (yellow)
|
|
- `#f56565` - Under-resourced (red)
|
|
|
|
### Globe Atmosphere
|
|
```javascript
|
|
map.setFog({
|
|
color: 'rgba(5, 10, 20, 0.9)',
|
|
'high-color': 'rgba(36, 92, 223, 0.35)',
|
|
'horizon-blend': 0.3,
|
|
'space-color': '#000814',
|
|
'star-intensity': 0.7
|
|
});
|
|
```
|
|
|
|
## Debugging Tips
|
|
|
|
### Console Logging
|
|
```javascript
|
|
// Check cluster state
|
|
map.on('click', 'clusters', (e) => {
|
|
console.log('Cluster clicked:', e.features[0].properties);
|
|
});
|
|
|
|
// Monitor data loading
|
|
map.on('sourcedata', (e) => {
|
|
if (e.sourceId === 'schools' && e.isSourceLoaded) {
|
|
console.log('Schools data loaded');
|
|
}
|
|
});
|
|
```
|
|
|
|
### Layer Inspection
|
|
```javascript
|
|
// Query visible features
|
|
const features = map.queryRenderedFeatures({ layers: ['clusters'] });
|
|
console.log('Visible clusters:', features.length);
|
|
|
|
// Check source data
|
|
const sourceData = map.getSource('schools')._data;
|
|
console.log('Total features:', sourceData.features.length);
|
|
```
|
|
|
|
## Performance Monitoring
|
|
|
|
### FPS Check
|
|
```javascript
|
|
map.on('render', () => {
|
|
const fps = 1000 / map._frameTime;
|
|
console.log('FPS:', fps.toFixed(2));
|
|
});
|
|
```
|
|
|
|
### Cluster Statistics
|
|
```javascript
|
|
map.on('load', () => {
|
|
const allFeatures = map.querySourceFeatures('schools');
|
|
const clusters = allFeatures.filter(f => f.properties.cluster);
|
|
console.log('Clusters rendered:', clusters.length);
|
|
});
|
|
```
|
|
|
|
## Accessibility Considerations
|
|
|
|
- Keyboard navigation implemented (arrow keys)
|
|
- High contrast color scheme
|
|
- Clear visual hierarchy
|
|
- Descriptive popup content
|
|
- Focus states for interactive elements
|
|
|
|
## Common Issues & Solutions
|
|
|
|
### Issue: Clusters not appearing
|
|
**Solution**: Check `clusterMaxZoom` - ensure it's lower than current zoom level
|
|
|
|
### Issue: Expansion zoom not working
|
|
**Solution**: Verify cluster source has `generateId: true`
|
|
|
|
### Issue: Poor performance with large datasets
|
|
**Solution**: Reduce `clusterRadius` or lower `clusterMaxZoom`
|
|
|
|
### Issue: Popup coordinates wrapping
|
|
**Solution**: Use coordinate normalization:
|
|
```javascript
|
|
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
|
|
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
|
|
}
|
|
```
|
|
|
|
## Extension Ideas
|
|
|
|
### Easy Additions
|
|
- Add school type filters (show only universities)
|
|
- Implement resource level filtering
|
|
- Add search by country/city
|
|
- Export visible schools as CSV
|
|
|
|
### Medium Complexity
|
|
- Time-series data (enrollment over years)
|
|
- Heat map overlay for density
|
|
- Statistical dashboard panel
|
|
- Comparative country analysis
|
|
|
|
### Advanced Features
|
|
- 3D building extrusions for schools
|
|
- Custom cluster markers
|
|
- Real-time enrollment updates
|
|
- WebGL custom layers
|
|
|
|
## Mapbox Token Notes
|
|
|
|
Current token is public example - suitable for:
|
|
- Development
|
|
- Testing
|
|
- Learning
|
|
- Low-traffic demos
|
|
|
|
For production:
|
|
1. Create Mapbox account
|
|
2. Generate new token
|
|
3. Set appropriate URL restrictions
|
|
4. Monitor usage limits
|
|
|
|
## Version Information
|
|
|
|
- **Mapbox GL JS**: v3.0.1
|
|
- **Projection**: Globe
|
|
- **Style**: dark-v11
|
|
- **Clustering**: Enabled (radius 50, maxZoom 14)
|
|
- **Dataset**: 311 schools, 142 countries
|
|
|
|
## Learning Outcomes
|
|
|
|
This iteration demonstrates:
|
|
1. Large dataset clustering configuration
|
|
2. Step-based expression styling
|
|
3. Interactive cluster expansion
|
|
4. Performance optimization techniques
|
|
5. Progressive detail disclosure UX
|
|
6. Multi-layer composition with filtering
|
|
|
|
## Next Steps
|
|
|
|
For iteration 9, consider:
|
|
- 3D extrusions based on enrollment
|
|
- Animated transitions between states
|
|
- Advanced filtering/search UI
|
|
- Time-based data animations
|
|
- Custom WebGL rendering
|
|
|
|
---
|
|
|
|
**Remember**: Always test clustering parameters with your dataset size. Adjust `clusterRadius` and `clusterMaxZoom` based on data density and UX requirements.
|