""" Creates an Excel checklist from pivot.geojson, with fields sorted largest to smallest, split across Timon (1st), Joey (2nd), Dimitra (3rd) in a single sheet side-by-side. """ import json import math import os try: import openpyxl from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter except ImportError: print("Installing openpyxl...") import subprocess, sys subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl"]) import openpyxl from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter def shoelace_area(coords): """Compute area in square degrees using the shoelace formula.""" n = len(coords) area = 0.0 for i in range(n): j = (i + 1) % n area += coords[i][0] * coords[j][1] area -= coords[j][0] * coords[i][1] return abs(area) / 2.0 def polygon_area_m2(ring): """ Approximate polygon area in m² using spherical coordinates. ring: list of [lon, lat] pairs """ R = 6371000 # Earth radius in meters area_deg2 = shoelace_area(ring) # Convert to m² using mean latitude mean_lat = sum(p[1] for p in ring) / len(ring) lat_rad = math.radians(mean_lat) # 1 degree lat ≈ R * pi/180 meters, 1 degree lon ≈ R * cos(lat) * pi/180 meters m_per_deg_lat = R * math.pi / 180 m_per_deg_lon = R * math.cos(lat_rad) * math.pi / 180 return area_deg2 * m_per_deg_lat * m_per_deg_lon def feature_area(feature): """Compute total area of a feature (MultiPolygon or Polygon) in m².""" geom = feature["geometry"] total = 0.0 if geom["type"] == "MultiPolygon": for polygon in geom["coordinates"]: # First ring is outer boundary total += polygon_area_m2(polygon[0]) elif geom["type"] == "Polygon": total += polygon_area_m2(geom["coordinates"][0]) return total # Load GeoJSON geojson_path = r"C:\Users\timon\Documents\SmartCane_code\laravel_app\storage\app\angata\pivot.geojson" with open(geojson_path, "r", encoding="utf-8") as f: gj = json.load(f) features = gj["features"] print(f"Total features: {len(features)}") # Compute areas and sort fields = [] for feat in features: field_name = feat["properties"].get("field", "?") area = feature_area(feat) fields.append({"field": field_name, "area_m2": area, "area_ha": area / 10000}) fields.sort(key=lambda x: x["area_m2"], reverse=True) # Print top 10 for verification print("\nTop 10 fields by area:") for i, f in enumerate(fields[:10]): print(f" {i+1:3d}. Field {f['field']:15s} {f['area_ha']:.2f} ha") # Split: index 0,3,6,... → Timon; 1,4,7,... → Joey; 2,5,8,... → Dimitra timon = [f for i, f in enumerate(fields) if i % 3 == 0] joey = [f for i, f in enumerate(fields) if i % 3 == 1] dimitra = [f for i, f in enumerate(fields) if i % 3 == 2] print(f"\nSplit: Timon={len(timon)}, Joey={len(joey)}, Dimitra={len(dimitra)}") # Create Excel wb = openpyxl.Workbook() ws = wb.active ws.title = "Field Checklist" # Color palette colors = { "timon": {"header_bg": "1F6AA5", "alt_bg": "D6E4F0"}, "joey": {"header_bg": "2E7D32", "alt_bg": "D7F0D8"}, "dimitra": {"header_bg": "7B1FA2", "alt_bg": "EDD7F0"}, } white_font = Font(name="Calibri", bold=True, color="FFFFFF", size=11) black_font = Font(name="Calibri", size=10) bold_black = Font(name="Calibri", bold=True, size=10) center_align = Alignment(horizontal="center", vertical="center", wrap_text=True) thin = Side(style="thin", color="CCCCCC") border = Border(left=thin, right=thin, top=thin, bottom=thin) # Column layout: each person gets 3 columns (Rank, Field, Area ha, Checked) # Spacing: col 1-4 = Timon, col 5 = spacer, col 6-9 = Joey, col 10 = spacer, col 11-14 = Dimitra persons = [ ("Timon", timon, 1), ("Joey", joey, 6), ("Dimitra", dimitra, 11), ] # Row 1: Person name header (merged across 4 cols) ws.row_dimensions[1].height = 25 ws.row_dimensions[2].height = 20 for name, data, start_col in persons: c = colors[name.lower()] # Merge cells for person name end_col = start_col + 3 ws.merge_cells( start_row=1, start_column=start_col, end_row=1, end_column=end_col ) cell = ws.cell(row=1, column=start_col, value=name) cell.font = white_font cell.fill = PatternFill("solid", fgColor=c["header_bg"]) cell.alignment = center_align cell.border = border # Sub-headers sub_headers = ["#", "Field", "Area (ha)", "Checked ✓"] for i, hdr in enumerate(sub_headers): cell = ws.cell(row=2, column=start_col + i, value=hdr) cell.font = white_font cell.fill = PatternFill("solid", fgColor=c["header_bg"]) cell.alignment = center_align cell.border = border # Data rows for row_i, field in enumerate(data): row_num = row_i + 3 ws.row_dimensions[row_num].height = 16 alt = row_i % 2 == 1 bg = c["alt_bg"] if alt else "FFFFFF" fill = PatternFill("solid", fgColor=bg) values = [row_i + 1, field["field"], round(field["area_ha"], 2), ""] for col_i, val in enumerate(values): cell = ws.cell(row=row_num, column=start_col + col_i, value=val) cell.font = black_font cell.fill = fill cell.border = border cell.alignment = Alignment(horizontal="center" if col_i != 1 else "left", vertical="center") # Column widths col_widths = {1: 5, 2: 14, 3: 10, 4: 12, # Timon 5: 2, # spacer 6: 5, 7: 14, 8: 10, 9: 12, # Joey 10: 2, # spacer 11: 5, 12: 14, 13: 10, 14: 12} # Dimitra for col, width in col_widths.items(): ws.column_dimensions[get_column_letter(col)].width = width # Freeze header rows ws.freeze_panes = "A3" # Title row above everything — insert a title row ws.insert_rows(1) ws.merge_cells("A1:N1") title_cell = ws.cell(row=1, column=1, value="Angata Pivot Field Checklist — sorted largest to smallest") title_cell.font = Font(name="Calibri", bold=True, size=13, color="1A1A1A") title_cell.alignment = Alignment(horizontal="center", vertical="center") title_cell.fill = PatternFill("solid", fgColor="F0F0F0") ws.row_dimensions[1].height = 28 # Re-freeze after insert ws.freeze_panes = "A4" out_path = r"C:\Users\timon\Documents\SmartCane_code\angata_field_checklist.xlsx" wb.save(out_path) print(f"\nExcel saved to: {out_path}") print(f"Total fields: {len(fields)}") print(f" Timon: {len(timon)} fields") print(f" Joey: {len(joey)} fields") print(f" Dimitra: {len(dimitra)} fields")