mirror of
https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer.git
synced 2026-06-19 04:12:55 +02:00
Chore/code cleanup (#43)
* čištění kódu podle flake8 * Update .gitignore * Update .gitignore (#41) * oprava komentářů a překlad do angličtiny * oprava přihlašování
This commit is contained in:
+351
-154
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
|
||||
QgsField, QgsCoordinateReferenceSystem, QgsCoordinateTransform,
|
||||
QgsWkbTypes, Qgis, QgsApplication, QgsAuthMethodConfig, QgsMessageLog)
|
||||
from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
|
||||
QgsField, QgsCoordinateReferenceSystem,
|
||||
QgsCoordinateTransform, QgsWkbTypes, Qgis,
|
||||
QgsMessageLog)
|
||||
from qgis.utils import iface
|
||||
from qgis.PyQt.QtCore import Qt, QMetaType
|
||||
from qgis.PyQt.QtWidgets import QApplication
|
||||
@@ -12,25 +13,33 @@ import json
|
||||
# Global cache to store translated terms from the Digital Archive
|
||||
TRANSLATIONS = {}
|
||||
|
||||
# Session s autentizační cookie po přihlášení; None = nepřihlášen (anonymní přístup)
|
||||
# Session with authentication cookie after login;
|
||||
# None = not logged in (anonymous access)
|
||||
AMCR_SESSION: requests.Session | None = None
|
||||
|
||||
|
||||
def _log(msg: str, level=Qgis.MessageLevel.Info):
|
||||
"""Shortcut: zapíše zprávu do QGIS logu (panel Zprávy → záložka AMČR)."""
|
||||
"""
|
||||
Shortcut: writes a message to the QGIS log
|
||||
(Messages panel → AMČR tab).
|
||||
"""
|
||||
QgsMessageLog.logMessage(msg, "AMČR login", level)
|
||||
|
||||
|
||||
def login_to_api(username: str, password: str):
|
||||
"""
|
||||
Přihlásí se do Digiarchiv API pomocí username a hesla.
|
||||
Vrátí requests.Session s nastavenou session cookie, nebo None při chybě.
|
||||
Logs in to the Digiarchiv API using a username and password.
|
||||
Returns a requests.Session with the session cookie set, or None on error.
|
||||
"""
|
||||
login_url = "https://digiarchiv.aiscr.cz/api/user/login"
|
||||
|
||||
_log(f"Přihlašuji uživatele: '{username}'")
|
||||
|
||||
if not username or not password:
|
||||
_log("CHYBA: username nebo heslo je prázdné.", Qgis.MessageLevel.Critical)
|
||||
_log(
|
||||
"CHYBA: username nebo heslo je prázdné.",
|
||||
Qgis.MessageLevel.Critical
|
||||
)
|
||||
return None
|
||||
|
||||
session = requests.Session()
|
||||
@@ -42,14 +51,22 @@ def login_to_api(username: str, password: str):
|
||||
|
||||
try:
|
||||
_log(f"Odesílám POST na {login_url} ...")
|
||||
response = session.post(login_url, json={"user": username, "pwd": password}, timeout=10)
|
||||
response = session.post(
|
||||
login_url,
|
||||
json={"user": username, "pwd": password},
|
||||
timeout=10
|
||||
)
|
||||
_log(f"HTTP status: {response.status_code}")
|
||||
response.raise_for_status()
|
||||
|
||||
# API vrací chyby se status kódem 200 – je nutné zkontrolovat tělo odpovědi
|
||||
# The API returns errors with status code 200 –
|
||||
# the response body must be checked
|
||||
body = response.json()
|
||||
if "error" in body:
|
||||
_log(f"CHYBA přihlášení (API): {body['error']}", Qgis.MessageLevel.Critical)
|
||||
_log(
|
||||
f"CHYBA přihlášení (API): {body['error']}",
|
||||
Qgis.MessageLevel.Critical
|
||||
)
|
||||
return None
|
||||
|
||||
_log("Přihlášení proběhlo úspěšně.")
|
||||
@@ -59,23 +76,25 @@ def login_to_api(username: str, password: str):
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
_log(f"CHYBA HTTP {e.response.status_code if e.response else '?'}: "
|
||||
f"{e.response.text[:300] if e.response else 'žádná odpověď'}", Qgis.MessageLevel.Critical)
|
||||
f"{e.response.text[:300] if e.response else 'žádná odpověď'}",
|
||||
Qgis.MessageLevel.Critical)
|
||||
return None
|
||||
except requests.exceptions.RequestException as e:
|
||||
_log(f"CHYBA sítě: {e}", Qgis.MessageLevel.Critical)
|
||||
return None
|
||||
|
||||
|
||||
def _get_session() -> requests.Session | None:
|
||||
"""
|
||||
Vrátí aktivní session. Pokud žádná není (restart QGIS), pokusí se
|
||||
automaticky přihlásit pomocí uložených přihlašovacích údajů.
|
||||
Vrátí None pokud přihlašovací údaje nejsou uloženy.
|
||||
Returns the active session. If none exists (e.g. after a QGIS restart),
|
||||
attempts automatic login using stored credentials.
|
||||
Returns None if no credentials are stored.
|
||||
"""
|
||||
global AMCR_SESSION
|
||||
if AMCR_SESSION is not None:
|
||||
return AMCR_SESSION
|
||||
|
||||
# Zkusit auto-login pomocí uložených údajů
|
||||
# Attempt auto-login using stored credentials
|
||||
from .amcr_dialog import LoginDialog
|
||||
username, password = LoginDialog.get_credentials()
|
||||
if username and password:
|
||||
@@ -87,19 +106,24 @@ def _get_session() -> requests.Session | None:
|
||||
|
||||
def _api_get(url, params, timeout=30) -> requests.Response:
|
||||
"""
|
||||
Provede GET request. Pokud API signalizuje vypršení přihlášení,
|
||||
provede jedno opakované přihlášení a zkusí znovu.
|
||||
Performs a GET request. If the API signals an expired login,
|
||||
re-authenticates once and retries.
|
||||
"""
|
||||
global AMCR_SESSION
|
||||
|
||||
def _is_auth_error(resp: requests.Response) -> bool:
|
||||
"""API vrací auth chyby se status 200 – je nutné zkontrolovat tělo."""
|
||||
"""The API returns auth errors with status 200 –
|
||||
the body must be checked."""
|
||||
if resp.status_code == 401:
|
||||
return True
|
||||
try:
|
||||
body = resp.json()
|
||||
err = str(body.get("error", "")).lower()
|
||||
return "unauthorized" in err or "not logged" in err or "session" in err
|
||||
return (
|
||||
"unauthorized" in err
|
||||
or "not logged" in err
|
||||
or "session" in err
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -107,8 +131,9 @@ def _api_get(url, params, timeout=30) -> requests.Response:
|
||||
resp = (session or requests).get(url, params=params, timeout=timeout)
|
||||
|
||||
if _is_auth_error(resp):
|
||||
_log("Session vypršela během stahování – obnovuji přihlášení...", Qgis.MessageLevel.Warning)
|
||||
AMCR_SESSION = None # Zrušit starou session
|
||||
_log("Session vypršela během stahování – obnovuji přihlášení...",
|
||||
Qgis.MessageLevel.Warning)
|
||||
AMCR_SESSION = None # Invalidate the old session
|
||||
from .amcr_dialog import LoginDialog
|
||||
username, password = LoginDialog.get_credentials()
|
||||
if username and password:
|
||||
@@ -116,18 +141,24 @@ def _api_get(url, params, timeout=30) -> requests.Response:
|
||||
if AMCR_SESSION:
|
||||
resp = AMCR_SESSION.get(url, params=params, timeout=timeout)
|
||||
else:
|
||||
_log("Opakované přihlášení selhalo.", Qgis.MessageLevel.Critical)
|
||||
_log("Opakované přihlášení selhalo.",
|
||||
Qgis.MessageLevel.Critical)
|
||||
else:
|
||||
_log("Přihlašovací údaje nejsou uloženy – pokračuji anonymně.", Qgis.MessageLevel.Warning)
|
||||
_log("Přihlašovací údaje nejsou uloženy – pokračuji anonymně.",
|
||||
Qgis.MessageLevel.Warning)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def load_translations():
|
||||
"""Fetches the official Czech translation dictionary from the AISCR API."""
|
||||
"""
|
||||
Fetches the official Czech translation dictionary
|
||||
from the Digiarchive API.
|
||||
"""
|
||||
global TRANSLATIONS
|
||||
if TRANSLATIONS:
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
url = "https://digiarchiv.aiscr.cz/api/assets/i18n/cs.json"
|
||||
try:
|
||||
r = requests.get(url, timeout=10)
|
||||
@@ -136,20 +167,31 @@ def load_translations():
|
||||
except Exception as e:
|
||||
print(f"Error downloading vocabulary: {e}")
|
||||
|
||||
|
||||
def tr_code(code):
|
||||
"""Translates a technical code into a human-readable string using the global cache."""
|
||||
if not code:
|
||||
"""
|
||||
Translates a technical code into a human-readable string
|
||||
using the global cache.
|
||||
"""
|
||||
if not code:
|
||||
return ""
|
||||
return TRANSLATIONS.get(code, code)
|
||||
|
||||
|
||||
def komp_projde_filtrem(komp, filter_areal, filter_datace, filters):
|
||||
if filter_areal and komp.get('komponenta_areal', {}).get('id', "") not in filters.get('f_areal', []):
|
||||
areal_id = komp.get('komponenta_areal', {}).get('id', "")
|
||||
if filter_areal and areal_id not in filters.get('f_areal', []):
|
||||
return False
|
||||
if filter_datace and komp.get('komponenta_obdobi', {}).get('id', "") not in filters.get('f_obdobi', []):
|
||||
|
||||
obdobi_id = komp.get('komponenta_obdobi', {}).get('id', "")
|
||||
if filter_datace and obdobi_id not in filters.get('f_obdobi', []):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false"):
|
||||
|
||||
def load_amcr_data(canvas, bb, filters=None,
|
||||
typ_dat="akce", komponenty="false"):
|
||||
"""
|
||||
Main processing function:
|
||||
1. Determines search area (Bounding Box)
|
||||
@@ -159,26 +201,35 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
load_translations()
|
||||
|
||||
# --- 1. COORDINATE TRANSFORMATION ---
|
||||
# Get current map extent and transform it from project CRS (usually S-JTSK) to WGS-84 for the API
|
||||
# Get current map extent and transform it
|
||||
# from project CRS (usually S-JTSK) to WGS-84 for the API
|
||||
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)
|
||||
|
||||
# Format the bounding box string as required by the API: minLat,minLon,maxLat,maxLon
|
||||
bbox_str = f"{extent_wgs.yMinimum()},{extent_wgs.xMinimum()},{extent_wgs.yMaximum()},{extent_wgs.xMaximum()}"
|
||||
|
||||
|
||||
# Format the bounding box string as required by the API:
|
||||
# minLat,minLon,maxLat,maxLon
|
||||
bbox_str = (
|
||||
f"{extent_wgs.yMinimum()},{extent_wgs.xMinimum()},"
|
||||
f"{extent_wgs.yMaximum()},{extent_wgs.xMaximum()}"
|
||||
)
|
||||
|
||||
url = "https://digiarchiv.aiscr.cz/api/search/query"
|
||||
|
||||
iface.messageBar().pushMessage("AMCR", "Hledám záznamy...", level=Qgis.MessageLevel.Info)
|
||||
|
||||
iface.messageBar().pushMessage(
|
||||
"AMCR",
|
||||
"Hledám záznamy...",
|
||||
level=Qgis.MessageLevel.Info
|
||||
)
|
||||
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
||||
|
||||
|
||||
try:
|
||||
# ==========================================
|
||||
# A) METADATA FETCHING (Fieldwork/Site)
|
||||
# ==========================================
|
||||
|
||||
|
||||
base_params = {
|
||||
"mapa": "true",
|
||||
"sort": "ident_cely asc",
|
||||
@@ -186,10 +237,11 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
}
|
||||
|
||||
# Restrict search to map window if requested
|
||||
if bb == "true":
|
||||
if bb == "true":
|
||||
base_params["loc_rpt"] = bbox_str
|
||||
|
||||
# Apply multi-select filters from the dialog using the ':or' syntax required by the API
|
||||
# Apply multi-select filters from the dialog using
|
||||
# the ':or' syntax required by the API
|
||||
if filters:
|
||||
for key, value in filters.items():
|
||||
if not value:
|
||||
@@ -200,21 +252,24 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
base_params[key] = str(value).strip()
|
||||
|
||||
docs = []
|
||||
current_page = 0
|
||||
current_page = 0
|
||||
BATCH_DOCS = 500 # Records per API request
|
||||
MAX_LIMIT = 20000 # Safety limit to prevent QGIS from freezing
|
||||
|
||||
|
||||
seen_ids = set()
|
||||
target_pian_ids_count = 0
|
||||
|
||||
# Check if we should skip negative results based on filter
|
||||
skip_negativni = filters.get('posevidence') == 'true' if filters else False
|
||||
skip_negativni = (
|
||||
filters.get('posevidence') == 'true'
|
||||
if filters
|
||||
else False
|
||||
)
|
||||
|
||||
# Check whether we should filter results based on component filters
|
||||
filter_areal = "f_areal" in filters if filters else False
|
||||
filter_datace = "f_obdobi" in filters if filters else False
|
||||
|
||||
|
||||
# --- API PAGINATION LOOP ---
|
||||
while True:
|
||||
base_params['rows'] = BATCH_DOCS
|
||||
@@ -222,17 +277,17 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
base_params['page'] = current_page
|
||||
elif 'page' in base_params:
|
||||
del base_params['page']
|
||||
|
||||
|
||||
try:
|
||||
resp_docs = _api_get(url, params=base_params, timeout=30)
|
||||
resp_json = resp_docs.json()
|
||||
data = resp_json.get('response', {})
|
||||
batch_docs = data.get('docs', [])
|
||||
num_found = data.get('numFound', 0)
|
||||
|
||||
num_found = data.get('numFound', 0)
|
||||
|
||||
if not batch_docs:
|
||||
break
|
||||
|
||||
|
||||
# Filter out duplicates and append to main list
|
||||
new_docs = []
|
||||
for d in batch_docs:
|
||||
@@ -240,44 +295,56 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
if ident and ident not in seen_ids:
|
||||
seen_ids.add(ident)
|
||||
new_docs.append(d)
|
||||
|
||||
|
||||
docs.extend(new_docs)
|
||||
print(f"Strana {current_page} stažena. Celkem záznamů: {len(docs)} / {num_found}")
|
||||
print(
|
||||
f"Strana {current_page} stažena. "
|
||||
f"Celkem záznamů: {len(docs)} / {num_found}"
|
||||
)
|
||||
|
||||
if len(docs) >= num_found:
|
||||
break
|
||||
if len(docs) >= MAX_LIMIT:
|
||||
iface.messageBar().pushMessage("AMCR", f"Limit {MAX_LIMIT} záznamů dosažen.", level=Qgis.MessageLevel.Warning)
|
||||
iface.messageBar().pushMessage(
|
||||
"AMCR",
|
||||
f"Limit {MAX_LIMIT} záznamů dosažen.",
|
||||
level=Qgis.MessageLevel.Warning
|
||||
)
|
||||
break
|
||||
|
||||
|
||||
current_page += 1
|
||||
QApplication.processEvents() # Keep UI responsive
|
||||
|
||||
QApplication.processEvents() # Keep UI responsive
|
||||
|
||||
except Exception as e:
|
||||
print(f"Chyba při stránkování na straně {current_page}: {e}")
|
||||
break
|
||||
|
||||
if not docs:
|
||||
iface.messageBar().pushMessage("AMCR", "Žádné záznamy nenalezeny.", level=Qgis.MessageLevel.Warning)
|
||||
return
|
||||
iface.messageBar().pushMessage(
|
||||
"AMCR",
|
||||
"Žádné záznamy nenalezeny.",
|
||||
level=Qgis.MessageLevel.Warning
|
||||
)
|
||||
return
|
||||
|
||||
# ==========================================
|
||||
# B) ATTRIBUTE PARSING
|
||||
# ==========================================
|
||||
|
||||
# pian_lookup maps a Geometry ID (PIAN) to a list of its associated metadata
|
||||
|
||||
# pian_lookup maps a Geometry ID (PIAN)
|
||||
# to a list of its associated metadata
|
||||
pian_lookup = {}
|
||||
target_pian_ids = set()
|
||||
actions_with_geom = 0
|
||||
|
||||
# Helper function for safe single-value extraction
|
||||
def g(doc, key, default=""):
|
||||
|
||||
# Helper: safely extract a single value
|
||||
def g(doc, key, default=""):
|
||||
val = doc.get(key)
|
||||
if isinstance(val, list):
|
||||
return str(val[0]) if val else default
|
||||
return str(val) if val is not None else default
|
||||
|
||||
# Helper function for safe list-value extraction and joining
|
||||
# Helper: safely extract and join a list of values
|
||||
def g_list(doc, key, translate=False):
|
||||
val = doc.get(key, [])
|
||||
if not isinstance(val, list):
|
||||
@@ -293,16 +360,23 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
continue
|
||||
|
||||
actions_with_geom += 1
|
||||
|
||||
# Extract protected data (fields not available in public Solr index)
|
||||
|
||||
# Extract protected fields
|
||||
az_chranene = doc.get('az_chranene_udaje', {})
|
||||
chranene = doc.get('akce_chranene_udaje') or doc.get('lokalita_chranene_udaje') or {}
|
||||
|
||||
# Format additional cadastral areas from dictionaries
|
||||
chranene = (
|
||||
doc.get('akce_chranene_udaje')
|
||||
or doc.get('lokalita_chranene_udaje')
|
||||
or {}
|
||||
)
|
||||
|
||||
# Format additional cadastral areas from nested dicts
|
||||
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]
|
||||
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 = chranene.get('lokalizace_okolnosti', "")
|
||||
@@ -314,7 +388,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
"ident_cely": doc.get('ident_cely', ''),
|
||||
"az_okres": g(doc, 'az_okres'),
|
||||
"katastr": g_list(doc, 'katastr'),
|
||||
"dalsi_katastr": dalsi_kat_str,
|
||||
"dalsi_katastr": dalsi_kat_str,
|
||||
"pristupnost": g(doc, 'pristupnost'),
|
||||
"loc": g_list(doc, 'loc')
|
||||
}
|
||||
@@ -322,53 +396,102 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
# Add entity-specific metadata
|
||||
if typ_dat == "akce":
|
||||
meta.update({
|
||||
"akce_hlavni_vedouci": g(doc, 'akce_hlavni_vedouci'),
|
||||
"akce_organizace": tr_code(g(doc, 'akce_organizace')),
|
||||
"akce_specifikace_data": tr_code(g(doc, 'akce_specifikace_data')),
|
||||
"akce_datum_zahajeni": g(doc, 'akce_datum_zahajeni'),
|
||||
"akce_datum_ukonceni": g(doc, 'akce_datum_ukonceni'),
|
||||
"akce_hlavni_typ": tr_code(g(doc, 'akce_hlavni_typ')),
|
||||
"akce_vedlejsi_typ": g_list(doc, 'akce_vedlejsi_typ', translate=True),
|
||||
"lokalizace_okolnosti": str(lokalizace) if lokalizace else "",
|
||||
"akce_je_nz": "Ano" if doc.get('akce_je_nz') is True else "Ne",
|
||||
"akce_hlavni_vedouci": g(
|
||||
doc,
|
||||
'akce_hlavni_vedouci'
|
||||
),
|
||||
"akce_organizace": tr_code(g(
|
||||
doc,
|
||||
'akce_organizace'
|
||||
)),
|
||||
"akce_specifikace_data": tr_code(g(
|
||||
doc,
|
||||
'akce_specifikace_data'
|
||||
)),
|
||||
"akce_datum_zahajeni": g(
|
||||
doc,
|
||||
'akce_datum_zahajeni'
|
||||
),
|
||||
"akce_datum_ukonceni": g(
|
||||
doc,
|
||||
'akce_datum_ukonceni'
|
||||
),
|
||||
"akce_hlavni_typ": tr_code(g(
|
||||
doc,
|
||||
'akce_hlavni_typ'
|
||||
)),
|
||||
"akce_vedlejsi_typ": g_list(
|
||||
doc,
|
||||
'akce_vedlejsi_typ',
|
||||
translate=True
|
||||
),
|
||||
"lokalizace_okolnosti": (
|
||||
str(lokalizace)
|
||||
if lokalizace
|
||||
else ""
|
||||
),
|
||||
"akce_je_nz": (
|
||||
"Ano"
|
||||
if doc.get('akce_je_nz') is True
|
||||
else "Ne"
|
||||
),
|
||||
})
|
||||
|
||||
elif typ_dat == "lokalita":
|
||||
meta.update({
|
||||
"lokalita_nazev": lokalita_nazev,
|
||||
"lokalita_popis": lokalita_popis,
|
||||
"lokalita_zachovalost": tr_code(g(doc, 'lokalita_zachovalost')),
|
||||
"lokalita_druh": tr_code(g(doc, 'lokalita_druh')),
|
||||
"lokalita_typ": tr_code(g(doc, 'lokalita_typ_lokality')),
|
||||
"lokalita_zachovalost": tr_code(g(
|
||||
doc,
|
||||
'lokalita_zachovalost'
|
||||
)),
|
||||
"lokalita_druh": tr_code(g(
|
||||
doc,
|
||||
'lokalita_druh'
|
||||
)),
|
||||
"lokalita_typ": tr_code(g(
|
||||
doc,
|
||||
'lokalita_typ_lokality'
|
||||
)),
|
||||
})
|
||||
|
||||
|
||||
# Documentation units (DJ) within the record
|
||||
djs = doc.get('az_dokumentacni_jednotka', [])
|
||||
|
||||
for dj in djs:
|
||||
# Filter out negative evidence units if requested
|
||||
# Skip negative evidence units if requested
|
||||
if skip_negativni and dj.get('dj_negativni_jednotka') is True:
|
||||
continue
|
||||
|
||||
komps = dj.get('dj_komponenta', [])
|
||||
|
||||
|
||||
if filter_areal or filter_datace:
|
||||
if not komps:
|
||||
continue
|
||||
if not any(komp_projde_filtrem(komp, filter_areal, filter_datace, filters) for komp in komps):
|
||||
if not any(
|
||||
komp_projde_filtrem(
|
||||
komp, filter_areal,
|
||||
filter_datace, filters
|
||||
)
|
||||
for komp in komps
|
||||
):
|
||||
continue
|
||||
|
||||
dj_id = dj.get('ident_cely')
|
||||
dj_typ = dj.get('dj_typ')
|
||||
|
||||
# Merge general meta with documentation unit specific data
|
||||
# Merge shared metadata with documentation unit-specific fields
|
||||
dj_meta = {
|
||||
**meta,
|
||||
'dj_id': dj_id,
|
||||
'dj_typ_value': dj_typ.get('value') if dj_typ else "",
|
||||
'dj_negativni': "Negativní" if dj.get('dj_negativni_jednotka') is True else "Pozitivní"
|
||||
'dj_negativni': (
|
||||
"Negativní"
|
||||
if dj.get('dj_negativni_jednotka') is True
|
||||
else "Pozitivní"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
# Link Documentation Unit to Geometry (PIAN)
|
||||
dj_pian = dj.get('dj_pian')
|
||||
if dj_pian:
|
||||
@@ -379,25 +502,39 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
pian_lookup[dj_pian_value] = []
|
||||
|
||||
if komponenty == "true":
|
||||
# One feature per component — all data on a single row, no relations needed
|
||||
# One feature per component –
|
||||
# all data on a single row, no relations needed
|
||||
if komps:
|
||||
for komp in komps:
|
||||
if not komp_projde_filtrem(komp, filter_areal, filter_datace, filters):
|
||||
if not komp_projde_filtrem(
|
||||
komp, filter_areal,
|
||||
filter_datace, filters
|
||||
):
|
||||
continue
|
||||
|
||||
komp_meta = {
|
||||
**dj_meta,
|
||||
'komponenta_id': komp.get('ident_cely', ""),
|
||||
'komponenta_areal': komp.get('komponenta_areal', {}).get('value', ""),
|
||||
'komponenta_obdobi': komp.get('komponenta_obdobi', {}).get('value', ""),
|
||||
'komponenta_id': komp.get(
|
||||
'ident_cely',
|
||||
""
|
||||
),
|
||||
'komponenta_areal': komp.get(
|
||||
'komponenta_areal',
|
||||
{}
|
||||
).get('value', ""),
|
||||
'komponenta_obdobi': komp.get(
|
||||
'komponenta_obdobi',
|
||||
{}
|
||||
).get('value', ""),
|
||||
}
|
||||
pian_lookup[dj_pian_value].append(komp_meta)
|
||||
target_pian_ids_count += 1
|
||||
else:
|
||||
# DJ without components — still include with empty component fields
|
||||
# DJ without components — still include
|
||||
# with empty component fields
|
||||
if filter_areal or filter_datace:
|
||||
continue
|
||||
|
||||
|
||||
empty_meta = {
|
||||
**dj_meta,
|
||||
'komponenta_id': "",
|
||||
@@ -410,11 +547,13 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
target_pian_ids_count += 1
|
||||
pian_lookup[dj_pian_value].append(dj_meta)
|
||||
|
||||
|
||||
if not target_pian_ids:
|
||||
iface.messageBar().pushMessage("AMCR", f"Nalezeno {len(docs)} záznamů, ale žádný nemá geometrii.", level=Qgis.MessageLevel.Warning)
|
||||
return
|
||||
|
||||
iface.messageBar().pushMessage(
|
||||
"AMCR",
|
||||
f"Nalezeno {len(docs)} záznamů, ale žádný nemá geometrii.",
|
||||
level=Qgis.MessageLevel.Warning
|
||||
)
|
||||
return
|
||||
|
||||
# ==========================================
|
||||
# C) GEOMETRY FETCHING (PIAN)
|
||||
@@ -422,17 +561,28 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
ids_list = list(target_pian_ids)
|
||||
total_pians = len(ids_list)
|
||||
docs_pian = []
|
||||
BATCH_PIAN = 200 # Geometry requests are batch-processed to stay under URL length limits
|
||||
|
||||
iface.messageBar().pushMessage("AMCR", f"Záznamů: {len(docs)} (z toho {actions_with_geom} s mapou). Stahuji {total_pians} unikátních geometrií, vykresluji {target_pian_ids_count} geometrií...", level=Qgis.MessageLevel.Info)
|
||||
|
||||
fl_pian = ["ident_cely", "pian_typ", "pian_chranene_udaje", "pian_presnost"]
|
||||
# Geometry requests are batch-processed
|
||||
# to stay under URL length limits:
|
||||
BATCH_PIAN = 200
|
||||
|
||||
iface.messageBar().pushMessage(
|
||||
"AMCR",
|
||||
f"Záznamů: {len(docs)} (z toho {actions_with_geom} s mapou). "
|
||||
f"Stahuji {total_pians} unikátních geometrií, "
|
||||
f"vykresluji {target_pian_ids_count} geometrií...",
|
||||
level=Qgis.MessageLevel.Info
|
||||
)
|
||||
|
||||
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]
|
||||
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",
|
||||
@@ -441,7 +591,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
"fl": ",".join(fl_pian)
|
||||
}
|
||||
try:
|
||||
QApplication.processEvents()
|
||||
QApplication.processEvents()
|
||||
r_pian = _api_get(url, params=params_pian, timeout=15)
|
||||
batch_docs = r_pian.json().get('response', {}).get('docs', [])
|
||||
docs_pian.extend(batch_docs)
|
||||
@@ -451,13 +601,25 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
# ==========================================
|
||||
# D) LAYER CREATION (QGIS Memory Layers)
|
||||
# ==========================================
|
||||
|
||||
|
||||
archeologicky_zaznam = "Akce" if typ_dat == "akce" else "Lokalita"
|
||||
|
||||
# Initialize three layers for different geometry types (S-JTSK CRS)
|
||||
vl_poly = QgsVectorLayer("Polygon?crs=epsg:5514", f"AMCR_{archeologicky_zaznam}_Polygony", "memory")
|
||||
vl_line = QgsVectorLayer("LineString?crs=epsg:5514", f"AMCR_{archeologicky_zaznam}_Linie", "memory")
|
||||
vl_point = QgsVectorLayer("Point?crs=epsg:5514", f"AMCR_{archeologicky_zaznam}_Body", "memory")
|
||||
vl_poly = QgsVectorLayer(
|
||||
"Polygon?crs=epsg:5514",
|
||||
f"AMCR_{archeologicky_zaznam}_Polygony",
|
||||
"memory"
|
||||
)
|
||||
vl_line = QgsVectorLayer(
|
||||
"LineString?crs=epsg:5514",
|
||||
f"AMCR_{archeologicky_zaznam}_Linie",
|
||||
"memory"
|
||||
)
|
||||
vl_point = QgsVectorLayer(
|
||||
"Point?crs=epsg:5514",
|
||||
f"AMCR_{archeologicky_zaznam}_Body",
|
||||
"memory"
|
||||
)
|
||||
layers = [vl_poly, vl_line, vl_point]
|
||||
|
||||
# Define attribute table structure
|
||||
@@ -486,7 +648,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
QgsField("ukonceni", QMetaType.Type.QString),
|
||||
QgsField("hlavni_typ", QMetaType.Type.QString),
|
||||
QgsField("vedlejsi_typ", QMetaType.Type.QString),
|
||||
QgsField("zjisteni", QMetaType.Type.QString),
|
||||
QgsField("zjisteni", QMetaType.Type.QString),
|
||||
QgsField("nahrazuje_NZ", QMetaType.Type.QString),
|
||||
]
|
||||
elif typ_dat == "lokalita":
|
||||
@@ -497,7 +659,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
QgsField("druh_lokality", QMetaType.Type.QString),
|
||||
QgsField("zachovalost", QMetaType.Type.QString)
|
||||
]
|
||||
|
||||
|
||||
cols.append(QgsField("Přístupnost", QMetaType.Type.QString))
|
||||
|
||||
# Use aliases for technical field names
|
||||
@@ -531,7 +693,7 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
"komponenta": "Komponenta",
|
||||
"komponenta_areal": "Areál",
|
||||
"komponenta_obdobi": "Období",
|
||||
}
|
||||
}
|
||||
|
||||
if komponenty == "true":
|
||||
cols += [
|
||||
@@ -547,36 +709,45 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
idx = vl.fields().lookupField(tech_name)
|
||||
if idx != -1:
|
||||
vl.setFieldAlias(idx, alias)
|
||||
|
||||
|
||||
# Lists to hold features before batch-adding to layers
|
||||
feats_p, feats_l, feats_pt = [], [], []
|
||||
|
||||
|
||||
# --- FEATURE POPULATION ---
|
||||
for doc in docs_pian:
|
||||
try:
|
||||
pid = doc.get('ident_cely', '')
|
||||
if pid not in pian_lookup:
|
||||
continue
|
||||
|
||||
continue
|
||||
|
||||
metas = pian_lookup[pid]
|
||||
|
||||
|
||||
# Extract WKT geometry from protected JSON data
|
||||
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 or {})
|
||||
|
||||
jdata = (
|
||||
json.loads(raw)
|
||||
if isinstance(raw, str)
|
||||
else (raw or {})
|
||||
)
|
||||
|
||||
wkt = None
|
||||
if jdata.get('geom_sjtsk_wkt'):
|
||||
wkt = jdata.get('geom_sjtsk_wkt', {}).get('value')
|
||||
elif jdata.get('geom_wkt'):
|
||||
wkt = jdata.get('geom_wkt', {}).get('value')
|
||||
|
||||
|
||||
pian_presnost = tr_code(str(doc.get('pian_presnost', '')))
|
||||
pian_typ = tr_code(str(doc.get('pian_typ', '')))
|
||||
|
||||
# Final precision filter check
|
||||
if filters and filters.get('f_pian_presnost') and doc.get('pian_presnost') not in filters.get('f_pian_presnost'):
|
||||
if (
|
||||
filters
|
||||
and filters.get('f_pian_presnost')
|
||||
and doc.get('pian_presnost')
|
||||
not in filters.get('f_pian_presnost')
|
||||
):
|
||||
continue
|
||||
|
||||
if wkt:
|
||||
@@ -586,41 +757,54 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
target_list = None
|
||||
if t == QgsWkbTypes.PolygonGeometry:
|
||||
target_list = feats_p
|
||||
referenced_layer = vl_poly
|
||||
elif t == QgsWkbTypes.LineGeometry:
|
||||
target_list = feats_l
|
||||
referenced_layer = vl_line
|
||||
elif t == QgsWkbTypes.PointGeometry:
|
||||
target_list = feats_pt
|
||||
referenced_layer = vl_point
|
||||
|
||||
|
||||
if target_list is None:
|
||||
continue
|
||||
|
||||
is_akce = (typ_dat == "akce")
|
||||
|
||||
# Create a QGIS feature for each documentation unit associated with this geometry
|
||||
# Create a QGIS feature for each documentation unit
|
||||
# associated with this geometry
|
||||
for meta in metas:
|
||||
feat = QgsFeature()
|
||||
feat.setGeometry(geom)
|
||||
atributy = [
|
||||
pid, pian_presnost, pian_typ, meta['dj_id'],
|
||||
meta['dj_typ_value'], meta['loc'], meta['ident_cely'],
|
||||
"https://digiarchiv.aiscr.cz/id/" + meta['ident_cely'],
|
||||
meta['az_okres'], meta['katastr'], meta['dalsi_katastr']
|
||||
pid,
|
||||
pian_presnost,
|
||||
pian_typ,
|
||||
meta['dj_id'],
|
||||
meta['dj_typ_value'],
|
||||
meta['loc'],
|
||||
meta['ident_cely'],
|
||||
"https://digiarchiv.aiscr.cz/id/"
|
||||
+ meta['ident_cely'],
|
||||
meta['az_okres'],
|
||||
meta['katastr'],
|
||||
meta['dalsi_katastr']
|
||||
]
|
||||
if is_akce:
|
||||
atributy.extend([
|
||||
meta['lokalizace_okolnosti'], 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['dj_negativni'], meta['akce_je_nz']
|
||||
meta['lokalizace_okolnosti'],
|
||||
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['dj_negativni'],
|
||||
meta['akce_je_nz']
|
||||
])
|
||||
else:
|
||||
atributy.extend([
|
||||
meta['lokalita_nazev'], meta['lokalita_popis'],
|
||||
meta['lokalita_typ'], meta['lokalita_druh'],
|
||||
meta['lokalita_nazev'],
|
||||
meta['lokalita_popis'],
|
||||
meta['lokalita_typ'],
|
||||
meta['lokalita_druh'],
|
||||
meta['lokalita_zachovalost']
|
||||
])
|
||||
|
||||
@@ -631,21 +815,21 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
meta.get('komponenta_id', ""),
|
||||
meta.get('komponenta_areal', ""),
|
||||
meta.get('komponenta_obdobi', ""),
|
||||
])
|
||||
|
||||
])
|
||||
|
||||
feat.setAttributes(atributy)
|
||||
target_list.append(feat)
|
||||
|
||||
|
||||
except Exception as ex:
|
||||
print(f"Chyba při tvorbě feature: {ex}")
|
||||
pass
|
||||
|
||||
|
||||
# --- ADDING TO QGIS INTERFACE ---
|
||||
proj = QgsProject.instance()
|
||||
added = 0
|
||||
layers_to_process = [
|
||||
(feats_p, vl_poly, "Polygony"),
|
||||
(feats_l, vl_line, "Linie"),
|
||||
(feats_p, vl_poly, "Polygony"),
|
||||
(feats_l, vl_line, "Linie"),
|
||||
(feats_pt, vl_point, "Body"),
|
||||
]
|
||||
|
||||
@@ -653,17 +837,30 @@ def load_amcr_data(canvas, bb, filters=None, typ_dat="akce", komponenty="false")
|
||||
if f:
|
||||
l.dataProvider().addFeatures(f)
|
||||
l.updateExtents()
|
||||
l.setName(f"AMCR_{archeologicky_zaznam}_{n}")
|
||||
l.setName(f"AMCR_{archeologicky_zaznam}_{n}")
|
||||
proj.addMapLayer(l)
|
||||
added += len(f)
|
||||
|
||||
|
||||
if added > 0:
|
||||
iface.messageBar().pushMessage("AMCR", f"Hotovo. Záznamů: {len(docs)} (s geom: {actions_with_geom}). Vykresleno: {added} prvků.", level=Qgis.MessageLevel.Success)
|
||||
iface.messageBar().pushMessage(
|
||||
"AMCR",
|
||||
f"Hotovo. Záznamů: {len(docs)} (s geom: {actions_with_geom}). "
|
||||
f"Vykresleno: {added} prvků.",
|
||||
level=Qgis.MessageLevel.Success
|
||||
)
|
||||
else:
|
||||
iface.messageBar().pushMessage("AMCR", "Žádná data k zobrazení.", level=Qgis.MessageLevel.Info)
|
||||
iface.messageBar().pushMessage(
|
||||
"AMCR",
|
||||
"Žádná data k zobrazení.",
|
||||
level=Qgis.MessageLevel.Info
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
iface.messageBar().pushMessage("Chyba", str(e), level=Qgis.MessageLevel.Critical)
|
||||
iface.messageBar().pushMessage(
|
||||
"Chyba",
|
||||
str(e),
|
||||
level=Qgis.MessageLevel.Critical
|
||||
)
|
||||
finally:
|
||||
# Always restore cursor, even after failure
|
||||
QApplication.restoreOverrideCursor()
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
Reference in New Issue
Block a user