Browse Source

separated positions

master
Chris 11 months ago
parent
commit
462ce7eb99
  1. 8
      README.md
  2. BIN
      out/20250212-175748-1692-X-T50(1).jpg
  3. BIN
      out/20250409-182840-1896-X-T50.jpg
  4. BIN
      out/20250602-172821-1999-X-T50(1).jpg
  5. 4
      out/gps.csv
  6. BIN
      out/tile_20250212-175748-1692-X-T50(1).jpg
  7. BIN
      out/tile_20250409-182840-1896-X-T50.jpg
  8. BIN
      out/tile_20250602-172821-1999-X-T50(1).jpg
  9. 7
      site_builder/constants/datafields.py
  10. 9
      site_builder/constants/fields.py
  11. 6
      site_builder/constants/pos.py
  12. 184
      site_builder/main.py
  13. 12
      site_builder/test_find_geo_extrema.py
  14. 130
      site_builder/test_make_pos_free.py
  15. 6
      site_builder/test_read_csv_to_dict.py
  16. 20
      site_builder/test_shifting.py

8
README.md

@ -221,6 +221,14 @@ Die so hochgeladenen Daten werden aber noch nicht verwendet.
> ❗HINWEIS > ❗HINWEIS
> Damit nicht gitea die SSH-Befehle interpretiert, müssen alle per SSH ausgeführten Commands in der Datei `.ssh/authorized_keys` eingetragen sein. > Damit nicht gitea die SSH-Befehle interpretiert, müssen alle per SSH ausgeführten Commands in der Datei `.ssh/authorized_keys` eingetragen sein.
### Troubleshooting
**F Neues Bild/Tile wird nach dem deploy nicht angezeigt**
**A** Kann mehrere Ursachen haben:
* Dateiname von Bild `<IMAGE>` und Tile `tile_<IMAGE>` stimmen nicht exakt übereinstimmen
* Dateiname in `gps.csv` stimmt nicht exakt überein
Passiert schnell, wenn mehrere Bildversionen hochgeladen werden. Dann per SSH aller Bilder mit Zeitstempel löschen und per import den Eintrag erneut erzeugen und hochladen.
### Hintergrundinfos ### Hintergrundinfos
#### Datei gps.csv #### Datei gps.csv

BIN
out/20250212-175748-1692-X-T50(1).jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 177 KiB

BIN
out/20250409-182840-1896-X-T50.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

BIN
out/20250602-172821-1999-X-T50(1).jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

4
out/gps.csv

@ -108,4 +108,6 @@
20240304-201704-110-X-S10_2.jpg 50.1721638888889 9.11536694444444 Pendel CIIX Neue Feldstärke 20240304-201704-110-X-S10_2.jpg 50.1721638888889 9.11536694444444 Pendel CIIX Neue Feldstärke
20241218-170830-1541-X-T50_v1.jpg 50.1192902666667 8.73992721666667 Pendel CIX Irgendwo am Osthafen 20241218-170830-1541-X-T50_v1.jpg 50.1192902666667 8.73992721666667 Pendel CIX Irgendwo am Osthafen
20250203-210719-1685-X-T50.jpg 50.1328722666667 8.91704896666667 Pendel CX Ein paar Menschenpaare 20250203-210719-1685-X-T50.jpg 50.1328722666667 8.91704896666667 Pendel CX Ein paar Menschenpaare
20250212-175748-1692-X-T50(1).jpg 50.1075388888889 8.71126138888889 Pendel CX Zwischen Brücken 20250212-175748-1692-X-T50(1).jpg 50.1075388888889 8.71126138888889 Pendel CXI Zwischen Brücken
20250409-182840-1896-X-T50.jpg 50.1117265333333 8.70644641666667 Pendel CXII Passantin mit Buch
20250602-172821-1999-X-T50(1).jpg 50.1096861111111 8.66920555555556 Pendel CXIII Trockenraum

1 20170313-180536-DSCF4779.jpg 50.1555543999528 8.95522409999722 Pendel I Baerlauchwald
108 20240304-201704-110-X-S10_2.jpg 50.1721638888889 9.11536694444444 Pendel CIIX Neue Feldstärke
109 20241218-170830-1541-X-T50_v1.jpg 50.1192902666667 8.73992721666667 Pendel CIX Irgendwo am Osthafen
110 20250203-210719-1685-X-T50.jpg 50.1328722666667 8.91704896666667 Pendel CX Ein paar Menschenpaare
111 20250212-175748-1692-X-T50(1).jpg 50.1075388888889 8.71126138888889 Pendel CX Pendel CXI Zwischen Brücken
112 20250409-182840-1896-X-T50.jpg 50.1117265333333 8.70644641666667 Pendel CXII Passantin mit Buch
113 20250602-172821-1999-X-T50(1).jpg 50.1096861111111 8.66920555555556 Pendel CXIII Trockenraum

BIN
out/tile_20250212-175748-1692-X-T50(1).jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
out/tile_20250409-182840-1896-X-T50.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
out/tile_20250602-172821-1999-X-T50(1).jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

7
site_builder/constants/datafields.py

@ -0,0 +1,7 @@
# Field position for data items
ID = 0
FILE = 1
GEO = 2
TITLE = 3
DESCRIPTION = 4
POS = 5

9
site_builder/constants/fields.py

@ -1,9 +0,0 @@
# Field position for data items
ID = 0
FILE = 1
LON = 2
LAT = 3
TITLE = 4
DESCRIPTION = 5
POSX = 6
POSY = 7

6
site_builder/constants/pos.py

@ -0,0 +1,6 @@
# Universal geo pos tuple
LAT = 0
LON = 1
X = 0
Y = 1

184
site_builder/main.py

@ -2,23 +2,45 @@ import argparse
import csv import csv
import os.path as path import os.path as path
import re import re
import constants.fields as F import constants.datafields as F
import constants.pos as P
importdir = 'images' importdir = 'images'
datafile = path.join(importdir, 'gps.csv') datafile = path.join(importdir, 'gps.csv')
args = None args = None
# ----------
# Image data
# ----------
# Based on imported CSV and added with attributes: # Based on imported CSV and added with attributes:
# data with appended attributes as list of lists. # data with appended attributes as list of lists.
# n: Number of images
# Items ---> See constants/fields.py <--- # Items ---> See constants/fields.py <---
# - ID # - ID: Image ID [0..n-1]
# - Image file name # - Image file name
# - lon # - lon
# - lat # - lat
# - title, description # - title
# - x, y: Default tile position # - description
# - pos as (x, y): Default tile position
data = None data = None
# Default tile positions. Key is the image ID and value is the position as (x, y)
# Example: { '1': (10,11), '2': (20,22), .... }
defaultpos = None
# ----------
# Playbook
# ----------
# List with changes for every single screen.
# Playbook item:
# - Screen number [0..n-1]
# - list of changes:
# - Image ID
# - pos as (x, y) or, if to hide, None
playbook = []
# Has message in error case # Has message in error case
error = None error = None
@ -92,12 +114,12 @@ def read_csv_to_dict(csv_file):
values = [] values = []
values.insert(F.ID, id) values.insert(F.ID, id)
values.insert(F.FILE, row[0]) values.insert(F.FILE, row[0])
values.insert(F.LON, float(geo[0])) # values.insert(F.LON, float(geo[0]))
values.insert(F.LAT, float(geo[1])) # values.insert(F.LAT, float(geo[1]))
values.insert(F.GEO, (float(geo[0]), float(geo[1])))
values.insert(F.TITLE, row[2]) values.insert(F.TITLE, row[2])
values.insert(F.DESCRIPTION, row[3]) values.insert(F.DESCRIPTION, row[3])
values.insert(F.POSX, None) values.insert(F.POS, None)
values.insert(F.POSY, None)
data.append(values) data.append(values)
logvv(f"Processed item : {values}") logvv(f"Processed item : {values}")
@ -113,7 +135,7 @@ def read_csv_to_dict(csv_file):
def read_importdata(): def read_importdata():
global data data = None
if not path.exists(args.importdir): if not path.exists(args.importdir):
exit_with_error(f"Error: Import directory '{args.importdir}' does not exist.") exit_with_error(f"Error: Import directory '{args.importdir}' does not exist.")
@ -131,6 +153,8 @@ def read_importdata():
log("Read successful") log("Read successful")
return data
def find_geo_extrema(data): def find_geo_extrema(data):
""" """
@ -141,20 +165,24 @@ def find_geo_extrema(data):
if len(data) == 0: if len(data) == 0:
return None return None
lonmin = lonmax = data[0][F.LON] lonmin = lonmax = data[0][F.GEO][P.LON]
latmin = latmax = data[0][F.LAT] latmin = latmax = data[0][F.GEO][P.LAT]
for entry in data[1:]: for entry in data[1:]:
if entry[F.LON] < lonmin: if entry[F.GEO][P.LON] < lonmin:
lonmin = entry[F.LON] lonmin = entry[F.GEO][P.LON]
if entry[F.LON] > lonmax: if entry[F.GEO][P.LON] > lonmax:
lonmax = entry[F.LON] lonmax = entry[F.GEO][P.LON]
if entry[F.LAT] < latmin: if entry[F.GEO][P.LAT] < latmin:
latmin = entry[F.LAT] latmin = entry[F.GEO][P.LAT]
if entry[F.LAT] > latmax: if entry[F.GEO][P.LAT] > latmax:
latmax = entry[F.LAT] latmax = entry[F.GEO][P.LAT]
ret = (latmin, latmax, lonmin, lonmax)
return (lonmin, lonmax, latmin, latmax) log(f'find_geo_extrema={ret}')
return ret
def translate_pos(min, max, pos, size): def translate_pos(min, max, pos, size):
@ -163,38 +191,55 @@ def translate_pos(min, max, pos, size):
return round((pos - min) / (max - min) * size) return round((pos - min) / (max - min) * size)
def append_tile_pos(data, geo_extrema, matrixdims): def append_tile_pos_DEPTRECATED(data, geo_extrema, matrixdims):
""" """
Adds two columns with x and y position (integer) of the tile in the canvas Adds one column with (x, y) position (integers) of the tile in the canvas
""" """
ret = [] ret = []
for e in data: for e in data:
x = translate_pos(geo_extrema[2], geo_extrema[3], e[2], matrixdims[0]) x = translate_pos(geo_extrema[2], geo_extrema[3], e[F.GEO][P.LON], matrixdims[0])
y = translate_pos(geo_extrema[0], geo_extrema[1], e[1], matrixdims[1]) y = translate_pos(geo_extrema[0], geo_extrema[1], e[F.GEO][P.LAT], matrixdims[1])
e.append(x) e.insert(F.POS, (x, y))
e.append(y)
ret.append(e) ret.append(e)
logvv(f'Data updated: {e}') logvv(f'Data updated: {e}')
return ret return ret
def create_default_tile_pos_dict(data, geo_extrema, matrixdims):
"""
Returns dictionary with default position for every image by its ID.
"""
ret = dict()
for d in data:
x = translate_pos(geo_extrema[2], geo_extrema[3], d[F.GEO][P.LON], matrixdims[0])
y = translate_pos(geo_extrema[0], geo_extrema[1], d[F.GEO][P.LAT], matrixdims[1])
ret[d[F.ID]]=(x, y)
logvv(f'create_default_tile_pos_dict: {ret}')
return ret
def create_playbook(data): def create_playbook(posdict):
""" """
Returns list of dict [delta1, delta2, ...] with changes (in both direections) for every single tour. Returns list of dict [delta1, delta2, ...] with changes (valid in both direections) for
The snapshot holds the complete canvas for the actual step. The playbook contains exclusively every single tour. The snapshot holds the complete canvas for the actual step.
changed values (delta). The playbook contains exclusively changed values (delta).
Rules to process: Rules to process:
- Loop over all n data items: start with the first (oldest) item (0) - Loop over all n default item positions items: start with the first (oldest) item (0)
- Copy previous snapshot (to find changes in a later step) - Copy previous snapshot (to find changes in a later step)
- Run recursively over the the snapshot items: Find an existing item at the position - Run recursively over the the snapshot items: Check if there is already an
of the actual image and change its position by the displacement rule. item at the position of the actual image. If true, change its position
by the displacement rule.
- Place the image of the actual item (a) at its default position in the snapshot - Place the image of the actual item (a) at its default position in the snapshot
- Create a item for the following image (a+1) and hide it (for scrolling backwards). - Create an item for the following image (a+1) and hide it (for scrolling backwards).
- Store all changes betweeen snapshot and previous snapshot in a new delta (a) and append it - Store all changes betweeen snapshot and previous snapshot in a new delta (a) and append it
to the return dict. to the return dict.
In error case, None will be returned. In error case, None will be returned.
@ -203,59 +248,68 @@ def create_playbook(data):
snapshot = prev_snapshot = [] snapshot = prev_snapshot = []
ret = [] ret = []
for a in data: for a in posdict:
prev_snapshot = snapshot prev_snapshot = snapshot
(x, y) = (a[F.POSX], a[F.POSY]) pos = posdict[a]
snapshot = make_pos_free(pos, snapshot, matrixdims)
snapshot = shifting_snapshot_items(x,y, snapshot)
return ret return ret
def shifting_snapshot_items(x, y, snapshot):
def make_pos_free(pos, snapshot, matrixdims):
""" """
Recursive change item chain starting with item at position x,y. Search for an existing tile in the snapshot at position pos.
Returns updated snapshot If exists, the new position will be calculated. With an
recursive call, this new position will be checked, too.
""" """
goon = True act = pos
(actx, acty) = (x, y)
ret = snapshot ret = snapshot
while goon: for i in ret:
goon = False if (ret[i] == act):
for i, item in enumerate(ret): shifted_pos = shifting(act, matrixdims)
if (item[F.POSX], item[F.POSY]) == (actx, acty): ret = make_pos_free(shifted_pos, snapshot, matrixdims)
# ret[i][F.POSX], ret[i][F.POSY] = ret[i] = shifted_pos
goon = True break
return ret return ret
def shifting(tup, matrixdims): def shifting(pos, matrixdims):
"""Strategy to shift. Above and on the horizontal middle line, the position """Strategy to shift for a pos tuple. Above and on the horizontal middle line, the position
will be shifted to the top. Positions below the horizontal middle line, will be shifted to the top. Positions below the horizontal middle line,
the position will be shifted in bottom direction. the position will be shifted in bottom direction. Can result in a shift outside of the canvas.
Returns the offset as (x, y) tuple, not the new position itself. Returns the new position as (x, y) tuple.
""" """
if pos[P.Y] > (matrixdims[P.Y] / 2):
return (pos[P.X], pos[P.Y] + 1)
else:
return (pos[P.X], pos[P.Y] - 1)
# def add_pos_column_to_data():
# """Enhance data with original pos."""
# global data
def calculate_orig_pos(): # geo_extrema = find_geo_extrema(data)
global data # data = append_tile_pos(data, geo_extrema, matrixdims)
geo_extrema = find_geo_extrema(data)
data = append_tile_pos(data, geo_extrema, matrixdims)
playbook = create_playbook(data)
def main(): def main():
global args global args
global defaultpos
global data
args=parse_args() args=parse_args()
read_importdata() data = read_importdata()
calculate_orig_pos() geo_extrema = find_geo_extrema(data)
log(f'matrixdims={matrixdims}')
defaultpos = create_default_tile_pos_dict(data, geo_extrema, matrixdims)
log("Finished.") log("Finished.")

12
site_builder/test_find_geo_extrema.py

@ -10,11 +10,11 @@ def patch_log(monkeypatch):
def test_find_geo_extrama_simpple(): def test_find_geo_extrama_simpple():
data = [ data = [
[0, 'dummy', 50.1, 8.1 ], [0, 'dummy', (50.1, 8.1) ],
[1, 'dummy', 50.2, 8.2 ], [1, 'dummy', (50.2, 8.2) ],
[2, 'dummy', 50.3, 8.4 ], [2, 'dummy', (50.3, 8.4) ],
[3, 'dummy', 49.3, 8.4 ], [3, 'dummy', (49.3, 8.4) ],
[4, 'dummy', 49.3, 7.4 ] [4, 'dummy', (49.3, 7.4) ]
] ]
result = main.find_geo_extrema(data) result = main.find_geo_extrema(data)
@ -24,7 +24,7 @@ def test_find_geo_extrama_simpple():
def test_find_geo_extrama_single(): def test_find_geo_extrama_single():
data = [ data = [
[0, 'dummy', 50.1, 8.1 ] [0, 'dummy', (50.1, 8.1) ]
] ]
result = main.find_geo_extrema(data) result = main.find_geo_extrema(data)

130
site_builder/test_make_pos_free.py

@ -0,0 +1,130 @@
import pytest
import main
#########
# In order to reduce the dependency of method shifting(),
# the position is not explicitly checked.
#########
@pytest.fixture(autouse=True)
def patch_log(monkeypatch):
monkeypatch.setattr(main, "log", lambda msg: None)
monkeypatch.setattr(main, "logvv", lambda msg: None)
def test_single_item_no_match():
matrixdims = (6, 3)
pos = (0,0)
before = {1:pos}
after = main.make_pos_free((2, 3), before, matrixdims)
assert after[1] == pos
def test_single_item_match_at_0_0():
pos = (0,0)
before = {1:pos}
matrixdims = (6, 3)
after = main.make_pos_free(pos, before, matrixdims)
assert after[1] != pos
def test_single_item_match_at_horizont():
pos = (0, 1)
before = {1:pos}
# Y
# 0
# 1 <--- Horizon
# 2
matrixdims = (99, 2)
after = main.make_pos_free(pos, before, matrixdims)
assert after[1] != pos
def test_single_item_match_at_upper():
pos = (0, 0)
before = {1:pos}
# Y
# 0
# 1 <--- Horizon
# 2
matrixdims = (99, 2)
after = main.make_pos_free(pos, before, matrixdims)
assert after[1] != pos
def test_single_item_match_at_lower():
pos = (0, 2)
before = {1:pos}
# Y
# 0 ^
# 1 <--- Horizon | Lower | Upper
# 2 V
matrixdims = (99, 2)
after = main.make_pos_free(pos, before, matrixdims)
assert after[1] != pos
def test_first_item():
pos = (1,1)
matrixdims = (6, 3)
before = []
after = main.make_pos_free(pos, before, matrixdims)
assert len(after) == 0
def test_check_direct_neighbours_upwards():
matrixdims = (1000, 1000)
pos = (100,200)
before = {
1:(pos[main.P.X] , pos[main.P.Y] ),
2:(pos[main.P.X] , pos[main.P.Y]-1 ),
}
after = main.make_pos_free(pos, before, matrixdims)
for i in after:
for j in after:
if i == j:
continue
assert after[j] != after[i], f"i={i}, j={j}: Two items may not have the same position: {after[i]}"
def test_multiple_item_no_match():
matrixdims = (6, 3)
pos = (1,1)
before = {
1:(pos[main.P.X]+1, pos[main.P.Y] ),
2:(pos[main.P.X]-1, pos[main.P.Y] ),
3:(pos[main.P.X] , pos[main.P.Y]+1),
4:(pos[main.P.X] , pos[main.P.Y]-1),
5:(pos[main.P.X]+1, pos[main.P.Y]+1),
6:(pos[main.P.X]+1, pos[main.P.Y]-1),
7:(pos[main.P.X]-1, pos[main.P.Y]+1),
8:(pos[main.P.X]-1, pos[main.P.Y]-1),
}
after = main.make_pos_free(pos, before, matrixdims)
for i in after:
for j in after:
if i == j:
continue
assert after[j] != after[i], f"i={i}, j={j}: Two items may not have the same position: {after[i]}"

6
site_builder/test_read_csv_to_dict.py

@ -18,8 +18,8 @@ def test_read_csv_to_dict_basic(tmp_path):
result = main.read_csv_to_dict(str(file_path)) result = main.read_csv_to_dict(str(file_path))
expected = [ expected = [
[1, 'img1', 12.34, 56.78, 'foo', 'bar', None, None], [1, 'img1', (12.34, 56.78), 'foo', 'bar', None],
[2, 'img2', 90.12, 34.56, 'baz', 'qux', None, None], [2, 'img2', (90.12, 34.56), 'baz', 'qux', None],
] ]
assert result == expected assert result == expected
@ -39,7 +39,7 @@ def test_read_csv_to_dict_extra_spaces(tmp_path):
file_path.write_text(content, encoding="utf-8") file_path.write_text(content, encoding="utf-8")
result = main.read_csv_to_dict(str(file_path)) result = main.read_csv_to_dict(str(file_path))
expected = [[1, 'img3', 3.23, 4.56, 'foo', 'bar', None, None]] expected = [[1, 'img3', (3.23, 4.56), 'foo', 'bar', None]]
assert result == expected assert result == expected

20
site_builder/test_shifting.py

@ -0,0 +1,20 @@
import pytest
import main
@pytest.fixture(autouse=True)
def patch_log(monkeypatch):
monkeypatch.setattr(main, "log", lambda msg: None)
monkeypatch.setattr(main, "logvv", lambda msg: None)
def test_shifting_top():
assert main.shifting((2,3), (4,5)) == (2,4)
def test_shifting_mid():
assert main.shifting((20,24), (21,49)) == (20,23)
def test_shifting_botom():
assert main.shifting((20,26), (21,49)) == (20,27)
Loading…
Cancel
Save