Skip to content

Instantly share code, notes, and snippets.

@ptts
Created December 8, 2024 15:13
Show Gist options
  • Save ptts/8514cfa4cf3ae73d8b126d5325c8e519 to your computer and use it in GitHub Desktop.
Save ptts/8514cfa4cf3ae73d8b126d5325c8e519 to your computer and use it in GitHub Desktop.
import { useQuery } from "@tanstack/react-query";
export const GeoLocationErrorCode = {
NOT_SUPPORTED: "NOT_SUPPORTED",
PERMISSION_DENIED: "PERMISSION_DENIED",
POSITION_UNAVAILABLE: "POSITION_UNAVAILABLE",
TIMEOUT: "TIMEOUT",
UNKNOWN: "UNKNOWN",
} as const;
type GeoLocationErrorCode =
(typeof GeoLocationErrorCode)[keyof typeof GeoLocationErrorCode];
class GeoLocationError extends Error {
public code: GeoLocationErrorCode;
constructor(code: GeoLocationErrorCode) {
super(code);
this.name = "GeoLocationError";
this.code = code;
}
}
export function useGeoLocation({
options,
}: { options?: PositionOptions } = {}) {
const query = useQuery<GeolocationPosition, GeoLocationError>({
queryKey: ["geolocation", options],
staleTime: 1000 * 60 * 30, // 30 minutes
queryFn: () => getCurrentPosition(),
enabled: false,
});
const isSupported = checkIsGeolocationSupported();
const requestLocation = () => query.refetch();
const calculateDistanceToCurrentPosition = ({
lat,
lon,
}: {
lat: number;
lon: number;
}) => {
if (!query.data) {
return null;
}
return calculateDistance(
query.data.coords.latitude,
query.data.coords.longitude,
lat,
lon,
);
};
return {
query,
requestLocation,
isSupported,
calculateDistanceToCurrentPosition,
};
}
function checkIsGeolocationSupported() {
return typeof navigator !== "undefined" && !!navigator.geolocation;
}
/**
* Returns the distance between two points in kilometers
*/
function calculateDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number,
): number {
const toRad = (value: number) => (value * Math.PI) / 180;
const R = 6371; // Radius of the Earth in kilometers
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) *
Math.cos(toRad(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c; // Distance in kilometers
return distance;
}
export function getCurrentPosition(
options?: PositionOptions,
): Promise<GeolocationPosition> {
const defaultOptions: PositionOptions = {
enableHighAccuracy: false,
timeout: 5_000,
maximumAge: 1_000 * 60 * 60, // 1 hour
};
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new GeoLocationError(GeoLocationErrorCode.NOT_SUPPORTED));
}
navigator.geolocation.getCurrentPosition(
resolve,
(error) => {
switch (error.code) {
case error.PERMISSION_DENIED: {
reject(
new GeoLocationError(GeoLocationErrorCode.PERMISSION_DENIED),
);
break;
}
case error.POSITION_UNAVAILABLE: {
reject(
new GeoLocationError(GeoLocationErrorCode.POSITION_UNAVAILABLE),
);
break;
}
case error.TIMEOUT: {
reject(new GeoLocationError(GeoLocationErrorCode.TIMEOUT));
break;
}
default: {
reject(new GeoLocationError(GeoLocationErrorCode.UNKNOWN));
break;
}
}
},
{ ...defaultOptions, ...options },
);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment