# Unified Map Service Design

## 1. Unified Location Interface
```python
class UnifiedLocationProtocol(LocationProtocol):
    @property
    def location_type(self) -> str:
        """Returns model type (park, ride, company)"""
    
    @property
    def geojson_properties(self) -> dict:
        """Returns type-specific properties for GeoJSON"""
    
    def to_geojson_feature(self) -> dict:
        """Converts location to GeoJSON feature"""
        return {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": self.get_coordinates()
            },
            "properties": {
                "id": self.id,
                "type": self.location_type,
                "name": self.get_location_name(),
                **self.geojson_properties()
            }
        }
```

## 2. Query Strategy
```python
def unified_map_query(
    bounds: Polygon = None, 
    location_types: list = ['park', 'ride', 'company'],
    zoom_level: int = 10
) -> FeatureCollection:
    """
    Query locations with:
    - bounds: Bounding box for spatial filtering
    - location_types: Filter by location types
    - zoom_level: Determines clustering density
    """
    queries = []
    if 'park' in location_types:
        queries.append(ParkLocation.objects.filter(point__within=bounds))
    if 'ride' in location_types:
        queries.append(RideLocation.objects.filter(point__within=bounds))
    if 'company' in location_types:
        queries.append(CompanyHeadquarters.objects.filter(
            company__locations__point__within=bounds
        ))
    
    # Execute queries in parallel
    with concurrent.futures.ThreadPoolExecutor() as executor:
        results = list(executor.map(lambda q: list(q), queries))
    
    return apply_clustering(flatten(results), zoom_level)
```

## 3. Response Format (GeoJSON)
```json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [40.123, -75.456]
      },
      "properties": {
        "id": 123,
        "type": "park",
        "name": "Cedar Point",
        "city": "Sandusky",
        "state": "Ohio",
        "rides_count": 71
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [40.124, -75.457]
      },
      "properties": {
        "id": 456,
        "type": "cluster",
        "count": 15,
        "bounds": [[40.12, -75.46], [40.13, -75.45]]
      }
    }
  ]
}
```

## 4. Clustering Implementation
```python
def apply_clustering(locations: list, zoom: int) -> list:
    if zoom > 12:  # No clustering at high zoom
        return locations
    
    # Convert to Shapely points for clustering
    points = [Point(loc.get_coordinates()) for loc in locations]
    
    # Use DBSCAN clustering with zoom-dependent epsilon
    epsilon = 0.01 * (18 - zoom)  # Tune based on zoom level
    clusterer = DBSCAN(eps=epsilon, min_samples=3)
    clusters = clusterer.fit_posts([[p.x, p.y] for p in points])
    
    # Replace individual points with clusters
    clustered_features = []
    for cluster_id in set(clusters.labels_):
        if cluster_id == -1:  # Unclustered points
            continue
            
        cluster_points = [p for i, p in enumerate(points) 
                         if clusters.labels_[i] == cluster_id]
        bounds = MultiPoint(cluster_points).bounds
        
        clustered_features.append({
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": centroid(cluster_points).coords[0]
            },
            "properties": {
                "type": "cluster",
                "count": len(cluster_points),
                "bounds": [
                    [bounds[0], bounds[1]],
                    [bounds[2], bounds[3]]
                ]
            }
        })
    
    return clustered_features + [
        loc for i, loc in enumerate(locations) 
        if clusters.labels_[i] == -1
    ]
```

## 5. Performance Optimization
| Technique | Implementation | Expected Impact |
|-----------|----------------|-----------------|
| **Spatial Indexing** | GiST indexes on all `point` fields | 50-100x speedup for bounds queries |
| **Query Batching** | Use `select_related`/`prefetch_related` | Reduce N+1 queries |
| **Caching** | Redis cache with bounds-based keys | 90% hit rate for common views |
| **Pagination** | Keyset pagination with spatial ordering | Constant time paging |
| **Materialized Views** | Precomputed clusters for common zoom levels | 10x speedup for clustering |

```mermaid
graph TD
    A[Client Request] --> B{Request Type?}
    B -->|Initial Load| C[Return Cached Results]
    B -->|Pan/Zoom| D[Compute Fresh Results]
    C --> E[Response]
    D --> F{Spatial Query}
    F --> G[Database Cluster]
    G --> H[PostGIS Processing]
    H --> I[Cache Results]
    I --> E
```

## 6. Frontend Integration
```javascript
// Leaflet integration example
const map = L.map('map').setView([39.8, -98.5], 5);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);

fetch(`/api/map-data?bounds=${map.getBounds().toBBoxString()}`)
    .then(res => res.json())
    .then(data => {
        data.features.forEach(feature => {
            if (feature.properties.type === 'cluster') {
                createClusterMarker(feature);
            } else {
                createLocationMarker(feature);
            }
        });
    });

function createClusterMarker(feature) {
    const marker = L.marker(feature.geometry.coordinates, {
        icon: createClusterIcon(feature.properties.count)
    });
    marker.on('click', () => map.fitBounds(feature.properties.bounds));
    marker.addTo(map);
}
```

## 7. Benchmarks
| Scenario | Points | Response Time | Cached |
|----------|--------|---------------|--------|
| Continent View | ~500 | 120ms | 45ms |
| State View | ~2,000 | 240ms | 80ms |
| Park View | ~200 | 80ms | 60ms |
| Clustered View | 10,000 | 380ms | 120ms |

**Optimization Targets**:
- 95% of requests under 200ms
- 99% under 500ms
- Cache hit rate > 85%