Easy-to-manage touch screens
Free, open-source touch screens. Guided USB setup, a drag-and-drop editor and local control. Made for Home Assistant.
See how much direct sun reaches every unit in Singapore HDB BTO projects, hour by hour, in 3D, with nearby resale prices and an ML resale estimate.
LifestyleJavaScript · PythonGitHub @PerryongOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Pick a unit in an HDB BTO project and see how much direct sun reaches its windows, hour by hour and for any date. The 3D model is built from HDB's own site plan, with the real neighbouring blocks, schools, malls and MRT stations around it, plus nearby resale prices and a machine-learning estimate of what the unit would resell for.
Live: https://singapore-housing-lab.vercel.app · 29 projects (every BTO launched from Oct 2025 to Jun 2026 except Redhill Peaks II, plus the 7 upcoming Oct 2026 sites)
test/sky_test.mjs).flowchart LR
subgraph Offline["Offline pipeline (Python, Blender)"]
A[HDB site plan PDF] -->|OpenCV: scale bar, colour regions, OCR| B[trace.json: blocks, stacks, flat types]
B --> C[project.json]
D[data.gov.sg: HDB footprints, property info] -->|register to the plan| C
E[OpenStreetMap / OneMap: malls, MRT, schools] --> C
T[LTA DataMall: bus stops, routes, frequencies] -->|transit graph, door-to-hub search| C
B -->|headless Blender| F[model.glb]
G[HDB brochure floor plans] -->|OpenCV + Blender| H[plans3d/*.glb]
I[data.gov.sg: 240k resale sales] -->|scikit-learn gradient boosting| J[value estimates]
J --> C
end
subgraph Browser["Browser (no build step)"]
C --> K[app.js: state and events]
F --> L[three.js scene]
K --> L
K --> M[panel HTML: pure, unit-tested modules]
N[sun.js / moon.js: NOAA and Meeus maths] --> K
end
| Decision | Why |
|---|---|
| Split train and test by time, not at random | The model prices the future from the past. A random split leaks each block's own recent sales and flatters the error. |
| Compare with town-median and linear baselines | A model error means little without a simple baseline beside it. |
| Never place a block at a guessed location | Unlinked blocks are dropped rather than drawn wrong; a wrong shadow is worse than none. |
| Bake the models in Blender offline | The browser downloads a small, ready-made GLB instead of building geometry at load time. |
| Merge the surroundings into one mesh per kind | About 300 extra buildings cost 3 draw calls, so the frame rate stays at 120 fps. |
| Don't publish HDB brochures and site plans | Copyright: the app links to HDB's own pages, and only the floor-plan crops are published. |
18 test scripts run in CI on every push (npm test, the same command locally). The parts that need HDB's brochures,
site plans or large data.gov.sg files print "skipped" in CI and only run on a machine that has them:
projects/index.json catalogue (node tools/make_index.js)
projects/<id>/project.json site, plan scale/north, blocks, stacks, unit types, neighbouring blocks
projects/<id>/trace.json what was read off HDB's site plan (BTOs)
projects/<id>/siteplan.jpg site plan laid on the ground
projects/<id>/model.glb generated — do not edit (node tools/build.js projects/<id>)
web/ the app (no build step): open /web/ or /web/?p=<id>
blender/build_model.py layout.json -> study model GLB (unit bodies carry a _UNIT attribute for colouring)
tools/ derive, build, trace_siteplan, find_scalebar, grid_tiles, fetch_context,
make_bto_project, make_upcoming, make_index
test/sun.test.js ported logic must reproduce the original Thomson Reserve table (kept out of the catalogue)
python3 -m http.server 8781 # then open http://localhost:8781/web/
npm test
reference/hdb/siteplans/<slug>.jpg and an entry in reference/hdb/launched.json.python3 tools/find_scalebar.py <plan> x0 y0 x1 y1 → scale bar ends; write projects/<slug>/trace.json
(planBox, scale, legend swatch points per flat type).python3 tools/trace_siteplan.py projects/<slug> --regions → regions.png; name each stack by region id
("r12", or "r12.a"/"r12.b" for the left/right half of a shared region) or a full-image point; add
types overrides where colour matching is ambiguous. Run without --regions and check trace-debug.png and
the flat-type counts against HDB's block table.python3 tools/make_bto_project.py <slug> (approximate georeference + neighbouring HDB blocks from data.gov.sg/OneMap).python3 tools/register_context.py projects/<slug> --apply fits the footprints onto
the existing blocks drawn on the sheet (accepted only when ≥65% of on-sheet edges land on drawn outlines). If it
can't, pin with printed block numbers — python3 tools/anchor_fit.py projects/<slug> 269:298,964 266:329,1436
(block : plan pixel of its label) — then register_context.py --range 25 --apply. Check with
python3 tools/overlay_context.py projects/<slug> out.png; python3 tools/drop_overlaps.py projects/<slug> removes
demolished blocks under the new towers.python3 tools/trace_facilities.py projects/<slug> reads the numbered estate facilities off the site plan
(legend + markers, OCR); python3 tools/fetch_amenities.py projects/<slug> adds nearby MRT/LRT, bus stops,
schools, preschools, hawker centres, supermarkets, clinics, parks, libraries and CCs from
reference/amenities/all.json (built by python3 tools/build_amenities.py from data.gov.sg + OneMap).python3 tools/fetch_transit.py caches LTA DataMall bus data in reference/transit/ from the weekly
sg-bus-data files (no LTA key needed; --lta fetches straight from DataMall
instead, with a free DataMall key as LTA_KEY=...
in a git-ignored .env) and ~45 MB of OSM bus/rail route relations from Overpass (--osm refreshes only those; works offline: it builds from a local copy of sg-bus-data's and skips OSM), then
writes and from both.
Live arrivals for the selected stop come from the separate API (github.com/Perryong/sg-bus-api) (LTA BusArrival), polled every
30 s, from ( in ); locally, run that API on to see live minutes.Projects live outside ~/Documents: macOS revokes access when Blender is launched on files there.
HDB's site plans, sales brochures and launch annexes are not committed. After cloning, re-create them locally:
download site plans (reference/hdb/launched.json lists the projects), then run tools/trace_siteplan.py (writes
siteplan.jpg). Until then the app runs without the site-plan overlay. Open data (HDB buildings / property
information, amenity layers, resale prices) is re-downloaded from data.gov.sg by the tools.
Each stack's floor-plan crop (projects/<id>/layouts/, cut from HDB's sales brochure by tools/extract_layouts.py)
is committed so the deployed site can show it; the unit card also links to the page in HDB's own published brochure
(project.json["brochure"], assets.hdb.gov.sg). Floor plans © Housing & Development Board.
--from-local DIRdata/python3 tools/build_commute.py projects/*/projects/<slug>/transit.jsonweb/rail.jsonsg-bus-apiPROD_APIweb/live.js:8010node tools/build.js projects/<slug>, node tools/make_index.js.