lynx-haversine-geolocation
v1.0.1
Published
Geolocation history management hook for Lynx/ReactLynx
Downloads
3
Readme
Lynx-haversine-geolocation

A ReactLynx hook (React18/19) to manage a geolocation history, using the Haversine formula to filter out nearby points and optimize tracking.
Works in LynxJS apps (iOS/Android/Web) and regular React projects.
🚀 Installation
npm install lynx-haversine-geolocationor with yarn:
yarn add lynx-haversine-geolocation✨ Features
📍 Calculate distances in meters using the Haversine formula
🔄 Manage a geolocation history
🎯 Automatically filter out points that are too close to the previous one
🪶 Compatible with React 18+ and @lynx-js/react
🔧 Example Usage (ReactLynx)
import { useCallback, useEffect, useState } from '@lynx-js/react';
import {
useGeolocationManager,
type TLocation,
type TLocationHistory,
} from 'lynx-haversine-geolocation';
import './App.css';
import arrow from './assets/arrow.png';
import lynxLogo from './assets/lynx-logo.png';
import reactLynxLogo from './assets/react-logo.png';
export function App() {
const [alterLogo, setAlterLogo] = useState(false);
const [latitude, setLatitude] = useState(48.8566);
const [longitude, setLongitude] = useState(2.3522);
const STORAGE_KEY = 'geolocations';
const { history, init, addLocation } = useGeolocationManager({
distanceThreshold: 100,
loadHistory: async () => {
const data = localStorage.getItem(STORAGE_KEY);
return data ? (JSON.parse(data) as TLocationHistory) : null;
},
saveHistory: async (history: TLocationHistory) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
},
});
useEffect(() => {
console.info('Hello, ReactLynx');
init();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onTapLogo = useCallback(() => {
setAlterLogo(!alterLogo);
}, [alterLogo]);
const handleAdd = () => {
const newLocation: TLocation = {
coords: {
latitude,
longitude,
accuracy: 5,
altitude: 35,
altitudeAccuracy: 1,
heading: 0,
speed: 0,
},
mocked: false,
timestamp: Date.now(),
};
addLocation(newLocation);
};
return (
<page>
<view className="Background" />
<view className="App">
{/* Banner avec logo */}
<view className="Banner">
<view className="Logo" bindtap={onTapLogo}>
{alterLogo ? (
<image src={reactLynxLogo} className="Logo--react" />
) : (
<image src={lynxLogo} className="Logo--lynx" />
)}
</view>
<text className="Title">React</text>
<text className="Subtitle">on Lynx</text>
</view>
{/* Contenu principal */}
<view className="Content">
<image src={arrow} className="Arrow" />
<text className="Description">Tap the logo and have fun!</text>
{/* Formulaire de géolocalisation */}
<view
style={{
marginTop: '20px',
padding: '16px',
backgroundColor: '#222',
borderRadius: '8px',
zIndex: 9,
}}
>
<text
style={{
fontSize: '18px',
fontWeight: 'bold',
marginBottom: '12px',
}}
>
Geolocation History: {history.locations.length} positions
</text>
<view
style={{
flexDirection: 'row',
alignItems: 'center',
marginBottom: '12px',
}}
>
<text>Latitude: {latitude}</text>
<view
style={{
display: 'flex',
flexDirection: 'row',
marginBottom: '12px',
alignItems: 'center',
justifyContent: 'space-around',
width: '100%',
}}
>
<view
bindtap={() => setLatitude((lat) => lat + 0.01)}
style={{
padding: '4px',
backgroundColor: '#333',
marginRight: '4px',
}}
>
<text style={{ color: '#fff' }}>+0.01</text>
</view>
<view
bindtap={() => setLatitude((lat) => lat - 0.01)}
style={{ padding: '4px', backgroundColor: '#333' }}
>
<text style={{ color: '#fff' }}>-0.01</text>
</view>
</view>
<view
style={{
display: 'flex',
flexDirection: 'row',
marginBottom: '12px',
alignItems: 'center',
justifyContent: 'space-around',
}}
>
<view
bindtap={() => setLatitude((lat) => lat + 0.0001)}
style={{
padding: '4px',
backgroundColor: '#333',
marginRight: '4px',
}}
>
<text style={{ color: '#fff' }}>+0.0001</text>
</view>
<view
bindtap={() => setLatitude((lat) => lat - 0.0001)}
style={{ padding: '4px', backgroundColor: '#333' }}
>
<text style={{ color: '#fff' }}>-0.0001</text>
</view>
</view>
<text>Longitude: {longitude}</text>
<view
style={{
display: 'flex',
flexDirection: 'row',
marginBottom: '12px',
alignItems: 'center',
justifyContent: 'space-around',
}}
>
<view
bindtap={() => setLongitude((lon) => lon + 0.01)}
style={{
padding: '4px',
backgroundColor: '#333',
marginRight: '4px',
}}
>
<text style={{ color: '#fff' }}>+0.01</text>
</view>
<view
bindtap={() => setLongitude((lon) => lon - 0.01)}
style={{ padding: '4px', backgroundColor: '#333' }}
>
<text style={{ color: '#fff' }}>-0.01</text>
</view>
</view>
<view
style={{
display: 'flex',
flexDirection: 'row',
marginBottom: '12px',
alignItems: 'center',
justifyContent: 'space-around',
}}
>
<view
bindtap={() => setLongitude((lon) => lon + 0.0001)}
style={{
padding: '4px',
backgroundColor: '#333',
marginRight: '4px',
}}
>
<text style={{ color: '#fff' }}>+0.0001</text>
</view>
<view
bindtap={() => setLongitude((lon) => lon - 0.0001)}
style={{ padding: '4px', backgroundColor: '#333' }}
>
<text style={{ color: '#fff' }}>-0.0001</text>
</view>
</view>
<view
bindtap={handleAdd}
style={{
padding: '8px',
backgroundColor: '#4CAF50',
borderRadius: '4px',
}}
>
<text style={{ color: 'white' }}>Add New Position</text>
</view>
</view>
<view>
{history.locations.map((loc, idx) => (
<text key={idx}>
Lat: {loc.coords.latitude}, Lon: {loc.coords.longitude}, Time:{' '}
{new Date(loc.timestamp).toLocaleTimeString()}
</text>
))}
</view>
</view>
</view>
<view style={{ flex: 1 }} />
</view>
</page>
);
}
📖 API
useGeolocationManager(options)
Options
distanceThreshold?: number→ Threshold in meters to consider two positions the same (default:100)loadHistory: () => Promise<TLocationHistory | null>→ Function to load the geolocation historysaveHistory: (history: TLocationHistory) => Promise<void>→ Function to save the history
Returns
history: TLocationHistory→ List of stored positionsinit: () => Promise<void>→ Initialize/load historyaddLocation: (location: TLocation) => Promise<void>→ Add a new position with Haversine filtering
🧩 Types
The following TypeScript types are used in react-haversine-geolocation:
TLocation
export type TLocation = {
coords: {
accuracy: number;
altitude: number;
altitudeAccuracy: number;
heading: number;
latitude: number;
longitude: number;
speed: number;
};
mocked: boolean;
timestamp: number;
};coords: GPS coordinates and related data.
mocked: whether the location is mocked or real.
timestamp: the time the location was recorded (milliseconds since epoch).
TLocationHistory
export type TLocationHistory = {
locations: TLocation[];
};- locations: an array of TLocation objects, representing the recorded history.
GeolocationOptions
export type GeolocationOptions = {
distanceThreshold?: number; // threshold in meters to consider two positions identical
loadHistory: () => Promise<TLocationHistory | null>; // function to load saved history
saveHistory: (history: TLocationHistory) => Promise<void>; // function to save history
};distanceThreshold (optional): meters to consider two positions the same (default: 100).
loadHistory: function that returns the saved history or null.
saveHistory: function that saves the history (can be localStorage, AsyncStorage, SQLite, etc.).
📐 Distance Calculation (Haversine)
The distance between two GPS points is calculated using the Haversine formula, which determines the great-circle distance between two points on a sphere using their latitude and longitude.

This formula is useful for:
Filtering out GPS points that are too close to each other.
Reducing noise in location tracking.
Optimizing storage and performance by avoiding redundant points.
Function signature:
getDistanceInMeters(lat1, lon1, lat2, lon2): numberParameters:
lat1, lon1 – latitude and longitude of the first point in decimal degrees.
lat2, lon2 – latitude and longitude of the second point in decimal degrees.
Returns: distance in meters.
Example
import { getDistanceInMeters } from "lynx-haversine-geolocation";
const distance = getDistanceInMeters(48.8566, 2.3522, 40.7128, -74.006);
console.log(`Distance: ${distance.toFixed(2)} meters`);📜 License
MIT
