dragndrop_hld/INFO_OBJECTS_METRIC_FIELD.md
alex f81dcccbb6 FULL WORKING V 1.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 16:15:50 -07:00

7.2 KiB

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)

{
  "mapinfoobjecttypeId": 1,
  "metric": "Lat: 40.760311 Lng: -124.172782"
}

Format: "Lat: {latitude} Lng: {longitude}"


Type 2: Polyline (Lines/Cables)

{
  "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)

{
  "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:

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)

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:

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°):

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.