Skip to content

Instantly share code, notes, and snippets.

@DEADB33F
Created October 7, 2024 10:14
Show Gist options
  • Select an option

  • Save DEADB33F/7390286f741ad7a84ccf60d92f7cbaf6 to your computer and use it in GitHub Desktop.

Select an option

Save DEADB33F/7390286f741ad7a84ccf60d92f7cbaf6 to your computer and use it in GitHub Desktop.
import sys
import json
import ctypes
import math
##################
#
# Save script with a .pyw extension (enables drag-dropping on windows)
# Extract integration diagnostics / log file from HA, Eg...
# https://i.imgur.com/1G7cB6D.png
# Drag-drop log onto python script
#
###################
offset_x,offset_y = -1.5, 3 # Offset in meters to apply to map coods
scale = 100 # Scale for SVG image (eg 100:1)
def load_json(file_path):
try:
with open(file_path, 'r') as json_file:
data = json.load(json_file)
except FileNotFoundError:
print(f"File not found: {file_path}")
except json.JSONDecodeError:
print(f"Error decoding JSON in file: {file_path}")
except Exception as e:
print(f"An error occurred: {str(e)}")
areas = data["data"]["map"]["area"]
area_list = {}
### Generate raw JSON coords file for each area
for area_hash in areas:
total_frame = areas[area_hash]["total_frame"]
frames = areas[area_hash]["data"]
if len( frames ) != total_frame:
print( f"Error: full coord data not available for area: '{area_hash}'")
else:
area_name = frames[0]["area_label"]["label"]
coords = []
# Collate frames
for frame in frames:
coords.extend( frame["data_couple"] )
# Save JSON coords list
with open(f"{area_name}.json", 'w') as json_file:
json.dump(coords, json_file,indent=2)
# Convert x,y dict to tuples
area_list[area_name] = [(xy['x']+offset_x, xy['y']+offset_y) for xy in coords]
### Generate SVG map of all areas
# Determine image height/width and viewbox dimensions
xs, ys = zip(*[v for l in area_list.values() for v in l])
width, height = (max(xs)-min(xs))*1000/scale, (max(ys)-min(ys))*1000/scale
viewbox = (
min(xs)-1,
min(-y for y in ys)-1,
max(xs)-min(xs)+2,
max(-y for y in ys)-min(-y for y in ys)+2
)
xml_data = f"""<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="{width}mm"
height="{height}mm"
viewBox="%f %f %f %f"
>""" % viewbox
for coords in area_list.values():
svg_path = ' '.join(['%s%f %f' % (['M', 'L'][i>0], x, -y) for i, (x, y) in enumerate(coords)])
xml_data += f'\n <path style="fill:green;stroke:black;stroke-width:0.1" d="{svg_path}"/>'
xml_data += "\n </svg>"
# Save overview map (100:1 scale)
with open(f"mowing_areas.svg", 'w') as svg_file:
svg_file.write( xml_data )
### Generate KML file
# Get lon/lat of mower, dock & RTK
locations = data["data"]["location"]
mower = (locations["device"]["longitude"],locations["device"]["latitude"])
rtk = lon_lat_delta(*mower, locations["RTK"]["longitude"],locations["RTK"]["latitude"] )
dock = lon_lat_delta(*mower, locations["dock"]["longitude"],locations["dock"]["latitude"] )
kml_data = f"""<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Mower Areas</name>"""
for area_name, coords in area_list.items():
# Convert relative coords to lon/lat srings
lonlat_str = "\n\t\t\t".join(f"{lon},{lat},0" for x, y in coords for lon, lat in [lon_lat_delta(*mower,x,y)])
kml_data += f"""
<Placemark>
<name>{area_name}</name>
<Style>
<LineStyle>
<color>00000000</color>
<width>2</width>
</LineStyle>
<PolyStyle>
<color>7f00ff00</color>
</PolyStyle>
</Style>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
{lonlat_str}
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>"""
kml_data += """
<Placemark>
<name>RTK</name>
<Style>
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png</href>
</Icon>
</IconStyle>
</Style>
<Point>
<coordinates>
%f,%f,0
</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Dock</name>
<Style>
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png</href>
</Icon>
</IconStyle>
</Style>
<Point>
<coordinates>
%f,%f,0
</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Mower</name>
<Style>
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png</href>
</Icon>
</IconStyle>
</Style>
<Point>
<coordinates>
%f,%f,0
</coordinates>
</Point>
</Placemark>
</Document>
</kml>""" % (*rtk, *dock, *mower)
# Save KML file
with open(f"mowing_areas.kml", 'w') as kml_file:
kml_file.write( kml_data )
### Show Stats
msg_text = f"{len(area_list)} Mowing areas processed\nArea stats:\n"
for area_name, coords in area_list.items():
perim,area = polygon_stats(coords)
msg_text += f"\n {area_name:>12} : {round(perim,2):>10}m, {round(area,2):>10} sq m"
MessageBox = ctypes.windll.user32.MessageBoxW
MessageBox(None, msg_text,'Success!', 0)
# Add delta (in meters) to lon/lat, return new lon/lat
def lon_lat_delta(lon, lat, x, y):
new_lon = lon + (x / (111320 * math.cos(math.radians(lat))))
new_lat = lat + (y / 111320)
return (new_lon, new_lat)
# Return peremiter and area of polygon (credit: ChatGPT)
def polygon_stats( polygon_coords ):
n,v = len(polygon_coords),polygon_coords
perimeter = sum(math.sqrt((v[(i + 1) % n][0] - v[i][0]) ** 2 +
(v[(i + 1) % n][1] - v[i][1]) ** 2)
for i in range(n))
area = abs(sum(v[i][0] * v[(i + 1) % n][1] -
v[(i + 1) % n][0] * v[i][1] for i in range(n))) / 2.0
return perimeter, area
# Test if a point is inside a polygon or not (credit: ChatGPT)
def is_point_in_polygon( test_coords, polygon_coords ):
x, y, v = *test_coords, polygon_coords
return sum((y > v[i][1]) != (y > v[(i + 1) % len(v)][1]) and
(x < (v[(i + 1) % len(v)][0] - v[i][0]) *
(y - v[i][1]) / (v[(i + 1) % len(v)][1] - v[i][1]) + v[i][0])
for i in range(len(v))) % 2 == 1
if __name__ == "__main__":
if len(sys.argv) > 1:
file_path = sys.argv[1]
load_json(file_path)
else:
MessageBox = ctypes.windll.user32.MessageBoxW
MessageBox(None, 'No file to open','Error', 0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment