Geolocation
Capacitor’s Geolocation plugin gives you the user’s position on iOS, Android, and the web from one API. Handle permissions, fall back gracefully when the device denies access, and remember that background location requires additional capabilities and review.
plugin, permissions, watch, accuracy
EXAMPLE
// 1) Install
// npm install @capacitor/geolocation
// npx cap sync
import { Component, signal } from '@angular/core';
import { Geolocation, Position, PositionOptions } from '@capacitor/geolocation';
// 2) One-shot location
@Component({
selector: 'app-where',
template: `
<ion-button (click)="locate()">Locate me</ion-button>
<p *ngIf="pos()">{{ pos()!.coords.latitude }}, {{ pos()!.coords.longitude }} ±{{ pos()!.coords.accuracy }}m</p>
<p *ngIf="error()">{{ error() }}</p>
`,
})
export class WherePage {
pos = signal<Position | null>(null);
error = signal<string | null>(null);
async locate() {
try {
const status = await Geolocation.checkPermissions();
if (status.location !== 'granted') {
const after = await Geolocation.requestPermissions({ permissions: ['location'] });
if (after.location !== 'granted') { this.error.set('Permission denied'); return; }
}
const p = await Geolocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 10_000,
maximumAge: 30_000,
});
this.pos.set(p);
} catch (e: any) {
this.error.set(e?.message ?? 'Could not get location');
}
}
}
// 3) Permissions — Info.plist (iOS)
// <key>NSLocationWhenInUseUsageDescription</key>
// <string>We use your location to find nearby venues.</string>
//
// For background access ALSO add:
// <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
// <string>We track your location for delivery updates.</string>
//
// Capabilities -> Background Modes -> Location updates (for background tracking)
// 4) Permissions — Android Manifest
// <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
// <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
// <!-- For background -->
// <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
// <!-- On Android 12+, Bluetooth scanning may also need a separate permission -->
// 5) Watching position changes
let watchId: string;
async function startWatch() {
watchId = await Geolocation.watchPosition({
enableHighAccuracy: true,
timeout: 30_000,
}, (position, err) => {
if (err) { console.error(err); return; }
if (position) {
console.log('moved to', position.coords.latitude, position.coords.longitude);
}
});
}
async function stopWatch() {
if (watchId) await Geolocation.clearWatch({ id: watchId });
}
// 6) Accuracy and battery trade-off
// • enableHighAccuracy: true — GPS-grade, more battery
// • enableHighAccuracy: false — network / Wi-Fi positioning, cheaper, less precise
// • Use 'maximumAge' to accept a slightly stale cached fix instead of refresh
// • timeout = how long to wait before giving up
// 7) Distance calculation (Haversine)
function haversine(a: { lat: number; lng: number }, b: { lat: number; lng: number }) {
const R = 6371e3;
const φ1 = (a.lat * Math.PI) / 180;
const φ2 = (b.lat * Math.PI) / 180;
const dφ = ((b.lat - a.lat) * Math.PI) / 180;
const dλ = ((b.lng - a.lng) * Math.PI) / 180;
const s = Math.sin(dφ / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(dλ / 2) ** 2;
return 2 * R * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s)); // metres
}
// 8) Reverse geocode — use a service (Mapbox, Google, OpenStreetMap Nominatim)
async function reverseGeocode(lat: number, lng: number) {
const r = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`);
return r.json(); // { display_name, address: { ... } }
}
// Respect each provider's usage policy / TOS.
// 9) Forward geocode — address → coords
async function forwardGeocode(query: string) {
const r = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=1`);
const arr = await r.json();
return arr[0] ? { lat: parseFloat(arr[0].lat), lng: parseFloat(arr[0].lon) } : null;
}
// 10) Display on a map
// Common choices:
// • Mapbox GL JS — beautiful tiles, free tier
// • Leaflet — open-source, simple, works on web + Capacitor
// • Google Maps — full-featured, paid above the free tier
// • OpenLayers — robust, lots of layer types
// For native maps: @capacitor/google-maps or @capacitor-community/capacitor-googlemaps-native
// 11) Background tracking caveats
// • iOS: app must call 'startUpdatingLocation' while in foreground + declare background modes;
// continuous background location triggers a 'using background location' warning to users
// • Android 10+: requires 'while-in-use' first; 'all the time' is a second prompt
// • Be honest with users — explain WHY background is needed
// • Battery: schedule updates at a low cadence; use SignificantLocationChanges on iOS
// • For delivery / ride-share apps, use a dedicated background plugin (e.g. background-geolocation)
// 12) Web fallback
// On web, Geolocation uses navigator.geolocation. HTTPS is required (except localhost).
// Permissions are per-origin; once denied, the user must clear site settings to grant again.
// 13) Mocking + tests
// • iOS Simulator: Debug → Location → choose a city or custom coord
// • Android Emulator: extended controls → location → set Lat/Lng or play a GPX track
// • Unit tests: stub @capacitor/geolocation in your test setup
// 14) UX patterns
// • Show a non-blocking explanation BEFORE prompting (rationale view)
// • Provide a 'use approximate location' option for users who decline precise
// • Don't poll location aggressively — battery + privacy
// • Honor 'precise off' on iOS 14+ / Android 12+ — accuracy may be ±5 km
// 15) Common bugs
// • Missing usage description → app crashes / silent denial on iOS
// • Web build but no HTTPS → 'User denied geolocation' from Chrome
// • Background mode declared but no actual background updates → review board rejects iOS app
// • Polling every second → battery drain + user backlash
// • Forgetting to clearWatch → multiple callbacks accumulate on hot reload
// • Hard-coded high-accuracy on web → fewer browsers honor it; results still cell-tower-grade
// • Asking for 'always' before 'when-in-use' on Android — flow rejected; ask incrementally
// • Sending raw coords to a third-party service without anonymising / aggregating → privacy issue
Why it matters
Capacitor’s Geolocation API works the same on web, iOS, and Android — but the permission story differs. Declare usage strings up front, ask for “when in use” first, justify background access with a clear explanation, and tune enableHighAccuracy, maximumAge, and watch cadence to balance battery against precision.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { Geolocation } from '@capacitor/geolocation';
const pos = await Geolocation.getCurrentPosition();
console.log(pos.coords.latitude, pos.coords.longitude);
Try it Yourself »
Discussion
Loading…