Free, lightweight offline hiking maps for Norway. A spiritual successor to the discontinued Hvor? app from Kartverket, with topographic maps from Kartverket, OpenStreetMap, and more. Available for Android and iOS, and always free. See RELEASES.md for what's new in each release.
- Offline base maps: Kartverket (topo, toporaster, nautical charts), MapAnt (LiDAR-based orienteering maps), OpenTopoMap, OpenStreetMap, and Sentinel-2 satellite imagery
- Overlays: waymarked hiking trails, OSM paths and tractor roads (the detail MapAnt lacks), slope steepness from NVE (with or without avalanche runout zones), mobile call coverage from erdetdekning.no (all operators or one of Telenor, Telia and Ice), a coordinate grid, and a crosshair with live elevation, slope and coverage readout
- GPS recording that keeps running with the screen locked, with live time, distance, ascent, and average speed on the map
- Real-time location sharing so friends and family can follow your trip live
- Follow several friends' live tracks on your own map, each in its own colour, with a banner that zooms to one friend or all of them, and a local nickname per friend so you see names instead of ids
- Share named points while live-tracking (meeting spot, parked car, hunting post); followers see them live and can save a copy, and you can move or delete your own
- Save, revisit, import (GPX, FIT and GeoJSON, several files or a whole zip at once, optionally straight into a folder), and export (GPX) tracks, compatible with other hiking and navigation apps
- Connect your Strava account (using your own Strava API app) to browse and import your planned routes
- Import from a Garmin Connect, Komoot, or ut.no activity URL, or a direct GPX/FIT link
- Organize saved tracks into folders, with move, rename, remove, per-folder select all/none, and undo
- Navigate along a saved track with remaining distance, elevation, an off-course arrow pointing back to the route, and a reverse-direction toggle, keeping any other tracks you're viewing visible alongside the route; tap the route to see its altitude chart
- Show several tracks at once, each in its own color (or pick a custom color per saved track), and tap one to focus it
- Elevation chart you can scrub to mark the matching point on the map, for a focused track or the route you are navigating
- Crop the start and end of a saved track
- Place search
- Distance measurement with the ruler or a two-finger gesture
- Off-trail hiking routes between ruler points: avoids cliffs, lakes and glaciers, prefers paths, and marks river and stream crossings
- Save and manage points of interest, each with an optional description and colour, and import GPX or GeoJSON waypoints (several files or a whole zip at once, ticking off which ones to keep)
- Coordinate readout in UTM, MGRS, latitude/longitude, or DMS
- Heading cone on the location dot showing which way the phone faces
- Compass rose that points the map north on tap, and locks it there on the next tap; long press (or Settings) switches to a larger dial with degree marks and heading
Kotlin Multiplatform sharing navigation, geometry, storage, and Compose Multiplatform UI between Android, iOS and a macOS desktop build. Maps render with MapLibre, and tracks and points are stored locally with SQLDelight. The optional live-tracking backend under web/ runs on Bun.
Requires Android 13 (API 33) or newer.
- Copy
local.properties.exampletolocal.propertiesand fill in your values - Place your
google-services.jsoninapp/(from the Firebase console) ./gradlew assembleDebug- Install and launch on a connected device:
./gradlew :app:runDebug(setANDROID_SERIALto pick one)
- Set up
local.propertiesas above (the shared module build needs it) - Build the shared framework:
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64 - Open
iosApp/Where.xcodeprojin Xcode - Wait for Xcode to fetch MapLibre via SPM on first open
- Select an iPhone simulator and press Run (Cmd+R)
The Gradle build phase in Xcode (embedAndSignAppleFrameworkForXcode) compiles the shared Kotlin framework automatically, so after the initial setup you can iterate from Xcode directly.
To verify just the shared framework without Xcode:
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64 # simulator
./gradlew :shared:linkDebugFrameworkIosArm64 # device
A map viewer that also follows live online tracks; it has no GPS, so it does not record or share its own position. The map renders through the Java FFM API, which needs a JDK 25 (the Gradle toolchain fetches BellSoft Liberica 25). See docs/release-desktop.md for signing.
- Set up
local.propertiesas above (the shared module build needs it) - Run it:
./gradlew :desktop:run - Build an unsigned DMG:
./gradlew :desktop:packageDmg(output underdesktop/build/compose/binaries/main/dmg/)
Data lives in ~/.where (database, preferences, map cache).
Crashlytics needs a build's dSYM to turn addresses into function names. Releases handle this in two
places: the Upload Crashlytics dSYMs build phase in Where.xcodeproj runs on every Release build,
and fastlane ios build uploads the dSYM gym produced. The shared Kotlin framework is static, so
Kotlin/Native frames live in the app's own dSYM — there is no separate framework dSYM to upload.
To check what actually arrived: Firebase console > Crashlytics > gear icon > dSYMs, which lists uploaded UUIDs and flags versions with missing symbols.
Release CI keeps the dSYM in the release-ipa workflow artifact for 90 days, so a recent build's
symbols can be re-uploaded by hand if the automatic upload failed. Open iosApp/Where.xcodeproj in
Xcode once first, so SPM resolves the package and the upload-symbols path below exists:
DD=$(ls -d ~/Library/Developer/Xcode/DerivedData/Where-* | head -1)
"$DD/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols" \
-gsp iosApp/Where/GoogleService-Info.plist -p ios <path to the unzipped .dSYM>
Dragging the .dSYM onto the Firebase console dSYMs page does the same thing. Symbolication is
retroactive: crashes already collected for that build redraw with real frames a few minutes later.
Their dSYM is usually gone for good. App Store Connect only offers a dSYM download for builds
submitted with bitcode, which Xcode dropped in 14 — there is no Download dSYM under Build
Metadata for a modern build, because Apple ships the binary as archived and has no dSYM of its own
to hand back. uploadSymbols: true sends symbols to Apple; it does not get them back. For a CI
release the only copy lived on the runner and went away with it.
Two things still work without a dSYM:
- Xcode > Window > Organizer > Crashes shows App Store crashes symbolicated by Apple, using the symbols submission already sent. Independent of Crashlytics, and covers builds from before this.
- Reproduce it on a local build. A crash you can trigger is worth more than a symbolicated report: running the same commit from Xcode gives a live stack plus the exception message.
See web/README.md.
GNU General Public License v2.0 or later (GPL-2.0-or-later).
