Init commit

This commit is contained in:
David Spáčil
2026-02-10 15:28:10 +01:00
commit ff73f32b16
37 changed files with 17016 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
#/***************************************************************************
# AmcrViewer
#
# Viewing and downloading the AMČR data.
# -------------------
# begin : 2026-02-03
# git sha : $Format:%H$
# copyright : (C) 2026 by David Spáčil
# email : spacil@arub.cz
# ***************************************************************************/
#
#/***************************************************************************
# * *
# * 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. *
# * *
# ***************************************************************************/
#################################################
# Edit the following to match your sources lists
#################################################
#Add iso code for any locales you want to support here (space separated)
# default is no locales
# LOCALES = af
LOCALES =
# If locales are enabled, set the name of the lrelease binary on your system. If
# you have trouble compiling the translations, you may have to specify the full path to
# lrelease
#LRELEASE = lrelease
#LRELEASE = lrelease-qt4
# translation
SOURCES = \
__init__.py \
amcr_viewer.py amcr_viewer_dialog.py
PLUGINNAME = amcr_viewer
PY_FILES = \
__init__.py \
amcr_viewer.py amcr_viewer_dialog.py
UI_FILES = amcr_viewer_dialog_base.ui
EXTRAS = metadata.txt icon.png
EXTRA_DIRS =
COMPILED_RESOURCE_FILES = resources.py
PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui
# QGISDIR points to the location where your plugin should be installed.
# This varies by platform, relative to your HOME directory:
# * Linux:
# .local/share/QGIS/QGIS3/profiles/default/python/plugins/
# * Mac OS X:
# Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins
# * Windows:
# AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins'
QGISDIR=C:\Users\Spacil\AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins
#################################################
# Normally you would not need to edit below here
#################################################
HELP = help/build/html
PLUGIN_UPLOAD = $(c)/plugin_upload.py
RESOURCE_SRC=$(shell grep '^ *<file' resources.qrc | sed 's@</file>@@g;s/.*>//g' | tr '\n' ' ')
.PHONY: default
default:
@echo While you can use make to build and deploy your plugin, pb_tool
@echo is a much better solution.
@echo A Python script, pb_tool provides platform independent management of
@echo your plugins and runs anywhere.
@echo You can install pb_tool using: pip install pb_tool
@echo See https://g-sherman.github.io/plugin_build_tool/ for info.
compile: $(COMPILED_RESOURCE_FILES)
%.py : %.qrc $(RESOURCES_SRC)
pyrcc5 -o $*.py $<
%.qm : %.ts
$(LRELEASE) $<
test: compile transcompile
@echo
@echo "----------------------"
@echo "Regression Test Suite"
@echo "----------------------"
@# Preceding dash means that make will continue in case of errors
@-export PYTHONPATH=`pwd`:$(PYTHONPATH); \
export QGIS_DEBUG=0; \
export QGIS_LOG_FILE=/dev/null; \
nosetests -v --with-id --with-coverage --cover-package=. \
3>&1 1>&2 2>&3 3>&- || true
@echo "----------------------"
@echo "If you get a 'no module named qgis.core error, try sourcing"
@echo "the helper script we have provided first then run make test."
@echo "e.g. source run-env-linux.sh <path to qgis install>; make test"
@echo "----------------------"
deploy: compile doc transcompile
@echo
@echo "------------------------------------------"
@echo "Deploying plugin to your .qgis2 directory."
@echo "------------------------------------------"
# The deploy target only works on unix like operating system where
# the Python plugin directory is located at:
# $HOME/$(QGISDIR)/python/plugins
mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help
# Copy extra directories if any
(foreach EXTRA_DIR,(EXTRA_DIRS), cp -R (EXTRA_DIR) (HOME)/(QGISDIR)/python/plugins/(PLUGINNAME)/;)
# The dclean target removes compiled python files from plugin directory
# also deletes any .git entry
dclean:
@echo
@echo "-----------------------------------"
@echo "Removing any compiled python files."
@echo "-----------------------------------"
find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete
find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \;
derase:
@echo
@echo "-------------------------"
@echo "Removing deployed plugin."
@echo "-------------------------"
rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
zip: deploy dclean
@echo
@echo "---------------------------"
@echo "Creating plugin zip bundle."
@echo "---------------------------"
# The zip target deploys the plugin and creates a zip file with the deployed
# content. You can then upload the zip file on http://plugins.qgis.org
rm -f $(PLUGINNAME).zip
cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME)
package: compile
# Create a zip package of the plugin named $(PLUGINNAME).zip.
# This requires use of git (your plugin development directory must be a
# git repository).
# To use, pass a valid commit or tag as follows:
# make package VERSION=Version_0.3.2
@echo
@echo "------------------------------------"
@echo "Exporting plugin to zip package. "
@echo "------------------------------------"
rm -f $(PLUGINNAME).zip
git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION)
echo "Created package: $(PLUGINNAME).zip"
upload: zip
@echo
@echo "-------------------------------------"
@echo "Uploading plugin to QGIS Plugin repo."
@echo "-------------------------------------"
$(PLUGIN_UPLOAD) $(PLUGINNAME).zip
transup:
@echo
@echo "------------------------------------------------"
@echo "Updating translation files with any new strings."
@echo "------------------------------------------------"
@chmod +x scripts/update-strings.sh
@scripts/update-strings.sh $(LOCALES)
transcompile:
@echo
@echo "----------------------------------------"
@echo "Compiled translation files to .qm files."
@echo "----------------------------------------"
@chmod +x scripts/compile-strings.sh
@scripts/compile-strings.sh $(LRELEASE) $(LOCALES)
transclean:
@echo
@echo "------------------------------------"
@echo "Removing compiled translation files."
@echo "------------------------------------"
rm -f i18n/*.qm
clean:
@echo
@echo "------------------------------------"
@echo "Removing uic and rcc generated files"
@echo "------------------------------------"
rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES)
doc:
@echo
@echo "------------------------------------"
@echo "Building documentation using sphinx."
@echo "------------------------------------"
cd help; make html
pylint:
@echo
@echo "-----------------"
@echo "Pylint violations"
@echo "-----------------"
@pylint --reports=n --rcfile=pylintrc . || true
@echo
@echo "----------------------"
@echo "If you get a 'no module named qgis.core' error, try sourcing"
@echo "the helper script we have provided first then run make pylint."
@echo "e.g. source run-env-linux.sh <path to qgis install>; make pylint"
@echo "----------------------"
# Run pep8 style checking
#http://pypi.python.org/pypi/pep8
pep8:
@echo
@echo "-----------"
@echo "PEP8 issues"
@echo "-----------"
@pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude $(PEP8EXCLUDE) . || true
@echo "-----------"
@echo "Ignored in PEP8 check:"
@echo $(PEP8EXCLUDE)
+608
View File
File diff suppressed because one or more lines are too long
+119
View File
@@ -0,0 +1,119 @@
# AMCR Viewer: QGIS Plugin Documentation
**Version:** 0.1.1
**Platform:** QGIS 3.4.x
**Module Type:** Data Acquisition & Visualization
**Source Data:** Archaeological Map of the Czech Republic (AIS CR)
---
## 1. Overview
**AMCR Viewer** is a QGIS plugin designed to facilitate direct access to the Digital Archive of the Archaeological Map of the Czech Republic (AMČR). It allows researchers to **query, retrieve, and visualize Fieldwork events[^1] data (metadata and geometry) directly within the GIS environment**, eliminating the need to manually export data from the web interface. **Only publicly accessible data are supported at the time** (accessibility = anonymous).
[^1]: Only Fieldwork events (Akce) are supported at the time.
### Key Features
* **Spatial Querying:** Option to filter records based on the current map canvas extent (Bounding Box).
* **Advanced Attribute Filtering:** Supports multi-criteria filtering using controlled vocabularies (Cadastral Area, District, Period, Type of Fieldwork event, Organization, Fieldwork Manager).
* **Dynamic Geometry Retrieval:** Automatically downloads and categorizes spatial data into Point, Line, and Polygon layers.
* **Semantic Interoperability:** Automatically translates internal system codes into human-readable labels using the AIS CR API.
---
## 2. Installation Guide
1. Obtain the plugin distribution package (ZIP archive containing the `amcr_viewer` directory).
2. Launch QGIS.
3. Navigate to **Plugins** **Manage and Install Plugins...**
4. Select the **Install from ZIP** tab.
5. Locate the source ZIP file and click **Install Plugin**.
6. Upon successful installation, the AMCR download button (load AMCR data) will appear in the interface.
---
## 3. User Manual
### 3.1 Data Retrieval
To initiate a search query, click the **Load AMCR Data** icon. The filter dialog provides the following options:
* **Spatial Filter:** *Checkbox "Limit search to current map extent":* If checked, the query is restricted to the geographical area currently visible in the QGIS canvas. If unchecked, the query searches the entire database (use with caution regarding data volume).
* It is possible to view only those Fieldwork events with positive outcome, if "Positive findings only" is checked. Only PIANs marked as (or rather PIANs belonging to Documentation units marked as) "Type of evidence" = "positive" are rendered.
* **Attribute Filters:**
* The dialog utilizes "Picker" widgets for controlled vocabularies (Region, District, Cadastral area, Organisation, Period, Activity Area).
* Click **Select...** to open a searchable selection window. Multiple values can be selected simultaneously (Logic: OR).
* **Fieldwork Manager (Dynamic List):**
* Due to the dynamic nature of the persons database, the list of Fieldwork Managers is retrieved from the AIS CR servers and needs to be updated the first time (and subsequently, if there is need).
* To refresh the list from the server, click the **Refresh (🔄)** button next to the selection field. This downloads the latest list of researchers from the API.
* If no filter is used, all accessible Fieldwork events/PIANs are returned (although the number of Fieldwork events to be loaded is capped at 20000 records; it is advisable to set at least one filter).
### 3.2 Layer Structure & Attributes
Upon successful retrieval, the plugin generates three temporary memory layers:
1. **AMCR Plochy (Polygons)**
2. **AMČR Linie (Lines)**
3. **AMČR Body (Points)**
The Attribute Table includes standardized fields such as:
* **Identification:** `Identifikátor` (Fieldwork event ID), `PIAN` (PIAN ID).
* **Classification:** `Hlavní typ` (Main Fieldwork event Type), `Vedlejší typ` (Secondary Fieldwork event Type), `PIAN typ` (PIAN Type).
* **Administration:** `Vedoucí akce` (Fieldwork Manager), `Organizace` (Organization), `Datum zahájeni`/`Datum ukončení` (Dates of start and end of the Fieldwork event).
* **Location:** `Katastr` (Main Cadastral area), `Další katastry` (Other Cadastral areas), `Okres` (District), `Definiční bod(y)` (PIAN point localization).
* **Links:** `Odkaz do Digiarchivu` (Direct URL to the DigiArchive record).
---
## 4. Technical Architecture
The plugin is developed in **Python 3** using the **PyQt5** framework for the GUI and the **Requests** library for HTTP communication.
### 4.1 File Structure
* `amcr_viewer.py`: Entry point; handles GUI integration and initialization.
* `amcr_dialog.py`: Manages the UI logic, including the custom `FilterableSelectionDialog` for handling large vocabularies.
* `amcr_tools.py`: Core logic module. Handles API requests, pagination, data parsing, and vector layer generation.
* `amcr_codelists.py`: Manages local caching of controlled vocabularies (`codelists/*.csv`).
### 4.2 Data Flow & API Integration
The plugin interacts with three primary endpoints of the AIS CR infrastructure:
1. **Search API (Solr):**
* Endpoint: `https://digiarchiv.aiscr.cz/api/search/query`
* Method: `GET`
* Parameters: `entity=akce`, `fl` (field list), `q` (query), `rows/page` (pagination).
* Logic: The plugin implements a `while True` loop to handle pagination, processing data in batches of 500 records to ensure stability.
2. **Translation API:**
* Endpoint: `https://digiarchiv.aiscr.cz/api/assets/i18n/cs.json`
* Function: Retrieves the mapping between system codes (e.g., `HES-xxxx`) and Czech labels. This dictionary is cached in memory during the session.
### 4.3 Data Persistence
* **Vocabularies:** Static vocabularies (e.g., Periods, Regions) are stored in `codelists/heslar.csv`.
* **Dynamic Data:** The list of investigators is downloaded on-demand and cached in `codelists/vedouci.csv`.
* **Layers:** Output layers are created as `memory` layers. They are non-persistent and will be lost if QGIS is closed without saving.
### 4.4 Constraints
* **Record Limit:** A safety cap of 20,000 records is enforced to prevent memory overflow in QGIS.
* **Batch Processing:** Geometry fetching is batched (50 IDs per request) to comply with URL length limitations and server load balancing.
## 6. Links and resources
* [AMCR/Digiarchive Documentation](https://amcr-help.aiscr.cz/) (only in Czech).
+36
View File
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
AmcrViewer
A QGIS plugin
Viewing and downloading the AMČR data.
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
begin : 2026-02-03
copyright : (C) 2026 by David Spáčil
email : spacil@arub.cz
git sha : $Format:%H$
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
This script initializes the plugin, making it known to QGIS.
"""
# noinspection PyPep8Naming
def classFactory(iface): # pylint: disable=invalid-name
"""Load AmcrViewer class from file AmcrViewer.
:param iface: A QGIS interface instance.
:type iface: QgsInterface
"""
#
from .amcr_viewer import AmcrViewer
return AmcrViewer(iface)
+172
View File
@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
import os
import csv
import codecs
import requests
import json
# Cesta k adresáři pluginu
PLUGIN_DIR = os.path.dirname(__file__)
CODELISTS_DIR = os.path.join(PLUGIN_DIR, 'codelists')
def ensure_codelists_dir():
if not os.path.exists(CODELISTS_DIR):
os.makedirs(CODELISTS_DIR)
# --- 1. NAČÍTÁNÍ DAT ---
def load_csv_data(filename):
"""Obecná funkce pro načtení CSV souboru do slovníku"""
data = {}
path = os.path.join(CODELISTS_DIR, filename)
if not os.path.exists(path):
return data
try:
with codecs.open(path, 'r', 'utf-8') as f:
reader = csv.reader(f, delimiter=';')
# Zkusíme přeskočit hlavičku, pokud tam je
first_row = next(reader, None)
# Pokud soubor není prázdný, zpracujeme ho
if first_row:
# Pokud první řádek vypadá jako data (neobsahuje slovo "Název"), vrátíme ho do hry
# Ale my budeme generovat soubory s hlavičkou, takže OK.
pass
for row in reader:
if len(row) >= 3:
label = row[0].strip()
code = row[1].strip()
category = row[2].strip()
# Tady můžeme filtrovat podle kategorie,
# nebo prostě vrátit všechno jako {label: code}
# Pro jednoduchost vracíme {label: code}
clean_code = code if code else None
data[label] = clean_code
except Exception as e:
print(f"AMČR Chyba čtení {filename}: {e}")
return data
def load_all_data():
"""
Načte statický heslář I dynamický heslář vedoucích.
Vrací slovník slovníků.
"""
ensure_codelists_dir()
# 1. Načteme hlavní statický heslář
# Musíme ho rozparsovat podle kategorií, tak jak to bylo předtím
categorized_data = {
'obdobi': {}, 'typ_akce': {}, 'areal': {},
'kraj': {}, 'organizace': {}, 'okres': {}, 'katastr': {},
'vedouci': {}
}
# Funkce pro roztřídění načteného slovníku (tohle je trochu redundance, ale pro zachování logiky)
def parse_file(filename):
path = os.path.join(CODELISTS_DIR, filename)
if not os.path.exists(path): return
try:
with codecs.open(path, 'r', 'utf-8') as f:
reader = csv.reader(f, delimiter=';')
next(reader, None) # Skip header
for row in reader:
if len(row) >= 3:
label = row[0].strip()
code = row[1].strip()
cat = row[2].strip()
clean = code if code else None
if cat in categorized_data:
categorized_data[cat][label] = clean
except: pass
# Načteme soubory
parse_file('heslar.csv') # Statické
parse_file('vedouci.csv') # Dynamické (pokud existuje)
return categorized_data
# --- 2. AKTUALIZACE DAT (DOWNLOAD) ---
def download_vedouci():
"""
Stáhne seznam vedoucích z API (pomocí onlyFacets) a uloží do codelists/vedouci.csv.
"""
ensure_codelists_dir()
# Tvá URL + pojistka, abychom dostali všechny záznamy (limit -1)
url = "https://digiarchiv.aiscr.cz/api/search/query?entity=akce&sort=datestamp%20desc&page=0&onlyFacets=True&rows=0"
try:
r = requests.get(url, timeout=20) # Raději delší timeout pro velký seznam
r.raise_for_status()
data = r.json()
# Cesta k datům dle tvého JSONu:
# {"facet_counts": { "f_vedouci": [ {"name": "Novák", ...}, ... ] }}
vedouci_list = data.get('facet_counts', {}).get('f_vedouci', [])
if not vedouci_list:
# Zkusíme ještě alternativní cestu, kdyby API vrátilo standardní Solr strukturu
# (facet_counts -> facet_fields -> f_vedouci)
vedouci_list = data.get('facet_counts', {}).get('facet_fields', {}).get('f_vedouci', [])
csv_path = os.path.join(CODELISTS_DIR, 'vedouci.csv')
count = 0
with codecs.open(csv_path, 'w', 'utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow(['Název', 'Kód', 'Kategorie'])
# NOVÁ LOGIKA PARSOVÁNÍ
for item in vedouci_list:
name = None
# Varianta A: Položka je slovník {"name": "Jan Novák", "value": 10}
if isinstance(item, dict):
name = item.get('name')
# Varianta B: Položka je jen string (kdyby se API vrátilo k plochému seznamu)
elif isinstance(item, str):
name = item
# Pokud máme jméno a není to číslo (count), zapíšeme
if name and not str(name).isdigit():
writer.writerow([name, name, 'vedouci'])
count += 1
return True, f"Staženo {count} jmen."
except Exception as e:
return False, str(e)
# --- GLOBAL DATA ---
# Toto se načte při startu QGISu
_DATA = load_all_data()
OBDOBI = _DATA['obdobi']
TYP_AKCE = _DATA['typ_akce']
AREAL = _DATA['areal']
KRAJE = _DATA['kraj']
ORGANIZACE = _DATA['organizace']
OKRESY = _DATA['okres']
KATASTRY = _DATA['katastr']
VEDOUCI = _DATA['vedouci'] # Tady to bude zpočátku prázdné, pokud soubor neexistuje
def refresh_vedouci_cache():
"""
Znovu načte soubor vedouci.csv a aktualizuje globální proměnnou VEDOUCI.
Použijeme 'update', aby se zachovala reference na objekt (pokud ho dialog už používá).
"""
temp_data = load_all_data()
new_vedouci = temp_data['vedouci']
# Vyčistíme a naplníme existující slovník (in-place update)
VEDOUCI.clear()
VEDOUCI.update(new_vedouci)
return len(VEDOUCI)
+190
View File
@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
from qgis.PyQt.QtWidgets import (QDialog, QVBoxLayout, QFormLayout,
QLineEdit, QDialogButtonBox,
QCheckBox, QGroupBox, QPushButton,
QListWidget, QListWidgetItem, QHBoxLayout,
QLabel, QMessageBox, QApplication, QWidget)
from qgis.PyQt.QtCore import Qt
from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE,
OKRESY, KATASTRY, VEDOUCI,
download_vedouci, refresh_vedouci_cache)
class FilterableSelectionDialog(QDialog):
def __init__(self, title, data_dict, preselected_codes, parent=None):
super().__init__(parent)
self.setWindowTitle(f"Výběr: {title}")
self.resize(400, 500)
self.data_dict = data_dict
self.preselected = preselected_codes if preselected_codes else []
layout = QVBoxLayout()
self.search_bar = QLineEdit()
self.search_bar.setPlaceholderText("Hledat v seznamu...")
self.search_bar.textChanged.connect(self.filter_list)
layout.addWidget(self.search_bar)
self.list_widget = QListWidget()
self.populate_list()
layout.addWidget(self.list_widget)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
self.setLayout(layout)
def populate_list(self):
sorted_names = sorted(self.data_dict.keys())
for name in sorted_names:
code = self.data_dict[name]
item = QListWidgetItem(name)
item.setData(Qt.UserRole, code)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
if code in self.preselected: item.setCheckState(Qt.Checked)
else: item.setCheckState(Qt.Unchecked)
self.list_widget.addItem(item)
def filter_list(self, text):
search_text = text.lower()
for i in range(self.list_widget.count()):
item = self.list_widget.item(i)
if search_text not in item.text().lower(): item.setHidden(True)
else: item.setHidden(False)
def get_selected_codes(self):
codes = []
labels = []
for i in range(self.list_widget.count()):
item = self.list_widget.item(i)
if item.checkState() == Qt.Checked:
codes.append(item.data(Qt.UserRole))
labels.append(item.text())
return codes, labels
# --- Main window ---
class AmcrFilterDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Filtr AMČR")
self.resize(500, 750)
# Cache for filtering
self.selection_cache = {
'organizace': [], 'kraj': [], 'obdobi': [], 'areal': [],
'typ_akce': [], 'okres': [], 'katastr': [], 'vedouci': []
}
layout = QVBoxLayout()
self.chk_bbox = QCheckBox("Omezit vyhledávání rozsahem okna")
self.chk_bbox.setChecked(True)
layout.addWidget(self.chk_bbox)
self.chk_posevidence = QCheckBox("Pouze pozitivní zjištění")
layout.addWidget(self.chk_posevidence)
layout.addSpacing(10)
def setup_picker(label_text, cache_key, data_source, extra_btn=None):
row_widget = QGroupBox(label_text)
# row_widget.setFlat(True)
row_layout = QHBoxLayout()
row_layout.setContentsMargins(5, 5, 5, 5)
display_field = QLineEdit()
display_field.setReadOnly(True)
display_field.setPlaceholderText("Nic nevybráno (vše)")
display_field.setStyleSheet("background-color: #f0f0f0; color: #333;")
btn = QPushButton("Vybrat...")
btn.setFixedWidth(80)
def open_dialog():
dlg = FilterableSelectionDialog(label_text, data_source, self.selection_cache[cache_key], self)
if dlg.exec_() == QDialog.Accepted:
codes, labels = dlg.get_selected_codes()
self.selection_cache[cache_key] = codes
if labels: display_field.setText(", ".join(labels))
else: display_field.clear()
btn.clicked.connect(open_dialog)
row_layout.addWidget(display_field)
row_layout.addWidget(btn)
if extra_btn:
row_layout.addWidget(extra_btn)
row_widget.setLayout(row_layout)
return row_widget
self.picker_kraj = setup_picker("Kraj", 'kraj', KRAJE)
layout.addWidget(self.picker_kraj)
self.picker_okres = setup_picker("Okres", 'okres', OKRESY)
layout.addWidget(self.picker_okres)
self.picker_katastr = setup_picker("Katastr", 'katastr', KATASTRY)
layout.addWidget(self.picker_katastr)
self.picker_org = setup_picker("Organizace", 'organizace', ORGANIZACE)
layout.addWidget(self.picker_org)
self.btn_update_vedouci = QPushButton("🔄")
self.btn_update_vedouci.setToolTip("Aktualizovat seznam vedoucích z API")
self.btn_update_vedouci.setFixedWidth(30)
self.btn_update_vedouci.clicked.connect(self.action_update_vedouci)
self.picker_vedouci = setup_picker("Vedoucí výzkumu", 'vedouci', VEDOUCI, extra_btn=self.btn_update_vedouci)
layout.addWidget(self.picker_vedouci)
self.picker_obdobi = setup_picker("Období", 'obdobi', OBDOBI)
layout.addWidget(self.picker_obdobi)
self.picker_areal = setup_picker("Areál / Druh", 'areal', AREAL)
layout.addWidget(self.picker_areal)
self.picker_typ = setup_picker("Typ výzkumu", 'typ_akce', TYP_AKCE)
layout.addWidget(self.picker_typ)
layout.addStretch(1)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
self.setLayout(layout)
def action_update_vedouci(self):
QApplication.setOverrideCursor(Qt.WaitCursor)
try:
success, msg = download_vedouci()
if success:
count = refresh_vedouci_cache()
QApplication.restoreOverrideCursor()
QMessageBox.information(self, "Úspěch", f"{msg}\nNyní je v paměti {count} osob.")
else:
QApplication.restoreOverrideCursor()
QMessageBox.warning(self, "Chyba", f"Nepodařilo se stáhnout data:\n{msg}")
except Exception as e:
QApplication.restoreOverrideCursor()
QMessageBox.critical(self, "Chyba", str(e))
def get_bbox(self):
return "true" if self.chk_bbox.isChecked() else "false"
def get_filters(self):
filters = {}
if self.chk_posevidence.isChecked(): filters['posevidence'] = 'true'
# Loading from cache
if self.selection_cache['organizace']: filters['f_organizace'] = self.selection_cache['organizace']
if self.selection_cache['kraj']: filters['f_kraj'] = self.selection_cache['kraj']
if self.selection_cache['okres']: filters['f_okres'] = self.selection_cache['okres']
if self.selection_cache['katastr']: filters['f_katastr'] = self.selection_cache['katastr']
if self.selection_cache['obdobi']: filters['f_obdobi'] = self.selection_cache['obdobi']
if self.selection_cache['areal']: filters['f_areal'] = self.selection_cache['areal']
if self.selection_cache['typ_akce']: filters['f_typ_vyzkumu'] = self.selection_cache['typ_akce']
if self.selection_cache['vedouci']: filters['f_vedouci'] = self.selection_cache['vedouci']
return filters
+418
View File
@@ -0,0 +1,418 @@
# -*- coding: utf-8 -*-
from qgis.gui import QgsMapToolIdentifyFeature
from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
QgsField, QgsCoordinateReferenceSystem, QgsCoordinateTransform,
QgsWkbTypes)
from qgis.utils import iface
from qgis.PyQt.QtCore import QVariant, Qt
from qgis.PyQt.QtWidgets import QMessageBox, QApplication
import requests
import json
import xml.etree.ElementTree as ET
import re
# Global translations cache
TRANSLATIONS = {}
# Download Digiarchive's vocabulary
def load_translations():
global TRANSLATIONS
if TRANSLATIONS: return
url = "https://digiarchiv.aiscr.cz/api/assets/i18n/cs.json"
try:
r = requests.get(url, timeout=10)
if r.status_code == 200:
TRANSLATIONS = r.json()
except Exception as e:
print(f"Chyba při stahování hesláře: {e}")
def tr_code(code):
if not code: return ""
return TRANSLATIONS.get(code, code)
def load_amcr_data(canvas, bb, filters=None):
load_translations()
# 1. Bounding box
extent = canvas.extent()
crs_src = canvas.mapSettings().destinationCrs()
crs_dest = QgsCoordinateReferenceSystem("EPSG:4326")
xform = QgsCoordinateTransform(crs_src, crs_dest, QgsProject.instance())
extent_wgs = xform.transformBoundingBox(extent)
bbox_str = f"{extent_wgs.yMinimum()},{extent_wgs.xMinimum()},{extent_wgs.yMaximum()},{extent_wgs.xMaximum()}"
url = "https://digiarchiv.aiscr.cz/api/search/query"
iface.messageBar().pushMessage("AMCR", "Hledám akce...", level=1)
QApplication.setOverrideCursor(Qt.WaitCursor)
try:
# ===================
# A) METADATA (Fieldwork event)
# ===================
# Field list
fl_akce = [
"ident_cely", "akce_typ", "akce_hlavni_vedouci", "akce_datum_zahajeni", "az_dj_pian", "akce_datum_ukonceni", "loc", "az_okres",
"katastr", "az_chranene_udaje", "akce_organizace", "akce_specifikace_data",
"akce_hlavni_typ", "akce_vedlejsi_typ", "akce_chranene_udaje",
"akce_je_nz", "pristupnost", "dj_negativni_jednotka"
]
base_params = {
"mapa": "true",
#"isExport": "true",
"entity": "akce",
"sort": "ident_cely asc",
"fl": ",".join(fl_akce)
}
if bb == "true":
base_params["loc_rpt"] = bbox_str
# Apply filters
if filters:
for key, value in filters.items():
if not value: continue
if isinstance(value, list):
base_params[key] = [f"{v}:or" for v in value]
else:
base_params[key] = str(value).strip()
docs_akce = []
current_page = 0
BATCH_AKCE = 500
MAX_LIMIT = 20000
seen_ids = set()
while True:
base_params['rows'] = BATCH_AKCE
if current_page > 0:
base_params['page'] = current_page
elif 'page' in base_params:
del base_params['page']
try:
resp_akce = requests.get(url, params=base_params, timeout=30)
resp_json = resp_akce.json()
data = resp_json.get('response', {})
batch_docs = data.get('docs', [])
num_found = data.get('numFound', 0)
if not batch_docs:
break
new_docs = []
for d in batch_docs:
ident = d.get('ident_cely')
if ident and ident not in seen_ids:
seen_ids.add(ident)
new_docs.append(d)
docs_akce.extend(new_docs)
print(f"Strana {current_page} stažena. Celkem záznamů: {len(docs_akce)} / {num_found}")
if len(docs_akce) >= num_found:
break
if len(docs_akce) >= MAX_LIMIT:
iface.messageBar().pushMessage("AMCR", f"Limit {MAX_LIMIT} záznamů dosažen.", level=1)
break
current_page += 1
QApplication.processEvents()
except Exception as e:
print(f"Chyba při stránkování na straně {current_page}: {e}")
break
if not docs_akce:
iface.messageBar().pushMessage("AMCR", "Žádné akce nenalezeny.", level=1)
return
# ==========================================
# Attribute parsing
# ==========================================
pian_lookup = {}
target_pian_ids = set()
actions_with_geom = 0
for akce in docs_akce:
piani = akce.get('az_dj_pian', [])
if not piani: continue
negative_pians = set()
# Pokud je aktivní filtr 'posevidence', projdeme dokumentační jednotky
if filters and filters.get('posevidence') == 'true':
djs = akce.get('az_dokumentacni_jednotka', [])
for dj in djs:
# Pokud je jednotka negativní
if dj.get('dj_negativni_jednotka') is True:
# Získáme ID pianu z objektu (např. {"id": "P-...", "value": "..."})
pian_obj = dj.get('dj_pian')
if pian_obj and isinstance(pian_obj, dict):
negative_pians.add(pian_obj.get('id'))
actions_with_geom += 1
def g(key, default=""):
val = akce.get(key)
if isinstance(val, list): return str(val[0]) if val else default
return str(val) if val is not None else default
def g_list(key, translate=False):
val = akce.get(key, [])
if not isinstance(val, list): val = [val] if val else []
if translate:
return ", ".join([tr_code(str(x)) for x in val if x])
return ", ".join([str(x) for x in val if x])
az_chranene = akce.get('az_chranene_udaje', {})
akce_chranene = akce.get('akce_chranene_udaje', {})
dalsi_kat = az_chranene.get('dalsi_katastr', [])
dalsi_kat_str = ""
if isinstance(dalsi_kat, list):
items = [x.get('value', '') if isinstance(x, dict) else str(x) for x in dalsi_kat]
dalsi_kat_str = ", ".join([i for i in items if i])
lokalizace = akce_chranene.get('lokalizace_okolnosti', "")
# Prepate metadata for fieldwork event
meta = {
"ident_cely": akce.get('ident_cely', ''),
"az_okres": g('az_okres'),
"katastr": g_list('katastr'),
"dalsi_katastr": dalsi_kat_str,
"akce_hlavni_vedouci": g('akce_hlavni_vedouci'),
"akce_organizace": tr_code(g('akce_organizace')),
"akce_specifikace_data": tr_code(g('akce_specifikace_data')),
"akce_datum_zahajeni": g('akce_datum_zahajeni'),
"akce_datum_ukonceni": g('akce_datum_ukonceni'),
"akce_hlavni_typ": tr_code(g('akce_hlavni_typ')),
"akce_vedlejsi_typ": g_list('akce_vedlejsi_typ', translate=True),
"lokalizace_okolnosti": str(lokalizace) if lokalizace else "",
"akce_je_nz": "Ano" if akce.get('akce_je_nz') is True else "Ne",
"pristupnost": g('pristupnost'),
"loc": g_list('loc')
}
for pid in piani:
if pid in negative_pians:
continue
pian_lookup[pid] = meta
target_pian_ids.add(pid)
if not target_pian_ids:
iface.messageBar().pushMessage("AMCR", f"Nalezeno {len(docs_akce)} akcí, ale žádná nemá geometrii.", level=1)
return
# ==========================================
# B) Geometry (PIAN)
# ==========================================
ids_list = list(target_pian_ids)
total_pians = len(ids_list)
docs_pian = []
BATCH_PIAN = 50
iface.messageBar().pushMessage("AMCR", f"Akcí: {len(docs_akce)} (z toho {actions_with_geom} s mapou). Stahuji {total_pians} geometrií...", level=1)
# Seznam polí pro PIAN
fl_pian = ["ident_cely", "pian_typ", "pian_chranene_udaje", "pian_presnost"]
for i in range(0, total_pians, BATCH_PIAN):
batch = ids_list[i : i + BATCH_PIAN]
or_query = " OR ".join(batch)
fq_pian = f"ident_cely:({or_query})"
params_pian = {
"mapa": "true",
"entity": "pian",
"q": fq_pian,
"rows": len(batch),
"fl": ",".join(fl_pian)
}
try:
QApplication.processEvents()
r_pian = requests.get(url, params=params_pian, timeout=15)
batch_docs = r_pian.json().get('response', {}).get('docs', [])
docs_pian.extend(batch_docs)
except Exception as e:
print(f"Chyba PIAN: {e}")
# ==========================================
# C) TVORBA VRSTEV
# ==========================================
vl_poly = QgsVectorLayer("Polygon?crs=epsg:5514", "AMCR Plochy", "memory")
vl_line = QgsVectorLayer("LineString?crs=epsg:5514", "AMCR Linie", "memory")
vl_point = QgsVectorLayer("Point?crs=epsg:5514", "AMCR Body", "memory")
layers = [vl_poly, vl_line, vl_point]
# Definice sloupců atributové tabulky
cols = [
QgsField("PIAN", QVariant.String),
QgsField("Přesnost", QVariant.String),
QgsField("PIAN typ", QVariant.String),
QgsField("Definiční bod(y) (WGS-84)", QVariant.String),
QgsField("Identifikátor", QVariant.String),
QgsField("Odkaz do Digiarchivu", QVariant.String),
QgsField("Okres", QVariant.String),
QgsField("Katastr", QVariant.String),
QgsField("Další katastry", QVariant.String),
QgsField("Vedoucí akce", QVariant.String),
QgsField("Organizace", QVariant.String),
QgsField("Specifikace data", QVariant.String),
QgsField("Datum zahájeni", QVariant.String),
QgsField("Datum ukončení", QVariant.String),
QgsField("Hlavní typ", QVariant.String),
QgsField("Vedlejší typ", QVariant.String),
QgsField("Akce lokalizace", QVariant.String),
QgsField("Akce - nahrazuje NZ", QVariant.String),
QgsField("Přístupnost", QVariant.String)
]
for vl in layers:
vl.dataProvider().addAttributes(cols)
vl.updateFields()
feats_p, feats_l, feats_pt = [], [], []
for doc in docs_pian:
try:
pid = doc.get('ident_cely', '')
if pid not in pian_lookup: continue
meta = pian_lookup[pid]
# Geometry processing
raw = doc.get('pian_chranene_udaje')
if isinstance(raw, list) and raw: raw = raw[0]
jdata = json.loads(raw) if isinstance(raw, str) else (raw if isinstance(raw, dict) else {})
wkt = None
if jdata.get('geom_sjtsk_wkt'): wkt = jdata['geom_sjtsk_wkt'].get('value')
elif jdata.get('geom_wkt'): wkt = jdata['geom_wkt'].get('value')
# PIAN attributes
pian_presnost = tr_code(str(doc.get('pian_presnost', '')))
pian_typ = tr_code(str(doc.get('pian_typ', '')))
if wkt:
geom = QgsGeometry.fromWkt(wkt)
if geom.isGeosValid():
feat = QgsFeature()
feat.setGeometry(geom)
feat.setAttributes([
pid,
pian_presnost,
pian_typ,
meta['loc'],
meta['ident_cely'],
"https://digiarchiv.aiscr.cz/id/" + meta['ident_cely'],
meta['az_okres'],
meta['katastr'],
meta['dalsi_katastr'],
meta['akce_hlavni_vedouci'],
meta['akce_organizace'],
meta['akce_specifikace_data'],
meta['akce_datum_zahajeni'],
meta['akce_datum_ukonceni'],
meta['akce_hlavni_typ'],
meta['akce_vedlejsi_typ'],
meta['lokalizace_okolnosti'],
meta['akce_je_nz'],
meta['pristupnost']
])
t = geom.type()
if t == QgsWkbTypes.PolygonGeometry: feats_p.append(feat)
elif t == QgsWkbTypes.LineGeometry: feats_l.append(feat)
elif t == QgsWkbTypes.PointGeometry: feats_pt.append(feat)
except Exception as ex:
print(f"Chyba při tvorbě feature: {ex}")
pass
proj = QgsProject.instance()
added = 0
for f, l, n in [(feats_p, vl_poly, "Plochy"), (feats_l, vl_line, "Linie"), (feats_pt, vl_point, "Body")]:
if f:
l.dataProvider().addFeatures(f)
l.updateExtents()
l.setName(f"AMČR {n} (Filtrováno)")
proj.addMapLayer(l)
added += len(f)
if added > 0:
iface.messageBar().pushMessage("AMCR", f"Hotovo. Akcí: {len(docs_akce)} (s geom: {actions_with_geom}). Vykresleno: {added} prvků.", level=0)
else:
iface.messageBar().pushMessage("AMCR", "Žádná data k zobrazení.", level=1)
except Exception as e:
iface.messageBar().pushMessage("Chyba", str(e), level=2)
finally:
QApplication.restoreOverrideCursor()
# class AmcrIdentifyTool(QgsMapToolIdentifyFeature):
# def __init__(self, canvas):
# super().__init__(canvas)
# self.canvas = canvas
# self.setCursor(Qt.CrossCursor)
# def canvasReleaseEvent(self, event):
# results = self.identify(event.x(), event.y(), self.IdentifyMode.TopDownStopAtFirst, self.VectorLayer)
# if not results: return
# feature = results[0].mFeature
# akce_id = None
# # Změna: hledáme 'ident_cely' (ID akce)
# idx = feature.fieldNameIndex('ident_cely')
# if idx != -1:
# akce_id = feature.attributes()[idx]
# # Fallback na starší názvy polí, kdyby něco
# if not akce_id:
# for col in ['akce_id', 'ident_cely', 'pian_id']:
# if col in feature.fields().names():
# akce_id = feature[col]
# break
# if not akce_id: return
# full_id = akce_id if "api.aiscr.cz" in str(akce_id) else f"https://api.aiscr.cz/id/{akce_id}"
# url = f"https://api.aiscr.cz/2.2/oai?verb=GetRecord&metadataPrefix=oai_amcr&identifier={full_id}"
# iface.messageBar().pushMessage("AMCR", f"Detail: {akce_id}...", level=1)
# QApplication.setOverrideCursor(Qt.WaitCursor)
# try:
# r = requests.get(url, timeout=5)
# if r.status_code == 200: self.show_detail(akce_id, r.text)
# except Exception as e:
# iface.messageBar().pushMessage("Chyba", str(e), level=2)
# finally:
# QApplication.restoreOverrideCursor()
# def show_detail(self, title, raw_xml):
# xml = re.sub(r'\sxmlns="[^"]+"', '', raw_xml, count=1)
# xml = re.sub(r'<(/?)[a-zA-Z0-9]+:', r'<\1', xml)
# info = ""
# try:
# root = ET.fromstring(xml)
# rec = root.find('.//archeologicky_zaznam')
# if not rec: info = "Zadna data."
# else:
# kat = rec.find('.//hlavni_katastr')
# info += f"<h3>{kat.text if kat is not None else '?'}</h3>"
# for dj in rec.findall('.//dokumentacni_jednotka'):
# pn = dj.find('pian')
# p_txt = pn.text if pn is not None else ""
# info += f"<hr><b>PIAN: {p_txt}</b><ul>"
# for k in dj.findall('komponenta'):
# ob = k.find('obdobi').text or "?"
# ar = k.find('areal').text or "?"
# info += f"<li>{ob} ({ar})</li>"
# info += "</ul>"
# dlg = QMessageBox()
# dlg.setWindowTitle(str(title))
# dlg.setText(info)
# dlg.setTextFormat(Qt.RichText)
# dlg.exec_()
# except: pass
+119
View File
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
from .amcr_tools import load_amcr_data#, AmcrIdentifyTool
from .amcr_dialog import AmcrFilterDialog
from .resources import *
import os.path
class AmcrViewer:
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
locale = QSettings().value('locale/userLocale')[0:2]
locale_path = os.path.join(
self.plugin_dir,
'i18n',
'AmcrViewer_{}.qm'.format(locale))
if os.path.exists(locale_path):
self.translator = QTranslator()
self.translator.load(locale_path)
QCoreApplication.installTranslator(self.translator)
self.actions = []
self.menu = self.tr(u'&AMČR Viewer')
self.first_start = None
def tr(self, message):
return QCoreApplication.translate('AmcrViewer', message)
def add_action(self, icon_path, text, callback, enabled_flag=True,
add_to_menu=True, add_to_toolbar=True, status_tip=None,
whats_this=None, parent=None):
icon = QIcon(icon_path)
action = QAction(icon, text, parent)
action.triggered.connect(callback)
action.setEnabled(enabled_flag)
if status_tip is not None:
action.setStatusTip(status_tip)
if whats_this is not None:
action.setWhatsThis(whats_this)
if add_to_toolbar:
self.iface.addToolBarIcon(action)
if add_to_menu:
self.iface.addPluginToMenu(self.menu, action)
self.actions.append(action)
return action
def initGui(self):
import os
plugin_dir = os.path.dirname(__file__)
icon = QIcon(os.path.join(plugin_dir, 'download.png'))
# icon_info = QIcon(os.path.join(plugin_dir, 'info.png'))
# Download data button
self.action_download = self.add_action(
icon,
text=self.tr(u'Načíst data z AMČR'),
callback=self.run_download,
parent=self.iface.mainWindow())
# # Info button (Checkable / Toggle)
# self.action_tool = self.add_action(
# icon_info,
# text=self.tr(u'Výpis údajů záznamu'),
# callback=self.run_tool,
# parent=self.iface.mainWindow())
# self.action_tool.setCheckable(True) # Toto tlačítko se zamačkává
self.first_start = True
def unload(self):
for action in self.actions:
self.iface.removePluginMenu(self.tr(u'&AMČR Viewer'), action)
self.iface.removeToolBarIcon(action)
if hasattr(self, 'tool'):
self.iface.mapCanvas().unsetMapTool(self.tool)
# --- Data downloading ---
def run_download(self):
dlg = AmcrFilterDialog()
result = dlg.exec_()
if result == 1:
filters = dlg.get_filters()
bbox = dlg.get_bbox()
canvas = self.iface.mapCanvas()
load_amcr_data(canvas, bbox, filters)
# --- Info button toggle ---
# def run_tool(self):
# if self.action_tool.isChecked():
# canvas = self.iface.mapCanvas()
# if not hasattr(self, 'tool'):
# self.tool = AmcrIdentifyTool(canvas)
# self.tool.deactivated.connect(lambda: self.action_tool.setChecked(False))
# canvas.setMapTool(self.tool)
# self.iface.messageBar().pushMessage("AMČR", "Info nástroj aktivní.", level=0)
# else:
# if self.iface.mapCanvas().mapTool() == getattr(self, 'tool', None):
# self.iface.mapCanvas().unsetMapTool(self.tool)
+44
View File
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
AmcrViewerDialog
A QGIS plugin
Viewing and downloading the AMČR data.
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
begin : 2026-02-03
git sha : $Format:%H$
copyright : (C) 2026 by David Spáčil
email : spacil@arub.cz
***************************************************************************/
/***************************************************************************
* *
* 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 import QtWidgets
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'amcr_viewer_dialog_base.ui'))
class AmcrViewerDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(AmcrViewerDialog, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing
# self.<objectname>, and you can use autoconnect slots - see
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# #widgets-and-dialogs-with-auto-connect
self.setupUi(self)
+67
View File
@@ -0,0 +1,67 @@
<ui version="4.0" >
<class>AmcrViewerDialogBase</class>
<widget class="QDialog" name="AmcrViewerDialogBase" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle" >
<string>AMČR Viewer</string>
</property>
<widget class="QDialogButtonBox" name="button_box" >
<property name="geometry" >
<rect>
<x>30</x>
<y>240</y>
<width>341</width>
<height>32</height>
</rect>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>AmcrViewerDialogBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="source_label" >
<x>248</x>
<y>254</y>
</hint>
<hint type="destination_label" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>AmcrViewerDialogBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="source_label" >
<x>316</x>
<y>260</y>
</hint>
<hint type="destination_label" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+13616
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0" language="af" sourcelanguage="en">
<context>
<name>@default</name>
<message>
<location filename="test_translations.py" line="48"/>
<source>Good morning</source>
<translation>Goeie more</translation>
</message>
</context>
</TS>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

+47
View File
@@ -0,0 +1,47 @@
# This file contains metadata for your plugin.
# This file should be included when you package your plugin.# Mandatory items:
[general]
name=AMČR Viewer
qgisMinimumVersion=3.4
description=Viewing and downloading the AMČR data.
version=0.1.1
author=David Spáčil
email=spacil@arub.cz
about=This plugin is intended for downloading the data (Fieldwork events data only, at the time) from the Digiarchive of the Archaeological Map of the Czech Republic (AMCR). As of now, only publicly accessible data can be downloaded.
tracker=http://bugs
repository=http://repo
# End of mandatory metadata
# Recommended items:
hasProcessingProvider=no
# Uncomment the following line and add your changelog:
# changelog=
# Tags are comma separated with spaces allowed
tags=python,AMCR,AIS CR,archaeology,PIAN,AMČR
homepage=http://homepage
category=Vector
icon=download.png
# experimental flag
experimental=True
# deprecated flag (applies to the whole plugin, not just a single version)
deprecated=False
# Since QGIS 3.8, a comma separated list of plugins to be installed
# (or upgraded) can be specified.
# Check the documentation for more information.
# plugin_dependencies=
# Category of the plugin: Raster, Vector, Database or Web
# category=
# If the plugin can run on QGIS Server.
server=False
+80
View File
@@ -0,0 +1,80 @@
#/***************************************************************************
# AmcrViewer
#
# Configuration file for plugin builder tool (pb_tool)
# Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
# -------------------
# begin : 2026-02-03
# copyright : (C) 2026 by David Spáčil
# email : spacil@arub.cz
# ***************************************************************************/
#
#/***************************************************************************
# * *
# * 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. *
# * *
# ***************************************************************************/
#
#
# You can install pb_tool using:
# pip install http://geoapt.net/files/pb_tool.zip
#
# Consider doing your development (and install of pb_tool) in a virtualenv.
#
# For details on setting up and using pb_tool, see:
# http://g-sherman.github.io/plugin_build_tool/
#
# Issues and pull requests here:
# https://github.com/g-sherman/plugin_build_tool:
#
# Sane defaults for your plugin generated by the Plugin Builder are
# already set below.
#
# As you add Python source files and UI files to your plugin, add
# them to the appropriate [files] section below.
[plugin]
# Name of the plugin. This is the name of the directory that will
# be created in .qgis2/python/plugins
name: amcr_viewer
# Full path to where you want your plugin directory copied. If empty,
# the QGIS default path will be used. Don't include the plugin name in
# the path.
plugin_path:
[files]
# Python files that should be deployed with the plugin
python_files: __init__.py amcr_viewer.py amcr_viewer_dialog.py
# The main dialog file that is loaded (not compiled)
main_dialog: amcr_viewer_dialog_base.ui
# Other ui files for dialogs you create (these will be compiled)
compiled_ui_files:
# Resource file(s) that will be compiled
resource_files: resources.qrc
# Other files required for the plugin
extras: metadata.txt icon.png
# Other directories to be deployed with the plugin.
# These must be subdirectories under the plugin directory
extra_dirs:
# ISO code(s) for any locales (translations), separated by spaces.
# Corresponding .ts files must exist in the i18n directory
locales:
[help]
# the built help directory that should be deployed with the plugin
dir: help/build/html
# the name of the directory to target in the deployed plugin
target: help
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python
# coding=utf-8
"""This script uploads a plugin package to the plugin repository.
Authors: A. Pasotti, V. Picavet
git sha : $TemplateVCSFormat
"""
import sys
import getpass
import xmlrpc.client
from optparse import OptionParser
standard_library.install_aliases()
# Configuration
PROTOCOL = 'https'
SERVER = 'plugins.qgis.org'
PORT = '443'
ENDPOINT = '/plugins/RPC2/'
VERBOSE = False
def main(parameters, arguments):
"""Main entry point.
:param parameters: Command line parameters.
:param arguments: Command line arguments.
"""
address = "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format(
protocol=PROTOCOL,
username=parameters.username,
password=parameters.password,
server=parameters.server,
port=parameters.port,
endpoint=ENDPOINT)
print("Connecting to: %s" % hide_password(address))
server = xmlrpc.client.ServerProxy(address, verbose=VERBOSE)
try:
with open(arguments[0], 'rb') as handle:
plugin_id, version_id = server.plugin.upload(
xmlrpc.client.Binary(handle.read()))
print("Plugin ID: %s" % plugin_id)
print("Version ID: %s" % version_id)
except xmlrpc.client.ProtocolError as err:
print("A protocol error occurred")
print("URL: %s" % hide_password(err.url, 0))
print("HTTP/HTTPS headers: %s" % err.headers)
print("Error code: %d" % err.errcode)
print("Error message: %s" % err.errmsg)
except xmlrpc.client.Fault as err:
print("A fault occurred")
print("Fault code: %d" % err.faultCode)
print("Fault string: %s" % err.faultString)
def hide_password(url, start=6):
"""Returns the http url with password part replaced with '*'.
:param url: URL to upload the plugin to.
:type url: str
:param start: Position of start of password.
:type start: int
"""
start_position = url.find(':', start) + 1
end_position = url.find('@')
return "%s%s%s" % (
url[:start_position],
'*' * (end_position - start_position),
url[end_position:])
if __name__ == "__main__":
parser = OptionParser(usage="%prog [options] plugin.zip")
parser.add_option(
"-w", "--password", dest="password",
help="Password for plugin site", metavar="******")
parser.add_option(
"-u", "--username", dest="username",
help="Username of plugin site", metavar="user")
parser.add_option(
"-p", "--port", dest="port",
help="Server port to connect to", metavar="80")
parser.add_option(
"-s", "--server", dest="server",
help="Specify server name", metavar="plugins.qgis.org")
options, args = parser.parse_args()
if len(args) != 1:
print("Please specify zip file.\n")
parser.print_help()
sys.exit(1)
if not options.server:
options.server = SERVER
if not options.port:
options.port = PORT
if not options.username:
# interactive mode
username = getpass.getuser()
print("Please enter user name [%s] :" % username, end=' ')
res = input()
if res != "":
options.username = res
else:
options.username = username
if not options.password:
# interactive mode
options.password = getpass.getpass()
main(options, args)
+281
View File
@@ -0,0 +1,281 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
# see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them
disable=locally-disabled,C0103
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct attribute names in class
# bodies
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
+128
View File
@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
# Resource object code
#
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.13)
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore
qt_resource_data = b"\
\x00\x00\x04\x0a\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\
\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\
\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x02\x15\
\x16\x11\x2c\x9d\x48\x83\xbb\x00\x00\x03\x8a\x49\x44\x41\x54\x48\
\xc7\xad\x95\x4b\x68\x5c\x55\x18\xc7\x7f\xe7\xdc\x7b\x67\xe6\xce\
\x4c\x66\x26\x49\xd3\x24\x26\xa6\xc6\xf8\x40\x21\xa5\x04\xb3\x28\
\xda\x98\x20\xa5\x0b\xad\x55\xa8\x2b\xc5\x50\x1f\xa0\x6e\x34\x2b\
\x45\x30\x14\x02\xba\x52\x69\x15\x17\x66\x63\x45\x97\x95\xa0\xad\
\x0b\xfb\xc0\x06\x25\xb6\x71\x61\x12\x41\x50\xdb\x2a\x21\xd1\xe2\
\x24\xf3\x9e\xc9\xcc\xbd\xe7\x1c\x17\x35\x43\x1e\x33\x21\xb6\xfd\
\x56\x87\xf3\x9d\xfb\xfb\x1e\xf7\xff\x9d\x23\x8c\x31\x43\x95\xf4\
\x85\x1e\x3f\x3b\x35\xac\xfd\xcc\x43\xdc\xa4\x49\x3b\xfe\x9d\x1d\
\xdb\x7b\x22\x90\x78\xf8\xb2\x28\xa7\xbe\x7d\xc1\x4b\x9d\x79\xdf\
\x18\x15\xe5\x16\x99\x10\x56\xde\x69\xdc\x3f\x22\xfd\xec\xd4\xf0\
\xad\x04\x03\x18\xa3\xa2\x7e\x76\x6a\x58\xde\x68\x2b\xb4\x36\xf8\
\xbe\xc6\x18\x53\xdb\xef\xe7\xfa\xec\xed\x67\x63\x10\x42\x00\xf0\
\xfb\xd5\x65\x2a\x15\x45\xc7\x6d\x0d\x00\xc4\xa2\xc1\xaa\x6f\x0d\
\x3e\x6c\xab\xc2\x1c\x56\xa4\x77\x4b\xb0\xf2\x35\x15\x5f\x21\x85\
\xe0\xc8\x6b\x5f\x92\x2d\x37\x33\x39\xf9\x03\x27\x8e\x1f\xa2\xf7\
\xbe\x9d\x04\x1c\x0b\x37\xe4\xac\xff\xa6\x30\x87\xbd\xba\x00\x6a\
\x06\x79\xe5\xf5\xaf\x89\xd9\x92\xc5\xcc\x0a\xd9\x7c\x19\xcf\xe9\
\xe2\xe4\xa9\x2f\x78\x7c\xff\x01\x72\x85\x0a\x2b\x65\x1f\xa5\x4c\
\xb5\xb2\x55\x16\x80\xbd\x31\xda\xda\x20\x1f\x7d\x3e\xcd\xc2\xfd\
\x59\xa6\x93\x39\x92\xd1\x22\xea\x9b\x16\xce\x9d\x3f\xce\xe0\x83\
\x03\x24\x82\x59\x3a\xdb\x7b\x88\xc7\x82\x68\x63\x58\xc9\xcc\x62\
\x8c\x21\x18\xb0\x6a\xc3\x37\x06\x49\x16\xff\x24\x6b\xa5\x49\xbb\
\x25\xbc\xa2\xa6\x21\xbb\x40\x7f\xdf\x00\x83\xbd\x01\x8e\x3c\xd5\
\x45\xd7\x8e\x6b\x9c\x9c\x98\x25\x1a\xb6\xe8\xbe\x3d\xc2\xdd\x77\
\x44\x48\xc4\x1c\x22\xe1\xeb\x58\x59\xaf\xcf\xd3\x33\x29\x2e\x34\
\x2d\x91\x93\x3e\xbe\x34\x78\x01\xc5\xe2\x61\xc5\xae\x72\x8e\x70\
\xc8\xc2\x0d\x5a\xbc\xf5\xee\x2f\x9c\xfa\x3e\x86\x69\x7a\x8e\xcf\
\x26\xe6\xf9\x63\xa1\x44\xa1\xa4\xd0\xda\x6c\x0d\x2f\x15\x7c\xb4\
\x67\x28\x59\x0a\xcf\xd6\x54\xe2\x06\x13\x87\x2b\x6f\x68\xa6\x27\
\xaf\x31\x32\x36\xc7\xb2\x7f\x17\xef\x7d\x7c\x8c\x33\x67\xcf\x12\
\x70\x24\x4a\x69\xd6\x6a\x46\xd6\xd3\x70\x72\xa9\x82\x67\x34\x45\
\xad\x28\xdb\x1a\x15\x34\x98\xff\x46\xed\xef\x37\x0d\x99\xbf\x4a\
\x3c\x30\x38\xc0\xc8\x4b\xaf\x92\x5a\x9c\xe2\xe0\x23\x6d\x74\xb4\
\xba\x84\x5d\x0b\x29\x45\x7d\xb8\x94\x82\x96\xb6\x10\xf3\xc5\x12\
\x2a\xef\x53\x11\x1a\x63\xad\x3f\x93\x19\x85\xf1\xb1\x77\x58\x5a\
\xf8\x99\x97\x9f\xe9\xa6\x75\x47\x90\xc6\xb8\x43\xd8\xb5\xb6\xce\
\xfc\xfa\xfd\x00\xfb\x3e\xf4\xc8\x05\x35\xba\x5e\xeb\x46\x21\xf9\
\xcf\x0a\xa9\x8c\x87\xe3\x48\xdc\x90\xb5\x6e\x98\x6a\xaa\x65\xf2\
\x52\x92\x43\x2f\x5e\xc2\x8c\x02\x1a\x10\xf5\x07\xac\xc3\x75\x70\
\x83\x92\x80\xb3\xf9\xd0\x26\xf8\x8f\xb3\x29\xc6\x3e\xb8\x8c\x19\
\x35\x75\x6b\x7b\x7e\x3c\xca\x45\x0c\x7e\x49\x31\xf4\x58\x3b\xf7\
\xf6\x34\x90\x88\x39\x04\x1c\x59\x1f\xfe\xdb\xd5\x3c\x5f\x9d\x4b\
\x32\xfd\x44\xb2\xba\xd7\xfa\xb6\x60\xcf\xde\x16\xdc\x90\x45\x4c\
\x4a\x2a\x9e\x62\xfe\x4e\xc5\xc8\xc1\x4e\xda\x76\x86\xe8\xe9\x0a\
\xe3\xd8\x92\x58\xd4\xc6\xb2\x44\x6d\x78\x2a\x53\xe1\xca\x7c\x99\
\x63\x5d\xbf\x56\x9d\xbd\x9f\x44\x18\x7a\xba\x95\x27\x0f\xb4\xd3\
\xdc\x18\xc0\xf3\x0d\x52\x40\xd8\xb5\xb0\xa4\x20\x14\xb2\x70\x6c\
\x81\x63\xcb\xaa\x42\xd6\xfd\xb7\xf4\xec\xa3\x06\xa0\x50\x52\xd8\
\x4e\x1b\x7e\x4a\xd3\x31\xf9\x29\xcf\xfe\xd4\x49\x7f\x5f\x13\xfb\
\xfa\x9b\x71\x43\x92\x58\xd4\x21\x18\x90\xac\xde\xb0\x42\x50\x13\
\x58\x33\xf3\x88\x6b\xa1\xfd\x65\x96\xf2\x79\xc6\x43\x7b\xd8\x75\
\x38\xcc\x3d\xdd\xd1\xaa\xcf\x71\xe4\xff\x7f\x91\x56\x33\xaf\xea\
\x37\xe7\xa1\x94\x21\x16\xb5\xd1\x06\x2c\x29\x36\xf5\x72\x9b\x96\
\x95\xc0\xc4\xda\x9d\x78\x83\x43\x53\x22\x80\x65\x09\x1c\xfb\x86\
\xc1\x00\xe7\x25\x70\x14\x48\x6f\x1e\x22\x51\xe3\x75\xd9\xb6\xa5\
\x81\xa3\x32\xb1\xfb\xf4\x0c\x30\xb8\xb1\x82\x9b\xb0\x09\x60\x30\
\xb1\xfb\xf4\xcc\xbf\xa0\xe9\x6e\xae\x5a\xdf\x4b\x81\x00\x00\x00\
\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
"
qt_resource_name = b"\
\x00\x07\
\x07\x3b\xe0\xb3\
\x00\x70\
\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\
\x00\x0b\
\x06\x1f\xb8\xc2\
\x00\x61\
\x00\x6d\x00\x63\x00\x72\x00\x5f\x00\x76\x00\x69\x00\x65\x00\x77\x00\x65\x00\x72\
\x00\x08\
\x0a\x61\x5a\xa7\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
"
qt_resource_struct_v1 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
"
qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9c\x23\xfd\x16\x70\
"
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
if qt_version < [5, 8, 0]:
rcc_version = 1
qt_resource_struct = qt_resource_struct_v1
else:
rcc_version = 2
qt_resource_struct = qt_resource_struct_v2
def qInitResources():
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()
+5
View File
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/plugins/amcr_viewer" >
<file>icon.png</file>
</qresource>
</RCC>
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
LRELEASE=$1
LOCALES=$2
for LOCALE in ${LOCALES}
do
echo "Processing: ${LOCALE}.ts"
# Note we don't use pylupdate with qt .pro file approach as it is flakey
# about what is made available.
$LRELEASE i18n/${LOCALE}.ts
done
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
QGIS_PREFIX_PATH=/usr/local/qgis-2.0
if [ -n "$1" ]; then
QGIS_PREFIX_PATH=$1
fi
echo ${QGIS_PREFIX_PATH}
export QGIS_PREFIX_PATH=${QGIS_PREFIX_PATH}
export QGIS_PATH=${QGIS_PREFIX_PATH}
export LD_LIBRARY_PATH=${QGIS_PREFIX_PATH}/lib
export PYTHONPATH=${QGIS_PREFIX_PATH}/share/qgis/python:${QGIS_PREFIX_PATH}/share/qgis/python/plugins:${PYTHONPATH}
echo "QGIS PATH: $QGIS_PREFIX_PATH"
export QGIS_DEBUG=0
export QGIS_LOG_FILE=/tmp/inasafe/realtime/logs/qgis.log
export PATH=${QGIS_PREFIX_PATH}/bin:$PATH
echo "This script is intended to be sourced to set up your shell to"
echo "use a QGIS 2.0 built in $QGIS_PREFIX_PATH"
echo
echo "To use it do:"
echo "source $BASH_SOURCE /your/optional/install/path"
echo
echo "Then use the make file supplied here e.g. make guitest"
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
LOCALES=$*
# Get newest .py files so we don't update strings unnecessarily
CHANGED_FILES=0
PYTHON_FILES=`find . -regex ".*\(ui\|py\)$" -type f`
for PYTHON_FILE in $PYTHON_FILES
do
CHANGED=$(stat -c %Y $PYTHON_FILE)
if [ ${CHANGED} -gt ${CHANGED_FILES} ]
then
CHANGED_FILES=${CHANGED}
fi
done
# Qt translation stuff
# for .ts file
UPDATE=false
for LOCALE in ${LOCALES}
do
TRANSLATION_FILE="i18n/$LOCALE.ts"
if [ ! -f ${TRANSLATION_FILE} ]
then
# Force translation string collection as we have a new language file
touch ${TRANSLATION_FILE}
UPDATE=true
break
fi
MODIFICATION_TIME=$(stat -c %Y ${TRANSLATION_FILE})
if [ ${CHANGED_FILES} -gt ${MODIFICATION_TIME} ]
then
# Force translation string collection as a .py file has been updated
UPDATE=true
break
fi
done
if [ ${UPDATE} == true ]
# retrieve all python files
then
echo ${PYTHON_FILES}
# update .ts
echo "Please provide translations by editing the translation files below:"
for LOCALE in ${LOCALES}
do
echo "i18n/"${LOCALE}".ts"
# Note we don't use pylupdate with qt .pro file approach as it is flakey
# about what is made available.
pylupdate4 -noobsolete ${PYTHON_FILES} -ts i18n/${LOCALE}.ts
done
else
echo "No need to edit any translation files (.ts) because no python files"
echo "has been updated since the last update translation. "
fi
+2
View File
@@ -0,0 +1,2 @@
# import qgis libs so that ve set the correct sip api version
import qgis # pylint: disable=W0611 # NOQA
+205
View File
@@ -0,0 +1,205 @@
# coding=utf-8
"""QGIS plugin implementation.
.. note:: 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.
.. note:: This source code was copied from the 'postgis viewer' application
with original authors:
Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk
Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org
Copyright (c) 2014 Tim Sutton, tim@linfiniti.com
"""
__author__ = 'tim@linfiniti.com'
__revision__ = '$Format:%H$'
__date__ = '10/01/2011'
__copyright__ = (
'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and '
'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org'
'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com'
)
import logging
from qgis.PyQt.QtCore import QObject, pyqtSlot, pyqtSignal
from qgis.core import QgsMapLayerRegistry
from qgis.gui import QgsMapCanvasLayer
LOGGER = logging.getLogger('QGIS')
#noinspection PyMethodMayBeStatic,PyPep8Naming
class QgisInterface(QObject):
"""Class to expose QGIS objects and functions to plugins.
This class is here for enabling us to run unit tests only,
so most methods are simply stubs.
"""
currentLayerChanged = pyqtSignal(QgsMapCanvasLayer)
def __init__(self, canvas):
"""Constructor
:param canvas:
"""
QObject.__init__(self)
self.canvas = canvas
# Set up slots so we can mimic the behaviour of QGIS when layers
# are added.
LOGGER.debug('Initialising canvas...')
# noinspection PyArgumentList
QgsMapLayerRegistry.instance().layersAdded.connect(self.addLayers)
# noinspection PyArgumentList
QgsMapLayerRegistry.instance().layerWasAdded.connect(self.addLayer)
# noinspection PyArgumentList
QgsMapLayerRegistry.instance().removeAll.connect(self.removeAllLayers)
# For processing module
self.destCrs = None
@pyqtSlot('QStringList')
def addLayers(self, layers):
"""Handle layers being added to the registry so they show up in canvas.
:param layers: list<QgsMapLayer> list of map layers that were added
.. note:: The QgsInterface api does not include this method,
it is added here as a helper to facilitate testing.
"""
#LOGGER.debug('addLayers called on qgis_interface')
#LOGGER.debug('Number of layers being added: %s' % len(layers))
#LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers()))
current_layers = self.canvas.layers()
final_layers = []
for layer in current_layers:
final_layers.append(QgsMapCanvasLayer(layer))
for layer in layers:
final_layers.append(QgsMapCanvasLayer(layer))
self.canvas.setLayerSet(final_layers)
#LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers()))
@pyqtSlot('QgsMapLayer')
def addLayer(self, layer):
"""Handle a layer being added to the registry so it shows up in canvas.
:param layer: list<QgsMapLayer> list of map layers that were added
.. note: The QgsInterface api does not include this method, it is added
here as a helper to facilitate testing.
.. note: The addLayer method was deprecated in QGIS 1.8 so you should
not need this method much.
"""
pass
@pyqtSlot()
def removeAllLayers(self):
"""Remove layers from the canvas before they get deleted."""
self.canvas.setLayerSet([])
def newProject(self):
"""Create new project."""
# noinspection PyArgumentList
QgsMapLayerRegistry.instance().removeAllMapLayers()
# ---------------- API Mock for QgsInterface follows -------------------
def zoomFull(self):
"""Zoom to the map full extent."""
pass
def zoomToPrevious(self):
"""Zoom to previous view extent."""
pass
def zoomToNext(self):
"""Zoom to next view extent."""
pass
def zoomToActiveLayer(self):
"""Zoom to extent of active layer."""
pass
def addVectorLayer(self, path, base_name, provider_key):
"""Add a vector layer.
:param path: Path to layer.
:type path: str
:param base_name: Base name for layer.
:type base_name: str
:param provider_key: Provider key e.g. 'ogr'
:type provider_key: str
"""
pass
def addRasterLayer(self, path, base_name):
"""Add a raster layer given a raster layer file name
:param path: Path to layer.
:type path: str
:param base_name: Base name for layer.
:type base_name: str
"""
pass
def activeLayer(self):
"""Get pointer to the active layer (layer selected in the legend)."""
# noinspection PyArgumentList
layers = QgsMapLayerRegistry.instance().mapLayers()
for item in layers:
return layers[item]
def addToolBarIcon(self, action):
"""Add an icon to the plugins toolbar.
:param action: Action to add to the toolbar.
:type action: QAction
"""
pass
def removeToolBarIcon(self, action):
"""Remove an action (icon) from the plugin toolbar.
:param action: Action to add to the toolbar.
:type action: QAction
"""
pass
def addToolBar(self, name):
"""Add toolbar with specified name.
:param name: Name for the toolbar.
:type name: str
"""
pass
def mapCanvas(self):
"""Return a pointer to the map canvas."""
return self.canvas
def mainWindow(self):
"""Return a pointer to the main window.
In case of QGIS it returns an instance of QgisApp.
"""
pass
def addDockWidget(self, area, dock_widget):
"""Add a dock widget to the main window.
:param area: Where in the ui the dock should be placed.
:type area:
:param dock_widget: A dock widget to add to the UI.
:type dock_widget: QDockWidget
"""
pass
def legendInterface(self):
"""Get the legend."""
return self.canvas
+19
View File
@@ -0,0 +1,19 @@
NCOLS 10
NROWS 10
XLLCENTER 1535380.000000
YLLCENTER 5083260.000000
DX 10
DY 10
NODATA_VALUE -9999
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
CRS
NOTES
+13
View File
@@ -0,0 +1,13 @@
<PAMDataset>
<Metadata>
<MDI key="AREA_OR_POINT">Point</MDI>
</Metadata>
<PAMRasterBand band="1">
<Metadata>
<MDI key="STATISTICS_MAXIMUM">9</MDI>
<MDI key="STATISTICS_MEAN">4.5</MDI>
<MDI key="STATISTICS_MINIMUM">0</MDI>
<MDI key="STATISTICS_STDDEV">2.872281323269</MDI>
</Metadata>
</PAMRasterBand>
</PAMDataset>
+1
View File
@@ -0,0 +1 @@
title: Tenbytenraster
+18
View File
@@ -0,0 +1,18 @@
<?xml version='1.0' encoding='iso-8859-1'?>
<ga_license_file>
<metadata>
<author>Tim Sutton, Linfiniti Consulting CC</author>
</metadata>
<datafile>
<filename>tenbytenraster.asc</filename>
<checksum>2700044251</checksum>
<publishable>Yes</publishable>
<accountable>Tim Sutton</accountable>
<source>Tim Sutton (QGIS Source Tree)</source>
<IP_owner>Tim Sutton</IP_owner>
<IP_info>This data is publicly available from QGIS Source Tree. The original
file was created and contributed to QGIS by Tim Sutton.</IP_info>
</datafile>
</ga_license_file>
+1
View File
@@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
+26
View File
@@ -0,0 +1,26 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="1.9.0-Master" minimumScale="-4.65661e-10" maximumScale="1e+08" hasScaleBasedVisibilityFlag="0">
<pipe>
<rasterrenderer opacity="1" alphaBand="0" classificationMax="9" classificationMinMaxOrigin="MinMaxFullExtentExact" band="1" classificationMin="0" type="singlebandpseudocolor">
<rasterTransparency/>
<rastershader>
<colorrampshader colorRampType="INTERPOLATED" clip="0">
<item alpha="255" value="0" label="0.000000" color="#d7191c"/>
<item alpha="255" value="1" label="1.000000" color="#e75b3a"/>
<item alpha="255" value="2" label="2.000000" color="#f89d59"/>
<item alpha="255" value="3" label="3.000000" color="#fdc980"/>
<item alpha="255" value="4" label="4.000000" color="#feedaa"/>
<item alpha="255" value="5" label="5.000000" color="#ecf7b9"/>
<item alpha="255" value="6" label="6.000000" color="#c7e8ad"/>
<item alpha="255" value="7" label="7.000000" color="#9cd3a6"/>
<item alpha="255" value="8" label="8.000000" color="#63abb0"/>
<item alpha="255" value="9" label="9.000000" color="#2b83ba"/>
</colorrampshader>
</rastershader>
</rasterrenderer>
<brightnesscontrast brightness="0" contrast="0"/>
<huesaturation colorizeGreen="128" colorizeOn="0" colorizeRed="255" colorizeBlue="128" grayscaleMode="0" saturation="0" colorizeStrength="100"/>
<rasterresampler maxOversampling="2"/>
</pipe>
<blendMode>0</blendMode>
</qgis>
+55
View File
@@ -0,0 +1,55 @@
# coding=utf-8
"""Dialog test.
.. note:: 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.
"""
__author__ = 'spacil@arub.cz'
__date__ = '2026-02-03'
__copyright__ = 'Copyright 2026, David Spáčil'
import unittest
from qgis.PyQt.QtGui import QDialogButtonBox, QDialog
from amcr_viewer_dialog import AmcrViewerDialog
from utilities import get_qgis_app
QGIS_APP = get_qgis_app()
class AmcrViewerDialogTest(unittest.TestCase):
"""Test dialog works."""
def setUp(self):
"""Runs before each test."""
self.dialog = AmcrViewerDialog(None)
def tearDown(self):
"""Runs after each test."""
self.dialog = None
def test_dialog_ok(self):
"""Test we can click OK."""
button = self.dialog.button_box.button(QDialogButtonBox.Ok)
button.click()
result = self.dialog.result()
self.assertEqual(result, QDialog.Accepted)
def test_dialog_cancel(self):
"""Test we can click cancel."""
button = self.dialog.button_box.button(QDialogButtonBox.Cancel)
button.click()
result = self.dialog.result()
self.assertEqual(result, QDialog.Rejected)
if __name__ == "__main__":
suite = unittest.makeSuite(AmcrViewerDialogTest)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
+64
View File
@@ -0,0 +1,64 @@
# coding=utf-8
"""Tests QGIS plugin init."""
__author__ = 'Tim Sutton <tim@linfiniti.com>'
__revision__ = '$Format:%H$'
__date__ = '17/10/2010'
__license__ = "GPL"
__copyright__ = 'Copyright 2012, Australia Indonesia Facility for '
__copyright__ += 'Disaster Reduction'
import os
import unittest
import logging
import configparser
LOGGER = logging.getLogger('QGIS')
class TestInit(unittest.TestCase):
"""Test that the plugin init is usable for QGIS.
Based heavily on the validator class by Alessandro
Passoti available here:
http://github.com/qgis/qgis-django/blob/master/qgis-app/
plugins/validator.py
"""
def test_read_init(self):
"""Test that the plugin __init__ will validate on plugins.qgis.org."""
# You should update this list according to the latest in
# https://github.com/qgis/qgis-django/blob/master/qgis-app/
# plugins/validator.py
required_metadata = [
'name',
'description',
'version',
'qgisMinimumVersion',
'email',
'author']
file_path = os.path.abspath(os.path.join(
os.path.dirname(__file__), os.pardir,
'metadata.txt'))
LOGGER.info(file_path)
metadata = []
parser = configparser.ConfigParser()
parser.optionxform = str
parser.read(file_path)
message = 'Cannot find a section named "general" in %s' % file_path
assert parser.has_section('general'), message
metadata.extend(parser.items('general'))
for expectation in required_metadata:
message = ('Cannot find metadata "%s" in metadata source (%s).' % (
expectation, file_path))
self.assertIn(expectation, dict(metadata), message)
if __name__ == '__main__':
unittest.main()
+60
View File
@@ -0,0 +1,60 @@
# coding=utf-8
"""Tests for QGIS functionality.
.. note:: 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.
"""
__author__ = 'tim@linfiniti.com'
__date__ = '20/01/2011'
__copyright__ = ('Copyright 2012, Australia Indonesia Facility for '
'Disaster Reduction')
import os
import unittest
from qgis.core import (
QgsProviderRegistry,
QgsCoordinateReferenceSystem,
QgsRasterLayer)
from .utilities import get_qgis_app
QGIS_APP = get_qgis_app()
class QGISTest(unittest.TestCase):
"""Test the QGIS Environment"""
def test_qgis_environment(self):
"""QGIS environment has the expected providers"""
r = QgsProviderRegistry.instance()
self.assertIn('gdal', r.providerList())
self.assertIn('ogr', r.providerList())
self.assertIn('postgres', r.providerList())
def test_projection(self):
"""Test that QGIS properly parses a wkt string.
"""
crs = QgsCoordinateReferenceSystem()
wkt = (
'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",'
'SPHEROID["WGS_1984",6378137.0,298.257223563]],'
'PRIMEM["Greenwich",0.0],UNIT["Degree",'
'0.0174532925199433]]')
crs.createFromWkt(wkt)
auth_id = crs.authid()
expected_auth_id = 'EPSG:4326'
self.assertEqual(auth_id, expected_auth_id)
# now test for a loaded layer
path = os.path.join(os.path.dirname(__file__), 'tenbytenraster.asc')
title = 'TestRaster'
layer = QgsRasterLayer(path, title)
auth_id = layer.crs().authid()
self.assertEqual(auth_id, expected_auth_id)
if __name__ == '__main__':
unittest.main()
+44
View File
@@ -0,0 +1,44 @@
# coding=utf-8
"""Resources test.
.. note:: 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.
"""
__author__ = 'spacil@arub.cz'
__date__ = '2026-02-03'
__copyright__ = 'Copyright 2026, David Spáčil'
import unittest
from qgis.PyQt.QtGui import QIcon
class AmcrViewerDialogTest(unittest.TestCase):
"""Test rerources work."""
def setUp(self):
"""Runs before each test."""
pass
def tearDown(self):
"""Runs after each test."""
pass
def test_icon_png(self):
"""Test we can click OK."""
path = ':/plugins/AmcrViewer/icon.png'
icon = QIcon(path)
self.assertFalse(icon.isNull())
if __name__ == "__main__":
suite = unittest.makeSuite(AmcrViewerResourcesTest)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
+55
View File
@@ -0,0 +1,55 @@
# coding=utf-8
"""Safe Translations Test.
.. note:: 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.
"""
from .utilities import get_qgis_app
__author__ = 'ismailsunni@yahoo.co.id'
__date__ = '12/10/2011'
__copyright__ = ('Copyright 2012, Australia Indonesia Facility for '
'Disaster Reduction')
import unittest
import os
from qgis.PyQt.QtCore import QCoreApplication, QTranslator
QGIS_APP = get_qgis_app()
class SafeTranslationsTest(unittest.TestCase):
"""Test translations work."""
def setUp(self):
"""Runs before each test."""
if 'LANG' in iter(os.environ.keys()):
os.environ.__delitem__('LANG')
def tearDown(self):
"""Runs after each test."""
if 'LANG' in iter(os.environ.keys()):
os.environ.__delitem__('LANG')
def test_qgis_translations(self):
"""Test that translations work."""
parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir)
dir_path = os.path.abspath(parent_path)
file_path = os.path.join(
dir_path, 'i18n', 'af.qm')
translator = QTranslator()
translator.load(file_path)
QCoreApplication.installTranslator(translator)
expected_message = 'Goeie more'
real_message = QCoreApplication.translate("@default", 'Good morning')
self.assertEqual(real_message, expected_message)
if __name__ == "__main__":
suite = unittest.makeSuite(SafeTranslationsTest)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
+61
View File
@@ -0,0 +1,61 @@
# coding=utf-8
"""Common functionality used by regression tests."""
import sys
import logging
LOGGER = logging.getLogger('QGIS')
QGIS_APP = None # Static variable used to hold hand to running QGIS app
CANVAS = None
PARENT = None
IFACE = None
def get_qgis_app():
""" Start one QGIS application to test against.
:returns: Handle to QGIS app, canvas, iface and parent. If there are any
errors the tuple members will be returned as None.
:rtype: (QgsApplication, CANVAS, IFACE, PARENT)
If QGIS is already running the handle to that app will be returned.
"""
try:
from qgis.PyQt import QtGui, QtCore
from qgis.core import QgsApplication
from qgis.gui import QgsMapCanvas
from .qgis_interface import QgisInterface
except ImportError:
return None, None, None, None
global QGIS_APP # pylint: disable=W0603
if QGIS_APP is None:
gui_flag = True # All test will run qgis in gui mode
#noinspection PyPep8Naming
QGIS_APP = QgsApplication(sys.argv, gui_flag)
# Make sure QGIS_PREFIX_PATH is set in your env if needed!
QGIS_APP.initQgis()
s = QGIS_APP.showSettings()
LOGGER.debug(s)
global PARENT # pylint: disable=W0603
if PARENT is None:
#noinspection PyPep8Naming
PARENT = QtGui.QWidget()
global CANVAS # pylint: disable=W0603
if CANVAS is None:
#noinspection PyPep8Naming
CANVAS = QgsMapCanvas(PARENT)
CANVAS.resize(QtCore.QSize(400, 400))
global IFACE # pylint: disable=W0603
if IFACE is None:
# QgisInterface is a stub implementation of the QGIS plugin interface
#noinspection PyPep8Naming
IFACE = QgisInterface(CANVAS)
return QGIS_APP, CANVAS, IFACE, PARENT