Compare commits

..

3 commits

Author SHA1 Message Date
Frank Adaemmer fa8ac8c9d8 clean up html 2023-05-20 23:22:41 +02:00
Frank Adaemmer 36a3400e5d generate and load weeks 2023-05-20 23:19:11 +02:00
Frank Adaemmer 988c2fe898 start readme 2023-05-20 23:17:17 +02:00
4 changed files with 237 additions and 128 deletions

77
README.md Normal file
View file

@ -0,0 +1,77 @@
# Mappersons - Visualize People on a World Map
Mappersons is a web app that allows you to visualize pictures of people on a world map along with their respective tasks and locations. With its intuitive slider feature, users can easily navigate through different calendar weeks to see how locations change over time.
![Mapperson](screenshot.png)
# Usage
To use Mappersons, follow these steps:
1. Place square images of people in the `persons` folder. Each person should have a unique filename (e.g. `jane.jpg`, `bob.jpg`, etc.).
2. Create a `persons.yml` file according section persons.yml
3. Create a `locations.yml` file according section locations.yml
4. Run the `python mappersons.py` script to render the map.
5. Serve the output with `python -m http.server 8080` to make it accessible through a web browser.
## persons.yml
### Structure
This YAML file has a structure that includes the following fields:
- `name`: The name of the person or entity.
- `image`: The image associated with the person or entity.
- `weeks`: A list of weeks, each containing:
- `location`: The location where tasks were performed. Identically to locations.yml
- `tasks`: A list of tasks performed during that week at that location.
### Example File Content
```yaml
---
- name: Lisa Lurch
image: lurch_l.jpg
weeks:
21/2023:
- location: Hamburg
tasks:
- Test new Feature
22/2023:
- location: Hamburg
tasks:
- Maintenance NSP
- name: Walter Wiesel
image: wiesel_w.jpg
weeks:
21/2023:
- location: Teisnach
tasks:
- Wrap up Ship 1
22/2023:
- location: Hamburg
tasks:
- Backoffice Ship 1
- Write report
```
## locations.yml
This file contains a list of locations with their corresponding latitude and longitude coordinates.
### Structure
The structure of this YAML file is as follows:
```yaml
- name: [Name of the location]
lat: [Latitude in decimal degrees]
lng: [Longitude in decimal degrees]
```
### Example File Content
```yaml
---
- name: Hamburg
lat: 53.64286
lng: 9.9753
- name: La Spezia
lat: 44.1064
lng: 9.8439
- name: Teisnach
lat: 49.0301
lng: 12.998
```

View file

@ -1,23 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>World Map with OpenStreetMap</title>
<title>Mappersons</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
crossorigin=""/>
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" />
<link href="jquery-ui.css" rel="stylesheet">
<style>
#map {
height: 100vh;
height: 90vh;
}
#slider {
position: relative;
width: 100%;
margin-bottom: 1em;
}
#custom-handle {
width: 8em;
height: 1.6em;
@ -26,71 +25,46 @@
text-align: center;
line-height: 1.6em;
}
</style>
<!-- Make sure you put this AFTER Leaflet's CSS -->
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM="
crossorigin=""></script>
integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script>
</head>
<body>
<div id="slider"> <div id="custom-handle" class="ui-slider-handle"></div></div>
<div id="slider">
<div id="custom-handle" class="ui-slider-handle"></div>
</div>
<div id="map"></div>
<script>
const map = L.map('map').setView([51.14027, 10.45863], 7);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
'<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
'<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery &copy <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
tileSize: 512,
zoomOffset: -1
}).addTo(map);
const locations = [
{
"city": "Hamburg",
"lat": "53.64286",
"lng": "9.9753",
"icon": "test5.png",
"anchor": [101,101]
},
// {
// "city": "Kiel",
// "lat": "54.3297",
// "lng": "10.1435"
// },
// {
// "city": "München",
// "lat": "48.1341",
// "lng": "11.5674",
// "icon": "test8.png",
// "anchor": [144,144]
// },
{
"city": "La Spezia",
"lat": "44.1064",
"lng": "9.8439",
"icon": "test8.png",
"anchor": [144,144]
},
{
"city": "Teisnach",
"lat": "49.0301",
"lng": "12.998",
"icon": "test13.png",
"anchor": [144,144]
}
];
const markers = locations.map(location => {
return L.marker([location.lat, location.lng], {icon: L.icon({
var markerLayer = L.layerGroup();
function loadWeek(locations) {
locations.map(location => {
return L.marker([location.lat, location.lng], {
icon: L.icon({
iconUrl: location.icon,
iconAnchor: location.anchor
})}).addTo(map);
})
})
.bindPopup(location.popup)
.addTo(markerLayer);
});
map.addLayer(markerLayer)
}
</script>
<script src="external/jquery/jquery.js"></script>
<script src="jquery-ui.js"></script>
@ -115,14 +89,26 @@ console.log(`The current week of ${year} is ${week}`);
create: function () {
handle.text(`KW ${week}/${year}`);
$.ajaxSetup({ cache: false });
$.getJSON(`cw_${week}_${year}.json`, function (data) {
loadWeek(data);
});
},
slide: function (event, ui) {
// ToDo: handle year change
var curweek = ui.value;
var curyear = year;
if (map.hasLayer(markerLayer)) {
markerLayer.clearLayers();
}
handle.text(`KW ${curweek}/${curyear}`);
$.ajaxSetup({ cache: false });
$.getJSON(`cw_${curweek}_${curyear}.json`, function (data) {
loadWeek(data);
});
}
});
</script>
</body>
</html>

View file

@ -1,9 +1,9 @@
import math
import json
import yaml
from PIL import Image, ImageDraw, ImageFilter
from pathlib import Path
import numpy as np
import math
def circular_mask(img):
center_x = int(img.width / 2)
@ -17,7 +17,7 @@ def circular_mask(img):
return img
def arrange_imager_in_circle(images, spacing=5, min_radius=0):
def arrange_images_in_circle(images, spacing=5, min_radius=0):
d = images[0].size[0]
dis = d + spacing
@ -51,7 +51,7 @@ def arrange_imager_in_circle(images, spacing=5, min_radius=0):
return masterImage
def create_circle(image_paths, output_path):
def create_circle(image_paths):
# Resize all images to 64x64 pixels
shrunk_images = [Image.open(path).resize((64, 64)) for path in image_paths]
@ -63,26 +63,72 @@ def create_circle(image_paths, output_path):
return
if len(circular_images) < 7:
output_img = arrange_imager_in_circle(circular_images)
output_img = arrange_images_in_circle(circular_images)
ow, oh = output_img.size
else:
inner_img = arrange_imager_in_circle(circular_images[:4])
inner_img = arrange_images_in_circle(circular_images[:4])
iw, ih = inner_img.size
mask = inner_img.split()[-1]
dr = circular_images[0].size[0]//2 + iw//2
output_img = arrange_imager_in_circle(circular_images[4:],min_radius=dr)
output_img = arrange_images_in_circle(circular_images[4:],min_radius=dr)
ow, oh = output_img.size
output_img.paste(inner_img,((ow-iw)//2,(oh-ih)//2),mask)
draw = ImageDraw.Draw(output_img)
dr = 3
draw.ellipse((ow//2-dr, oh//2-dr, ow//2+dr, oh//2+dr),fill=(255,0,0),outline=False)
output_path = output_img.save(output_path)
return output_img
def generateWeek(week, persons, locations, output_path=Path('.')):
loc = {}
for person in persons:
print(person)
if week in person['weeks'].keys():
cur_name = person['name']
cur_image = person['image']
for task in person['weeks'][week]:
cur_loc = task['location']
cur_tasks = task['tasks']
if cur_loc not in loc.keys():
loc[cur_loc] = {}
if cur_name not in loc[cur_loc].keys():
loc[cur_loc][cur_name] = {
'image': cur_image,
'tasks': []
}
loc[cur_loc][cur_name]['tasks'] += cur_tasks
week_json = []
w,y = week.split('/')
json_file = "cw_%s_%s.json" % (w,y)
for cur_loc, cur_persons in loc.items():
print(cur_loc)
print(cur_persons)
png_file = "%s_%s_%s.png" % (cur_loc.lower(),w,y)
[print(x) for x in cur_persons]
image_paths = [("persons/%s" % x['image']) for n,x in cur_persons.items()]
img = create_circle(image_paths)
img.save(output_path / Path(png_file))
popup = "<h2>%s</h2>" % cur_loc
for cur_per, cur_info in cur_persons.items():
popup += "<h3>%s</h3>" % cur_per
popup += "</br>".join(cur_info['tasks'])
l = [x for x in locations if x['name'] == cur_loc][0]
week_json.append({
"city": cur_loc,
"lat": l['lat'],
"lng": l['lng'],
"icon": png_file,
"anchor": [x//2 for x in img.size],
"popup": popup
})
print()
with open(output_path / Path(json_file),"w") as j:
json.dump(week_json, j)
if __name__ == "__main__":
p = Path("test_images")
image_paths = [str(x) for x in p.glob("*.jpg")]*2
for i in range(len(image_paths)):
output_path = "test%i.png" % i
create_circle(image_paths[:i+1], output_path)
with open("persons.yml","r") as fp:
persons = yaml.safe_load(fp)
with open("locations.yml","r") as fp:
locations = yaml.safe_load(fp)
generateWeek('21/2023', persons, locations)
generateWeek('22/2023', persons, locations)

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB