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
> 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
#### 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
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

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 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.")

12
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)

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))
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

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