You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
7.2 KiB
263 lines
7.2 KiB
import argparse |
|
import csv |
|
import os.path as path |
|
import re |
|
import constants.fields as F |
|
|
|
importdir = 'images' |
|
datafile = path.join(importdir, 'gps.csv') |
|
args = None |
|
|
|
# Based on imported CSV and added with attributes: |
|
# data with appended attributes as list of lists. |
|
# Items ---> See constants/fields.py <--- |
|
# - ID |
|
# - Image file name |
|
# - lon |
|
# - lat |
|
# - title, description |
|
# - x, y: Default tile position |
|
data = None |
|
|
|
# Has message in error case |
|
error = None |
|
|
|
# How many tiles on the canvas in (x, y) |
|
matrixdims = (6, 3) |
|
|
|
|
|
def parse_args(): |
|
parser = argparse.ArgumentParser(description="Site Builder Main Program") |
|
parser.add_argument('-i', '--importdir', type=str, help=f'Path to images directory, default={importdir}', default=importdir) |
|
parser.add_argument('-d', '--datafile', type=str, help=f'Filename of CSV data file, default={datafile}', default=datafile) |
|
parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output') |
|
parser.add_argument('-vv', '--veryverbose', action='store_true', help='Enable very verbose output') |
|
return parser.parse_args() |
|
|
|
|
|
def log(message): |
|
if args.verbose or args.veryverbose: |
|
print(message) |
|
|
|
def logvv(message): |
|
if args.veryverbose: |
|
print(message) |
|
|
|
|
|
def exit_with_error(message): |
|
print(message) |
|
exit(1) |
|
|
|
|
|
def read_csv_to_dict(csv_file): |
|
""" |
|
Reads a tab-separated CSV file and returns a list of dictionaries, one per row |
|
in the full data item size. Placeholder for unfilled fields is None. |
|
Fields: |
|
0: ID (integer), ascending starting with '1' <-- not in CSV |
|
1: image file name (string) |
|
2: latitude and longitude (floats), separated by space. |
|
3. title (string) |
|
4. description (string) |
|
The items must be sorted chronologically. Assumes the first row contains headers. |
|
In error case, returns None |
|
""" |
|
global message |
|
data = [] |
|
with open(csv_file, newline='', encoding='utf-8') as f: |
|
reader = csv.reader(f, skipinitialspace=False, delimiter='\t') |
|
log(f"Read lines from {csv_file}") |
|
|
|
id = 0 |
|
for row in reader: |
|
|
|
logvv(f"Processing row: {row}") |
|
|
|
if not row or len(row) < 4: |
|
message = f"Invalid row: {row}" |
|
return None |
|
|
|
if not ' ' in row[1].strip(): |
|
message = f'Invalid location: {row[1]}' |
|
return None |
|
|
|
geo = re.findall(r'[\d]*[.][\d]+', row[1]) |
|
|
|
if len(geo) != 2: |
|
message = f'Invalid loc/lon: {row[1]}' |
|
|
|
id += 1 |
|
# Order and size must match constants.fields |
|
# values = [id, row[0], float(geo[0]), float(geo[1]), row[2], row[3] ] |
|
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.TITLE, row[2]) |
|
values.insert(F.DESCRIPTION, row[3]) |
|
values.insert(F.POSX, None) |
|
values.insert(F.POSY, None) |
|
data.append(values) |
|
|
|
logvv(f"Processed item : {values}") |
|
|
|
if len(data) == 0: |
|
message = f'Empty file: {csv_file}' |
|
return None |
|
else: |
|
log(f'Imported {len(data)} records.') |
|
|
|
return data |
|
|
|
|
|
def read_importdata(): |
|
|
|
global data |
|
|
|
if not path.exists(args.importdir): |
|
exit_with_error(f"Error: Import directory '{args.importdir}' does not exist.") |
|
|
|
if not path.exists(args.datafile): |
|
exit_with_error(f"Error: Data file '{args.datafile}' does not exist.") |
|
|
|
log(f"Import directory: {args.importdir}") |
|
log(f"Data file: {args.datafile}") |
|
|
|
data = read_csv_to_dict(args.datafile) |
|
|
|
if data is None: |
|
exit_with_error(message) |
|
|
|
log("Read successful") |
|
|
|
|
|
def find_geo_extrema(data): |
|
""" |
|
Find min and max location values for lon and lat. |
|
Returns (lonmin, lonmax, latmin, latmax) or None in error case. |
|
""" |
|
|
|
if len(data) == 0: |
|
return None |
|
|
|
lonmin = lonmax = data[0][F.LON] |
|
latmin = latmax = data[0][F.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] |
|
|
|
return (lonmin, lonmax, latmin, latmax) |
|
|
|
|
|
def translate_pos(min, max, pos, size): |
|
"""Values must be numbers.""" |
|
|
|
return round((pos - min) / (max - min) * size) |
|
|
|
|
|
def append_tile_pos(data, geo_extrema, matrixdims): |
|
""" |
|
Adds two columns with x and y position (integer) 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]) |
|
|
|
e.append(x) |
|
e.append(y) |
|
|
|
ret.append(e) |
|
logvv(f'Data updated: {e}') |
|
|
|
return ret |
|
|
|
|
|
def create_playbook(data): |
|
""" |
|
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). |
|
Rules to process: |
|
- Loop over all n data 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. |
|
- 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). |
|
- 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. |
|
""" |
|
|
|
snapshot = prev_snapshot = [] |
|
ret = [] |
|
|
|
for a in data: |
|
prev_snapshot = snapshot |
|
|
|
(x, y) = (a[F.POSX], a[F.POSY]) |
|
|
|
snapshot = shifting_snapshot_items(x,y, snapshot) |
|
|
|
return ret |
|
|
|
def shifting_snapshot_items(x, y, snapshot): |
|
""" |
|
Recursive change item chain starting with item at position x,y. |
|
Returns updated snapshot |
|
""" |
|
|
|
goon = True |
|
(actx, acty) = (x, y) |
|
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 |
|
|
|
|
|
|
|
return ret |
|
|
|
def shifting(tup, matrixdims): |
|
"""Strategy to shift. 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. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
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 main(): |
|
global args |
|
args=parse_args() |
|
read_importdata() |
|
calculate_orig_pos() |
|
log("Finished.") |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |