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

0.5.0 Release #26

Merged
merged 8 commits into from
Jun 1, 2021
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## 0.5.0 ***(June 1, 2021)***

### New Features

* [Update to support more basemap Types (XYZ and TMS)](https://github.com/Riverscapes/QRAVEPlugin/issues/21)
* [Adding the ability to opt-out of automatic updates](https://github.com/Riverscapes/QRAVEPlugin/issues/23)

### Bug Fixes

* [Failing gracefully when the project XML doesn't conform to the latest version](https://github.com/Riverscapes/QRAVEPlugin/issues/28)
* [Empty tree branches don't cause crashes anymore](https://github.com/Riverscapes/QRAVEPlugin/issues/15)
* [Raster transparencies now work correctly](https://github.com/Riverscapes/QRAVEPlugin/issues/22)
* [...lots of little annoying bugs](https://github.com/Riverscapes/QRAVEPlugin/issues/18)


## 0.4.0 ***(May 13, 2021)***

* [Fixed a blocking bug that prevented working on QGIS Versions less than 3.18](https://github.com/Riverscapes/QRAVEPlugin/issues/16)
Expand Down
2 changes: 1 addition & 1 deletion __version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.4.0"
__version__ = "0.5.0"
2 changes: 2 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"initialized": false,
"loadDefaultView": true,
"basemapsInclude": true,
"autoUpdate": true,
"lastDigestSync": null,
"lastResourceSync": null,
"lastBrowsePath": null,
"pluginVersion": null,
"basemapRegion": "United States"
},
"constants": {
Expand Down
106 changes: 67 additions & 39 deletions src/classes/basemaps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations
import os
import requests
import urllib.parse
from typing import Dict

import lxml.etree
Expand All @@ -22,47 +23,28 @@

class QRaveBaseMap():

def __init__(self, parent: QStandardItem, layer_url: str, meta: Dict[str, str]):
def __init__(self, parent: QStandardItem, layer_url: str, tile_type: str, meta: Dict[str, str]):
self.parent = parent
self.meta = meta
self.loaded = False
self.tile_type = tile_type
self.settings = Settings()
self.layer_url = layer_url.replace('?', '')

self.tm = QgsApplication.taskManager()
self.reset()

def reset(self):
self.parent.removeRows(0, self.parent.rowCount())
loading_layer = QStandardItem('loading...')
f = loading_layer.font()
f.setItalic(True)
loading_layer.setFont(f)
loading_layer.setEnabled(False)
self.parent.appendRow(loading_layer)

def _load_layers_done(self, exception, result=None):
"""This is called when doSomething is finished.
Exception is not None if doSomething raises an exception.
result is the return value of doSomething."""
if exception is None:
if result is None:
self.settings.log('Completed with no exception and no result ', Qgis.Warning)
else:
try:
self.parent.removeRows(0, self.parent.rowCount())
for lyr in result.findall('Capability/Layer'):
self.parse_layer(lyr, self.parent)
if self.tile_type == 'wms':
self.parent.removeRows(0, self.parent.rowCount())
loading_layer = QStandardItem('loading...')
f = loading_layer.font()
f.setItalic(True)
loading_layer.setFont(f)
loading_layer.setEnabled(False)
self.parent.appendRow(loading_layer)

except Exception as e:
self.settings.log(str(e), Qgis.Critical)

else:
self.settings.log("Exception: {}".format(exception),
Qgis.Critical)
raise exception

def parse_layer(self, root_el, parent: QStandardItem):
def _parse_wms_layer(self, root_el, parent: QStandardItem):

try:
title = root_el.find('Title').text
Expand All @@ -76,6 +58,7 @@ def parse_layer(self, root_el, parent: QStandardItem):
abstract = abstract_fnd.text if abstract_fnd is not None else "No abstract provided"

urlWithParams = "crs={}&format={}&layers={}&styles&url={}".format(srs, lyr_format, name, self.layer_url)

lyr_item = QStandardItem(QIcon(':/plugins/qrave_toolbar/layers/Raster.png'), title)

extra_meta = {
Expand All @@ -87,7 +70,7 @@ def parse_layer(self, root_el, parent: QStandardItem):
ProjectTreeData(
QRaveTreeTypes.LEAF,
None,
QRaveMapLayer(title, QRaveMapLayer.LayerTypes.WMS, urlWithParams, extra_meta)
QRaveMapLayer(title, QRaveMapLayer.LayerTypes.WEBTILE, tile_type=self.tile_type, layer_uri=urlWithParams, meta=extra_meta)
),
Qt.UserRole)
lyr_item.setToolTip(wrap_by_word(abstract, 20))
Expand All @@ -100,24 +83,46 @@ def parse_layer(self, root_el, parent: QStandardItem):
Qgis.Warning
)
lyr_item = QStandardItem(QIcon(':/plugins/qrave_toolbar/BrowseFolder.png'), root_el.find('Title').text)
lyr_item.setData(ProjectTreeData(QRaveTreeTypes.BASEMAP_SUB_FOLDER), Qt.UserRole),
lyr_item.setData(ProjectTreeData(QRaveTreeTypes.BASEMAP_SUB_FOLDER), Qt.UserRole)

parent.appendRow(lyr_item)

for sublyr in root_el.findall('Layer'):
self.parse_layer(sublyr, lyr_item)
self._parse_wms_layer(sublyr, lyr_item)

def _wms_fetch_done(self, exception, result=None):
"""This is called when doSomething is finished.
Exception is not None if doSomething raises an exception.
result is the return value of doSomething."""
if exception is None:
if result is None:
self.settings.log('Completed with no exception and no result ', Qgis.Warning)
else:
try:
self.parent.removeRows(0, self.parent.rowCount())
for lyr in result.findall('Capability/Layer'):
self._parse_wms_layer(lyr, self.parent)

except Exception as e:
self.settings.log(str(e), Qgis.Critical)

else:
self.settings.log("Exception: {}".format(exception),
Qgis.Critical)
raise exception

def load_layers(self, force=False):
if self.loaded is True and force is False:
return

def _layer_fetch(task):
def _wms_fetch(task):
result = requestFetch(self.layer_url + REQUEST_ARGS)
return lxml.etree.fromstring(result)

self.settings.log('Fetching WMS Capabilities: {}'.format(self.layer_url), Qgis.Info)
ns_task = QgsTask.fromFunction('Loading WMS Data', _layer_fetch, on_finished=self._load_layers_done)
self.tm.addTask(ns_task)
if self.tile_type == 'wms':
self.settings.log('Fetching WMS Capabilities: {}'.format(self.layer_url), Qgis.Info)
ns_task = QgsTask.fromFunction('Loading WMS Data', _wms_fetch, on_finished=self._wms_fetch_done)
self.tm.addTask(ns_task)


class BaseMaps(Borg):
Expand Down Expand Up @@ -151,13 +156,36 @@ def load(self):

for layer in group_layer.findall('Layer'):
layer_label = layer.attrib['name']
# TODO: Need to go a little backward compatible. We can remove this logic after July 1, 2021
tile_type = layer.attrib['type'] if 'type' in layer.attrib else 'wms'
layer_url = layer.attrib['url']

q_layer = QStandardItem(QIcon(':/plugins/qrave_toolbar/RaveAddIn_16px.png'), layer_label)

meta = {meta.attrib['name']: meta.text for meta in layer.findall('Metadata/Meta')}
# We set the data to be Basemaps to help us load this stuff later
q_layer.setData(ProjectTreeData(QRaveTreeTypes.LEAF, None, QRaveBaseMap(q_layer, layer_url, meta)), Qt.UserRole)

basemap_obj = QRaveBaseMap(q_layer, layer_url, tile_type, meta)

if tile_type == 'wms':
pt_data = basemap_obj
else:
encoded_url = urllib.parse.quote_plus(layer_url)
url_with_params = 'type=xyz&url={}'.format(encoded_url)
pt_data = QRaveMapLayer(
layer_label,
QRaveMapLayer.LayerTypes.WEBTILE,
tile_type=tile_type,
layer_uri=url_with_params,
meta=meta
)

# WMS is complicated because it needs a lookup
q_layer.setData(
ProjectTreeData(QRaveTreeTypes.LEAF, None, pt_data),
Qt.UserRole
)

# We set the data to be Basemaps to help us load this stuff later
q_group_layer.appendRow(q_layer)
except Exception as e:
settings = Settings()
Expand Down
4 changes: 2 additions & 2 deletions src/classes/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def _build_views(self):

for view in self.business_logic.findall('Views/View'):
name = view.attrib['name']
view_id = view.attrib['id']
view_id = view.attrib['id'] if 'id' in view.attrib else None

if name is None or view_id is None:
continue
Expand Down Expand Up @@ -182,7 +182,7 @@ def _recurse_tree(self, bl_el=None, proj_el=None, parent: QStandardItem = None):
# Repeaters are a separate case
elif child_node.tag == 'Repeater':
qrepeater = QStandardItem(QIcon(':/plugins/qrave_toolbar/BrowseFolder.png'), child_node.attrib['label'])
qrepeater.setData(ProjectTreeData(QRaveTreeTypes.PROJECT_REPEATER_FOLDER, project=self, data=children_container.attrib), Qt.UserRole),
qrepeater.setData(ProjectTreeData(QRaveTreeTypes.PROJECT_REPEATER_FOLDER, project=self, data=dict(children_container.attrib)), Qt.UserRole),
curr_item.appendRow(qrepeater)
repeat_xpath = child_node.attrib['xpath']
repeat_node = child_node.find('Node')
Expand Down
18 changes: 10 additions & 8 deletions src/classes/qrave_map_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,36 @@ class LayerTypes():
POINT = 'point'
RASTER = 'raster'
FILE = 'file'
WMS = 'WMS'
# Tile Types
WEBTILE = 'WEBTILE'

def __init__(self,
label: str,
layer_type: str,
layer_uri: str,
bl_attr: Dict[str, str] = None,
meta: Dict[str, str] = None,
layer_name: str = None
layer_name: str = None,
tile_type: str = None
):
self.label = label
self.layer_uri = layer_uri

if isinstance(layer_uri, str) and len(layer_uri) > 0 and layer_type != QRaveMapLayer.LayerTypes.WMS:
if isinstance(layer_uri, str) and len(layer_uri) > 0 and layer_type != QRaveMapLayer.LayerTypes.WEBTILE:
self.layer_uri = os.path.abspath(layer_uri)

self.bl_attr = bl_attr
self.meta = meta
self.transparency = 0
self.layer_name = layer_name
self.tile_type = tile_type

if layer_type not in QRaveMapLayer.LayerTypes.__dict__.values():
settings = Settings()
settings.log('Layer type "{}" is not valid'.format(layer_type), Qgis.Critical)
self.layer_type = layer_type

self.exists = self.layer_type == QRaveMapLayer.LayerTypes.WMS or os.path.isfile(layer_uri)
self.exists = self.layer_type == QRaveMapLayer.LayerTypes.WEBTILE or os.path.isfile(layer_uri)

@staticmethod
def _addgrouptomap(sGroupName, sGroupOrder, parentGroup):
Expand Down Expand Up @@ -146,19 +149,18 @@ def add_layer_to_map(item: QStandardItem):
layer_uri = map_layer.layer_uri
rOutput = None
# This might be a basemap
if map_layer.layer_type == QRaveMapLayer.LayerTypes.WMS:
if map_layer.layer_type == QRaveMapLayer.LayerTypes.WEBTILE:
rOutput = QgsRasterLayer(layer_uri, map_layer.label, 'wms')

elif map_layer.layer_type in [QRaveMapLayer.LayerTypes.LINE, QRaveMapLayer.LayerTypes.POLYGON, QRaveMapLayer.LayerTypes.POINT]:
if map_layer.layer_name is not None:
layer_uri += "|layername={}".format(map_layer.layer_name)
rOutput = QgsVectorLayer(layer_uri, map_layer.label, "ogr")
rOutput = QgsVectorLayer(layer_uri, map_layer.label, "ogr")

elif map_layer.layer_type == QRaveMapLayer.LayerTypes.RASTER:
# Raster
rOutput = QgsRasterLayer(layer_uri, map_layer.label)


if rOutput is not None:
##########################################
# Symbology
Expand Down Expand Up @@ -219,7 +221,7 @@ def add_layer_to_map(item: QStandardItem):
# rOutput.triggerRepaint()
except Exception as e:
settings.log('Error deriving transparency from layer: {}'.format(e))

QgsProject.instance().addMapLayer(rOutput, False)
parentGroup.insertLayer(item.row(), rOutput)

Expand Down
18 changes: 13 additions & 5 deletions src/dock_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,13 @@ def default_tree_action(self, idx: QModelIndex):
else:
QRaveMapLayer.add_layer_to_map(item)

# Expand is the default option for wms because we might need to load the layers
elif isinstance(item_data.data, QRaveBaseMap):
# Expand is the default option because we might need to load the layers
pass
if item_data.data.tile_type == 'wms':
pass
# All the XYZ layers can be added normally.
else:
QRaveMapLayer.add_layer_to_map(item)

elif item_data.type in [QRaveTreeTypes.PROJECT_ROOT]:
self.change_meta(item, item_data, True)
Expand Down Expand Up @@ -476,16 +480,20 @@ def open_menu(self, position):

# This is the layer context menu
if isinstance(data, QRaveMapLayer):
if data.layer_type == QRaveMapLayer.LayerTypes.WMS:
if data.layer_type == QRaveMapLayer.LayerTypes.WEBTILE:
self.basemap_context_menu(idx, item, project_tree_data)
elif data.layer_type == QRaveMapLayer.LayerTypes.FILE:
self.file_layer_context_menu(idx, item, project_tree_data)
else:
self.map_layer_context_menu(idx, item, project_tree_data)

# A QARaveBaseMap is just a container for layers
elif isinstance(data, QRaveBaseMap):
self.folder_dumb_context_menu(idx, item, project_tree_data)
# A WMS QARaveBaseMap is just a container for layers
if data.tile_type == 'wms':
self.folder_dumb_context_menu(idx, item, project_tree_data)
# Every other kind of basemap is an add-able layer
else:
self.basemap_context_menu(idx, item, project_tree_data)

elif project_tree_data.type == QRaveTreeTypes.PROJECT_ROOT:
self.project_context_menu(idx, item, project_tree_data)
Expand Down
2 changes: 2 additions & 0 deletions src/options_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self, parent=None):
def setValues(self):
self.basemapsInclude.setChecked(self.settings.getValue('basemapsInclude'))
self.loadDefaultView.setChecked(self.settings.getValue('loadDefaultView'))
self.autoUpdate.setChecked(self.settings.getValue('autoUpdate'))

# Set the combo box
self.basemapRegion.clear()
Expand All @@ -52,6 +53,7 @@ def commit_settings(self, btn):
self.settings.setValue('basemapsInclude', self.basemapsInclude.isChecked())
self.settings.setValue('loadDefaultView', self.loadDefaultView.isChecked())
self.settings.setValue('basemapRegion', self.basemapRegion.currentText())
self.settings.setValue('autoUpdate', self.autoUpdate.isChecked())

elif role == QDialogButtonBox.ResetRole:
self.settings.resetAllSettings()
Expand Down
Loading