Inspiration
We were outside the hackathon venue and watched someone sprint toward a portable washroom, phone out, visibly regretting not knowing where the nearest one was five minutes earlier. That was it! That was the whole spark. We had the name before we had the idea: "Compiss". Once you have a name that good, you're contractually obligated to build the app.
What it does
COMPISS is a compass. Not a map, not a list, not a route, but one arrow. Open it, and the arrow swings to point at the nearest toilet. Walk, and it corrects in real time. Rotate the phone in place, and the arrow holds its bearing. Get close enough and it turns into a checkmark. Double-tap to skip to the next-closest one if the first is locked, gross, or paywalled.
How we built it
It's a single HTML file without a framework, no build step, no backend. Two data sources drive it:
- Toilet locations. They come from OpenStreetMap. A one-off Node script queries the Overpass API for every
amenity=toiletsin the city, strips out private ones, collapses duplicate mappings of the same toilet, and writes the result to a static JSON file. At runtime the app never touches the network for toilet data and instead just executes one fetch of a small local file. - Position and heading: come from the browser's Geolocation and DeviceOrientation APIs. We compute bearing and distance between the user and each candidate toilet using the Haversine formula, then rank by distance with a penalty added for customers-only locations so a free toilet slightly farther away wins over a locked one slightly closer.
The needle itself is just SVG rotated with CSS transforms, driven by a render loop that only repaints when something changes.
Challenges we ran into
The compass was the hard part, by far. iOS and Android don't agree on what "heading" even means: iOS hands you true north directly, Android makes you build a rotation matrix out of raw alpha/beta/gamma values yourself. Screen rotation has to be subtracted out or the arrow is 90° off the moment you go landscape. Holding the phone flat versus holding it upright are two different math problems, so we blend between them based on tilt. And naively averaging compass degrees is a trap: 359deg and 1deg average to 180°, which sends the needle across the entire dial instead of nudging one degree. We ended up smoothing in vector space (cos/sin) instead of raw degrees, and accumulating rotation with the shortest-path delta so the needle never unwinds through a full spin.
On the data side, Overpass rate-limits aggressively, so the scraper retries across mirrors with backoff, and OSM being crowdsourced means the same toilet is very often mapped twice.
What we learned
Mostly: that phone compasses are a lot less trustworthy than they feel, and that solving that well matters more to the feel of an app than any amount of extra UI. We also learned that constraint is a feature, cutting every screen down to one arrow forced a lot of good decisions we wouldn't have made if we'd let ourselves build a map view "just in case."
Log in or sign up for Devpost to join the conversation.