# 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.