diff --git a/README.md b/README.md index fc12761..174a34a 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,14 @@ Die so hochgeladenen Daten werden aber noch nicht verwendet. > ❗HINWEIS > 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 `` und Tile `tile_` 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 #### Datei gps.csv diff --git a/out/20250212-175748-1692-X-T50(1).jpg b/out/20250212-175748-1692-X-T50(1).jpg index 2a353ba..8f474fb 100644 Binary files a/out/20250212-175748-1692-X-T50(1).jpg and b/out/20250212-175748-1692-X-T50(1).jpg differ diff --git a/out/20250409-182840-1896-X-T50.jpg b/out/20250409-182840-1896-X-T50.jpg new file mode 100644 index 0000000..584487d Binary files /dev/null and b/out/20250409-182840-1896-X-T50.jpg differ diff --git a/out/20250602-172821-1999-X-T50(1).jpg b/out/20250602-172821-1999-X-T50(1).jpg new file mode 100644 index 0000000..11ded87 Binary files /dev/null and b/out/20250602-172821-1999-X-T50(1).jpg differ diff --git a/out/gps.csv b/out/gps.csv index 2a8b19c..63b4881 100644 --- a/out/gps.csv +++ b/out/gps.csv @@ -108,4 +108,6 @@ 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 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 diff --git a/out/tile_20250212-175748-1692-X-T50(1).jpg b/out/tile_20250212-175748-1692-X-T50(1).jpg index beb0e1a..9d38dbb 100644 Binary files a/out/tile_20250212-175748-1692-X-T50(1).jpg and b/out/tile_20250212-175748-1692-X-T50(1).jpg differ diff --git a/out/tile_20250409-182840-1896-X-T50.jpg b/out/tile_20250409-182840-1896-X-T50.jpg new file mode 100644 index 0000000..510aefa Binary files /dev/null and b/out/tile_20250409-182840-1896-X-T50.jpg differ diff --git a/out/tile_20250602-172821-1999-X-T50(1).jpg b/out/tile_20250602-172821-1999-X-T50(1).jpg new file mode 100644 index 0000000..848070f Binary files /dev/null and b/out/tile_20250602-172821-1999-X-T50(1).jpg differ diff --git a/site_builder/constants/datafields.py b/site_builder/constants/datafields.py new file mode 100644 index 0000000..0557d01 --- /dev/null +++ b/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 diff --git a/site_builder/constants/fields.py b/site_builder/constants/fields.py deleted file mode 100644 index 582abc7..0000000 --- a/site_builder/constants/fields.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/site_builder/constants/pos.py b/site_builder/constants/pos.py new file mode 100644 index 0000000..7d8a5b0 --- /dev/null +++ b/site_builder/constants/pos.py @@ -0,0 +1,6 @@ +# Universal geo pos tuple +LAT = 0 +LON = 1 + +X = 0 +Y = 1 \ No newline at end of file diff --git a/site_builder/main.py b/site_builder/main.py index 4b1f515..7fb92c3 100644 --- a/site_builder/main.py +++ b/site_builder/main.py @@ -2,23 +2,45 @@ import argparse import csv import os.path as path import re -import constants.fields as F +import constants.datafields as F +import constants.pos as P importdir = 'images' datafile = path.join(importdir, 'gps.csv') args = None +# ---------- +# Image data +# ---------- # Based on imported CSV and added with attributes: # data with appended attributes as list of lists. +# n: Number of images # Items ---> See constants/fields.py <--- -# - ID +# - ID: Image ID [0..n-1] # - Image file name # - lon # - lat -# - title, description -# - x, y: Default tile position +# - title +# - description +# - pos as (x, y): Default tile position 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 error = None @@ -92,12 +114,12 @@ def read_csv_to_dict(csv_file): values = [] values.insert(F.ID, id) values.insert(F.FILE, row[0]) - values.insert(F.LON, float(geo[0])) - values.insert(F.LAT, float(geo[1])) + # values.insert(F.LON, float(geo[0])) + # 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.DESCRIPTION, row[3]) - values.insert(F.POSX, None) - values.insert(F.POSY, None) + values.insert(F.POS, None) data.append(values) logvv(f"Processed item : {values}") @@ -113,7 +135,7 @@ def read_csv_to_dict(csv_file): def read_importdata(): - global data + data = None if not path.exists(args.importdir): exit_with_error(f"Error: Import directory '{args.importdir}' does not exist.") @@ -131,6 +153,8 @@ def read_importdata(): log("Read successful") + return data + def find_geo_extrema(data): """ @@ -141,20 +165,24 @@ def find_geo_extrema(data): if len(data) == 0: return None - lonmin = lonmax = data[0][F.LON] - latmin = latmax = data[0][F.LAT] + lonmin = lonmax = data[0][F.GEO][P.LON] + latmin = latmax = data[0][F.GEO][P.LAT] for entry in data[1:]: - if entry[F.LON] < lonmin: - lonmin = entry[F.LON] - if entry[F.LON] > lonmax: - lonmax = entry[F.LON] - if entry[F.LAT] < latmin: - latmin = entry[F.LAT] - if entry[F.LAT] > latmax: - latmax = entry[F.LAT] + if entry[F.GEO][P.LON] < lonmin: + lonmin = entry[F.GEO][P.LON] + if entry[F.GEO][P.LON] > lonmax: + lonmax = entry[F.GEO][P.LON] + if entry[F.GEO][P.LAT] < latmin: + latmin = entry[F.GEO][P.LAT] + if entry[F.GEO][P.LAT] > latmax: + 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): @@ -163,38 +191,55 @@ def translate_pos(min, max, pos, 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 = [] for e in data: - x = translate_pos(geo_extrema[2], geo_extrema[3], e[2], matrixdims[0]) - y = translate_pos(geo_extrema[0], geo_extrema[1], e[1], matrixdims[1]) + 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[F.GEO][P.LAT], matrixdims[1]) - e.append(x) - e.append(y) + e.insert(F.POS, (x, y)) ret.append(e) logvv(f'Data updated: {e}') 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. - The snapshot holds the complete canvas for the actual step. The playbook contains exclusively - changed values (delta). + Returns list of dict [delta1, delta2, ...] with changes (valid in both direections) for + every single tour. The snapshot holds the complete canvas for the actual step. + The playbook contains exclusively changed values (delta). 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) - - Run recursively over the the snapshot items: Find an existing item at the position - of the actual image and change its position by the displacement rule. + - Run recursively over the the snapshot items: Check if there is already an + 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 - - 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 to the return dict. In error case, None will be returned. @@ -203,59 +248,68 @@ def create_playbook(data): snapshot = prev_snapshot = [] ret = [] - for a in data: + for a in posdict: 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 -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. - Returns updated snapshot + Search for an existing tile in the snapshot at position pos. + If exists, the new position will be calculated. With an + recursive call, this new position will be checked, too. """ - goon = True - (actx, acty) = (x, y) + act = pos ret = snapshot - - while goon: - goon = False - for i, item in enumerate(ret): - if (item[F.POSX], item[F.POSY]) == (actx, acty): - # ret[i][F.POSX], ret[i][F.POSY] = - goon = True - - + + for i in ret: + if (ret[i] == act): + shifted_pos = shifting(act, matrixdims) + ret = make_pos_free(shifted_pos, snapshot, matrixdims) + ret[i] = shifted_pos + break return ret -def shifting(tup, matrixdims): - """Strategy to shift. Above and on the horizontal middle line, the position +def shifting(pos, matrixdims): + """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, - the position will be shifted in bottom direction. - Returns the offset as (x, y) tuple, not the new position itself. + the position will be shifted in bottom direction. Can result in a shift outside of the canvas. + 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 calculate_orig_pos(): - global data - geo_extrema = find_geo_extrema(data) - data = append_tile_pos(data, geo_extrema, matrixdims) - playbook = create_playbook(data) +# def add_pos_column_to_data(): +# """Enhance data with original pos.""" +# global data +# geo_extrema = find_geo_extrema(data) +# data = append_tile_pos(data, geo_extrema, matrixdims) def main(): global args + global defaultpos + global data args=parse_args() - read_importdata() - calculate_orig_pos() + data = read_importdata() + geo_extrema = find_geo_extrema(data) + log(f'matrixdims={matrixdims}') + defaultpos = create_default_tile_pos_dict(data, geo_extrema, matrixdims) log("Finished.") diff --git a/site_builder/test_find_geo_extrema.py b/site_builder/test_find_geo_extrema.py index 6b0a09c..a321f59 100644 --- a/site_builder/test_find_geo_extrema.py +++ b/site_builder/test_find_geo_extrema.py @@ -10,11 +10,11 @@ def patch_log(monkeypatch): def test_find_geo_extrama_simpple(): data = [ - [0, 'dummy', 50.1, 8.1 ], - [1, 'dummy', 50.2, 8.2 ], - [2, 'dummy', 50.3, 8.4 ], - [3, 'dummy', 49.3, 8.4 ], - [4, 'dummy', 49.3, 7.4 ] + [0, 'dummy', (50.1, 8.1) ], + [1, 'dummy', (50.2, 8.2) ], + [2, 'dummy', (50.3, 8.4) ], + [3, 'dummy', (49.3, 8.4) ], + [4, 'dummy', (49.3, 7.4) ] ] result = main.find_geo_extrema(data) @@ -24,7 +24,7 @@ def test_find_geo_extrama_simpple(): def test_find_geo_extrama_single(): data = [ - [0, 'dummy', 50.1, 8.1 ] + [0, 'dummy', (50.1, 8.1) ] ] result = main.find_geo_extrema(data) diff --git a/site_builder/test_make_pos_free.py b/site_builder/test_make_pos_free.py new file mode 100644 index 0000000..9d9dfb3 --- /dev/null +++ b/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]}" diff --git a/site_builder/test_read_csv_to_dict.py b/site_builder/test_read_csv_to_dict.py index 85a3579..03c3531 100644 --- a/site_builder/test_read_csv_to_dict.py +++ b/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)) expected = [ - [1, 'img1', 12.34, 56.78, 'foo', 'bar', None, None], - [2, 'img2', 90.12, 34.56, 'baz', 'qux', None, None], + [1, 'img1', (12.34, 56.78), 'foo', 'bar', None], + [2, 'img2', (90.12, 34.56), 'baz', 'qux', None], ] 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") 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 diff --git a/site_builder/test_shifting.py b/site_builder/test_shifting.py new file mode 100644 index 0000000..6690f91 --- /dev/null +++ b/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)