From b6ee8efbc6f3e93756d48de2f579fc53f606aedf Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 May 2023 20:20:20 +1000 Subject: [PATCH 01/11] Use dataset object for more GUI work --- koordinates/api/__init__.py | 1 + koordinates/api/dataset.py | 47 +++++++- koordinates/api/publisher.py | 58 ++++++++++ koordinates/api/utils.py | 23 +++- koordinates/gui/dataset_browser_items.py | 15 +-- koordinates/gui/dataset_dialog.py | 60 ++++++----- koordinates/gui/dataset_utils.py | 102 +++++++++--------- .../gui/detail_widgets/header_widget.py | 35 +++--- koordinates/gui/star_button.py | 11 +- 9 files changed, 242 insertions(+), 110 deletions(-) create mode 100644 koordinates/api/publisher.py diff --git a/koordinates/api/__init__.py b/koordinates/api/__init__.py index 2023740..31ff2e1 100644 --- a/koordinates/api/__init__.py +++ b/koordinates/api/__init__.py @@ -5,6 +5,7 @@ ) from .data_browser import DataBrowserQuery # NOQA from .dataset import Dataset # NOQA +from .publisher import Publisher, PublisherTheme # NOQA from .enums import ( # NOQA DataType, VectorFilter, diff --git a/koordinates/api/dataset.py b/koordinates/api/dataset.py index 8d64a54..03f95fc 100644 --- a/koordinates/api/dataset.py +++ b/koordinates/api/dataset.py @@ -12,12 +12,14 @@ QgsFields, QgsMemoryProviderUtils, QgsCoordinateReferenceSystem, - QgsFeature + QgsFeature, + QgsWkbTypes ) from .enums import DataType from .repo import Repo from .utils import ApiUtils +from .publisher import Publisher class Dataset: @@ -29,6 +31,10 @@ def __init__(self, details: Dict): self.details = details self.id = details['id'] self.datatype = ApiUtils.data_type_from_dataset_response(self.details) + self.geometry_type: QgsWkbTypes.GeometryType = \ + ApiUtils.geometry_type_from_dataset_response( + self.details + ) self.capabilities = ApiUtils.capabilities_from_dataset_response( self.details @@ -49,11 +55,26 @@ def title(self) -> str: """ return self.details.get("title", 'Layer') - def publisher_name(self) -> Optional[str]: + def html_description(self) -> str: """ - Returns the publisher name + Returns the HTML dataset description """ - return self.details.get("publisher", {}).get("name") + return self.details.get("description_html", '') + + def url_canonical(self) -> Optional[str]: + """ + Returns the canonical URL for the dataset + """ + return self.details.get('url_canonical') + + def publisher(self) -> Optional[Publisher]: + """ + Returns the publisher details + """ + if not self.details.get("publisher"): + return None + + return Publisher(self.details["publisher"]) def is_starred(self) -> bool: """ @@ -61,6 +82,12 @@ def is_starred(self) -> bool: """ return self.details.get('is_starred', False) + def thumbnail_url(self) -> Optional[str]: + """ + Returns the dataset's thumbnail URL + """ + return self.details.get('thumbnail_url') + def created_at_date(self) -> Optional[datetime.date]: """ Returns the created at / first published at date @@ -89,6 +116,18 @@ def updated_at_date(self) -> Optional[datetime.date]: return None + def number_downloads(self) -> int: + """ + Returns the number of dataset downloads + """ + return self.details.get("num_downloads", 0) + + def number_views(self) -> int: + """ + Returns the number of dataset views + """ + return self.details.get("num_views", 0) + def clone_url(self) -> Optional[str]: """ Returns the clone URL for the dataset diff --git a/koordinates/api/publisher.py b/koordinates/api/publisher.py new file mode 100644 index 0000000..f5ee4ad --- /dev/null +++ b/koordinates/api/publisher.py @@ -0,0 +1,58 @@ +from typing import ( + Dict, + Optional +) + + +class PublisherTheme: + """ + Represents a publisher theme + """ + def __init__(self, details: Dict): + self.details = details + + def background_color(self) -> Optional[str]: + """ + Returns the background color + """ + return self.details.get('background_color') + + def logo(self) -> Optional[str]: + """ + Returns the publisher's logo + """ + return self.details.get('logo') + + +class PublisherSite: + """ + Represents a publisher site + """ + + def __init__(self, details: Dict): + self.details = details + + def name(self) -> Optional[str]: + """ + Returns the site name + """ + return self.details.get("name") + + +class Publisher: + """ + Represents a publisher + """ + + def __init__(self, details: Dict): + self.details = details + self.site = PublisherSite(self.details['site']) \ + if self.details.get('site') else None + self.theme = PublisherTheme(self.details['theme']) \ + if self.details.get('theme') else None + + def name(self) -> Optional[str]: + """ + Returns the publisher's name + """ + return self.details.get("name") diff --git a/koordinates/api/utils.py b/koordinates/api/utils.py index 7a390f9..de157d3 100644 --- a/koordinates/api/utils.py +++ b/koordinates/api/utils.py @@ -3,7 +3,10 @@ from qgis.PyQt.QtCore import QUrlQuery -from qgis.core import QgsGeometry +from qgis.core import ( + QgsGeometry, + QgsWkbTypes +) from .enums import ( DataType, @@ -54,6 +57,24 @@ def data_type_from_dataset_response(dataset: dict) -> DataType: elif dataset.get('type') == 'repo': return DataType.Repositories + @staticmethod + def geometry_type_from_dataset_response(dataset: dict) \ + -> QgsWkbTypes.GeometryType: + """ + Extracts geometry type from a dataset response + """ + if dataset.get('data', {}).get('geometry_type') in ( + 'polygon', 'multipolygon'): + return QgsWkbTypes.PolygonGeometry + elif dataset.get('data', {}).get('geometry_type') in ( + 'point', 'multipoint'): + return QgsWkbTypes.PointGeometry + elif dataset.get('data', {}).get('geometry_type') in ( + 'linestring', 'multilinestring'): + return QgsWkbTypes.LineGeometry + + return QgsWkbTypes.UnknownGeometry + @staticmethod def access_from_dataset_response(dataset: dict) -> PublicAccessType: """ diff --git a/koordinates/gui/dataset_browser_items.py b/koordinates/gui/dataset_browser_items.py index 648b930..4a2673c 100644 --- a/koordinates/gui/dataset_browser_items.py +++ b/koordinates/gui/dataset_browser_items.py @@ -440,7 +440,7 @@ def __init__(self, dataset: Dict, column_count, parent): self.setThumbnail(GuiUtils.get_svg_as_image(thumbnail_svg, 150, 150)) else: - thumbnail_url = self.dataset.details.get('thumbnail_url') + thumbnail_url = self.dataset.thumbnail_url() if thumbnail_url: downloadThumbnail(thumbnail_url, self) @@ -450,8 +450,7 @@ def __init__(self, dataset: Dict, column_count, parent): private_icon.setToolTip(self.tr('Private')) self.layout().set_private_icon(private_icon) - self.star_button = StarButton(dataset_id=self.dataset.id, - checked=self.dataset.is_starred()) + self.star_button = StarButton(self.dataset) self.layout().set_star_button(self.star_button) title_layout = QHBoxLayout() @@ -472,13 +471,15 @@ def __init__(self, dataset: Dict, column_count, parent): title_font_size = int(12 / font_scale) detail_font_size = int(10 / font_scale) + publisher_name = self.dataset.publisher().name() if \ + self.dataset.publisher() else '' self.title_label.setText( f"""

{self.dataset.title()}
""" f"""{self.dataset.publisher_name()}

""" + font-family: Arial, Sans">{publisher_name}

""" ) license = self.dataset.details.get('license') @@ -688,7 +689,7 @@ def process_thumbnail(self, img: Optional[QImage]) -> QImage: painter.setBrush(QBrush(QColor(0, 0, 0, 150))) painter.drawRoundedRect(QRectF(15, 100, 117, 32), 4, 4) - icon = DatasetGuiUtils.get_icon_for_dataset(self.dataset.details, + icon = DatasetGuiUtils.get_icon_for_dataset(self.dataset, IconStyle.Light) if icon: painter.drawImage(QRectF(21, 106, 20, 20), @@ -697,7 +698,7 @@ def process_thumbnail(self, img: Optional[QImage]) -> QImage: int(20 * scale_factor))) description = DatasetGuiUtils.get_type_description( - self.dataset.details + self.dataset ) font_scale = self.screen().logicalDotsPerInch() / 92 @@ -718,7 +719,7 @@ def process_thumbnail(self, img: Optional[QImage]) -> QImage: painter.setPen(QPen(QColor(255, 255, 255))) painter.drawText(QPointF(47, 112), description) - subtitle = DatasetGuiUtils.get_subtitle(self.dataset.details, + subtitle = DatasetGuiUtils.get_subtitle(self.dataset, short_format=True) if subtitle: font = QFont('Arial') diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index b02753f..cf57a75 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -83,21 +83,16 @@ def __init__(self, parent, dataset: Dict): else: self.attachments = [] - self.dataset_type: DataType = ApiUtils.data_type_from_dataset_response( - self.dataset + self.setWindowTitle('Dataset Details - {}'.format( + self.dataset_obj.title()) ) - self.public_access_type = ApiUtils.access_from_dataset_response( - self.dataset - ) - - self.setWindowTitle('Dataset Details - {}'.format(dataset.get('title', 'layer'))) self.setStyleSheet('DatasetDialog {background-color: white; }') layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 20) - self.header_widget = HeaderWidget(dataset) + self.header_widget = HeaderWidget(self.dataset_obj) layout.addWidget(self.header_widget) title_hl = QHBoxLayout() @@ -115,11 +110,12 @@ def __init__(self, parent, dataset: Dict): self.label_title.setText( f"""{dataset.get('title', '')}""" + font-size: {title_font_size}pt;">""" + f"""{self.dataset_obj.title()}""" ) title_hl.addWidget(self.label_title) - if self.public_access_type == PublicAccessType.none: + if self.dataset_obj.access == PublicAccessType.none: private_icon = QSvgWidget(GuiUtils.get_icon_svg('private.svg')) private_icon.setFixedSize(QSize(24, 24)) private_icon.setToolTip(self.tr('Private')) @@ -127,12 +123,12 @@ def __init__(self, parent, dataset: Dict): title_hl.addStretch() - is_starred = self.dataset.get('is_starred', False) - self.star_button = StarButton(dataset_id=self.dataset['id'], checked=is_starred) + self.star_button = StarButton(self.dataset_obj) title_hl.addWidget(self.star_button) if Capability.Clone in self.dataset_obj.capabilities: - self.clone_button = CloneButton(self.dataset_obj, close_parent_on_clone=True) + self.clone_button = CloneButton(self.dataset_obj, + close_parent_on_clone=True) title_hl.addWidget(self.clone_button) else: self.clone_button = None @@ -156,7 +152,7 @@ def __init__(self, parent, dataset: Dict): self.thumbnail_label = QLabel() self.thumbnail_label.setFixedSize(256, 195) - thumbnail_url = dataset.get('thumbnail_url') + thumbnail_url = self.dataset_obj.thumbnail_url() if thumbnail_url: downloadThumbnail(thumbnail_url, self) @@ -169,15 +165,16 @@ def __init__(self, parent, dataset: Dict): base_details_right_pane_layout = QHBoxLayout() base_details_right_pane_layout.setContentsMargins(12, 0, 0, 0) - icon_name = DatasetGuiUtils.get_icon_for_dataset(self.dataset, IconStyle.Dark) + icon_name = DatasetGuiUtils.get_icon_for_dataset(self.dataset_obj, + IconStyle.Dark) icon_label = SvgLabel(icon_name, 24, 24) base_details_right_pane_layout.addWidget(icon_label) summary_label = QLabel() summary_label.setAlignment(Qt.AlignmentFlag.AlignTop) - description = DatasetGuiUtils.get_type_description(self.dataset) - subtitle = DatasetGuiUtils.get_subtitle(self.dataset, + description = DatasetGuiUtils.get_type_description(self.dataset_obj) + subtitle = DatasetGuiUtils.get_subtitle(self.dataset_obj, short_format=False) summary_label.setText("""

Optional[str]: return None @staticmethod - def get_icon_for_dataset(dataset: Dict, style: IconStyle) -> Optional[str]: + def get_icon_for_dataset(dataset: Dataset, style: IconStyle) \ + -> Optional[str]: if style == IconStyle.Light: suffix = 'light' else: suffix = 'dark' - data_type = ApiUtils.data_type_from_dataset_response(dataset) - - if data_type == DataType.Vectors: - if dataset.get('data', {}).get('geometry_type') in ( - 'polygon', 'multipolygon'): + if dataset.datatype == DataType.Vectors: + if dataset.geometry_type == QgsWkbTypes.PolygonGeometry: return 'polygon-{}.svg'.format(suffix) - elif dataset.get('data', {}).get('geometry_type') in ('point', 'multipoint'): + elif dataset.geometry_type == QgsWkbTypes.PointGeometry: return 'point-{}.svg'.format(suffix) - elif dataset.get('data', {}).get('geometry_type') in ( - 'linestring', 'multilinestring'): + elif dataset.geometry_type == QgsWkbTypes.LineGeometry: return 'line-{}.svg'.format(suffix) - elif data_type == DataType.Rasters: + elif dataset.datatype == DataType.Rasters: return 'raster-{}.svg'.format(suffix) - elif data_type == DataType.Grids: + elif dataset.datatype == DataType.Grids: return 'grid-{}.svg'.format(suffix) - elif data_type == DataType.Tables: + elif dataset.datatype == DataType.Tables: return 'table-{}.svg'.format(suffix) - elif data_type == DataType.Documents: + elif dataset.datatype == DataType.Documents: return 'document-{}.svg'.format(suffix) - elif data_type == DataType.Sets: + elif dataset.datatype == DataType.Sets: return 'set-{}.svg'.format(suffix) - elif data_type == DataType.Repositories: + elif dataset.datatype == DataType.Repositories: return 'repo-{}.svg'.format(suffix) - elif data_type == DataType.PointClouds: + elif dataset.datatype == DataType.PointClouds: return 'point-cloud-{}.svg'.format(suffix) return None @@ -99,70 +99,64 @@ def get_data_type(dataset: Dict) -> Optional[str]: return None @staticmethod - def get_type_description(dataset: Dict) -> Optional[str]: - data_type = ApiUtils.data_type_from_dataset_response(dataset) - if data_type == DataType.Vectors: - if dataset.get('data', {}).get('geometry_type') in ( - 'polygon', 'multipolygon'): + def get_type_description(dataset: Dataset) -> Optional[str]: + if dataset.datatype == DataType.Vectors: + if dataset.geometry_type == QgsWkbTypes.PolygonGeometry: return 'Polygon Layer' - elif dataset.get('data', {}).get('geometry_type') in ('point', 'multipoint'): + elif dataset.geometry_type == QgsWkbTypes.PointGeometry: return 'Point Layer' - elif dataset.get('data', {}).get('geometry_type') in ( - 'linestring', 'multilinestring'): + elif dataset.geometry_type == QgsWkbTypes.LineGeometry: return 'Line Layer' - elif data_type == DataType.Rasters: + elif dataset.datatype == DataType.Rasters: return 'Raster Layer' - elif data_type == DataType.Grids: + elif dataset.datatype == DataType.Grids: return 'Grid Layer' - elif data_type == DataType.Tables: + elif dataset.datatype == DataType.Tables: return 'Table' - elif data_type == DataType.Documents: + elif dataset.datatype == DataType.Documents: return 'Document' - elif data_type == DataType.Sets: + elif dataset.datatype == DataType.Sets: return 'Set' - elif data_type == DataType.Repositories: + elif dataset.datatype == DataType.Repositories: return 'Repository' - elif data_type == DataType.PointClouds: + elif dataset.datatype == DataType.PointClouds: return 'Point Cloud' return None @staticmethod - def get_subtitle(dataset: Dict, short_format: bool = True) -> Optional[str]: - data_type = ApiUtils.data_type_from_dataset_response(dataset) - if data_type == DataType.Vectors: + def get_subtitle(dataset: Dataset, short_format: bool = True) -> Optional[str]: + if dataset.datatype == DataType.Vectors: - count = dataset.get("data", {}).get("feature_count") or 0 + count = dataset.details.get("data", {}).get("feature_count") or 0 - if dataset.get('data', {}).get('geometry_type') in ( - 'polygon', 'multipolygon'): + if dataset.geometry_type == QgsWkbTypes.PolygonGeometry: return '{} Polygons'.format(DatasetGuiUtils.format_count(count)) - elif dataset.get('data', {}).get('geometry_type') in ('point', 'multipoint'): + elif dataset.geometry_type == QgsWkbTypes.PointGeometry: return '{} Points'.format(DatasetGuiUtils.format_count(count)) - elif dataset.get('data', {}).get('geometry_type') in ( - 'linestring', 'multilinestring'): + elif dataset.geometry_type == QgsWkbTypes.LineGeometry: return '{} Lines'.format(DatasetGuiUtils.format_count(count)) - elif data_type in (DataType.Rasters, DataType.Grids): - count = dataset.get("data", {}).get("feature_count") or 0 - res = dataset.get("data", {}).get("raster_resolution") or 0 + elif dataset.datatype in (DataType.Rasters, DataType.Grids): + count = dataset.details.get("data", {}).get("feature_count") or 0 + res = dataset.details.get("data", {}).get("raster_resolution") or 0 return '{}m, {} Tiles'.format(res, DatasetGuiUtils.format_count(count)) - elif data_type == DataType.Tables: - count = dataset.get("data", {}).get("feature_count") or 0 + elif dataset.datatype == DataType.Tables: + count = dataset.details.get("data", {}).get("feature_count") or 0 return '{} Rows'.format(DatasetGuiUtils.format_count(count)) - elif data_type == DataType.Documents: - ext = dataset.get('extension', '').upper() - file_size = dataset.get('file_size') + elif dataset.datatype == DataType.Documents: + ext = dataset.details.get('extension', '').upper() + file_size = dataset.details.get('file_size') if file_size: return '{} {}'.format(ext, QgsFileUtils.representFileSize(file_size)) return ext - elif data_type == DataType.Sets: + elif dataset.datatype == DataType.Sets: return None - elif data_type == DataType.Repositories: + elif dataset.datatype == DataType.Repositories: return None - elif data_type == DataType.PointClouds: - count = dataset.get("data", {}).get("feature_count") or 0 - point_count = dataset.get("data", {}).get("point_count") or 0 + elif dataset.datatype == DataType.PointClouds: + count = dataset.details.get("data", {}).get("feature_count") or 0 + point_count = dataset.details.get("data", {}).get("point_count") or 0 if short_format: return '{} Tiles'.format( DatasetGuiUtils.format_count(count) diff --git a/koordinates/gui/detail_widgets/header_widget.py b/koordinates/gui/detail_widgets/header_widget.py index e8cf606..0826987 100644 --- a/koordinates/gui/detail_widgets/header_widget.py +++ b/koordinates/gui/detail_widgets/header_widget.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Optional from qgis.PyQt.QtCore import ( Qt @@ -14,6 +14,10 @@ from koordinates.gui.gui_utils import FONT_FAMILIES from koordinates.gui.detail_widgets.svg_framed_button import SvgFramedButton from koordinates.gui.detail_widgets.thumbnail_label_widget import ThumbnailLabel +from ...api import ( + Dataset, + PublisherTheme +) class HeaderWidget(QFrame): @@ -22,16 +26,19 @@ class HeaderWidget(QFrame): themed by the publisher """ - def __init__(self, dataset: Dict, parent=None): + def __init__(self, dataset: Dataset, parent=None): super().__init__(parent) self.dataset = dataset self.setFixedHeight(72) self.setFrameShape(QFrame.NoFrame) - background_color = self.dataset.get('publisher', {}).get('theme', - {}).get( - 'background_color') or '555657' + self.publisher_theme = self.dataset.publisher().theme \ + if self.dataset.publisher() else None + background_color = self.publisher_theme.background_color() \ + if self.publisher_theme else None + background_color = background_color or '555657' + self.setStyleSheet( 'HeaderWidget {{ background-color: #{}; }}'.format( background_color)) @@ -39,7 +46,7 @@ def __init__(self, dataset: Dict, parent=None): hl = QHBoxLayout() hl.setContentsMargins(15, 0, 15, 0) - logo = self.dataset.get('publisher', {}).get('theme', {}).get('logo') + logo = self.publisher_theme.logo() if self.publisher_theme else None if logo: logo = 'https:{}'.format(logo) logo_widget = ThumbnailLabel(logo, 145, 35) @@ -57,7 +64,7 @@ def __init__(self, dataset: Dict, parent=None): url_layout = QHBoxLayout() url_layout.setContentsMargins(12, 7, 12, 7) - url_label = QLabel(self.dataset.get('url_canonical', '')) + url_label = QLabel(self.dataset.url_canonical() or '') if background_color: url_label.setStyleSheet( """QLabel { @@ -91,21 +98,25 @@ def __init__(self, dataset: Dict, parent=None): if font_scale > 1: org_font_size = int(12 / font_scale) - publisher_site = self.dataset.get("publisher").get('site', - {}).get("name") + publisher_site = self.dataset.publisher().site \ + if self.dataset.publisher() else None + publisher_site_name = publisher_site.name() if publisher_site else '' + org_details_label = QLabel() org_details_label.setStyleSheet('padding-left: 10px;') + publisher_name = self.dataset.publisher().name() \ + if self.dataset.publisher() else '' org_details_label.setText( f"""

{self.dataset.get('publisher', {}).get('name')}
""" + f""">{publisher_name}
""" f"""via {publisher_site}

""" + >via {publisher_site_name}

""" ) hl.addWidget(org_details_label) @@ -113,5 +124,5 @@ def __init__(self, dataset: Dict, parent=None): self.setLayout(hl) def _copy_url(self): - url = self.dataset.get('url_canonical', '') + url = self.dataset.url_canonical() QApplication.clipboard().setText(url) diff --git a/koordinates/gui/star_button.py b/koordinates/gui/star_button.py index 8b287b8..36b54b6 100644 --- a/koordinates/gui/star_button.py +++ b/koordinates/gui/star_button.py @@ -6,16 +6,16 @@ ) from .gui_utils import GuiUtils -from ..api import KoordinatesClient +from ..api import KoordinatesClient, Dataset class StarButton(QSvgWidget): - def __init__(self, checked: bool, dataset_id, parent=None): + def __init__(self, dataset: Dataset, parent=None): super().__init__(parent) self.setMouseTracking(True) - self._dataset_id = dataset_id - self._checked = checked + self.dataset = dataset + self._checked = dataset.is_starred() self._hover = False self.setFixedSize(24, 24) self._update_icon() @@ -42,7 +42,8 @@ def _update_icon(self): def mousePressEvent(self, event): if event.button() == Qt.LeftButton: to_star = not self._checked - KoordinatesClient.instance().star(self._dataset_id, is_starred=to_star) + KoordinatesClient.instance().star(self.dataset.id, + is_starred=to_star) self._checked = to_star self._update_icon() else: From e732be71a9a0c6301c4f40f3cfaf937ca96e0031 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 May 2023 20:39:00 +1000 Subject: [PATCH 02/11] Show thumbnail for point cloud --- koordinates/gui/dataset_browser_items.py | 15 ++++++-- koordinates/gui/dataset_dialog.py | 49 ++++++++++++++++-------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/koordinates/gui/dataset_browser_items.py b/koordinates/gui/dataset_browser_items.py index 4a2673c..88c1c89 100644 --- a/koordinates/gui/dataset_browser_items.py +++ b/koordinates/gui/dataset_browser_items.py @@ -578,6 +578,14 @@ def setThumbnail(self, img: Optional[QImage]): self.update_thumbnail() def update_thumbnail(self): + thumbnail_svg = DatasetGuiUtils.thumbnail_icon_for_dataset( + self.dataset + ) + if thumbnail_svg: + size = 150 + self.raw_thumbnail = GuiUtils.get_svg_as_image( + thumbnail_svg, size, size) + thumbnail = self.process_thumbnail(self.raw_thumbnail) dpi_ratio = self.window().screen().devicePixelRatio() width = int(thumbnail.width() / dpi_ratio) @@ -652,9 +660,10 @@ def process_thumbnail(self, img: Optional[QImage]) -> QImage: if img is not None: resized = img.scaled(image_size.width(), - image_size.height(), - Qt.KeepAspectRatioByExpanding, - Qt.SmoothTransformation) + image_size.height(), + Qt.KeepAspectRatioByExpanding, + Qt.SmoothTransformation) + if resized.width() > image_size.width(): left = int((resized.width() - image_size.width())/2) else: diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index cf57a75..850bcc1 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -152,9 +152,17 @@ def __init__(self, parent, dataset: Dict): self.thumbnail_label = QLabel() self.thumbnail_label.setFixedSize(256, 195) - thumbnail_url = self.dataset_obj.thumbnail_url() - if thumbnail_url: - downloadThumbnail(thumbnail_url, self) + + thumbnail_svg = DatasetGuiUtils.thumbnail_icon_for_dataset( + self.dataset_obj + ) + if thumbnail_svg: + self.setThumbnail(GuiUtils.get_svg_as_image(thumbnail_svg, + 195, 195)) + else: + thumbnail_url = self.dataset_obj.thumbnail_url() + if thumbnail_url: + downloadThumbnail(thumbnail_url, self) contents_layout = QVBoxLayout() contents_layout.setContentsMargins(0, 0, 15, 15) @@ -439,21 +447,28 @@ def setThumbnail(self, img): painter.setCompositionMode(QPainter.CompositionMode_SourceIn) if img is not None: - resized = img.scaled(image_size.width(), - image_size.height(), - Qt.KeepAspectRatioByExpanding, - Qt.SmoothTransformation) - if resized.width() > image_size.width(): - left = int((resized.width() - image_size.width()) / 2) + if img.size() != image_size: + if image_size.width() != img.width() and image_size.height() != img.height(): + resized = img.scaled(image_size.width(), + image_size.height(), + Qt.KeepAspectRatioByExpanding, + Qt.SmoothTransformation) + else: + resized = img + + if resized.width() > image_size.width(): + left = int((resized.width() - image_size.width()) / 2) + else: + left = 0 + if resized.height() > image_size.height(): + top = int((resized.height() - image_size.height()) / 2) + else: + top = 0 + + cropped = resized.copy(QRect(left, top, image_size.width(), image_size.height())) + painter.drawImage(0, 0, cropped) else: - left = 0 - if resized.height() > image_size.height(): - top = int((resized.height() - image_size.height()) / 2) - else: - top = 0 - - cropped = resized.copy(QRect(left, top, image_size.width(), image_size.height())) - painter.drawImage(0, 0, cropped) + painter.drawImage(0, 0, img) else: painter.setBrush(QBrush(QColor('#cccccc'))) painter.setPen(Qt.NoPen) From 8bf0fa4d5444759f91e5b749a24ad43ccfeb2d27 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 May 2023 20:49:05 +1000 Subject: [PATCH 03/11] Further point cloud thumbnail appearance fixes --- koordinates/gui/dataset_dialog.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index 850bcc1..6201397 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -445,6 +445,8 @@ def setThumbnail(self, img): painter.drawRoundedRect(0, 0, image_size.width(), image_size.height(), 9, 9) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.setBrush(QBrush(QColor('#dddddd'))) + painter.drawRect(0, 0, image_size.width(), image_size.height()) if img is not None: if img.size() != image_size: @@ -465,8 +467,13 @@ def setThumbnail(self, img): else: top = 0 - cropped = resized.copy(QRect(left, top, image_size.width(), image_size.height())) - painter.drawImage(0, 0, cropped) + if left > 0 or top > 0: + cropped = resized.copy(QRect(left, top, image_size.width(), image_size.height())) + painter.drawImage(0, 0, cropped) + else: + painter.drawImage(int((image_size.width() - resized.width()) / 2), + int((image_size.height() - resized.height()) / 2), + resized) else: painter.drawImage(0, 0, img) else: From edf8e055ac12e78a4a72ac2484f5f037b3d3d984 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 May 2023 21:06:57 +1000 Subject: [PATCH 04/11] Show repository name in details dialog --- koordinates/api/dataset.py | 25 ++++++++++--------------- koordinates/api/repo.py | 11 ++++++++++- koordinates/gui/action_button.py | 12 ++++++++---- koordinates/gui/dataset_dialog.py | 27 +++++++++++++++++++++++++++ koordinates/icons/repo-book.svg | 10 ++++++++++ koordinates/test/test_dataset.py | 2 +- 6 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 koordinates/icons/repo-book.svg diff --git a/koordinates/api/dataset.py b/koordinates/api/dataset.py index 03f95fc..37b8429 100644 --- a/koordinates/api/dataset.py +++ b/koordinates/api/dataset.py @@ -40,7 +40,7 @@ def __init__(self, details: Dict): self.details ) self.access = ApiUtils.access_from_dataset_response(self.details) - self.repository: Optional[Repo] = None + self._repository: Optional[Repo] = None self.gridded_extent: Optional[QgsGeometry] = None if 'data' in self.details and self.details['data'].get( @@ -128,28 +128,23 @@ def number_views(self) -> int: """ return self.details.get("num_views", 0) - def clone_url(self) -> Optional[str]: + def repository(self) -> Optional[Repo]: """ - Returns the clone URL for the dataset + Returns the repository information for the dataset """ - if self.repository is not None: - return self.repository.clone_url() - - if isinstance(self.details.get("repository"), dict): - url = self.details["repository"].get("clone_location_https") - if url: - return url + if self._repository is not None: + return self._repository repo_detail_url = self.details.get('repository') - if repo_detail_url: + if repo_detail_url and isinstance(repo_detail_url, dict): + self._repository = Repo(repo_detail_url) + elif repo_detail_url: from .client import KoordinatesClient - self.repository = KoordinatesClient.instance().retrieve_repository( + self._repository = KoordinatesClient.instance().retrieve_repository( repo_detail_url ) - if self.repository: - return self.repository.clone_url() - return None + return self._repository def to_map_layer(self) -> Optional[QgsMapLayer]: """ diff --git a/koordinates/api/repo.py b/koordinates/api/repo.py index 308fcff..8186a68 100644 --- a/koordinates/api/repo.py +++ b/koordinates/api/repo.py @@ -1,4 +1,7 @@ -from typing import Dict +from typing import ( + Dict, + Optional +) class Repo: @@ -10,6 +13,12 @@ def __init__(self, definition: Dict): self.definition = definition self.id = definition['id'] + def title(self) -> Optional[str]: + """ + Returns the repository title + """ + return self.definition.get('title') + def clone_url(self) -> str: """ Returns the clone URL for the repository diff --git a/koordinates/gui/action_button.py b/koordinates/gui/action_button.py index 91c1624..f048d8f 100644 --- a/koordinates/gui/action_button.py +++ b/koordinates/gui/action_button.py @@ -95,9 +95,10 @@ def _update_state(self): """ Updates button state based on current operations """ - is_cloning = KartOperationManager.instance().is_cloning( - self.dataset.clone_url() - ) + is_cloning = self.dataset.repository() and \ + KartOperationManager.instance().is_cloning( + self.dataset.repository().clone_url() + ) self.setEnabled(not is_cloning) if is_cloning: icon = GuiUtils.get_icon('cloning_button.svg') @@ -115,9 +116,12 @@ def _update_state(self): self.setText(self.tr('Clone')) def cloneRepository(self): + if not self.dataset.repository(): + return + if self._close_parent_on_clone: self.parent().close() - url = self.dataset.clone_url() + url = self.dataset.repository().clone_url() title = self.dataset.title() from .action_dialog import ActionDialog diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index 6201397..faa4d68 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -200,6 +200,33 @@ def __init__(self, parent, dataset: Dict): base_details_right_pane_layout_vl = QVBoxLayout() base_details_right_pane_layout_vl.setContentsMargins(0, 0, 0, 0) base_details_right_pane_layout_vl.addLayout(base_details_right_pane_layout) + + if self.dataset_obj.repository(): + base_details_right_pane_layout = QHBoxLayout() + base_details_right_pane_layout.setContentsMargins(12, 0, 0, 0) + + icon_label = SvgLabel(GuiUtils.get_icon_svg('repo-book.svg'), 24, 24) + base_details_right_pane_layout.addWidget(icon_label) + + summary_label = QLabel() + summary_label.setAlignment(Qt.AlignmentFlag.AlignTop) + + description = self.tr('Repository') + subtitle = self.dataset_obj.repository().title() + + summary_label.setText("""

{}
+ {}

""".format( + FONT_FAMILIES, + base_font_size, + description, + subtitle)) + + base_details_right_pane_layout.addSpacing(10) + base_details_right_pane_layout.addWidget(summary_label, 1) + base_details_right_pane_layout_vl.addLayout(base_details_right_pane_layout) + base_details_right_pane_layout_vl.addStretch() base_details_layout.addLayout(base_details_right_pane_layout_vl) diff --git a/koordinates/icons/repo-book.svg b/koordinates/icons/repo-book.svg new file mode 100644 index 0000000..8b006ce --- /dev/null +++ b/koordinates/icons/repo-book.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/koordinates/test/test_dataset.py b/koordinates/test/test_dataset.py index 8cf4be0..90e3abf 100644 --- a/koordinates/test/test_dataset.py +++ b/koordinates/test/test_dataset.py @@ -87,7 +87,7 @@ def test_point_cloud(self): ) self.assertEqual(point_cloud_dataset.datatype, DataType.PointClouds) - self.assertEqual(point_cloud_dataset.clone_url(), + self.assertEqual(point_cloud_dataset.repository().clone_url(), 'https://test.koordinates.com/koordinates/aaa-laz') self.assertEqual(point_cloud_dataset.id, 'aaa') self.assertEqual(point_cloud_dataset.capabilities, From 1cf48d4d7b00ca86aec877b0e7d3abc6feab2be7 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 May 2023 21:23:34 +1000 Subject: [PATCH 05/11] Fix CRS display in details dialog --- koordinates/api/dataset.py | 30 ++++++++++++++++++ koordinates/gui/dataset_browser_items.py | 2 +- koordinates/gui/dataset_dialog.py | 25 ++++++++------- koordinates/gui/dataset_utils.py | 31 ++++++++++--------- .../detail_widgets/details_table_widget.py | 2 ++ 5 files changed, 63 insertions(+), 27 deletions(-) diff --git a/koordinates/api/dataset.py b/koordinates/api/dataset.py index 37b8429..abe2eff 100644 --- a/koordinates/api/dataset.py +++ b/koordinates/api/dataset.py @@ -22,6 +22,33 @@ from .publisher import Publisher +class Crs: + """ + Represents a CRS + """ + + def __init__(self, details: Dict): + self.details = details + + def name(self) -> Optional[str]: + """ + Returns the CRS name + """ + return self.details.get('name') + + def id(self) -> Optional[str]: + """ + Returns the CRS ID + """ + return self.details.get('id') + + def url_external(self) -> Optional[str]: + """ + Returns the external URL for the CRS + """ + return self.details.get('url_external') + + class Dataset: """ Represents a dataset @@ -49,6 +76,9 @@ def __init__(self, details: Dict): self.details['data']['gridded_extent'] ) + self.crs: Optional[Crs] = Crs(self.details['data']['crs']) \ + if self.details.get('data', {}).get('crs') else None + def title(self) -> str: """ Returns the dataset's title diff --git a/koordinates/gui/dataset_browser_items.py b/koordinates/gui/dataset_browser_items.py index 88c1c89..9802b7c 100644 --- a/koordinates/gui/dataset_browser_items.py +++ b/koordinates/gui/dataset_browser_items.py @@ -763,7 +763,7 @@ def show_details(self): # dlg = RepositoryDialog(self, self.dataset) return else: - dlg = DatasetDialog(self, self.dataset.details) + dlg = DatasetDialog(self, self.dataset) dlg.exec() def _geomFromGeoJson(self, geojson) -> Optional[QgsGeometry]: diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index faa4d68..11cf100 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -69,11 +69,11 @@ class DatasetDialog(QDialog): A dialog showing details of a dataset """ - def __init__(self, parent, dataset: Dict): + def __init__(self, parent, dataset: Dataset): super().__init__(parent) - self.dataset = dataset - self.dataset_obj = Dataset(dataset) + self.dataset = dataset.details + self.dataset_obj = dataset self.details = KoordinatesClient.instance().dataset_details( self.dataset_obj) @@ -394,15 +394,18 @@ def format_date(value: datetime.date): def get_technical_details(self) -> List[Tuple]: res = [ - ('Data type', DatasetGuiUtils.get_data_type(self.dataset)) + ('Data type', DatasetGuiUtils.get_data_type(self.dataset_obj)) ] - crs_display = self.dataset.get('data', {}).get('crs_display') - crs = self.dataset.get('data', {}).get('crs') + crs = self.dataset_obj.crs + crs_display = crs.name() if crs else '' + crs_id = crs.id() if crs else '' if crs_display: - res.append(('CRS', '{} • {}'.format(crs_display, - crs - ))) + res.append(('CRS', '{} • {}'.format( + crs_display, + crs.url_external(), + crs_id + ))) feature_count = self.dataset.get("data", {}).get("feature_count", 0) empty_count = self.dataset.get("data", {}).get('empty_geometry_count', 0) @@ -437,10 +440,10 @@ def get_history_details(self) -> List[Tuple]: if Capability.RevisionCount in self.dataset_obj.capabilities: data_revisions_count = \ KoordinatesClient.instance().data_revisions_count( - self.dataset["id"]) + self.dataset_obj.id) total_revisions_count = \ KoordinatesClient.instance().total_revisions_count( - self.dataset["id"]) + self.dataset_obj.id) if data_revisions_count is not None or total_revisions_count is not None: res.append( diff --git a/koordinates/gui/dataset_utils.py b/koordinates/gui/dataset_utils.py index a76da8c..04c9b4d 100644 --- a/koordinates/gui/dataset_utils.py +++ b/koordinates/gui/dataset_utils.py @@ -68,33 +68,34 @@ def get_icon_for_dataset(dataset: Dataset, style: IconStyle) \ return None @staticmethod - def get_data_type(dataset: Dict) -> Optional[str]: - data_type = ApiUtils.data_type_from_dataset_response(dataset) - if data_type == DataType.Vectors: - if dataset.get('data', {}).get('geometry_type') == 'polygon': + def get_data_type(dataset: Dataset) -> Optional[str]: + if dataset.datatype == DataType.Vectors: + if dataset.details.get('data', {}).get('geometry_type') == 'polygon': return 'Vector polygon' - elif dataset.get('data', {}).get('geometry_type') == 'multipolygon': + elif dataset.details.get('data', {}).get('geometry_type') == 'multipolygon': return 'Vector multipolygon' - elif dataset.get('data', {}).get('geometry_type') == 'point': + elif dataset.details.get('data', {}).get('geometry_type') == 'point': return 'Vector point' - elif dataset.get('data', {}).get('geometry_type') == 'multipoint': + elif dataset.details.get('data', {}).get('geometry_type') == 'multipoint': return 'Vector multipoint' - elif dataset.get('data', {}).get('geometry_type') == 'linestring': + elif dataset.details.get('data', {}).get('geometry_type') == 'linestring': return 'Vector line' - elif dataset.get('data', {}).get('geometry_type') == 'multilinestring': + elif dataset.details.get('data', {}).get('geometry_type') == 'multilinestring': return 'Vector multiline' - elif data_type == DataType.Rasters: + elif dataset.datatype == DataType.Rasters: return 'Raster' - elif data_type == DataType.Grids: + elif dataset.datatype == DataType.Grids: return 'Grid' - elif data_type == DataType.Tables: + elif dataset.datatype == DataType.Tables: return 'Table' - elif data_type == DataType.Documents: + elif dataset.datatype == DataType.Documents: return 'Document' - elif data_type == DataType.Sets: + elif dataset.datatype == DataType.Sets: return 'Set' - elif data_type == DataType.Repositories: + elif dataset.datatype == DataType.Repositories: return 'Repository' + elif dataset.datatype == DataType.PointClouds: + return 'Point cloud' return None diff --git a/koordinates/gui/detail_widgets/details_table_widget.py b/koordinates/gui/detail_widgets/details_table_widget.py index cf985f9..5a00c53 100644 --- a/koordinates/gui/detail_widgets/details_table_widget.py +++ b/koordinates/gui/detail_widgets/details_table_widget.py @@ -59,6 +59,7 @@ def push_row(self, title: str, value: str): title)) title_label.setTextInteractionFlags( Qt.TextInteractionFlag.TextBrowserInteraction) + title_label.setOpenExternalLinks(True) fm = QFontMetrics(QFont()) title_label.setFixedWidth(fm.width('x') * 30) @@ -73,6 +74,7 @@ def push_row(self, title: str, value: str): """color: black">{}""".format(value)) value_label.setTextInteractionFlags( Qt.TextInteractionFlag.TextBrowserInteraction) + value_label.setOpenExternalLinks(True) value_label.setAlignment( Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft) value_label.setWordWrap(True) From 172d56f9c59a374a7883e9dcb93d56f34e6db1c9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 May 2023 21:24:33 +1000 Subject: [PATCH 06/11] Cleanup --- koordinates/gui/dataset_dialog.py | 73 +++++++++++++++---------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index 11cf100..6c963d6 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -72,11 +72,10 @@ class DatasetDialog(QDialog): def __init__(self, parent, dataset: Dataset): super().__init__(parent) - self.dataset = dataset.details - self.dataset_obj = dataset + self.dataset = dataset self.details = KoordinatesClient.instance().dataset_details( - self.dataset_obj) + self.dataset) if self.details.get('attachments'): self.attachments = KoordinatesClient.instance().get_json(self.details['attachments']) @@ -84,7 +83,7 @@ def __init__(self, parent, dataset: Dataset): self.attachments = [] self.setWindowTitle('Dataset Details - {}'.format( - self.dataset_obj.title()) + self.dataset.title()) ) self.setStyleSheet('DatasetDialog {background-color: white; }') @@ -92,7 +91,7 @@ def __init__(self, parent, dataset: Dataset): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 20) - self.header_widget = HeaderWidget(self.dataset_obj) + self.header_widget = HeaderWidget(self.dataset) layout.addWidget(self.header_widget) title_hl = QHBoxLayout() @@ -111,11 +110,11 @@ def __init__(self, parent, dataset: Dataset): f"""""" - f"""{self.dataset_obj.title()}""" + f"""{self.dataset.title()}""" ) title_hl.addWidget(self.label_title) - if self.dataset_obj.access == PublicAccessType.none: + if self.dataset.access == PublicAccessType.none: private_icon = QSvgWidget(GuiUtils.get_icon_svg('private.svg')) private_icon.setFixedSize(QSize(24, 24)) private_icon.setToolTip(self.tr('Private')) @@ -123,18 +122,18 @@ def __init__(self, parent, dataset: Dataset): title_hl.addStretch() - self.star_button = StarButton(self.dataset_obj) + self.star_button = StarButton(self.dataset) title_hl.addWidget(self.star_button) - if Capability.Clone in self.dataset_obj.capabilities: - self.clone_button = CloneButton(self.dataset_obj, + if Capability.Clone in self.dataset.capabilities: + self.clone_button = CloneButton(self.dataset, close_parent_on_clone=True) title_hl.addWidget(self.clone_button) else: self.clone_button = None - if Capability.Add in self.dataset_obj.capabilities: - self.add_button = AddButton(self.dataset_obj) + if Capability.Add in self.dataset.capabilities: + self.add_button = AddButton(self.dataset) title_hl.addWidget(self.add_button) else: self.add_button = None @@ -154,13 +153,13 @@ def __init__(self, parent, dataset: Dataset): self.thumbnail_label.setFixedSize(256, 195) thumbnail_svg = DatasetGuiUtils.thumbnail_icon_for_dataset( - self.dataset_obj + self.dataset ) if thumbnail_svg: self.setThumbnail(GuiUtils.get_svg_as_image(thumbnail_svg, 195, 195)) else: - thumbnail_url = self.dataset_obj.thumbnail_url() + thumbnail_url = self.dataset.thumbnail_url() if thumbnail_url: downloadThumbnail(thumbnail_url, self) @@ -173,7 +172,7 @@ def __init__(self, parent, dataset: Dataset): base_details_right_pane_layout = QHBoxLayout() base_details_right_pane_layout.setContentsMargins(12, 0, 0, 0) - icon_name = DatasetGuiUtils.get_icon_for_dataset(self.dataset_obj, + icon_name = DatasetGuiUtils.get_icon_for_dataset(self.dataset, IconStyle.Dark) icon_label = SvgLabel(icon_name, 24, 24) base_details_right_pane_layout.addWidget(icon_label) @@ -181,8 +180,8 @@ def __init__(self, parent, dataset: Dataset): summary_label = QLabel() summary_label.setAlignment(Qt.AlignmentFlag.AlignTop) - description = DatasetGuiUtils.get_type_description(self.dataset_obj) - subtitle = DatasetGuiUtils.get_subtitle(self.dataset_obj, + description = DatasetGuiUtils.get_type_description(self.dataset) + subtitle = DatasetGuiUtils.get_subtitle(self.dataset, short_format=False) summary_label.setText("""

List[Tuple]: def get_history_details(self) -> List[Tuple]: res = [] - first_published = self.dataset_obj.created_at_date() + first_published = self.dataset.created_at_date() if first_published: res.append(('Date Added', self.format_date(first_published))) - last_updated = self.dataset_obj.updated_at_date() + last_updated = self.dataset.updated_at_date() if last_updated: res.append(('Last updated', self.format_date(last_updated))) - if Capability.RevisionCount in self.dataset_obj.capabilities: + if Capability.RevisionCount in self.dataset.capabilities: data_revisions_count = \ KoordinatesClient.instance().data_revisions_count( - self.dataset_obj.id) + self.dataset.id) total_revisions_count = \ KoordinatesClient.instance().total_revisions_count( - self.dataset_obj.id) + self.dataset.id) if data_revisions_count is not None or total_revisions_count is not None: res.append( From b1e7c556167bf39439b9ed824072d5e4000b1227 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 9 May 2023 21:57:50 +1000 Subject: [PATCH 07/11] Show more point cloud details --- koordinates/gui/dataset_dialog.py | 36 +++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index 6c963d6..2ac07a3 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -414,10 +414,38 @@ def get_technical_details(self) -> List[Tuple]: self.format_number(empty_count)) res.append(('Feature count', feature_count_label)) - fields = self.dataset.details.get('data', {}).get('fields', []) - if fields: - res.append(('_Attributes', ", ".join( - [f.get("name", '') for f in fields]))) + if self.dataset.datatype == DataType.PointClouds: + point_count = self.dataset.details.get("data", {}).get( + "point_count") or 0 + res.append(('Point count', self.format_number(point_count))) + tile_count = self.dataset.details.get("data", {}).get( + "feature_count") or 0 + res.append(('Tile count', self.format_number(tile_count))) + density = self.dataset.details.get("data", {}).get("point_density_sqm", {}) or 0 + res.append(('Point density', '{:.2f} points per m² • {:.2f} points per US ft²'.format(density, + density / 10.7639))) + las_version = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + "lasVersion") or 0 + pdrf = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + "pointDataRecordFormat") or 0 + res.append(('Point cloud type', 'LAZ {} PDRF{}'.format(las_version, pdrf))) + + format_as_stored = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + "format") or '' + if format_as_stored == 'las': + format_as_stored = 'LAZ' + optimization = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + "optimization") or '' + if optimization == 'copc': + optimization = 'COPc' + + res.append(('Format as stored', '{} {}'.format(format_as_stored, optimization))) + + else: + fields = self.dataset.details.get('data', {}).get('fields', []) + if fields: + res.append(('_Attributes', ", ".join( + [f.get("name", '') for f in fields]))) primary_key_fields = self.dataset.details.get('data', {}).get('primary_key_fields', []) if primary_key_fields: From 536cf58741c3b7e5ed63f184f337fdaa1a446b40 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 10 May 2023 13:23:51 +1000 Subject: [PATCH 08/11] Show point cloud detail tables --- koordinates/gui/dataset_dialog.py | 70 +++++++++++++++++- koordinates/gui/detail_widgets/__init__.py | 1 + .../gui/detail_widgets/table_widget.py | 74 +++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 koordinates/gui/detail_widgets/table_widget.py diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index 2ac07a3..c5267c5 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -56,7 +56,8 @@ HeaderWidget, DetailsTable, AttachmentWidget, - MetadataWidget + MetadataWidget, + TableWidget ) pluginPath = os.path.split(os.path.dirname(__file__))[0] @@ -335,6 +336,8 @@ def __init__(self, parent, dataset: Dataset): tech_details_grid.set_details(self.get_technical_details()) contents_layout.addLayout(tech_details_grid) + self.append_dataset_tables(contents_layout) + contents_layout.addSpacing(40) history_grid = DetailsTable('History & Version Control') @@ -544,3 +547,68 @@ def setThumbnail(self, img): thumbnail.setDevicePixelRatio(scale_factor) self.thumbnail_label.setPixmap(thumbnail) + + def append_dataset_tables(self, layout): + """ + Appends dataset specific tables to the layout + """ + heading_font_size = 10 + if platform.system() == 'Darwin': + heading_font_size = 14 + + if self.dataset.datatype == DataType.PointClouds: + heading = QLabel( + """{}""".format(self.tr('Classifications')) + ) + layout.addSpacing(20) + layout.addWidget(heading) + layout.addSpacing(10) + + headings = ['', + self.tr('Class'), + self.tr('Point count'), + self.tr('% of dataset') + ] + + point_count = self.dataset.details.get("data", {}).get( + "point_count") or 1 + + contents = [] + for classification in self.dataset.details.get('data', {}).get('classifications', []): + row = [ + str(classification.get('id')), + str(classification.get('name')), + self.format_number(classification.get('count', 0)), + '{:.2f}%'.format(100*classification.get('count', 0) / point_count), + ] + contents.append(row) + + table = TableWidget(headings, contents) + layout.addWidget(table) + + heading = QLabel( + """{}""".format(self.tr('Dimensions')) + ) + layout.addSpacing(20) + layout.addWidget(heading) + layout.addSpacing(10) + + headings = [self.tr('Name'), + self.tr('Data type') + ] + + contents = [] + for field in self.dataset.details.get('data', {}).get( + 'fields', []): + row = [ + str(field.get('name')), + str(field.get('type')) + ] + contents.append(row) + + table = TableWidget(headings, contents) + layout.addWidget(table) diff --git a/koordinates/gui/detail_widgets/__init__.py b/koordinates/gui/detail_widgets/__init__.py index c1e60dc..b840c66 100644 --- a/koordinates/gui/detail_widgets/__init__.py +++ b/koordinates/gui/detail_widgets/__init__.py @@ -8,3 +8,4 @@ from .horizontal_line_widget import HorizontalLine # NOQA from .metadata_widget import MetadataWidget # NOQA from .statistic_widget import StatisticWidget # NOQA +from .table_widget import TableWidget # NOQA diff --git a/koordinates/gui/detail_widgets/table_widget.py b/koordinates/gui/detail_widgets/table_widget.py new file mode 100644 index 0000000..7d8e047 --- /dev/null +++ b/koordinates/gui/detail_widgets/table_widget.py @@ -0,0 +1,74 @@ +import platform +from typing import ( + Optional, + List +) + +from qgis.PyQt.QtWidgets import ( + QWidget, + QVBoxLayout, + QLabel +) + +from ..gui_utils import FONT_FAMILIES + + +class TableWidget(QWidget): + """ + Displays a styled table of data + """ + + BACKGROUND_COLOR = "#ffffff" + HEADING_BACKGROUND_COLOR = "#f5f5f7" + BORDER_COLOR = "#eaeaea" + + def __init__(self, + headings: List[str], + contents: List[List[str]], + parent: Optional[QWidget] = None): + super().__init__(parent) + + vl = QVBoxLayout() + vl.setContentsMargins(0, 0, 0, 0) + + self.table_label = QLabel() + + base_font_size = 10 + if platform.system() == 'Darwin': + base_font_size = 14 + + padding = int(base_font_size * 0.75) + + html = f""" + + """ + if headings: + html += "" + for cell in headings: + html += f"""""" + html += "" + + for row in contents: + html += "" + for cell in row: + html += f"""""" + + html += "" + + html += """ +
{cell}
{cell}
+ """ + self.table_label.setText(html) + + vl.addWidget(self.table_label) + self.setLayout(vl) From faaec9cffacdc7517f0bae79a2cb486f8bc86ad3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 10 May 2023 13:34:22 +1000 Subject: [PATCH 09/11] Move toward horizontal table layout --- koordinates/gui/dataset_dialog.py | 40 ++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index c5267c5..ac9b083 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -24,6 +24,7 @@ QDialog, QVBoxLayout, QScrollArea, + QGridLayout, ) from qgis.PyQt.QtSvg import QSvgWidget @@ -560,11 +561,28 @@ def append_dataset_tables(self, layout): heading = QLabel( """{}""".format(self.tr('Classifications')) + """color: black">{}""".format( + self.tr('Point Cloud Characteristics') + ) ) layout.addSpacing(20) layout.addWidget(heading) - layout.addSpacing(10) + layout.addWidget(HorizontalLine()) + layout.addSpacing(7) + + gl = QGridLayout() + heading = QLabel( + """{}""".format( + self.tr('Classifications') + ) + ) + heading.setAlignment(Qt.AlignLeft | Qt.AlignTop) + vl = QVBoxLayout() + vl.setContentsMargins(0, 5, 10, 0) + vl.addWidget(heading) + gl.addLayout(vl, 0, 0) headings = ['', self.tr('Class'), @@ -586,16 +604,18 @@ def append_dataset_tables(self, layout): contents.append(row) table = TableWidget(headings, contents) - layout.addWidget(table) + gl.addWidget(table, 0, 1) heading = QLabel( - """{}""".format(self.tr('Dimensions')) + """color: #868889">{}""".format(self.tr('Dimensions')) ) - layout.addSpacing(20) - layout.addWidget(heading) - layout.addSpacing(10) + heading.setAlignment(Qt.AlignLeft | Qt.AlignTop) + vl = QVBoxLayout() + vl.setContentsMargins(0, 5, 10, 0) + vl.addWidget(heading) + gl.addLayout(vl, 1, 0) headings = [self.tr('Name'), self.tr('Data type') @@ -611,4 +631,6 @@ def append_dataset_tables(self, layout): contents.append(row) table = TableWidget(headings, contents) - layout.addWidget(table) + gl.addWidget(table, 1, 1) + gl.setColumnStretch(1, 1) + layout.addLayout(gl) From b466dd5b1492ee1dc19ee0efeddff46530a21482 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 10 May 2023 13:47:52 +1000 Subject: [PATCH 10/11] Allow dynamic table expansion --- .../gui/detail_widgets/table_widget.py | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/koordinates/gui/detail_widgets/table_widget.py b/koordinates/gui/detail_widgets/table_widget.py index 7d8e047..9d2af08 100644 --- a/koordinates/gui/detail_widgets/table_widget.py +++ b/koordinates/gui/detail_widgets/table_widget.py @@ -4,6 +4,11 @@ List ) +from qgis.PyQt.QtCore import ( + Qt, + QUrl +) +from qgis.PyQt.QtGui import QDesktopServices from qgis.PyQt.QtWidgets import ( QWidget, QVBoxLayout, @@ -22,6 +27,8 @@ class TableWidget(QWidget): HEADING_BACKGROUND_COLOR = "#f5f5f7" BORDER_COLOR = "#eaeaea" + INITIAL_VISIBLE_ROWS = 4 + def __init__(self, headings: List[str], contents: List[List[str]], @@ -32,7 +39,17 @@ def __init__(self, vl.setContentsMargins(0, 0, 0, 0) self.table_label = QLabel() + self.headings = headings + self.contents = contents + + self.rebuild_table() + self.table_label.setTextInteractionFlags(Qt.TextBrowserInteraction) + self.table_label.linkActivated.connect(self.link_clicked) + + vl.addWidget(self.table_label) + self.setLayout(vl) + def rebuild_table(self, expand=False): base_font_size = 10 if platform.system() == 'Darwin': base_font_size = 14 @@ -45,9 +62,9 @@ def __init__(self, border-collapse: collapse; "> """ - if headings: + if self.headings: html += "" - for cell in headings: + for cell in self.headings: html += f"""{cell}""" html += "" - for row in contents: + if not expand: + visible_rows = self.contents[:self.INITIAL_VISIBLE_ROWS] + else: + visible_rows = self.contents[:] + + for row in visible_rows: html += "" for cell in row: html += f""" self.INITIAL_VISIBLE_ROWS and not expand: + html += "" + html += """
Show more
""" + html += "" + html += """ """ self.table_label.setText(html) - vl.addWidget(self.table_label) - self.setLayout(vl) + def link_clicked(self, link): + if link == 'more': + self.rebuild_table(True) + else: + QDesktopServices.openUrl(QUrl(link)) From f02c97cb15472925d57e9bb6eece6194c7df9ea5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 11 May 2023 08:32:02 +1000 Subject: [PATCH 11/11] Lint --- koordinates/gui/action_button.py | 31 ++--- koordinates/gui/dataset_browser_items.py | 6 +- koordinates/gui/dataset_dialog.py | 108 +++++++++++------- koordinates/gui/dataset_utils.py | 37 +++--- .../gui/detail_widgets/header_widget.py | 10 +- .../gui/detail_widgets/table_widget.py | 2 +- 6 files changed, 116 insertions(+), 78 deletions(-) diff --git a/koordinates/gui/action_button.py b/koordinates/gui/action_button.py index f048d8f..c2a44d6 100644 --- a/koordinates/gui/action_button.py +++ b/koordinates/gui/action_button.py @@ -11,10 +11,6 @@ ) from qgis.utils import iface -from ..core import ( - KartUtils, - KartNotInstalledException -) from .gui_utils import GuiUtils from ..api import ( KoordinatesClient, @@ -25,6 +21,10 @@ from ..core import ( KartOperationManager ) +from ..core import ( + KartUtils, + KartNotInstalledException +) COLOR_INDEX = 0 @@ -72,7 +72,8 @@ class CloneButton(ActionButton): BUTTON_TEXT = "#323233" BUTTON_HOVER = "#e4e4e6" - def __init__(self, dataset: Dataset, parent=None, close_parent_on_clone=False): + def __init__(self, dataset: Dataset, parent=None, + close_parent_on_clone=False): super().__init__(parent) self.dataset = dataset @@ -95,10 +96,12 @@ def _update_state(self): """ Updates button state based on current operations """ - is_cloning = self.dataset.repository() and \ - KartOperationManager.instance().is_cloning( - self.dataset.repository().clone_url() - ) + is_cloning = \ + self.dataset.repository() and \ + KartOperationManager.instance().is_cloning( + self.dataset.repository().clone_url() + ) + self.setEnabled(not is_cloning) if is_cloning: icon = GuiUtils.get_icon('cloning_button.svg') @@ -136,11 +139,11 @@ def cloneRepository(self): try: KartUtils.clone_kart_repo( - title=title, - url=url, - username="kart", - password=KoordinatesClient.instance().apiKey, - parent=iface.mainWindow() + title=title, + url=url, + username="kart", + password=KoordinatesClient.instance().apiKey, + parent=iface.mainWindow() ) except KartNotInstalledException: iface.messageBar().pushMessage( diff --git a/koordinates/gui/dataset_browser_items.py b/koordinates/gui/dataset_browser_items.py index 9802b7c..f016e58 100644 --- a/koordinates/gui/dataset_browser_items.py +++ b/koordinates/gui/dataset_browser_items.py @@ -660,9 +660,9 @@ def process_thumbnail(self, img: Optional[QImage]) -> QImage: if img is not None: resized = img.scaled(image_size.width(), - image_size.height(), - Qt.KeepAspectRatioByExpanding, - Qt.SmoothTransformation) + image_size.height(), + Qt.KeepAspectRatioByExpanding, + Qt.SmoothTransformation) if resized.width() > image_size.width(): left = int((resized.width() - image_size.width())/2) diff --git a/koordinates/gui/dataset_dialog.py b/koordinates/gui/dataset_dialog.py index ac9b083..77c4926 100644 --- a/koordinates/gui/dataset_dialog.py +++ b/koordinates/gui/dataset_dialog.py @@ -1,8 +1,11 @@ +import datetime import locale import os import platform -import datetime -from typing import Dict, List, Tuple +from typing import ( + List, + Tuple +) from qgis.PyQt import uic from qgis.PyQt.QtCore import ( @@ -17,6 +20,7 @@ QBrush, QColor ) +from qgis.PyQt.QtSvg import QSvgWidget from qgis.PyQt.QtWidgets import ( QFrame, QLabel, @@ -26,7 +30,6 @@ QScrollArea, QGridLayout, ) -from qgis.PyQt.QtSvg import QSvgWidget from .action_button import ( AddButton, @@ -36,6 +39,15 @@ DatasetGuiUtils, IconStyle ) +from .detail_widgets import ( + HorizontalLine, + StatisticWidget, + HeaderWidget, + DetailsTable, + AttachmentWidget, + MetadataWidget, + TableWidget +) from .gui_utils import ( GuiUtils, FONT_FAMILIES, @@ -44,22 +56,12 @@ from .svg_label import SvgLabel from .thumbnails import downloadThumbnail from ..api import ( - ApiUtils, DataType, PublicAccessType, Capability, KoordinatesClient, Dataset ) -from .detail_widgets import ( - HorizontalLine, - StatisticWidget, - HeaderWidget, - DetailsTable, - AttachmentWidget, - MetadataWidget, - TableWidget -) pluginPath = os.path.split(os.path.dirname(__file__))[0] @@ -80,7 +82,8 @@ def __init__(self, parent, dataset: Dataset): self.dataset) if self.details.get('attachments'): - self.attachments = KoordinatesClient.instance().get_json(self.details['attachments']) + self.attachments = KoordinatesClient.instance().get_json( + self.details['attachments']) else: self.attachments = [] @@ -200,13 +203,15 @@ def __init__(self, parent, dataset: Dataset): base_details_right_pane_layout_vl = QVBoxLayout() base_details_right_pane_layout_vl.setContentsMargins(0, 0, 0, 0) - base_details_right_pane_layout_vl.addLayout(base_details_right_pane_layout) + base_details_right_pane_layout_vl.addLayout( + base_details_right_pane_layout) if self.dataset.repository(): base_details_right_pane_layout = QHBoxLayout() base_details_right_pane_layout.setContentsMargins(12, 0, 0, 0) - icon_label = SvgLabel(GuiUtils.get_icon_svg('repo-book.svg'), 24, 24) + icon_label = SvgLabel(GuiUtils.get_icon_svg('repo-book.svg'), 24, + 24) base_details_right_pane_layout.addWidget(icon_label) summary_label = QLabel() @@ -226,7 +231,8 @@ def __init__(self, parent, dataset: Dataset): base_details_right_pane_layout.addSpacing(10) base_details_right_pane_layout.addWidget(summary_label, 1) - base_details_right_pane_layout_vl.addLayout(base_details_right_pane_layout) + base_details_right_pane_layout_vl.addLayout( + base_details_right_pane_layout) base_details_right_pane_layout_vl.addStretch() @@ -317,8 +323,9 @@ def __init__(self, parent, dataset: Dataset): contents_layout.addSpacing(40) - if self.details.get('metadata') and (self.details['metadata'].get('iso') or - self.details['metadata'].get('dc')): + if self.details.get('metadata') and ( + self.details['metadata'].get('iso') or + self.details['metadata'].get('dc')): heading = QLabel( """{}""".format( FONT_FAMILIES, @@ -329,7 +336,8 @@ def __init__(self, parent, dataset: Dataset): for source in ('iso', 'dc'): if self.details['metadata'].get(source): contents_layout.addWidget( - MetadataWidget(source, self.details['metadata'][source])) + MetadataWidget(source, + self.details['metadata'][source])) contents_layout.addSpacing(40) @@ -356,7 +364,8 @@ def __init__(self, parent, dataset: Dataset): scroll_area.setWidgetResizable(True) scroll_area_layout.addWidget(scroll_area) - scroll_area.viewport().setStyleSheet("#qt_scrollarea_viewport{ background: transparent; }") + scroll_area.viewport().setStyleSheet( + "#qt_scrollarea_viewport{ background: transparent; }") layout.addLayout(scroll_area_layout, 1) @@ -410,8 +419,10 @@ def get_technical_details(self) -> List[Tuple]: crs_id ))) - feature_count = self.dataset.details.get("data", {}).get("feature_count", 0) - empty_count = self.dataset.details.get("data", {}).get('empty_geometry_count', 0) + feature_count = self.dataset.details.get("data", {}).get( + "feature_count", 0) + empty_count = self.dataset.details.get("data", {}).get( + 'empty_geometry_count', 0) feature_count_label = self.format_number(feature_count) if empty_count: feature_count_label += ' • {} with empty or null geometries'.format( @@ -425,25 +436,34 @@ def get_technical_details(self) -> List[Tuple]: tile_count = self.dataset.details.get("data", {}).get( "feature_count") or 0 res.append(('Tile count', self.format_number(tile_count))) - density = self.dataset.details.get("data", {}).get("point_density_sqm", {}) or 0 - res.append(('Point density', '{:.2f} points per m² • {:.2f} points per US ft²'.format(density, - density / 10.7639))) - las_version = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + density = self.dataset.details.get("data", {}).get( + "point_density_sqm", {}) or 0 + res.append(('Point density', + '{:.2f} points per m² • {:.2f} points per US ft²'.format( + density, + density / 10.7639))) + las_version = self.dataset.details.get("data", {}).get( + 'tile_format_stored', {}).get( "lasVersion") or 0 - pdrf = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + pdrf = self.dataset.details.get("data", {}).get( + 'tile_format_stored', {}).get( "pointDataRecordFormat") or 0 - res.append(('Point cloud type', 'LAZ {} PDRF{}'.format(las_version, pdrf))) + res.append(('Point cloud type', + 'LAZ {} PDRF{}'.format(las_version, pdrf))) - format_as_stored = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + format_as_stored = self.dataset.details.get("data", {}).get( + 'tile_format_stored', {}).get( "format") or '' if format_as_stored == 'las': format_as_stored = 'LAZ' - optimization = self.dataset.details.get("data", {}).get('tile_format_stored', {}).get( + optimization = self.dataset.details.get("data", {}).get( + 'tile_format_stored', {}).get( "optimization") or '' if optimization == 'copc': optimization = 'COPc' - res.append(('Format as stored', '{} {}'.format(format_as_stored, optimization))) + res.append(('Format as stored', + '{} {}'.format(format_as_stored, optimization))) else: fields = self.dataset.details.get('data', {}).get('fields', []) @@ -451,7 +471,8 @@ def get_technical_details(self) -> List[Tuple]: res.append(('_Attributes', ", ".join( [f.get("name", '') for f in fields]))) - primary_key_fields = self.dataset.details.get('data', {}).get('primary_key_fields', []) + primary_key_fields = self.dataset.details.get('data', {}).get( + 'primary_key_fields', []) if primary_key_fields: res.append(('_Primary key', ", ".join(primary_key_fields))) @@ -503,7 +524,8 @@ def setThumbnail(self, img): painter.setPen(Qt.NoPen) painter.setBrush(QBrush(QColor(255, 0, 0))) - painter.drawRoundedRect(0, 0, image_size.width(), image_size.height(), 9, 9) + painter.drawRoundedRect(0, 0, image_size.width(), image_size.height(), + 9, 9) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.setBrush(QBrush(QColor('#dddddd'))) @@ -529,12 +551,14 @@ def setThumbnail(self, img): top = 0 if left > 0 or top > 0: - cropped = resized.copy(QRect(left, top, image_size.width(), image_size.height())) + cropped = resized.copy(QRect(left, top, image_size.width(), + image_size.height())) painter.drawImage(0, 0, cropped) else: - painter.drawImage(int((image_size.width() - resized.width()) / 2), - int((image_size.height() - resized.height()) / 2), - resized) + painter.drawImage( + int((image_size.width() - resized.width()) / 2), + int((image_size.height() - resized.height()) / 2), + resized) else: painter.drawImage(0, 0, img) else: @@ -594,13 +618,15 @@ def append_dataset_tables(self, layout): "point_count") or 1 contents = [] - for classification in self.dataset.details.get('data', {}).get('classifications', []): + for classification in self.dataset.details.get('data', {}).get( + 'classifications', []): row = [ str(classification.get('id')), str(classification.get('name')), self.format_number(classification.get('count', 0)), - '{:.2f}%'.format(100*classification.get('count', 0) / point_count), - ] + '{:.2f}%'.format( + 100 * classification.get('count', 0) / point_count), + ] contents.append(row) table = TableWidget(headings, contents) diff --git a/koordinates/gui/dataset_utils.py b/koordinates/gui/dataset_utils.py index 04c9b4d..a0598f9 100644 --- a/koordinates/gui/dataset_utils.py +++ b/koordinates/gui/dataset_utils.py @@ -1,7 +1,6 @@ from enum import Enum from typing import ( - Optional, - Dict + Optional ) from qgis.core import ( @@ -10,7 +9,6 @@ ) from ..api import ( - ApiUtils, DataType, Dataset ) @@ -70,17 +68,23 @@ def get_icon_for_dataset(dataset: Dataset, style: IconStyle) \ @staticmethod def get_data_type(dataset: Dataset) -> Optional[str]: if dataset.datatype == DataType.Vectors: - if dataset.details.get('data', {}).get('geometry_type') == 'polygon': + if dataset.details.get('data', {}).get( + 'geometry_type') == 'polygon': return 'Vector polygon' - elif dataset.details.get('data', {}).get('geometry_type') == 'multipolygon': + elif dataset.details.get('data', {}).get( + 'geometry_type') == 'multipolygon': return 'Vector multipolygon' - elif dataset.details.get('data', {}).get('geometry_type') == 'point': + elif dataset.details.get('data', {}).get( + 'geometry_type') == 'point': return 'Vector point' - elif dataset.details.get('data', {}).get('geometry_type') == 'multipoint': + elif dataset.details.get('data', {}).get( + 'geometry_type') == 'multipoint': return 'Vector multipoint' - elif dataset.details.get('data', {}).get('geometry_type') == 'linestring': + elif dataset.details.get('data', {}).get( + 'geometry_type') == 'linestring': return 'Vector line' - elif dataset.details.get('data', {}).get('geometry_type') == 'multilinestring': + elif dataset.details.get('data', {}).get( + 'geometry_type') == 'multilinestring': return 'Vector multiline' elif dataset.datatype == DataType.Rasters: return 'Raster' @@ -126,13 +130,18 @@ def get_type_description(dataset: Dataset) -> Optional[str]: return None @staticmethod - def get_subtitle(dataset: Dataset, short_format: bool = True) -> Optional[str]: + def get_subtitle(dataset: Dataset, short_format: bool = True) \ + -> Optional[str]: + """ + Return a subtitle to use for a Dataset + """ if dataset.datatype == DataType.Vectors: count = dataset.details.get("data", {}).get("feature_count") or 0 if dataset.geometry_type == QgsWkbTypes.PolygonGeometry: - return '{} Polygons'.format(DatasetGuiUtils.format_count(count)) + return '{} Polygons'.format( + DatasetGuiUtils.format_count(count)) elif dataset.geometry_type == QgsWkbTypes.PointGeometry: return '{} Points'.format(DatasetGuiUtils.format_count(count)) elif dataset.geometry_type == QgsWkbTypes.LineGeometry: @@ -149,7 +158,8 @@ def get_subtitle(dataset: Dataset, short_format: bool = True) -> Optional[str]: ext = dataset.details.get('extension', '').upper() file_size = dataset.details.get('file_size') if file_size: - return '{} {}'.format(ext, QgsFileUtils.representFileSize(file_size)) + return '{} {}'.format(ext, QgsFileUtils.representFileSize( + file_size)) return ext elif dataset.datatype == DataType.Sets: return None @@ -157,7 +167,8 @@ def get_subtitle(dataset: Dataset, short_format: bool = True) -> Optional[str]: return None elif dataset.datatype == DataType.PointClouds: count = dataset.details.get("data", {}).get("feature_count") or 0 - point_count = dataset.details.get("data", {}).get("point_count") or 0 + point_count = dataset.details.get("data", {}).get( + "point_count") or 0 if short_format: return '{} Tiles'.format( DatasetGuiUtils.format_count(count) diff --git a/koordinates/gui/detail_widgets/header_widget.py b/koordinates/gui/detail_widgets/header_widget.py index 0826987..b7e1b1f 100644 --- a/koordinates/gui/detail_widgets/header_widget.py +++ b/koordinates/gui/detail_widgets/header_widget.py @@ -1,5 +1,3 @@ -from typing import Optional - from qgis.PyQt.QtCore import ( Qt ) @@ -11,12 +9,12 @@ QApplication ) -from koordinates.gui.gui_utils import FONT_FAMILIES from koordinates.gui.detail_widgets.svg_framed_button import SvgFramedButton -from koordinates.gui.detail_widgets.thumbnail_label_widget import ThumbnailLabel +from koordinates.gui.detail_widgets.thumbnail_label_widget import \ + ThumbnailLabel +from koordinates.gui.gui_utils import FONT_FAMILIES from ...api import ( - Dataset, - PublisherTheme + Dataset ) diff --git a/koordinates/gui/detail_widgets/table_widget.py b/koordinates/gui/detail_widgets/table_widget.py index 9d2af08..b2eb810 100644 --- a/koordinates/gui/detail_widgets/table_widget.py +++ b/koordinates/gui/detail_widgets/table_widget.py @@ -90,7 +90,7 @@ def rebuild_table(self, expand=False): if len(self.contents) > self.INITIAL_VISIBLE_ROWS and not expand: html += "" html += """