PK 3qOXTb " quick_map_services/about_dialog.py# -*- coding: utf-8 -*-
"""
/***************************************************************************
AboutDialog
A QGIS plugin
Collection of internet map services
-------------------
begin : 2014-11-21
git sha : $Format:%H$
copyright : (C) 2014 by NextGIS
email : info@nextgis.com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
import os
from qgis.PyQt import uic
from qgis.PyQt.QtGui import QPixmap
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox
from .compat import configparser
CURR_PATH = os.path.dirname(__file__)
FORM_CLASS, _ = uic.loadUiType(os.path.join(CURR_PATH, 'about_dialog_base.ui'))
class AboutDialog(QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(AboutDialog, self).__init__(parent)
self.setupUi(self)
self.btnHelp = self.buttonBox.button(QDialogButtonBox.Help)
self.lblLogo.setPixmap(QPixmap(os.path.join(CURR_PATH, 'icons/mapservices.png')))
cfg = configparser.ConfigParser()
cfg.read(os.path.join(os.path.dirname(__file__), 'metadata.txt'))
version = cfg.get('general', 'version')
self.lblVersion.setText(self.tr('Version: %s') % (version))
self.tbInfo.setHtml(self.get_about_text())
self.tbLicense.setPlainText(self.get_license_text())
self.tb3rd.setHtml(self.get_3rd_text())
def get_about_text(self):
return self.tr('
Convenient list of basemaps + seach string for finding datasets and basemaps. Please contribute new services via http://qms.nextgis.com
'
'Developers: NextGIS
'
'Issue tracker: GitHub
'
'Source code: GitHub
')
def get_license_text(self):
with open(os.path.join(CURR_PATH, 'LICENSE')) as f:
return f.read()
def get_3rd_text(self):
return self.tr('Python tile layer: TileLayer Plugin by Minoru Akagi
'
'Some icons from QGIS: QGIS GitHub
')
PK 3qON quick_map_services/qms_news.pyimport os
import sys
import datetime
from .compat import get_file_dir
from .plugin_locale import Locale
plugin_dir = get_file_dir(__file__)
class News(object):
"""docstring for News"""
def __init__(self, qms_news, date_start=None, date_finish=None):
super(News, self).__init__()
html = qms_news.get_text(Locale.get_locale())
self.html = ' %s' % (plugin_dir + '/icons/news.png', html)
self.date_start = date_start
if self.date_start is None:
self.date_start = datetime.datetime.now()
self.date_finish = date_finish
def is_time_to_show(self):
if self.date_start > datetime.datetime.now():
return False
if self.date_finish is None:
return True
return self.date_finish > datetime.datetime.now()
PK 3qOS< ' quick_map_services/about_dialog_base.ui
Dialog
0
0
432
326
About QuickMapServices
-
Qt::Horizontal
QDialogButtonBox::Close
-
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;">
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Droid Sans'; font-size:16pt; font-weight:600;">QuickMapServices</span></p></body></html>
Qt::AlignCenter
-
0
0
TextLabel
Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
-
0
false
Info
-
true
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;">
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"><br /></p></body></html>
Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse
true
License
-
true
Third-party Components
-
true
-
TextLabel
Qt::AlignCenter
buttonBox
accepted()
Dialog
accept()
248
254
157
274
buttonBox
rejected()
Dialog
reject()
316
260
286
274
PK 3qO/F F ) quick_map_services/qms_service_toolbox.pyfrom __future__ import absolute_import
import ast
import sys
from os import path
from qgis.PyQt import uic
from qgis.PyQt.QtGui import (
QImage,
QPixmap,
QCursor,
QFont,
)
from qgis.PyQt.QtWidgets import (
QApplication,
QWidget,
QDockWidget,
QHBoxLayout,
QLabel,
QToolButton,
QSizePolicy,
QListWidgetItem,
QGridLayout,
)
from qgis.PyQt.QtCore import (
QThread,
pyqtSignal,
Qt,
QTimer,
QMutex,
QByteArray
)
from qgis.core import (
QgsMessageLog,
QgsCoordinateReferenceSystem,
QgsGeometry
)
from .rb_result_renderer import RubberBandResultRenderer
from .data_source_serializer import DataSourceSerializer
from .qgis_map_helpers import add_layer_to_map
from .qms_external_api_python.client import Client
from .qgis_settings import QGISSettings
from .plugin_settings import PluginSettings
from .singleton import singleton
from .compat import URLError
from .compat2qgis import QGisMessageLogLevel, getCanvasDestinationCrs, QgsCoordinateTransform
from .qms_news import News
def plPrint(msg, level=QGisMessageLogLevel.Info):
QgsMessageLog.logMessage(
msg,
"QMS",
level
)
STATUS_FILTER_ALL = 'all'
STATUS_FILTER_ONLY_WORKS = 'works'
class Geoservice(object):
def __init__(self, attributes, image_qByteArray):
self.attributes = attributes
self.image_qByteArray = image_qByteArray
def isValid(self):
return self.attributes.get("id") is not None
@property
def id(self):
return self.attributes.get("id")
def saveSelf(self, qSettings):
qSettings.setValue(
"{}/json".format(self.id),
unicode(self.attributes)
)
qSettings.setValue(
"{}/image".format(self.id),
self.image_qByteArray
)
def loadSelf(self, id, qSettings):
service_json = qSettings.value("{}/json".format(self.id), None)
self.attributes = ast.literal_eval(service_json)
self.image_qByteArray = settings.value("{}/image".format(self.id), type=QByteArray)
@singleton
class CachedServices(object):
def __init__(self):
self.geoservices = []
self.load_last_used_services()
def load_last_used_services(self):
for geoservice, image_ba in PluginSettings.get_last_used_services():
geoservice = Geoservice( geoservice, image_ba)
if geoservice.isValid:
self.geoservices.append(geoservice)
def add_service(self, geoservice, image_ba):
new_gs = Geoservice(geoservice, image_ba)
geoservices4store = [new_gs]
for gs in self.geoservices:
if gs.id == new_gs.id:
continue
geoservices4store.append(gs)
self.geoservices = geoservices4store[0:5]
PluginSettings.set_last_used_services(self.geoservices)
def get_cached_services(self):
return [(geoservice.attributes, geoservice.image_qByteArray) for geoservice in self.geoservices]
FORM_CLASS, _ = uic.loadUiType(path.join(
path.dirname(__file__), 'qms_service_toolbox.ui'))
class QmsServiceToolbox(QDockWidget, FORM_CLASS):
def __init__(self, iface):
QDockWidget.__init__(self, iface.mainWindow())
self.setupUi(self)
self.newsFrame.setVisible(False)
self.iface = iface
self.search_threads = None # []
self.extent_renderer = RubberBandResultRenderer()
self.cmbStatusFilter.addItem(self.tr('All'), STATUS_FILTER_ALL)
self.cmbStatusFilter.addItem(self.tr('Valid'), STATUS_FILTER_ONLY_WORKS)
self.cmbStatusFilter.currentIndexChanged.connect(self.start_search)
if hasattr(self.txtSearch, 'setPlaceholderText'):
self.txtSearch.setPlaceholderText(self.tr("Search string..."))
self.delay_timer = QTimer(self)
self.delay_timer.setSingleShot(True)
self.delay_timer.setInterval(250)
self.delay_timer.timeout.connect(self.start_search)
self.txtSearch.textChanged.connect(self.delay_timer.start)
self.btnFilterByExtent.toggled.connect(self.toggle_filter_button)
self.one_process_work = QMutex()
self.add_last_used_services()
self.show_news()
def show_news(self):
client = Client()
client.set_proxy(*QGISSettings.get_qgis_proxy())
qms_news = client.get_news()
if qms_news is None:
self.newsFrame.setVisible(False)
return
news = News(qms_news)
if news.is_time_to_show():
self.newsLabel.setText(news.html)
self.newsFrame.setVisible(True)
else:
self.newsFrame.setVisible(False)
def toggle_filter_button(self, checked):
self.txtSearch.setDisabled(checked)
if checked:
self.iface.mapCanvas().extentsChanged.connect(self.start_search)
self.iface.mapCanvas().destinationCrsChanged.connect(self.start_search)
self.start_search()
else:
self.iface.mapCanvas().extentsChanged.disconnect(self.start_search)
self.iface.mapCanvas().destinationCrsChanged.disconnect(self.start_search)
def start_search(self):
search_text = None
geom_filter = None
# status filter
status_filter = None
sel_value = self.cmbStatusFilter.itemData(self.cmbStatusFilter.currentIndex())
if sel_value != STATUS_FILTER_ALL:
status_filter = sel_value
if not self.btnFilterByExtent.isChecked():
# text search
search_text = unicode(self.txtSearch.text())
if not search_text:
self.lstSearchResult.clear()
# self.clearSearchResult()
self.add_last_used_services()
return
else:
# extent filter
extent = self.iface.mapCanvas().extent()
map_crs = getCanvasDestinationCrs(self.iface)
if map_crs.postgisSrid() != 4326:
crsDest = QgsCoordinateReferenceSystem(4326) # WGS 84
xform = QgsCoordinateTransform(map_crs, crsDest)
extent = xform.transform(extent)
geom_filter = extent.asWktPolygon()
if self.search_threads:
self.search_threads.data_downloaded.disconnect()
self.search_threads.search_finished.disconnect()
self.search_threads.stop()
self.search_threads.wait()
self.lstSearchResult.clear()
# self.clearSearchResult()
searcher = SearchThread(search_text,
self.one_process_work,
parent=self.iface.mainWindow(),
geom_filter=geom_filter,
status_filter=status_filter)
searcher.data_downloaded.connect(self.show_result)
searcher.error_occurred.connect(self.show_error)
searcher.search_started.connect(self.search_started_process)
searcher.search_finished.connect(self.search_finished_progress)
self.search_threads = searcher
searcher.start()
def add_last_used_services(self):
services = CachedServices().get_cached_services()
if len(services) == 0:
return
self.lstSearchResult.insertItem(0, self.tr("Last used:"))
# l = QLabel(self.tr("Last used:"))
# l.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
# self.lSearchResult.addWidget(l)
for attributes, image_qByteArray in services:
custom_widget = QmsSearchResultItemWidget(
attributes,
image_qByteArray
)
new_item = QListWidgetItem(self.lstSearchResult)
new_item.setSizeHint(custom_widget.sizeHint())
self.lstSearchResult.addItem(new_item)
self.lstSearchResult.setItemWidget(
new_item,
custom_widget
)
# self.lSearchResult.addWidget(custom_widget)
# w = QWidget()
# w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# self.lSearchResult.addWidget(w)
def search_started_process(self):
self.lstSearchResult.clear()
self.lstSearchResult.insertItem(0, self.tr('Searching...'))
def search_finished_progress(self):
self.lstSearchResult.takeItem(0)
if self.lstSearchResult.count() == 0:
new_widget = QLabel()
new_widget.setTextFormat(Qt.RichText)
new_widget.setOpenExternalLinks(True)
new_widget.setWordWrap(True)
new_widget.setText(
u" {}
{}
".format(
self.tr(u"No results."),
self.tr(u"You can add a service to become searchable. Start here.").format(
u"https://qms.nextgis.com/create"
),
)
)
new_item = QListWidgetItem(self.lstSearchResult)
new_item.setSizeHint(new_widget.sizeHint())
self.lstSearchResult.addItem(new_item)
self.lstSearchResult.setItemWidget(
new_item,
new_widget
)
def show_result(self, geoservice, image_ba):
if geoservice:
custom_widget = QmsSearchResultItemWidget(geoservice, image_ba, extent_renderer=self.extent_renderer)
new_item = QListWidgetItem(self.lstSearchResult)
new_item.setSizeHint(custom_widget.sizeHint())
self.lstSearchResult.addItem(new_item)
self.lstSearchResult.setItemWidget(
new_item,
custom_widget
)
else:
new_item = QListWidgetItem()
new_item.setText(self.tr('No results!'))
new_item.setData(Qt.UserRole, None)
self.lstSearchResult.addItem(new_item)
self.lstSearchResult.update()
def show_error(self, error_text):
self.lstSearchResult.clear()
new_widget = QLabel()
new_widget.setTextFormat(Qt.RichText)
new_widget.setOpenExternalLinks(True)
new_widget.setWordWrap(True)
new_widget.setText(
u" {}
{}
".format(
self.tr('Error'),
error_text
)
)
new_item = QListWidgetItem(self.lstSearchResult)
new_item.setSizeHint(new_widget.sizeHint())
self.lstSearchResult.addItem(new_item)
self.lstSearchResult.setItemWidget(
new_item,
new_widget
)
class QmsSearchResultItemWidget(QWidget):
def __init__(self, geoservice, image_ba, parent=None, extent_renderer=None):
QWidget.__init__(self, parent)
self.extent_renderer = extent_renderer
self.layout = QHBoxLayout(self)
self.layout.setContentsMargins(5, 10, 5, 10)
self.setLayout(self.layout)
self.service_icon = QLabel(self)
self.service_icon.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.service_icon.resize(24, 24)
qimg = QImage.fromData(image_ba)
pixmap = QPixmap.fromImage(qimg)
self.service_icon.setPixmap(pixmap)
self.layout.addWidget(self.service_icon)
self.service_desc_layout = QGridLayout(self)
self.service_desc_layout.setSpacing(0)
self.layout.addLayout(self.service_desc_layout)
self.service_name = QLabel(self)
self.service_name.setTextFormat(Qt.RichText)
self.service_name.setWordWrap(True)
self.service_name.setText(u" {} ".format(geoservice.get('name', u"")))
self.service_desc_layout.addWidget(self.service_name, 0, 0, 1, 3)
self.service_type = QLabel(self)
self.service_type.setTextFormat(Qt.RichText)
self.service_type.setWordWrap(True)
self.service_type.setText(geoservice.get('type', u"").upper() + " ")
self.service_desc_layout.addWidget(self.service_type, 1, 0)
self.service_deteils = QLabel(self)
self.service_deteils.setTextFormat(Qt.RichText)
self.service_deteils.setWordWrap(True)
self.service_deteils.setOpenExternalLinks(True)
self.service_deteils.setText(u"{1}, ".format(
Client().geoservice_info_url(geoservice.get('id', u"")),
self.tr('details')
))
self.service_desc_layout.addWidget(self.service_deteils, 1, 1)
self.service_report = QLabel(self)
self.service_report.setTextFormat(Qt.RichText)
self.service_report.setWordWrap(True)
self.service_report.setOpenExternalLinks(True)
self.service_report.setText(u"{1}".format(
Client().geoservice_report_url(geoservice.get('id', u"")),
self.tr('report a problem')
))
self.service_desc_layout.addWidget(self.service_report, 1, 2)
self.service_desc_layout.setColumnStretch(2, 1)
self.status_label = QLabel(self)
self.status_label.setTextFormat(Qt.RichText)
self.status_label.setText(u'\u2022')
status = geoservice.get('cumulative_status', u'')
if status == 'works':
self.status_label.setStyleSheet("color: green; font-size: 30px")
if status == 'failed':
self.status_label.setStyleSheet("color: red; font-size: 30px")
if status == 'problematic':
self.status_label.setStyleSheet("color: yellow; font-size: 30px")
self.layout.addWidget(self.status_label)
self.addButton = QToolButton()
self.addButton.setText(self.tr("Add"))
self.addButton.clicked.connect(self.addToMap)
self.layout.addWidget(self.addButton)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
self.geoservice = geoservice
self.image_ba = image_ba
def addToMap(self):
try:
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
client = Client()
client.set_proxy(*QGISSettings.get_qgis_proxy())
geoservice_info = client.get_geoservice_info(self.geoservice)
ds = DataSourceSerializer.read_from_json(geoservice_info)
add_layer_to_map(ds)
CachedServices().add_service(self.geoservice, self.image_ba)
except Exception as ex:
plPrint(unicode(ex))
pass
finally:
QApplication.restoreOverrideCursor()
def mouseDoubleClickEvent(self, event):
self.addToMap()
def enterEvent(self, event):
extent = self.geoservice.get('extent', None)
if self.extent_renderer and extent:
if ';' in extent:
extent = extent.split(';')[1]
geom = QgsGeometry.fromWkt(extent)
self.extent_renderer.show_feature(geom)
def leaveEvent(self, event):
if self.extent_renderer:
self.extent_renderer.clear_feature()
class SearchThread(QThread):
search_started = pyqtSignal()
search_finished = pyqtSignal()
data_downloaded = pyqtSignal(object, QByteArray)
error_occurred = pyqtSignal(object)
def __init__(self, search_text, mutex, parent=None, geom_filter=None, status_filter=None):
QThread.__init__(self, parent)
self.search_text = search_text
self.geom_filter = geom_filter
self.status_filter = status_filter
self.searcher = Client()
self.searcher.set_proxy(*QGISSettings.get_qgis_proxy())
self.mutex = mutex
self.img_cach = {}
self.need_stop = False
def run(self):
self.search_started.emit()
results = []
# search
try:
self.mutex.lock()
results = self.searcher.get_geoservices(
search_str=self.search_text,
intersects_boundary=self.geom_filter,
cumulative_status=self.status_filter
)
ext_results = []
for result in results:
if self.need_stop:
break
# get icon
ba = QByteArray()
icon_id = result.get("icon")
if self.img_cach.get(icon_id) is None:
if icon_id:
ba = QByteArray(self.searcher.get_icon_content(icon_id, 24, 24))
else:
ba = QByteArray(self.searcher.get_default_icon(24, 24))
self.img_cach[icon_id] = ba
else:
ba = self.img_cach[icon_id]
# get extent
extent = result['extent']
# area = None
area = 0.0
if extent:
if extent.startswith('SRID'):
extent = extent.split(';')[1]
area = QgsGeometry.fromWkt(extent).area()
ext_results.append([area, result, ba])
ext_results.sort(key=lambda x: x[0])
for result in ext_results:
self.data_downloaded.emit(result[1], result[2])
self.search_finished.emit()
except URLError:
error_text = (self.tr("Network error!\n{0}")).format(unicode(sys.exc_info()[1]))
# error_text = 'net'
self.error_occurred.emit(error_text)
except Exception:
error_text = (self.tr("Error of processing!\n{0}: {1}")).format(unicode(sys.exc_info()[0].__name__), unicode(sys.exc_info()[1]))
# error_text = 'common'
self.error_occurred.emit(error_text)
self.mutex.unlock()
def stop(self):
self.need_stop = True
PK 3qOl>5
5
quick_map_services/icon.pngPNG
IHDR szz sBIT|d pHYs t tfx tEXtSoftware www.inkscape.org< tEXtTitle GIS icon theme 0.2S tEXtAuthor Robert Szczepanek_V (tEXtDescription http://robert.szczepanek.pl/4 RtEXtCopyright CC Attribution-ShareAlike http://creativecommons.org/licenses/by-sa/3.0/^Z IDATXWkp~9eo/$lHHB.X&VkUNeTJ#SuNkN[]Tj[;EmmƪUD,D*@ &%M~]N@2P<ϙy{;K84~ n 7 X
@z>D0`g(
/+K <` BKsSQd#V"m69Xћ+N Ȁ? P
eMVۺs9mCz,Us-8!KV_9mC >NV |gm36ZܻD= GS{iSxO>?QR~י,֊F?DAxPg]Ѐ?~ݭ
É=hpDqIa<7y7G8K:71
>^<蝾zBҊ ^PgNmUNMaWVԖںLe*H͂i\rG\߲/'-{QrT '^lMvPGmٰn 7;c;kĮ䏿z'^|Cn6*s{T%ӲlR,t;y;%$
ࡩ+ =sՒUJ%[P
*UM;y}*DeIXNc4-PaM.'3U%6d%LHewD(O%w"2|ٲʯܱttP|l%2EUF
IURb[*t0Jr>Fʗ=V_ Ev kZl#{.ھs^z^^PkۛdY&Ncx N[?a>]25`l<海O{?<^a 7S MKӒ=͖qj)rMUuf:|01Th&3ЬNcÂl$Y)ˊ,&+nә|@3H.0j"]bTbMfHџ!>?hjbdY+ 6"vQt&)aΩdh&!0Q"1
S )Pb2Źibip-pDre' ID)JT,K@ f/?X-! ' Ep>aCm[qͻ'sB'Aə/,CVtx-b DGG瑵@ɋ^g3/![0c=fX vg\-aܭY5 pf||̜_wE h^.]EPgk Yr#Lj> %!mq}WwyfIH[xyEj1& Tg
ˌL \]i94
N>F l>Tx8=LR2JbGS WM'ɂ,88!8r[LVBJLM#^4D |0[uZ4
^QFJȴWIMpz&,!0!.IҰk6U~q;NNDK*D)vҲa\kx)d˟w͝]f lc$F(/~X]r_O۽KEZ&kr{=zpD 1㫚VvߞLBp'z/y횥-$jH7}i^y%($zhnr*wn|snݰ n
|1aJDc]չk] /HU~aBmw*{^W\oO5
Rdas>Ӧ^e n |ų5/Ԗ_9 9*.U:V(|݉D"fcѶ7Rshz[#~I" ۷{B߶t.8iW` uj#X"%fd_4geO4ot z lR(g,3X&