# Info Objects - Metric Field Analysis

## Overview

The `metric` field in info objects (map-info-object) is **NOT** a simple integer or boolean. It's a **calculated string** containing measurements based on the geometry type.

## Data from Verofy API (Map 15685)

### Type 1: Marker (Point)
```json
{
  "mapinfoobjecttypeId": 1,
  "metric": "Lat: 40.760311 Lng: -124.172782"
}
```
**Format:** `"Lat: {latitude} Lng: {longitude}"`

---

### Type 2: Polyline (Lines/Cables)
```json
{
  "mapinfoobjecttypeId": 2,
  "metric": "Mileage: 0.1519; Footage: 802"
}
```
**Format:** `"Mileage: {miles}; Footage: {feet}"`

**Calculation:**
- Calculate the total length of the LineString
- Convert to miles and feet
- Format as shown

---

### Type 3: Polygon (Boundaries/Parcels)
```json
{
  "mapinfoobjecttypeId": 3,
  "metric": "Square Miles: 0.0065"
}
```
**Format:** `"Square Miles: {sq_miles}"`

**Calculation:**
- Calculate the area of the Polygon
- Convert to square miles
- Format as shown

---

## Required Implementation

### For Cables (Type 2 - Polyline)

Current code sends polygons/parcels to info objects. For cables specifically:

```python
from shapely.geometry import LineString
import geopandas as gpd

def calculate_line_metric(geometry):
    """Calculate metric string for a line geometry"""
    if geometry.geom_type != 'LineString':
        return "Invalid geometry type"

    # Calculate length in degrees (WGS84)
    # Convert to approximate miles and feet
    # Note: This is approximate - for accurate results, reproject to appropriate CRS

    # Rough conversion: 1 degree ≈ 69 miles at equator
    # For more accuracy, use geopy or pyproj

    length_degrees = geometry.length
    length_miles = length_degrees * 69  # Approximate
    length_feet = length_miles * 5280

    return f"Mileage: {length_miles:.4f}; Footage: {length_feet:.0f}"

# In _upload_cables():
metric = calculate_line_metric(row.geometry)
info_data = {
    "mapProjectId": int(map_id),
    "name": str(row.get('Name', f'Cable-{idx}')),
    "mapinfoobjecttypeId": 2,
    "data": data,
    "color": "#ffffff",
    "alpha": "1.00",
    "metric": metric  # ✅ Calculated value
}
```

---

### For Cabinet Boundaries & Parcels (Type 3 - Polygon)

```python
from shapely.geometry import Polygon

def calculate_polygon_metric(geometry):
    """Calculate metric string for a polygon geometry"""
    if geometry.geom_type != 'Polygon':
        return "Invalid geometry type"

    # Calculate area in square degrees (WGS84)
    # Convert to square miles
    # Note: This is approximate - for accurate results, reproject to appropriate CRS

    area_sq_degrees = geometry.area
    # Rough conversion: 1 degree² ≈ 4,761 square miles at equator
    # This varies by latitude, so this is very approximate
    area_sq_miles = area_sq_degrees * 4761

    return f"Square Miles: {area_sq_miles:.4f}"

# In _upload_cabinet_boundaries() and _upload_parcels():
metric = calculate_polygon_metric(row.geometry)
info_data = {
    "mapProjectId": int(map_id),
    "name": str(row.get('Name', f'Boundary-{idx}')),
    "mapinfoobjecttypeId": 3,
    "data": data,
    "color": "#ffffff",
    "alpha": "0.40",
    "metric": metric  # ✅ Calculated value
}
```

---

## Better Implementation Using GeoPandas

GeoPandas can handle CRS transformations for more accurate calculations:

```python
def calculate_geometry_metric(geometry, geom_type):
    """
    Calculate metric string for any geometry type

    Args:
        geometry: Shapely geometry object
        geom_type: 1 (Point), 2 (LineString), 3 (Polygon)
    """
    if geom_type == 1:  # Point/Marker
        return f"Lat: {geometry.y:.6f} Lng: {geometry.x:.6f}"

    elif geom_type == 2:  # LineString/Cable
        # For accurate length, project to UTM or other metric CRS
        # Rough approximation using great circle distance
        from shapely.ops import transform
        import pyproj
        from functools import partial

        # Define projection from WGS84 to a metric system (meters)
        project = partial(
            pyproj.transform,
            pyproj.Proj('EPSG:4326'),  # WGS84
            pyproj.Proj('EPSG:3857')   # Web Mercator (meters)
        )

        # Transform and calculate length
        line_projected = transform(project, geometry)
        length_meters = line_projected.length
        length_miles = length_meters * 0.000621371
        length_feet = length_meters * 3.28084

        return f"Mileage: {length_miles:.4f}; Footage: {length_feet:.0f}"

    elif geom_type == 3:  # Polygon
        # Similar projection for area
        from shapely.ops import transform
        import pyproj
        from functools import partial

        project = partial(
            pyproj.transform,
            pyproj.Proj('EPSG:4326'),  # WGS84
            pyproj.Proj('EPSG:3857')   # Web Mercator (meters)
        )

        polygon_projected = transform(project, geometry)
        area_sq_meters = polygon_projected.area
        area_sq_miles = area_sq_meters * 0.000000386102

        return f"Square Miles: {area_sq_miles:.4f}"

    return "Unknown type"
```

---

## Simplified Approach (Good Enough for Most Cases)

Since all geometries are in WGS84 (EPSG:4326), and the study area is around Eureka, CA (latitude ~40°):

```python
def calculate_metric_simple(geometry, geom_type):
    """
    Simplified metric calculation
    Good enough approximation for small areas at mid-latitudes
    """
    if geom_type == 1:  # Point
        return f"Lat: {geometry.y:.6f} Lng: {geometry.x:.6f}"

    elif geom_type == 2:  # LineString
        # At 40° latitude: 1 degree longitude ≈ 53 miles
        # 1 degree latitude ≈ 69 miles
        coords = list(geometry.coords)
        total_miles = 0

        for i in range(len(coords) - 1):
            lon1, lat1 = coords[i]
            lon2, lat2 = coords[i + 1]

            # Approximate distance
            dlat = (lat2 - lat1) * 69
            dlon = (lon2 - lon1) * 53  # At 40° latitude
            segment_miles = (dlat**2 + dlon**2)**0.5
            total_miles += segment_miles

        total_feet = total_miles * 5280
        return f"Mileage: {total_miles:.4f}; Footage: {total_feet:.0f}"

    elif geom_type == 3:  # Polygon
        # Approximate area calculation
        # At 40° latitude: 1 deg² ≈ 3,657 square miles
        area_sq_degrees = geometry.area
        area_sq_miles = area_sq_degrees * 3657
        return f"Square Miles: {area_sq_miles:.4f}"

    return ""
```

---

## Recommendation

**Use GeoPandas with proper CRS transformation** for accurate results:

1. Read geometries (already in WGS84 / EPSG:4326)
2. Convert to a local projected CRS (like UTM Zone 10N - EPSG:32610 for California)
3. Calculate length/area in meters
4. Convert to miles/feet/square miles
5. Format as string

This ensures accurate measurements regardless of latitude.

---

## Impact on Current Code

**Current assumption:** `"metric": 0` ❌

**Reality:** `"metric": "Mileage: 0.1519; Footage: 802"` ✅

**Changes needed:**
1. Add metric calculation functions
2. Update `_upload_cables()` to calculate line metrics
3. Update `_upload_cabinet_boundaries()` to calculate polygon metrics
4. Update `_upload_parcels()` to calculate polygon metrics

This is **more complex than originally thought** but necessary for API compatibility.
