''' This class is used to build a config file
'''
import logging, sys, os, json
from collections import defaultdict
# Set up debugging
LOCAL_LOGGER = logging.getLogger(__name__)
# Create console handler
LOCAL_HANDLER = logging.StreamHandler(sys.stdout)
# Create formatter
LOCAL_FORMATTER = logging.Formatter('%(asctime)s -- %(name)s -- %(levelname)s - %(message)s')
# Add formatter to ch
LOCAL_HANDLER.setFormatter(LOCAL_FORMATTER)
# Add handler to logger
LOCAL_LOGGER.addHandler(LOCAL_HANDLER)
[docs]class ConfigBuilder():
def __init__(self):
'''
A list of geographical extents [ [min_x, max_x, min_y, max_y], ... ]
NB: An extent is a list of coords defining boundaries of model
'''
self.extent_list = []
'''
'''
self.config_list = []
[docs] def add_config_list(self, config_list):
''' Add model config dict to internal list
:param config_list: list of model config dicts
'''
self.config_list += config_list
[docs] def has_output(self):
'''
Returns True iff there are model config dicts that can be written
out to a config file
'''
return len(self.config_list) > 0
[docs] def add_ext(self, ext):
''' Adds an extent to this instance's internal extent list
:param ext: single extent [min_x, max_x, min_y, max_y]
'''
self.extent_list.append(ext)
[docs] def reduce_extents(self):
''' Reduces the internal list of extents to just one extent
:returns: single extent [min_x, max_x, min_y, max_y]
'''
LOCAL_LOGGER.debug("reduce_extents()")
out_extent = [sys.float_info.max, -sys.float_info.max, sys.float_info.max, -sys.float_info.max]
for extent in self.extent_list:
if len(extent) < 4:
continue
if extent[0] < out_extent[0]:
out_extent[0] = float(extent[0])
if extent[1] > out_extent[1]:
out_extent[1] = float(extent[1])
if extent[2] < out_extent[2]:
out_extent[2] = float(extent[2])
if extent[3] > out_extent[3]:
out_extent[3] = float(extent[3])
return out_extent
[docs] def create_json_config(self, output_filename, dest_dir, params):
''' Creates a JSON file of GLTF objects to display in 3D
:param output_filename: name of file containing created config file
:param dest_dir: destination directory for output file
:param params: model input parameters, SimpleNamespace() object,
keys are: 'name' 'crs' 'init_cam_dist' and optional 'proj4_defn'
'''
LOCAL_LOGGER.debug("create_json_config(%s)", output_filename)
# Sort by display name before saving to file, sort by display name, then model URL
sorted_model_dict_list = sorted(self.config_list,
key=lambda x: (x['display_name'], x['model_url']))
config_dict = {"properties": {"crs": params.crs, "extent": self.reduce_extents(),
"name": params.name,
"init_cam_dist": params.init_cam_dist
},
"type": "GeologicalModel",
"version": 1.0
}
# Are there any sidebar group labels that we can use?
# If not, then put them in "Not Grouped"
if hasattr(params, 'grp_struct_dict'):
config_dict['groups'] = defaultdict(list)
for model in sorted_model_dict_list:
model_file = model['model_url']
if model_file in params.grp_struct_dict:
group_name = params.grp_struct_dict[model_file][0]
config_dict["groups"][group_name].append(model)
else:
config_dict["groups"]["Not Grouped"].append(model)
# Are there WMS layers?
if hasattr(params, 'wms_services'):
for layer in params.wms_services:
config_dict['groups']['WMS Layers'].append({ **layer,
"type": "WMSLayer",
"displayed": True,
"include": True})
# Is there a proj4 definition?
if hasattr(params, 'proj4_defn'):
config_dict["properties"]["proj4_defn"] = params.proj4_defn
try:
with open(os.path.join(dest_dir, os.path.basename(output_filename)), "w") as file_p:
json.dump(config_dict, file_p, indent=4, sort_keys=True)
except OSError as os_exc:
LOCAL_LOGGER.error("Cannot open file %s %s", output_filename, os_exc)
[docs] def add_config(self, gs_dict, label_str, popup_dict, file_name, model_name,
file_ext='.gltf', position=[0.0, 0.0, 0.0], styling={}):
''' Adds more config information to popup dictionary
:param gs_dict: group structure dictionary (group struct dict format: \
{ filename: ( group_name, { insert_key1: val, insert_key2: val } } )
:param label_str: string to use as a display name for this part of the \
model, if none available in group struct dict
:param popup_dict: information to display in popup window \
( popup dict format: { object_name: { 'attr_name': attr_val, ... } } where 'attr_name' is one of: 'name', 'title', 'href')
:param file_name: file and path (without extension) of model part source file
:param file_ext: optional file extension of 'file_name', defaults to '.gltf'
:param position: optional [x,y,z] position of model part
:param styling: optional dict of styling parameters e.g. \
{ "labels": [ \
{ \
"display_name": "MARKER_TOPMOROAKVELKERRI_GRP", \
"position": [ \
425500.0, \
8028000.0, \
228.699997 \
] \
}, \
] \
}
:returns: a dict of model configuration info, which includes the popup dict
'''
LOCAL_LOGGER.debug("add_config2popup(%s, %s, %s)", label_str, file_name, file_ext)
np_filename = os.path.basename(file_name)
modelconf_dict = {}
modelconf_dict['styling'] = styling
model_url = np_filename + file_ext
modelconf_dict['model_url'] = model_url
if file_ext.upper() == ".PNG":
# PNG files do not have any coordinates, so they must be supplied
modelconf_dict['type'] = 'ImagePlane'
modelconf_dict['position'] = position
else:
modelconf_dict['type'] = 'GLTFObject'
# Add in URLs to export model part to DXF
for part_name, p_dict in popup_dict.items():
p_dict.setdefault('href',[])
p_dict['href'].append({'label': 'Export DXF', 'URL': 'http://geomodels.auscope.org/'+model_name+'?service=EXPORT&filename='+model_url+'&format=DXF'})
modelconf_dict['popups'] = popup_dict
self.add_inserts(gs_dict, model_url, modelconf_dict, label_str.replace('_', ' '))
modelconf_dict['include'] = True
modelconf_dict['displayed'] = True
self.config_list.append(modelconf_dict)
[docs] def add_inserts(self, gs_dict, model_part_key, modelconf_dict, alt_name):
''' Sets the 'display_name' and other inserts from the group structure dictionary
:param gs_dict: group structure dictionary (group struct dict format: \
{ filename: ( group_name, { insert_key1: val, insert_key2: val } } ))
:param model_part_key: model part key in 'gs_dict'
:param modelconf_dict: dictionary of model information
:param alt_name: alternative 'display_name' when not supplied in 'gs_dict'
'''
# Include inserts from group struct dict
if model_part_key in gs_dict and len(gs_dict[model_part_key]) > 1:
for ins_k, ins_v in gs_dict[model_part_key][1].items():
modelconf_dict[ins_k] = ins_v
# If display name not in group structure dict, use alt name string
if 'display_name' not in modelconf_dict:
modelconf_dict['display_name'] = alt_name
[docs] def add_vol_config(self, gs_dict, geom_obj, style_obj, meta_obj):
''' Create a dictionary containing volume configuration data
:param gs_dict: group structure dictionary (group struct dict format: \
{ filename: ( group_name, { insert_key1: val, insert_key2: val } } ))
:param geom_obj: ModelGeometries object
:param style_obj: STYLE object
:param meta_obj: METADATA object
:returns: a dict of volume config data
'''
model_url = os.path.basename(meta_obj.src_filename)+'.gz'
modelconf_dict = {'model_url': model_url, 'type': '3DVolume',
'include': True, 'displayed': False}
self.add_inserts(gs_dict, model_url, modelconf_dict, meta_obj.name)
modelconf_dict['volumeData'] = {'dataType': geom_obj.vol_data_type,
'dataDims': geom_obj.vol_sz,
'origin': geom_obj.vol_origin,
'size': geom_obj.get_vol_side_lengths(),
'maxVal': geom_obj.get_max_data(),
'minVal': geom_obj.get_min_data(),
'rotation': geom_obj.get_rotation()}
if style_obj.get_colour_table():
modelconf_dict['volumeData']['colourLookup'] = style_obj.get_colour_table()
if style_obj.get_label_table():
modelconf_dict['volumeData']['labelLookup'] = style_obj.get_label_table()
self.config_list.append(modelconf_dict)