Files
aiscr-qgis-amcr-viewer/amcr_viewer/amcr_tools.py
T
Claude 444d1c4826 Oprava geometrií a filtrování PIAN
- Fallback geometrie z geom_wkt (WGS-84) se nyní transformuje do S-JTSK;
  dříve se souřadnice ve stupních vkládaly přímo do vrstvy EPSG:5514
  a prvek se vykreslil úplně mimo.
- Nevalidní geometrie (např. samoprůniky polygonů) se před zahozením
  zkusí opravit přes makeValid().
- Filtr přesnosti PIAN normalizuje hodnotu pian_presnost (API ji může
  vracet jako jednoprvkový seznam) – dříve mohl tiše zahazovat záznamy.
  Stejná normalizace pro pian_typ kvůli překladu kódu.
- Přístupy ke komponenta_areal/komponenta_obdobi a az_chranene_udaje
  ošetřeny proti hodnotě None (klíč existuje, ale je null).
2026-06-12 10:50:03 +02:00

897 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
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
from qgis.PyQt.QtGui import QCursor
import requests
import json
# Global cache to store translated terms from the Digital Archive
TRANSLATIONS = {}
# 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: 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):
"""
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
)
return None
session = requests.Session()
session.headers.update({
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json",
"User-Agent": "QGIS-Plugin/1.0 (AISCR Data Fetcher)"
})
try:
_log(f"Odesílám POST na {login_url} ...")
response = session.post(
login_url,
json={"user": username, "pwd": password},
timeout=10
)
_log(f"HTTP status: {response.status_code}")
response.raise_for_status()
# 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
)
return None
_log("Přihlášení proběhlo úspěšně.")
global AMCR_SESSION
AMCR_SESSION = session
return session
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)
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:
"""
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
# Attempt auto-login using stored credentials
from .amcr_dialog import LoginDialog
username, password = LoginDialog.get_credentials()
if username and password:
_log("Session vypršela nebo chybí automatické přihlášení...")
AMCR_SESSION = login_to_api(username, password)
return AMCR_SESSION
def _api_get(url, params, timeout=30) -> requests.Response:
"""
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:
"""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
)
except Exception:
return False
session = _get_session()
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 # Invalidate the old session
from .amcr_dialog import LoginDialog
username, password = LoginDialog.get_credentials()
if username and password:
AMCR_SESSION = login_to_api(username, password)
if AMCR_SESSION:
resp = AMCR_SESSION.get(url, params=params, timeout=timeout)
else:
_log("Opakované přihlášení selhalo.",
Qgis.MessageLevel.Critical)
else:
_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 Digiarchive API.
"""
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"Error downloading vocabulary: {e}")
def tr_code(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):
# 'or {}' the key may be present with a None value
areal_id = (komp.get('komponenta_areal') or {}).get('id', "")
if filter_areal and areal_id not in filters.get('f_areal', []):
return False
obdobi_id = (komp.get('komponenta_obdobi') or {}).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"):
"""
Main processing function:
1. Determines search area (Bounding Box)
2. Fetches metadata and geometries from API
3. Creates QGIS memory layers and populates them with features
"""
load_translations()
# --- 1. COORDINATE TRANSFORMATION ---
# 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()},"
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
)
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
try:
# ==========================================
# A) METADATA FETCHING (Fieldwork/Site)
# ==========================================
base_params = {
"mapa": "true",
"sort": "ident_cely asc",
"entity": typ_dat
}
# Restrict search to map window if requested
if bb == "true":
base_params["loc_rpt"] = bbox_str
# 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:
continue
if isinstance(value, list):
base_params[key] = [f"{v}:or" for v in value]
else:
base_params[key] = str(value).strip()
docs = []
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
)
# 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
if current_page > 0:
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)
if not batch_docs:
break
# Filter out duplicates and append to main list
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.extend(new_docs)
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
)
break
current_page += 1
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
# ==========================================
# B) ATTRIBUTE PARSING
# ==========================================
# 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: 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: safely extract and join a list of values
def g_list(doc, key, translate=False):
val = doc.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])
# Process each downloaded metadata record
for doc in docs:
piani = doc.get('az_dj_pian', [])
if not piani:
continue
actions_with_geom += 1
# Extract protected fields ('or {}' key may hold None)
az_chranene = doc.get('az_chranene_udaje') or {}
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
]
dalsi_kat_str = ", ".join([i for i in items if i])
lokalizace = chranene.get('lokalizace_okolnosti', "")
lokalita_nazev = chranene.get('nazev', "")
lokalita_popis = chranene.get('popis', "")
# Core metadata structure
meta = {
"ident_cely": doc.get('ident_cely', ''),
"az_okres": g(doc, 'az_okres'),
"katastr": g_list(doc, 'katastr'),
"dalsi_katastr": dalsi_kat_str,
"pristupnost": g(doc, 'pristupnost'),
"loc": g_list(doc, 'loc')
}
# 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"
),
})
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'
)),
})
# Documentation units (DJ) within the record
djs = doc.get('az_dokumentacni_jednotka', [])
for dj in djs:
# 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
):
continue
dj_id = dj.get('ident_cely')
dj_typ = dj.get('dj_typ')
# 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í"
)
}
# Link Documentation Unit to Geometry (PIAN)
dj_pian = dj.get('dj_pian')
if dj_pian:
dj_pian_value = dj_pian.get('id')
if dj_pian_value:
target_pian_ids.add(dj_pian_value)
if dj_pian_value not in pian_lookup:
pian_lookup[dj_pian_value] = []
if komponenty == "true":
# 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
):
continue
komp_meta = {
**dj_meta,
'komponenta_id': komp.get(
'ident_cely',
""
),
'komponenta_areal': (
komp.get('komponenta_areal')
or {}
).get('value', ""),
'komponenta_obdobi': (
komp.get('komponenta_obdobi')
or {}
).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
if filter_areal or filter_datace:
continue
empty_meta = {
**dj_meta,
'komponenta_id': "",
'komponenta_areal': "",
'komponenta_obdobi': "",
}
pian_lookup[dj_pian_value].append(empty_meta)
target_pian_ids_count += 1
else:
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
# ==========================================
# C) GEOMETRY FETCHING (PIAN)
# ==========================================
ids_list = list(target_pian_ids)
total_pians = len(ids_list)
docs_pian = []
# 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]
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 = _api_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}")
# ==========================================
# 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"
)
layers = [vl_poly, vl_line, vl_point]
# Define attribute table structure
cols = [
QgsField("pian", QMetaType.Type.QString),
QgsField("presnost", QMetaType.Type.QString),
QgsField("pian_typ", QMetaType.Type.QString),
QgsField("dj", QMetaType.Type.QString),
QgsField("typ_dj", QMetaType.Type.QString),
QgsField("definicni_body", QMetaType.Type.QString),
QgsField(typ_dat, QMetaType.Type.QString),
QgsField("odkaz_do_digiarchivu", QMetaType.Type.QString),
QgsField("okres", QMetaType.Type.QString),
QgsField("katastr", QMetaType.Type.QString),
QgsField("dalsi_katastry", QMetaType.Type.QString)
]
# Extend table based on data type
if typ_dat == "akce":
cols += [
QgsField("akce_lokalizace", QMetaType.Type.QString),
QgsField("vedouci", QMetaType.Type.QString),
QgsField("organizace", QMetaType.Type.QString),
QgsField("specifikace_data", QMetaType.Type.QString),
QgsField("zahajeni", QMetaType.Type.QString),
QgsField("ukonceni", QMetaType.Type.QString),
QgsField("hlavni_typ", QMetaType.Type.QString),
QgsField("vedlejsi_typ", QMetaType.Type.QString),
QgsField("zjisteni", QMetaType.Type.QString),
QgsField("nahrazuje_NZ", QMetaType.Type.QString),
]
elif typ_dat == "lokalita":
cols += [
QgsField("nazev_lokality", QMetaType.Type.QString),
QgsField("popis_lokality", QMetaType.Type.QString),
QgsField("typ_lokality", QMetaType.Type.QString),
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
alias_map = {
"pian": "PIAN",
"presnost": "Přesnost",
"pian_typ": "PIAN typ",
"dj": "Dokumentační jednotka",
"typ_dj": "Typ dokumentační jednotky",
"definicni_body": "Definiční bod(y) (WGS-84)",
typ_dat: archeologicky_zaznam,
"odkaz_do_digiarchivu": "Odkaz do Digitálního archivu AMČR",
"okres": "Okres",
"katastr": "Katastr",
"dalsi_katastry": "Další katastry",
"akce_lokalizace": "Akce lokalizace",
"vedouci": "Vedoucí akce",
"organizace": "Organizace",
"specifikace_data": "Specifikace data",
"zahajeni": "Datum zahájeni",
"ukonceni": "Datum ukončení",
"hlavni_typ": "Hlavní typ",
"vedlejsi_typ": "Vedlejší typ",
"zjisteni": "Zjištění",
"nahrazuje_NZ": "Akce nahrazuje NZ",
"nazev_lokality": "Název lokality",
"popis_lokality": "Popis lokality",
"typ_lokality": "Typ lokality",
"druh_lokality": "Druh lokality",
"zachovalost": "Zachovalost",
"komponenta": "Komponenta",
"komponenta_areal": "Areál",
"komponenta_obdobi": "Období",
}
if komponenty == "true":
cols += [
QgsField("komponenta", QMetaType.Type.QString),
QgsField("komponenta_areal", QMetaType.Type.QString),
QgsField("komponenta_obdobi", QMetaType.Type.QString),
]
for vl in layers:
vl.dataProvider().addAttributes(cols)
vl.updateFields()
for tech_name, alias in alias_map.items():
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 = [], [], []
# Transform for PIANs that only provide WGS-84 geometry (geom_wkt)
# the target layers are in S-JTSK (EPSG:5514)
xform_wgs_to_sjtsk = QgsCoordinateTransform(
QgsCoordinateReferenceSystem("EPSG:4326"),
QgsCoordinateReferenceSystem("EPSG:5514"),
QgsProject.instance()
)
# --- FEATURE POPULATION ---
for doc in docs_pian:
try:
pid = doc.get('ident_cely', '')
if pid not in pian_lookup:
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 {})
)
wkt = None
wkt_is_wgs = False
if jdata.get('geom_sjtsk_wkt'):
wkt = jdata.get('geom_sjtsk_wkt', {}).get('value')
elif jdata.get('geom_wkt'):
# Fallback geometry is in WGS-84 and must be
# transformed to S-JTSK before use
wkt = jdata.get('geom_wkt', {}).get('value')
wkt_is_wgs = True
# The API may return the value as a single-item list
# normalize before comparing against filter codes
raw_presnost = doc.get('pian_presnost', '')
if isinstance(raw_presnost, list):
raw_presnost = raw_presnost[0] if raw_presnost else ''
raw_typ = doc.get('pian_typ', '')
if isinstance(raw_typ, list):
raw_typ = raw_typ[0] if raw_typ else ''
pian_presnost = tr_code(str(raw_presnost))
pian_typ = tr_code(str(raw_typ))
# Final precision filter check
if (
filters
and filters.get('f_pian_presnost')
and str(raw_presnost)
not in filters.get('f_pian_presnost')
):
continue
if wkt:
geom = QgsGeometry.fromWkt(wkt)
if geom.isNull():
continue
if wkt_is_wgs:
geom.transform(xform_wgs_to_sjtsk)
if not geom.isGeosValid():
# Try to repair (e.g. self-intersections)
# instead of silently dropping the feature
geom = geom.makeValid()
if geom.isGeosValid():
t = geom.type()
target_list = None
if t == QgsWkbTypes.PolygonGeometry:
target_list = feats_p
elif t == QgsWkbTypes.LineGeometry:
target_list = feats_l
elif t == QgsWkbTypes.PointGeometry:
target_list = feats_pt
if target_list is None:
continue
is_akce = (typ_dat == "akce")
# 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']
]
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']
])
else:
atributy.extend([
meta['lokalita_nazev'],
meta['lokalita_popis'],
meta['lokalita_typ'],
meta['lokalita_druh'],
meta['lokalita_zachovalost']
])
atributy.append(meta['pristupnost'])
if komponenty == "true":
atributy.extend([
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_pt, vl_point, "Body"),
]
for f, l, n in layers_to_process:
if f:
l.dataProvider().addFeatures(f)
l.updateExtents()
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}). "
f"Vykresleno: {added} prvků.",
level=Qgis.MessageLevel.Success
)
else:
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
)
finally:
# Always restore cursor, even after failure
QApplication.restoreOverrideCursor()