Compare commits

..
7 Commits
Author SHA1 Message Date
david-spacil 2fdbb15001 feature: aktualizace hesláře 2026-06-29 13:50:53 +02:00
david-spacil 8248d1bcae feature: nové heslářové globals + cache v dialogu 2026-06-29 13:50:39 +02:00
david-spacil abb06b9858 feature: globals pro PAS
založeny nové heslářové globals, pak přidány do funkce refresh_globals
2026-06-29 13:49:12 +02:00
david-spacil e84ee8e543 feature: update funkcí pro stahování dat
funkce fetch_set a download_heslare byly upraveny pro stahování dat nejen z OAI-PMH API, ale nově i z API digiarchivu (= osoby se nově nestahují z hesláře osob, ale z facetek digiarchivu, kde mají osoby přidělené "role" nálezce/vedoucí)
2026-06-29 13:48:23 +02:00
david-spacil 66532c108b feature: rozšíření slovníčku
slovníček interních vs api klíčů byl rozšířen o nová pole (nálezce, okolnosti, ...) a zároveň byla změněna jeho struktura: nově obsahuje i base url vzhledem k tomu, že si skript pro data sahá do dvou různých API; přidáno base url pro digiarchiv
2026-06-29 13:44:54 +02:00
david-spacil 86da8b9cf4 feature: tlačítko v menu
v kontextové nabídce přibylo tlačítko pro filtrování PASových záznamů; ikona je zatím placeholder
2026-06-29 13:41:44 +02:00
david-spacil a95831f180 feature: dialog: přidána nová pole
přidána pole samostatných nálezů pro filtrovací dialog + upravena viditelnost některých spolčných polí (např. skrýt areál, zobrazit organizaci nejen pro akce, ale i pro PAS, ...)
2026-06-29 13:39:44 +02:00
9 changed files with 602 additions and 1144 deletions

No files matched your search

+8 -22
View File
@@ -1,26 +1,15 @@
name: Release QGIS Plugin name: Release QGIS Plugin
# Spouští se na push tagu, ne na publikaci releasu. Organizace má zapnuté
# immutable releases: k už publikovanému releasu nelze nic přiložit, příloha
# tedy musí vzniknout dřív, než se release zveřejní. Workflow proto založí
# koncept releasu i se ZIPem; text se dopisuje a publikuje ručně.
#
# Pozor: workflow se čte z commitu, na který tag ukazuje. Tag musí být
# založen až na commitu, který tento soubor obsahuje.
on: on:
push: release:
tags: types: [published]
- 'v*'
permissions:
contents: write
jobs: jobs:
build-and-release: build-and-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# 1. Stáhne kód z tagu, který běh spustil # 1. Stáhne kód z repozitáře
- name: Checkout code - name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
@@ -31,14 +20,11 @@ jobs:
# -r = rekurzivně, -x = ignorovat skryté git soubory # -r = rekurzivně, -x = ignorovat skryté git soubory
zip -r amcr_viewer.zip amcr_viewer -x "*.git*" zip -r amcr_viewer.zip amcr_viewer -x "*.git*"
# 3. Založí koncept releasu i s přílohou # 3. Nahraje ZIP k Releasu
# Tagy s pomlčkou (v2.0.0-alpha.1) se označí jako pre-release. - name: Upload Release Asset
- name: Create draft release with asset
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
if: startsWith(github.ref, 'refs/tags/')
with: with:
files: amcr_viewer.zip files: amcr_viewer.zip
draft: true
name: AMCR Viewer ${{ github.ref_name }}
prerelease: ${{ contains(github.ref_name, '-') }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4 -10
View File
@@ -90,16 +90,10 @@ Další pravidla:
`changelog=` v `metadata.txt` (formát `vX.Y.Z (RRRR-MM-DD)` + odrážky). `changelog=` v `metadata.txt` (formát `vX.Y.Z (RRRR-MM-DD)` + odrážky).
- Datum v changelogu ber z **deterministického zdroje**, ne z paměti, např. - Datum v changelogu ber z **deterministického zdroje**, ne z paměti, např.
`python -c "import datetime; print(datetime.date.today().isoformat())"`. `python -c "import datetime; print(datetime.date.today().isoformat())"`.
- Release se spouští **pushnutím tagu `vX.Y.Z`**, ne publikací releasu - Release se vytváří publikací GitHub Release; workflow
v UI. Workflow `.github/workflows/release_plugin.yml` zabalí složku `.github/workflows/release_plugin.yml` zabalí složku `amcr_viewer/` do
`amcr_viewer/` do `amcr_viewer.zip` a založí **koncept** releasu i s touto `amcr_viewer.zip` a přiloží ji k releasu. Do ZIPu se nesmí dostat `.git*`
přílohou; text se dopíše a release zveřejní ručně. Do ZIPu se nesmí dostat soubory.
`.git*` soubory.
- Organizace má zapnuté **immutable releases** – k publikovanému releasu už
nelze nic přiložit. Proto příloha vzniká na konceptu, ještě před
zveřejněním; workflow spouštěný na `release: published` by vždy selhal.
- Workflow se čte z commitu, na který **tag ukazuje**. Tag proto zakládej až
na commitu, který obsahuje aktuální podobu workflow – jinak se nespustí nic.
## Pull requesty ## Pull requesty
+15 -179
View File
@@ -3,12 +3,10 @@ from qgis.PyQt.QtWidgets import (QDialog, QVBoxLayout,
QLineEdit, QDialogButtonBox, QLineEdit, QDialogButtonBox,
QCheckBox, QGroupBox, QPushButton, QCheckBox, QGroupBox, QPushButton,
QListWidget, QListWidgetItem, QHBoxLayout, QListWidget, QListWidgetItem, QHBoxLayout,
QMessageBox, QLabel, QFormLayout, QMessageBox, QLabel, QFormLayout)
QGridLayout, QScrollArea, QFrame, QWidget)
from qgis.PyQt.QtCore import Qt, QSettings from qgis.PyQt.QtCore import Qt, QSettings
from qgis.core import (QgsTask, QgsApplication, from qgis.core import (QgsTask, QgsApplication,
QgsMessageLog, Qgis, QgsAuthMethodConfig) QgsMessageLog, Qgis, QgsAuthMethodConfig)
from qgis.gui import QgsDateEdit
from qgis.utils import iface from qgis.utils import iface
from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE, from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE,
OKRESY, KATASTRY, VEDOUCI, PIAN_PRESNOST, OKRESY, KATASTRY, VEDOUCI, PIAN_PRESNOST,
@@ -19,17 +17,6 @@ from .amcr_codelists import (OBDOBI, TYP_AKCE, KRAJE, AREAL, ORGANIZACE,
download_heslare, refresh_globals) download_heslare, refresh_globals)
# The date filter of the API requires both bounds; a one-sided range makes
# the server fail with an ArrayIndexOutOfBoundsException and '*' is not
# accepted either. An empty picker is therefore replaced by these sentinels,
# which are also the default limits of QgsDateEdit.
DATE_OPEN_FROM = "0001-01-01"
DATE_OPEN_TO = "9999-12-31"
# Shown by a date picker that is left empty
DATE_NULL_TEXT = "neomezeno"
# Keep Python references to running tasks. QgsTaskManager only holds the # Keep Python references to running tasks. QgsTaskManager only holds the
# C++ object; without a Python-side reference the wrapper can be garbage # C++ object; without a Python-side reference the wrapper can be garbage
# collected before the task finishes, which crashes QGIS. # collected before the task finishes, which crashes QGIS.
@@ -190,10 +177,6 @@ class AmcrFilterDialog(QDialog):
'nalezce': [], 'nalezce': [],
} }
# Date range pickers, filled by setup_date_range():
# (API field, label for messages, 'from' widget, 'to' widget)
self.date_ranges = []
layout = QVBoxLayout() layout = QVBoxLayout()
# Filter by current map canvas extent # Filter by current map canvas extent
@@ -226,6 +209,13 @@ class AmcrFilterDialog(QDialog):
) )
layout.addWidget(self.picker_katastr) layout.addWidget(self.picker_katastr)
self.picker_presnost = self.setup_picker(
"PIAN – přesnost",
'pian_presnost',
PIAN_PRESNOST
)
layout.addWidget(self.picker_presnost)
self.picker_pristupnost = self.setup_picker( self.picker_pristupnost = self.setup_picker(
"Přístupnost", "Přístupnost",
'pristupnost', 'pristupnost',
@@ -235,15 +225,7 @@ class AmcrFilterDialog(QDialog):
# Filters valid for Akce # Filters valid for Akce
if self.typ_dat in ["lokalita", "akce"]: if self.typ_dat in ["pas", "akce"]:
self.picker_presnost = self.setup_picker(
"PIAN – přesnost",
'pian_presnost',
PIAN_PRESNOST
)
layout.addWidget(self.picker_presnost)
if self.typ_dat in ["samostatny_nalez", "akce"]:
self.picker_org = self.setup_picker( self.picker_org = self.setup_picker(
"Organizace", "Organizace",
'organizace', 'organizace',
@@ -268,12 +250,6 @@ class AmcrFilterDialog(QDialog):
) )
layout.addWidget(self.picker_typ) layout.addWidget(self.picker_typ)
self.box_datum = self.setup_date_range("Datum", [
("akce_datum_zahajeni", "Zahájení", "Datum zahájení"),
("akce_datum_ukonceni", "Ukončení", "Datum ukončení"),
])
layout.addWidget(self.box_datum)
# Filters valid for Lokality # Filters valid for Lokality
if self.typ_dat == "lokalita": if self.typ_dat == "lokalita":
@@ -310,7 +286,7 @@ class AmcrFilterDialog(QDialog):
self.picker_obdobi = self.setup_picker("Období", 'obdobi', OBDOBI) self.picker_obdobi = self.setup_picker("Období", 'obdobi', OBDOBI)
layout.addWidget(self.picker_obdobi) layout.addWidget(self.picker_obdobi)
if self.typ_dat == "samostatny_nalez": if self.typ_dat == "pas":
self.picker_nalez_kategorie = self.setup_picker( self.picker_nalez_kategorie = self.setup_picker(
"Kategorie nálezu", "Kategorie nálezu",
'nalez_kategorie', 'nalez_kategorie',
@@ -346,14 +322,7 @@ class AmcrFilterDialog(QDialog):
) )
layout.addWidget(self.picker_nalezce) layout.addWidget(self.picker_nalezce)
# Lokalita has no date field in the index at all, so the block if self.typ_dat != "pas":
# is built only for the two entities that do
self.box_datum = self.setup_date_range("Datum nálezu", [
("samostatny_nalez_datum_nalezu", "", "Datum nálezu"),
])
layout.addWidget(self.box_datum)
if self.typ_dat != "samostatny_nalez":
self.picker_areal = self.setup_picker("Areál", 'areal', AREAL) self.picker_areal = self.setup_picker("Areál", 'areal', AREAL)
layout.addWidget(self.picker_areal) layout.addWidget(self.picker_areal)
@@ -375,7 +344,7 @@ class AmcrFilterDialog(QDialog):
self.lbl_komponenty_warning.setVisible(False) self.lbl_komponenty_warning.setVisible(False)
layout.addWidget(self.lbl_komponenty_warning) layout.addWidget(self.lbl_komponenty_warning)
if self.typ_dat != "samostatny_nalez": if self.typ_dat != "pas":
self.chk_komponenty.toggled.connect( self.chk_komponenty.toggled.connect(
self.lbl_komponenty_warning.setVisible self.lbl_komponenty_warning.setVisible
) )
@@ -383,21 +352,6 @@ class AmcrFilterDialog(QDialog):
# Pushes everything above to the top # Pushes everything above to the top
layout.addStretch(1) layout.addStretch(1)
# The filter stack is taller than the window on every entity
# (over 1000 px for 'akce'), so it scrolls. The buttons stay
# outside the scroll area, otherwise the user would have to
# scroll to the bottom just to confirm the dialog.
content = QWidget()
content.setLayout(layout)
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.Shape.NoFrame)
scroll.setWidget(content)
outer = QVBoxLayout()
outer.addWidget(scroll)
# Main dialog OK/Cancel/Update buttons # Main dialog OK/Cancel/Update buttons
buttons = QDialogButtonBox() buttons = QDialogButtonBox()
@@ -418,9 +372,9 @@ class AmcrFilterDialog(QDialog):
buttons.accepted.connect(self.accept) buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject) buttons.rejected.connect(self.reject)
outer.addWidget(buttons) layout.addWidget(buttons)
self.setLayout(outer) self.setLayout(layout)
def setup_picker(self, label_text, cache_key, data_source, extra_btn=None): def setup_picker(self, label_text, cache_key, data_source, extra_btn=None):
""" """
@@ -483,94 +437,6 @@ class AmcrFilterDialog(QDialog):
row_widget.setLayout(row_layout) row_widget.setLayout(row_layout)
return row_widget return row_widget
def setup_date_range(self, title, rows):
"""
Creates a compact date range block: one row per API date field,
each with a 'from' and a 'to' picker.
rows is a list of (api_field, row_label, name_for_messages).
An empty row_label is used when the group box title already names
the field, which keeps the single-row variant from repeating itself.
A picker left empty means an open bound; the sentinel is
substituted in get_filters(), not here, so that an untouched
block adds no filter at all.
"""
row_widget = QGroupBox(title)
grid = QGridLayout()
grid.setContentsMargins(5, 5, 5, 5)
grid.setVerticalSpacing(3)
grid.setHorizontalSpacing(6)
for row, (api_field, row_label, name) in enumerate(rows):
if row_label:
grid.addWidget(QLabel(row_label), row, 0)
date_from = self._date_edit(
f"{name} – od (prázdné = bez dolní meze)"
)
date_to = self._date_edit(
f"{name} – do (prázdné = bez horní meze)"
)
separator = QLabel("–")
separator.setAlignment(Qt.AlignmentFlag.AlignCenter)
grid.addWidget(date_from, row, 1)
grid.addWidget(separator, row, 2)
grid.addWidget(date_to, row, 3)
self.date_ranges.append((api_field, name, date_from, date_to))
grid.setColumnStretch(1, 1)
grid.setColumnStretch(3, 1)
row_widget.setLayout(grid)
return row_widget
@staticmethod
def _date_edit(tooltip):
"""
A date picker that may stay empty.
clear() is essential here – setEmpty() looks empty but leaves
isNull() False with today's date, which would silently apply
a filter the user never set.
"""
widget = QgsDateEdit()
widget.setAllowNull(True)
widget.setNullRepresentation(DATE_NULL_TEXT)
widget.setDisplayFormat("d. M. yyyy")
widget.setCalendarPopup(True)
widget.clear()
widget.setToolTip(tooltip)
return widget
def accept(self):
"""
Blocks the dialog on a reversed date range. The API answers such
a query with zero records and no error, which is indistinguishable
from a genuinely empty result.
"""
reversed_ranges = [
name for _, name, date_from, date_to in self.date_ranges
if not date_from.isNull() and not date_to.isNull()
and date_from.date() > date_to.date()
]
if reversed_ranges:
QMessageBox.warning(
self,
"Neplatné rozmezí",
"U těchto filtrů je počáteční datum novější než koncové:\n"
+ "\n".join(f"• {name}" for name in reversed_ranges)
+ "\n\nDotaz by nevrátil žádný záznam. Opravte rozmezí, "
"nebo jedno z polí vyprázdněte."
)
return
super().accept()
def action_update_heslare(self): def action_update_heslare(self):
# Create the task instance and keep a reference so the Python # Create the task instance and keep a reference so the Python
# wrapper survives until the task finishes # wrapper survives until the task finishes
@@ -623,10 +489,7 @@ class AmcrFilterDialog(QDialog):
return "true" if self.chk_bbox.isChecked() else "false" return "true" if self.chk_bbox.isChecked() else "false"
def get_komponenty(self): def get_komponenty(self):
if self.typ_dat in ["akce", "lokalita"]: return "true" if self.chk_komponenty.isChecked() else "false"
return "true" if self.chk_komponenty.isChecked() else "false"
else:
return "false"
def get_filters(self): def get_filters(self):
"""Compiles the user selections from the cache into """Compiles the user selections from the cache into
@@ -671,33 +534,6 @@ class AmcrFilterDialog(QDialog):
if self.selection_cache['lokalita_zachovalost']: if self.selection_cache['lokalita_zachovalost']:
filters['f_lokalita_zachovalost'] = self.selection_cache['lokalita_zachovalost'] filters['f_lokalita_zachovalost'] = self.selection_cache['lokalita_zachovalost']
# Samostatné nálezy
if self.selection_cache['nalez_kategorie']:
filters['f_kategorie'] = self.selection_cache['nalez_kategorie']
if self.selection_cache['druh_nalezu']:
filters['f_druh_nalezu'] = self.selection_cache['druh_nalezu']
if self.selection_cache['specifikace']:
filters['f_specifikace'] = self.selection_cache['specifikace']
if self.selection_cache['nalezove_okolnosti']:
filters['f_nalezove_okolnosti'] = self.selection_cache['nalezove_okolnosti']
if self.selection_cache['nalezce']:
filters['f_nalezce'] = self.selection_cache['nalezce']
# Date ranges – the API needs both bounds, so an empty picker is
# replaced by a sentinel. A block with both pickers empty adds no
# filter at all; sending the full 0001–9999 range would only
# clutter the log without narrowing anything.
for api_field, _, date_from, date_to in self.date_ranges:
if date_from.isNull() and date_to.isNull():
continue
od = (DATE_OPEN_FROM if date_from.isNull()
else date_from.date().toString("yyyy-MM-dd"))
do = (DATE_OPEN_TO if date_to.isNull()
else date_to.date().toString("yyyy-MM-dd"))
filters[api_field] = f"{od},{do}"
return filters return filters
+383 -690
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -90,7 +90,7 @@ class AmcrViewer:
""" """
# Define paths for action-specific icons # Define paths for action-specific icons
icon_akce_path = os.path.join(self.plugin_dir, 'akce.png') icon_akce_path = os.path.join(self.plugin_dir, 'akce.png')
icon_pas_path = os.path.join(self.plugin_dir, 'sn.png') icon_pas_path = os.path.join(self.plugin_dir, 'akce.png')
icon_lokality_path = os.path.join(self.plugin_dir, 'lokality.png') icon_lokality_path = os.path.join(self.plugin_dir, 'lokality.png')
icon_amcr_help_path = os.path.join(self.plugin_dir, 'amcr-help.png') icon_amcr_help_path = os.path.join(self.plugin_dir, 'amcr-help.png')
@@ -113,7 +113,7 @@ class AmcrViewer:
self.action_download_pas = self.add_action( self.action_download_pas = self.add_action(
icon_path=icon_pas_path, icon_path=icon_pas_path,
text=self.tr(u'Stáhnout data samostatných nálezů | AMČR Viewer'), text=self.tr(u'Stáhnout data samostatných nálezů | AMČR Viewer'),
callback=lambda checked=False: self.run_download('samostatny_nalez'), callback=lambda checked=False: self.run_download('pas'),
parent=self.iface.mainWindow(), parent=self.iface.mainWindow(),
add_to_menu=False, add_to_menu=False,
add_to_toolbar=False add_to_toolbar=False
@@ -131,7 +131,7 @@ class AmcrViewer:
self.plugin_menu.addAction(self.action_download_lokality) self.plugin_menu.addAction(self.action_download_lokality)
self.action_login_dialog = self.add_action( self.action_login_dialog = self.add_action(
icon_path=icon_pas_path, icon_path=icon_akce_path,
text=self.tr(u'Přihlásit se | AMČR Viewer'), text=self.tr(u'Přihlásit se | AMČR Viewer'),
callback=lambda checked=False: self.login(), callback=lambda checked=False: self.login(),
parent=self.iface.mainWindow(), parent=self.iface.mainWindow(),
File diff suppressed because it is too large. Load diff
+4 -11
View File
@@ -8,7 +8,7 @@ name=AMČR Viewer
qgisMinimumVersion=3.44.0 qgisMinimumVersion=3.44.0
qgisMaximumVersion=4.99.0 qgisMaximumVersion=4.99.0
description=Viewing and downloading the AMČR data. description=Viewing and downloading the AMČR data.
version=2.1.1 version=2.0.2
author=David Spáčil author=David Spáčil
email=spacil@arub.cz email=spacil@arub.cz
@@ -23,16 +23,9 @@ repository=https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer
hasProcessingProvider=no hasProcessingProvider=no
# Uncomment the following line and add your changelog: # Uncomment the following line and add your changelog:
changelog= changelog=
Plný seznam změn v češtině je dostupný zde: https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases/tag/v2.1.1 Aktualizace na verzi 2.0.0 výrazně mění chování pluginu. Před updatem je doporučeno přečíst si seznam změn níže.
v2.1.1 (2026-09-01) Version 2.0.0 changes the plugin behavior dramatically. It is advised to check the changelog below.
* Added download of Individual finds (PAS), including a dedicated menu entry Plný seznam změn v češtině je dostupný zde: https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases/tag/v2.0.0
* Added filtering by date
* Added filtering of project fieldwork events and a Project column in the attribute table
* Codelists are now built from the Digiarchiv API as well, not only from OAI-PMH; persons come from its facets by role (finder, fieldwork leader)
* Updated the bundled codelist file
* Filter dialog is now scrollable
* Empty API results are now diagnosable from the Messages panel: the query URL and API errors are logged
* Code clean-up
v2.0.2 (2026-06-13) v2.0.2 (2026-06-13)
* Plugin-wide fixes and optimalizations (details https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/pull/49) * Plugin-wide fixes and optimalizations (details https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/pull/49)
v2.0.1 (2026-06-10) v2.0.1 (2026-06-10)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 B

+7 -23
View File
@@ -9,7 +9,7 @@
id="svg1" id="svg1"
xml:space="preserve" xml:space="preserve"
sodipodi:docname="icon.svg" sodipodi:docname="icon.svg"
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)" inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -24,12 +24,12 @@
inkscape:deskcolor="#d1d1d1" inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm" inkscape:document-units="mm"
inkscape:zoom="22.627417" inkscape:zoom="22.627417"
inkscape:cx="0.57452426" inkscape:cx="6.8059028"
inkscape:cy="15.556349" inkscape:cy="14.606174"
inkscape:window-width="1920" inkscape:window-width="1920"
inkscape:window-height="1131" inkscape:window-height="1009"
inkscape:window-x="0" inkscape:window-x="1912"
inkscape:window-y="0" inkscape:window-y="-8"
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs inkscape:current-layer="layer1" /><defs
id="defs1"><clipPath id="defs1"><clipPath
@@ -96,20 +96,4 @@
d="M 3.3486328 2.4970052 L 3.3486328 3.8323242 L 2.7424683 3.8323242 L 4.2085286 5.738151 L 5.674589 3.8323242 L 5.0684245 3.8323242 L 5.0684245 2.4970052 L 3.3486328 2.4970052 z " d="M 3.3486328 2.4970052 L 3.3486328 3.8323242 L 2.7424683 3.8323242 L 4.2085286 5.738151 L 5.674589 3.8323242 L 5.0684245 3.8323242 L 5.0684245 2.4970052 L 3.3486328 2.4970052 z "
inkscape:export-filename="path32.png" inkscape:export-filename="path32.png"
inkscape:export-xdpi="3000" inkscape:export-xdpi="3000"
inkscape:export-ydpi="3000" /><rect inkscape:export-ydpi="3000" /></g></svg>
style="opacity:1;fill:#85140e;fill-opacity:1;stroke:none;stroke-width:0.414999;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;paint-order:stroke fill markers"
id="rect1"
width="1.672105"
height="1.2511554"
x="-4.501821"
y="4.2814822" /><text
xml:space="preserve"
style="font-size:0.705556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;opacity:1;fill:#85140e;fill-opacity:1;stroke:none;stroke-width:0.414999;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;paint-order:stroke fill markers"
x="-2.6360023"
y="5.1136947"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-size:0.705556px;fill:#85140e;fill-opacity:1;stroke-width:0.415"
x="-2.6360023"
y="5.1136947">lokality</tspan></text></g></svg>

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB