Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ANU Fire Project module #1389

Merged
merged 3 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions MAVProxy/modules/mavproxy_anufireproject/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
'''
Add functionality to help with the ANU Fire Project
e.g. download data from ACT government website and create KML layers from same

AP_FLAKE8_CLEAN

'''

import json
import math
import os
import pathlib
import time

from MAVProxy.modules.lib import mp_module
from MAVProxy.modules.lib import mp_util


class ANUFireProject(mp_module.MPModule):
def __init__(self, mpstate):
super(ANUFireProject, self).__init__(mpstate, "anufireproject", "")

self.kml_module_initialised = False

# stores the directory into which our persistent state should
# be dropped:
self.dot_mavproxy_path = mp_util.dot_mavproxy("anufp")
pathlib.Path(self.dot_mavproxy_path).mkdir(parents=True, exist_ok=True)

self.add_command(
'anufp',
self.cmd,
"ANU FireProject utility functions",
["<burnkml>", "<burnkml> (BURNID)"],
)

self.burn_data_url = 'https://services1.arcgis.com/E5n4f1VY84i0xSjy/arcgis/rest/services/Prescribed_Burns_Public/FeatureServer/0/query?f=json&where=1%3D1&spatialRel=esriSpatialRelIntersects&geometry=%7B%22xmin%22%3A16505903.50821903%2C%22ymin%22%3A-4198481.646243979%2C%22xmax%22%3A16627897.005362216%2C%22ymax%22%3A-4144822.852387765%2C%22spatialReference%22%3A%7B%22wkid%22%3A102100%7D%7D&geometryType=esriGeometryEnvelope&inSR=102100&outFields=BURN_NAME%2CBOP_ID%2CHECTARES%2CWORKS_DESCRIPTION%2CBURN_TIMEFRAME%2CESRI_OID&orderByFields=ESRI_OID%20ASC&outSR=102100' # noqa

self.burn_json = None
self.burn_json_time = 0
self.burn_json_lifetime = 600 # seconds we cache the json for
self.burn_json_refresh_attempt_time = 0

try:
# try not to set one variable without setting the other here.
cache_path = self.burn_data_cache_path()
content = cache_path.read_bytes()
mtime = cache_path.stat().st_mtime
self.burn_json = content
self.burn_json_time = mtime
except FileNotFoundError:
pass

def burn_data_cache_path(self):
'''returns a pathlib object corresponding to the filepath where we
cache the downloaded burn JSON'''
return pathlib.Path(os.path.join(self.dot_mavproxy_path, "burn_json.txt"))

def usage(self):
'''returns usage string'''
return "Usage: anufp <burnkml (BURNID)>"

def cmd(self, args):
'''handle commands from stdin'''
if len(args) < 1:
print(self.usage())
return

if args[0] == "burnkml":
return self.cmd_burnkml(args[1:])

print(self.usage())

def get_burn_json_object(self):
'''returns a json object containing the burn data'''
self.ensure_burn_json()
return json.loads(self.burn_json)

def cmd_burnkml(self, args):
'''command to trigger creation of a KML layer from a burn ID (e.g. FB511)'''
kml = self.module('kmlread')
if kml is None:
self.message("Need kmlread module")
return

if len(args) != 1:
print(self.usage())
print("BURNID is usually of the form FBxxx e.g. FB511")
return
burn_name = args[0]

burn_json = self.get_burn_json_object()
if burn_json is None:
return

found_feature = None
seen_bops = []
for feature in burn_json["features"]:
bop_id = feature["attributes"]["BOP_ID"]
seen_bops.append(bop_id)
if bop_id == burn_name:
found_feature = feature
break
if found_feature is None:
self.message(f"Burn {burn_name} not found in downloaded JSON (found {seen_bops})")
return

if "geometry" not in feature:
self.message(f"geometry missing from feature {burn_name}")
return

rings = feature["geometry"]["rings"]

for ring in rings:
coordinates = []
for item in ring:
lat, lon = self.MetersToLatLon(item[0], item[1])
coordinates.append((lat, lon))

kml.add_polygon(burn_name, coordinates)

# from: https://gist.githubusercontent.com/maptiler/fddb5ce33ba995d5523de9afdf8ef118/raw/d7565390d2480bfed3c439df5826f1d9e4b41761/globalmaptiles.py # noqa
def MetersToLatLon(self, mx, my):
"Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum"
originShift = 2 * math.pi * 6378137 / 2.0
# 20037508.342789244

lon = (mx / originShift) * 180.0
lat = (my / originShift) * 180.0

lat = 180 / math.pi * (2 * math.atan(math.exp(lat * math.pi / 180.0)) - math.pi / 2.0)
return lat, lon

def should_refresh_burn_json(self):
'''returns true if we should download the data from the ACT government
website again'''
now = time.time()
if now - self.burn_json_refresh_attempt_time < 60:
return False

if self.burn_json is None:
return True

if now - self.burn_json_time > self.burn_json_lifetime:
return True

return False

def message(self, msg):
'''simply emit msg prefixed with an identifying string'''
print(f"anufp: {msg}")

def refresh_burn_json(self):
'''downloads content from ACT Government URL, caches it in the
filesystem'''
tstart = time.time()
self.message("Refreshing burn data JSON")
self.burn_json_refresh_attempt_time = tstart
content = mp_util.download_url(self.burn_data_url)
if content is None:
return
self.burn_json = content
tstop = time.time()
self.burn_json_time = tstop

# cache the data:
cache_path = self.burn_data_cache_path()
cache_path.write_bytes(content)

self.message(f"Refreshed burn data JSON ({tstop-tstart}s)")

def ensure_burn_json(self):
'''checks to see if we should refresh the JSON data, and does that if
required'''
if not self.should_refresh_burn_json():
return
self.refresh_burn_json()

def idle_task(self):
# download the burn JSON once at startup to avoid delay on
# first kml command.
if self.burn_json is None:
self.ensure_burn_json()


def init(mpstate):
'''initialise module'''
return ANUFireProject(mpstate)
49 changes: 26 additions & 23 deletions MAVProxy/modules/mavproxy_kmlread.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import random
import re

from pymavlink import mavutil, mavwp
from pymavlink import mavutil

from MAVProxy.modules.lib import mp_module
# from MAVProxy.modules.lib.mp_settings import MPSetting
Expand All @@ -29,7 +29,7 @@

class KmlReadModule(mp_module.MPModule):
def __init__(self, mpstate):
super(KmlReadModule, self).__init__(mpstate, "kmlread", "Add kml or kmz layers to map")
super(KmlReadModule, self).__init__(mpstate, "kmlread", "Add kml or kmz layers to map", public=True)
self.add_command('kml', self.cmd_param, "kml map handling",
["<clear|snapwp|snapfence>",
"<load> (FILENAME)", '<layers>'])
Expand All @@ -43,9 +43,9 @@ def __init__(self, mpstate):
self.initialised_map_module = False
self.menu_needs_refreshing = True
self.map_objects = {}
self.counter = 0

# the fence manager
self.fenceloader = mavwp.MAVFenceLoader()
self.snap_points = []

# make the initial map menu
Expand Down Expand Up @@ -314,13 +314,31 @@ def clearkml(self):
self.curtextlayers = []
self.menu_needs_refreshing = True

def add_polygon(self, name, coords):
'''add a polygon to the KML list. coords is a list of lat/lng tuples in degrees'''
self.snap_points.extend(coords)

# print("Adding " + name)
newcolour = (random.randint(0, 255), 0, random.randint(0, 255))
layer_name = f"{name}-{self.counter}"
curpoly = mp_slipmap.SlipPolygon(
layer_name,
coords,
layer=2,
linewidth=2,
colour=newcolour,
)
self.add_map_object(curpoly)
self.allayers.append(curpoly)
self.curlayers.append(layer_name)
self.counter += 1

def loadkml(self, filename):
'''Load a kml from file and put it on the map'''
# Open the zip file
nodes = kmlread.readkmz(filename)

self.snap_points = []
counter = 0

# go through each object in the kml...
if nodes is None:
Expand All @@ -334,28 +352,13 @@ def loadkml(self, filename):
if point is None:
continue

counter += 1

# and place any polygons on the map
if point[0] == 'Polygon':
self.snap_points.extend(point[2])

# print("Adding " + point[1])
newcolour = (random.randint(0, 255), 0, random.randint(0, 255))
layer_name = point[1]+"-"+str(counter)
curpoly = mp_slipmap.SlipPolygon(
layer_name,
point[2],
layer=2,
linewidth=2,
colour=newcolour,
)
self.add_map_object(curpoly)
self.allayers.append(curpoly)
self.curlayers.append(layer_name)
(pointtype, name, coords) = point
if pointtype == 'Polygon':
self.add_polygon(name, coords)

# and points - barrell image and text
if point[0] == 'Point':
if pointtype == 'Point':
# print("Adding " + point[1])
curpoint = mp_slipmap.SlipIcon(
point[1],
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def package_files(directory):
license='GPLv3',
packages=['MAVProxy',
'MAVProxy.modules',
'MAVProxy.modules.mavproxy_anufireproject',
'MAVProxy.modules.mavproxy_fieldcheck',
'MAVProxy.modules.mavproxy_map',
'MAVProxy.modules.mavproxy_mmap',
Expand Down
Loading