This repository stores indoor mapping data for the Beaty Biodiversity Museum. The files are GeoJSON, which is a standard text format for map features such as points, lines, and polygons.
The long-term goal is to keep the data easy to review in common map tools while moving it closer to indoor mapping conventions such as Apple's Indoor Mapping Data Format (IMDF), now also published as an OGC Community Standard.
Supporting documentation:
DATA_SOURCES.mdexplains source data, local review decisions, and derivation methods.DATA_DICTIONARY.mddefines local fields, local category values, and terms that need museum confirmation.
This repository includes a viewer for exploring the canonical files in geojson/. It provides layer controls, ground and basement filtering, feature search, fixture-to-fixture routing, and an inspector for useful IDMF properties and issue provenance.
Visit the live demo here: https://apps.beatymuseum.ubc.ca/map
Routes are constrained to locally confirmed LineStrings in geojson/navigation_path.geojson. Access projections in navigation_access.geojson connect fixtures to that network, while deduplicated physical viewing locations live in navigation_stop.geojson. Junctions are generated from path geometry during validation and display rather than stored. Routing minimizes total distance across these approved segments. Unit polygons are never treated as free routing space, and the viewer reports that no approved route is available instead of inventing a shortcut.
The Museum Floor Unit is derived from the Basement Level minus its Ramp Units. Rebuild it after changing either source geometry:
python3 -m pip install -r requirements.txt
python3 scripts/build_gallery_unit.py
python3 scripts/build_preview_geojson.pyNavigation revisions are generated as approval artifacts before canonical data changes. Rebuild the approved corridor-centering proposal with python3 scripts/build_navigation_proposals.py centered; applying it requires explicit review and uses python3 scripts/build_navigation_proposals.py apply-centered.
Install the exact JavaScript dependencies:
cp .env.example .env
npm ciRun the viewer locally in development mode:
npm run devOpen http://localhost:3000/map. The /map suffix is required because the application uses that base path.
To test a production build locally:
npm run build
npm run startThe build reads the canonical map layers from geojson/. The build script also copies those files into the standalone Next.js output. preview.geojson is a review artifact and is not loaded by the viewer.
.env.example contains the local base path. The deployment manifest owns the
production application name and /map ingress path; the indoor_mapping
inventory record supplies APP_HOST, APP_PORT, and any deployment-root
override.
Run the complete local validation before publishing a change:
npm run checkProduction deployment is owned by ansible-deploy. From an activated
controller checkout, deploy a reviewed revision with:
./scripts/deploy indoor_mapping <revision>Controller-driven deployment reads deploy/deployment.yml, runs
deploy/build.sh on the controller, and transfers the resulting
standalone artifact. The managed host does not need the repository or GitHub
access. Inventory owns host placement and APP_HOST/APP_PORT; the repository
owns installation, readiness, and the default release root. The
deployed process remains bound to loopback and is exposed only through nginx.
deploy/install.sh initializes once and delegates later deployments to
deploy/update.sh. Usage events are stored outside immutable releases at
$SERVICE_CREATOR_DEPLOY_ROOT/data/usage.sqlite3, so normal updates preserve them.
Explicit install.sh --reinstall repeats initialization without changing map
data. Normal updates replace only the immutable application release.
The manifest adapters are the only production deployment route; the former
checkout-based deploy, update, and readiness scripts have been
removed.
The viewer records completed searches, selected features, requested routes, opened exhibit images, and anonymous session timestamps. It does not record map zooms, pans, touch coordinates, or individual keystrokes. Records are kept for 365 days by default.
Local data is stored in .data/usage.sqlite3. Set USAGE_DB_PATH to override
the location and USAGE_RETENTION_DAYS to change retention. Export the current
database to CSV with:
npm run usage:export -- usage-events.csvA read-only summary dashboard is available at /map/analytics. It uses HTTP
Basic authentication, defaulting to username beaty and password beaty.
Set ANALYTICS_USERNAME and ANALYTICS_PASSWORD in production to override
those credentials.
The in-map Report a problem form sends a structured issue to GitHub without
giving visitors access to GitHub credentials or arbitrary issue fields. Configure
GITHUB_ISSUE_TOKEN with a fine-grained token limited to Issues: read/write
on this repository. GITHUB_ISSUE_REPOSITORY selects the fixed destination and
defaults to beatybiodiversitymuseum/indoor-mapping.
Reports receive only the needs review label so the map-data ingestion workflow
cannot process visitor text before a maintainer reviews it. The persistent
per-session submission limit defaults to five reports per hour and can be changed
with REPORT_RATE_LIMIT_PER_HOUR.
The current map data is in the geojson/ folder:
| File | What it contains | Current feature type |
|---|---|---|
geojson/manifest.json |
IMDF package metadata | n/a |
geojson/address.geojson |
Museum postal address | address |
geojson/venue.geojson |
Confirmed overall venue boundary around Beaty, BRC, AERL, and the overhang | venue |
geojson/building.geojson |
Confirmed building records for Beaty, BRC, and AERL | building |
geojson/footprint.geojson |
Confirmed OSM-derived building and overhang footprints | footprint |
geojson/level.geojson |
Confirmed underground museum gallery level | level |
geojson/unit.geojson |
Confirmed Museum Floor unit | unit |
geojson/opening.geojson |
Doors, entrances, exits, ramps, and other passages | opening |
geojson/anchor.geojson |
Starter anchor point for the gallery unit | anchor |
geojson/amenity.geojson |
Restrooms, waste bins, visitor services, and equipment | amenity |
geojson/exhibit.geojson |
Permanent exhibits, including windows, drawers, shadowboxes, floor displays, and standalone displays | exhibit |
geojson/occupant.geojson |
Occupants, currently empty | occupant |
geojson/detail.geojson |
IMDF detail features, currently empty | detail |
geojson/section.geojson |
Sections, currently empty | section |
geojson/geofence.geojson |
Geofences, currently empty | geofence |
geojson/kiosk.geojson |
Kiosks, currently empty | kiosk |
geojson/navigation_path.geojson |
Confirmed walkable route segments | navigation extension |
geojson/navigation_access.geojson |
Explicit fixture-to-path access projections | navigation extension |
geojson/navigation_stop.geojson |
Deduplicated exhibit viewing and route-start locations | navigation extension |
geojson/relationship.geojson |
Feature relationships, currently empty | relationship |
geojson/fixture.geojson |
Cabinets, drawer/island boxes, tables, cases, and flat floor-display footprints | fixture |
preview.geojson |
Stacked GeoJSON.io review file for the canonical map layers | mixed |
Mapped indoor gallery features reference one confirmed underground level:
41d0e8ca-d315-4b25-938c-7955db2daf2e
geojson.io is a beginner-friendly website for checking and previewing GeoJSON.
Use preview.geojson when you want the easiest visual review. It combines the main review layers in draw order so unit/level/footprint shapes load first and amenity points load last.
- Go to geojson.io.
- Click
Import, choosepreview.geojson, and wait for the map to load near UBC in Vancouver. - Look for red error messages in the main right-side
JSONeditor. - Click existing features on the map to orient yourself before adding a new point.
- Select an existing feature, or draw a temporary
Linebetween two known reference points. - With the feature selected, click the
Measurementsbutton in the top toolbar. - For a line, the measurement dialog shows the bounding box, length, selectable units such as meters, and vertex count.
- Use meters for museum measurements unless another unit is clearer.
- Delete the temporary line before copying final GeoJSON if it was only used for measurement.
- Click the
Pointdrawing tool in the top toolbar. - Click the map where the new location should go.
- Select the new point.
- In the lower
Feature Editorpanel, click theGeoJSONtab. This lower panel edits only the selected feature. - Hover over the lower GeoJSON snippet so its
Copybutton appears. - Click
Copyto copy only the selected point feature.
- Open a new
Add or correct a map locationissue. - Fill in the name, confirmation method, reference points, GPS coordinates, photos, and notes.
- In
Optional: pasted GeoJSON feature, paste the copied selected point feature between the fencedjsonlines. - Confirm the pasted feature is a single
Pointfeature and uses[longitude, latitude]coordinate order.
Before saving a change, confirm:
- The map still loads.
- The edited feature appears in the right place.
- There are no JSON errors.
- You did not accidentally remove the opening
{, closing}, or commas between features.
Many locations at the Beaty can be added to our maps. Consider visitor-facing points of interest, such as exhibits, fossil excavation viewing points, cabinet exhibit points, drawer exhibit points, kiosks, service points, or other things a visitor may search for or navigate to.
You do not need to know how to code to help add a location. The most important thing is to collect clear, accurate information so that the JSON entry can be created or reviewed correctly.
If you are submitting a new location, use the map-location issue template and select exactly one layer: exhibit, fixture, amenity, or opening. The template asks for the information a maintainer needs without requiring you to edit GeoJSON.
Open issues labeled map data or titled with the Location: prefix can be consumed by the Generate GeoJSON from Issues GitHub Actions workflow. The workflow runs when a matching issue is opened, edited, labeled, or reopened, and it can also be run manually or by its weekly scheduled backstop. Issue-triggered runs include the triggering issue directly and also batch any other open matching issues found by the GitHub CLI. The workflow also creates the map data and needs review labels if they are missing, then applies them to matching issues.
The workflow:
- Reads open
map dataissues. - Reads the selected map layer and validates geometry appropriate to that layer.
- Updates
geojson/exhibit.geojson,fixture.geojson,amenity.geojson, oropening.geojson. - Rebuilds
preview.geojsonfor GeoJSON.io review. - Writes
reports/issue-geojson-review.md. - Opens or updates a pull request labeled
needs review.
Generated features include source_issue_number, source_issue_title, and source_url. The pull request review is the approval gate: review the map changes in the PR, edit the generated GeoJSON if needed, and merge only after the candidate data is accepted. Geometry must fall within the configured UBC Vancouver bounding box, and the generated PR body includes Closes #... lines for converted issues. Validation rejects exhibits stored as amenities, unresolved exhibit-to-fixture or navigation references, duplicate issue records across layers, invalid level references, and stale previews.
The best way to figure out the location is to use multiple GPS readings. However, the GPS is not always great underground. Then, use confirmed points and measure from them. It's best if you can establish a reference point along a straight line.
Measuring from existing maps is a very helpful approach as well.
Use the name that a visitor, museum staff member, or exhibit label would recognize.
Good examples:
Gift Shop Cash Register
Visitor Information Kiosk
Avoid vague names such as:
Thing near wall
Display
Cabinet
If there is a label on the object, copy the label exactly. If there is no label, describe it clearly and consistently.
Write down what the point represents.
Examples:
kiosk
service point
visitor information point
accessibility points
restroom
If you are not sure which category to use, write your best plain-language description. A maintainer can choose the final category.
The amenity point should usually mark where a visitor should stand, look, or arrive — not necessarily the exact middle of the object.
Every new amenity should include how the location was confirmed. This helps reviewers know whether the point is reliable.
Use one of these methods where possible:
visually confirmed on site
measured on site
recorded with phone GPS
derived from existing wayfinding data
checked against a floor plan
confirmed by museum staff
Phone GPS can be useful, but indoor GPS is often inaccurate. If you use a phone, record the location while standing as close as possible to the amenity and include a note that it was collected indoors with a phone.
If you cannot confidently place the point in GeoJSON, collect enough information for someone else to place it.
Useful options:
- Use your phone to record GPS coordinates while standing at the visitor viewing point.
- Take a photo showing the amenity and nearby fixed objects, such as walls, doors, stairs, elevators, columns, or cabinets.
- Measure with a tape measure from a confirmed spot already on the map.
- Mark the point on a printed floor plan or screenshot.
- Write a short description of the location using nearby landmarks.
Good measurement examples:
Standing point is 1.2 m in front of Cabinet 01, centered on the cabinet face.
Point is 0.8 m east of the southwest corner of Drawer Island 03.
Point is directly in front of the fossil excavation glass, aligned with the center of the viewing edge.
Phone GPS recorded while standing at the viewing point. Indoor GPS may be approximate.
If measuring from a confirmed spot, use a spot that is unlikely to move, such as:
- a wall corner
- a doorway
- a column
- a stair or elevator entrance
- a cabinet or fixture already mapped in
geojson/fixture.geojson
Avoid measuring from movable objects such as chairs, temporary signs, or garbage bins.
Record whether a normal visitor can see or access the amenity.
Examples:
visible to visitors
not visible to visitors
staff-only
temporarily hidden
behind glass
inside cabinet
Record whether visitors may reasonably search for this amenity or use it as a destination.
Examples:
yes, visitors may search for it
yes, it is a major exhibit point
no, it is only supporting information
unknown
If the amenity belongs to a cabinet, drawer box, island, or other mapped object, write down the related fixture name or ID if you know it.
Examples:
related to Cabinet 01
related to Drawer Island 03
related fixture unknown
Do not invent a fixture ID. If you do not know it, leave a note.
Before submitting a new amenity, make sure you have recorded:
- Name of the amenity
- Type of amenity
- Exact visitor standing/viewing point
- How the location was confirmed
- Whether it is visible to visitors
- Whether it is useful for navigation
- Related cabinet, drawer, island, or fixture, if known
- Any uncertainty or notes for the reviewer
A maintainer can turn these notes into a correct entry in geojson/amenity.geojson.
Copy an existing amenity entry that is similar to the one you want to add, paste it as a new feature, and then change only the values that describe the new amenity.
Be careful to:
- Give the new feature a unique
id. - Keep coordinates in this order:
[longitude, latitude]. - Use a point marker for the amenity location.
- Keep the existing
level_idunless the amenity is on a different floor. - Keep commas between features.
- Test the full file in geojson.io before submitting.
If you are not sure what a field means, do not guess. Add a plain-language note instead, or ask a maintainer to review it.
Openings are places where a person can pass through a boundary, such as exterior entrances, doors, and internal thresholds between spaces. Follow the same process as Amenities, but record them as openings.
Submit your map edit as a GitHub Issue by clicking "Issues" and using the provided "Add or correct a map location" template.

