dragndrop_hld/INFO_OBJECTS_METRIC_FIELD.md

263 lines
7.2 KiB
Markdown
Raw Normal View History

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