mirror of
https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer.git
synced 2026-06-17 11:22:53 +02:00
Init commit
This commit is contained in:
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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)
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 993 B |
+11
@@ -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>
|
||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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
@@ -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()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/plugins/amcr_viewer" >
|
||||||
|
<file>icon.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# import qgis libs so that ve set the correct sip api version
|
||||||
|
import qgis # pylint: disable=W0611 # NOQA
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
title: Tenbytenraster
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
|
||||||
@@ -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>
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user