feat: add configurable people maps
Add independent map profiles, offline city lookup, bundled world artwork and collision-free portrait placement with optional person details. Deploy with updated requirements, migrations 0029-0031 and collectstatic.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# People maps
|
||||
|
||||
People maps are independent of login accounts. One person can appear on several
|
||||
maps, with a different location on each map. Existing HTML sections are untouched.
|
||||
|
||||
## Deployment
|
||||
|
||||
Install `requirements.txt`, run `python manage.py migrate`, and collect static files
|
||||
using your normal deployment settings. Migrations 0029–0031 are generated by Django. They create the map tables, add
|
||||
the built-in background selector and add the optional people directory setting.
|
||||
They do not change existing pages or import live content.
|
||||
No production database changes were made during implementation.
|
||||
|
||||
## Admin workflow
|
||||
|
||||
1. Under **Pages → Map people**, create people with a name, photo, qualifications,
|
||||
role, institution, biography and optional profile URL.
|
||||
2. Under **Pages → People maps**, create a map. Choose a built-in background using **Background style**: Classic silhouette, Atlas with country borders, Midnight dotted continents, or Ocean gradient. Thumbnail previews appear below the selector. All four SVG maps are bundled in static files and ship to the server through `collectstatic`; no upload or external map service is required. Choose Aurora (dark), Atlas (light),
|
||||
or Classic (the original black / blue / turquoise palette). Classic reproduces
|
||||
the palette and portrait treatment, rather than the exact hand-drawn silhouette
|
||||
in the reference screenshot. Optionally upload your own world artwork; its
|
||||
projection must be equirectangular (longitude -180…180, latitude 90…-90).
|
||||
3. Add memberships on the map edit screen. Select an existing person using the
|
||||
searchable selector, or create one with the adjacent add button.
|
||||
4. Choose **Country and city** or **Coordinates** for each membership. City mode
|
||||
accepts an English country name or ISO code and a recognized city name or alias.
|
||||
Coordinates are resolved and stored on save. The local GeoNames dataset covers
|
||||
cities of at least 15,000 inhabitants. Canonical city names take priority over another town's aliases. Unknown or ambiguous cities require the
|
||||
coordinates method; there is no silent guess. Coordinates mode accepts decimal
|
||||
latitude (-90…90) and longitude (-180…180), including zero and negative values.
|
||||
5. In the map's **Map placements** inline, enter the exact local page path, such as
|
||||
`/about/`, `/`, `/projects/example/`, `/projects/example/module/`, or a custom
|
||||
page's path. Select before or after content, display order, and active status.
|
||||
Add several placements to reuse the same map. Separate maps can have different
|
||||
members. Placements can also be managed under **Pages → Map placements**.
|
||||
|
||||
Paths are exact and case-sensitive, including trailing slashes. Update a placement
|
||||
when changing a page's URL. Page publication and project access rules still apply:
|
||||
this feature adds no new public page routes. Disabling a map hides all its placements;
|
||||
disabling one placement hides only that occurrence. To replace an existing HTML
|
||||
map, first configure the new map and placement, then separately disable the old
|
||||
HTML section when ready. No existing section is automatically replaced.
|
||||
|
||||
Admins can turn **Show people details below the map** off on each map to hide the directory cards. It is on by default. Portraits remain visible on the map; when details are hidden, portraits do not link to hidden cards.
|
||||
|
||||
## Display behavior
|
||||
|
||||
The map uses local SVG artwork, CSS and JavaScript, without remote map tiles,
|
||||
tracking, API keys or geocoding requests. Portraits and labels are placed in the
|
||||
nearest available rectangle on deterministic expanding rings. Leaders connect each
|
||||
displaced portrait to the actual geographic anchor. If the map becomes very dense,
|
||||
its canvas grows vertically to preserve non-overlap. Minimum canvas width is 900px;
|
||||
phones scroll the map horizontally. Every person also has a readable directory card
|
||||
below the map, including without JavaScript. Selecting a portrait jumps to its card.
|
||||
Different placements generate unique anchor IDs.
|
||||
|
||||
Natural Earth land data is public domain:
|
||||
https://www.naturalearthdata.com/about/terms-of-use/
|
||||
City resolution uses the `geonamescache` package and its GeoNames data:
|
||||
https://www.geonames.org/ (CC BY).
|
||||
|
||||
## Verification
|
||||
|
||||
```sh
|
||||
venv/bin/python manage.py test apps.pages.tests apps.products.tests --settings=config.settings.preview --noinput
|
||||
venv/bin/python manage.py makemigrations --check --dry-run --settings=config.settings.preview
|
||||
venv/bin/python scripts/verify_people_map.py
|
||||
```
|
||||
|
||||
The optional browser verification needs Playwright and installed Google Chrome.
|
||||
It uses synthetic preview data, renders the actual template and assets without
|
||||
writing database records, tests 14 people and 60 people at the same location at
|
||||
desktop and phone widths, checks bounding boxes for collisions, tests portrait
|
||||
navigation and page overflow, and saves screenshots to `artifacts/people-map/`.
|
||||
Reference in New Issue
Block a user