Skip to content

Commit e4c4115

Browse files
authored
Merge pull request #7 from Eternal-Encoders/gps
First version Gps
2 parents d27747a + 97502aa commit e4c4115

File tree

10 files changed

+289
-19
lines changed

10 files changed

+289
-19
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from "react";
2+
import { Circle } from "react-konva";
3+
4+
interface GpsPointProps {
5+
coords: {x: number, y: number}
6+
}
7+
8+
function GpsPoint({ coords }: GpsPointProps) {
9+
return (
10+
<Circle
11+
x={coords.x}
12+
y={coords.y}
13+
width={30}
14+
height={30}
15+
fill={'#000000'}
16+
/>
17+
)
18+
}
19+
20+
export default React.memo(GpsPoint);

src/components/konva-components/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import Audience from './audience';
22
import AudienceIcon from './audience-icon';
33
import AudienceText from './audience-text';
44
import Path from './path';
5+
import GpsPoint from './gpsPoint';
56

67
export {
78
Audience,
89
AudienceIcon,
910
AudienceText,
10-
Path
11+
Path,
12+
GpsPoint
1113
}

src/components/map/to-render-map/MapHook.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function useMapHook({ mapSize, stageRef }: MapHookProps) {
2929
let lastDist = 0;
3030
let lastAngle: number | null = null;
3131

32-
/* ON RESIZE */
32+
/* ON MOUNT - SET RESIZE */
3333
useEffect(() => {
3434
const handleResize = () => {
3535
setWidth(window.innerWidth);

src/components/map/to-render-map/ToRenderMap.tsx

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@ import { useGetFloorQuery } from '../../../features/api/apiSlice';
66
import { selectFloor } from '../../../features/floor/floorSlice';
77
import { selectSearchPoints } from '../../../features/pointsSearch/pointsSearchSlice';
88
import { useAppSelector } from '../../../store/hook';
9-
import { IAuditorium, IService } from '../../../utils/interfaces';
9+
import { IAuditorium, IFloorGps, IService, UserGps } from '../../../utils/interfaces';
1010
import { getAudiences, getService } from '../../../utils/translateToKonva';
1111
import { useMapHook } from './MapHook';
1212

1313
import RoutePoint from '../../../widgets/routePoint/RoutePoint';
1414
import PathMap from '../path-map/PathMap';
1515
import style from './to-render-map-style.module.css';
16+
import { boundGpsToMap, createPredictor } from '../../../utils/gps';
17+
import { GpsPoint } from '../../konva-components';
1618

1719

1820
interface ToRenderMapProps {
1921
instFullName: string,
2022
firstFloor: number,
21-
lastFloor: number
23+
lastFloor: number,
24+
userLoc: UserGps
2225
}
2326

24-
function ToRenderMap({ instFullName }: ToRenderMapProps) {
27+
function ToRenderMap({ instFullName, userLoc }: ToRenderMapProps) {
2528
const stageRef = useRef<Konva.Stage>(null);
2629

2730
const currentFloor = useAppSelector(selectFloor)
@@ -31,20 +34,24 @@ function ToRenderMap({ instFullName }: ToRenderMapProps) {
3134
floor: currentFloor
3235
})
3336

37+
let mapGps: IFloorGps | null = null;
38+
let coordsPredictor: ((loc: UserGps) => {x: number, y: number}) | null = null;
3439
let floor: IAuditorium[] = []
3540
let services: IService[] = []
3641
let mapSize = {
3742
width: 0,
3843
height: 0
39-
}
44+
}
4045

4146
if (data) {
42-
floor = data.audiences
43-
services = data.service
47+
mapGps = data.gps;
48+
coordsPredictor = mapGps ? createPredictor(mapGps) : null;
49+
floor = data.audiences;
50+
services = data.service;
4451
mapSize = {
4552
width: data.width,
4653
height: data.height
47-
}
54+
};
4855
}
4956

5057
const {
@@ -55,7 +62,10 @@ function ToRenderMap({ instFullName }: ToRenderMapProps) {
5562
zoomStage,
5663
handleTouch,
5764
handleTouchEnd
58-
} = useMapHook({mapSize, stageRef})
65+
} = useMapHook({
66+
mapSize,
67+
stageRef
68+
});
5969

6070
return (
6171
<Stage
@@ -82,11 +92,18 @@ function ToRenderMap({ instFullName }: ToRenderMapProps) {
8292
{points.from &&
8393
points.from.floor === currentFloor &&
8494
points.from.institute === instFullName &&
85-
<RoutePoint point={ points.from } isStart={ true }/>}
95+
<RoutePoint point={ points.from } isStart={ true }/>
96+
}
8697
{points.to &&
8798
points.to.floor === currentFloor &&
8899
points.to.institute === instFullName &&
89-
<RoutePoint point={ points.to } isStart={ false }/>}
100+
<RoutePoint point={ points.to } isStart={ false }/>
101+
}
102+
{mapGps && coordsPredictor &&
103+
<GpsPoint
104+
coords={boundGpsToMap(coordsPredictor(userLoc), mapSize)}
105+
/>
106+
}
90107
</Layer>
91108
</Stage>
92109
)

src/pages/institutes-page/InstitutesPage.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import ToRenderMap from '../../components/map/to-render-map/ToRenderMap';
77
import { useGetInstituteByUrlQuery } from '../../features/api/apiSlice';
88
import { setContent } from '../../features/sideBar/sideBarSlice';
99
import { useAppDispatch } from '../../store/hook';
10-
import { SideBarContent } from '../../utils/interfaces';
10+
import { IInstituteGps, SideBarContent } from '../../utils/interfaces';
1111
import MapUI from '../../widgets/map-ui/MapUi';
12+
import { useGpsHook } from '../../shared/hooks/GpsHook';
13+
import { GPS_BUFFER } from '../../utils/const';
14+
import { approxGps, getClosestFloor } from '../../utils/gps';
15+
import { floorSet } from '../../features/floor/floorSlice';
1216

1317
function InstitutesPage() {
1418
const dispatch = useAppDispatch()
@@ -21,6 +25,19 @@ function InstitutesPage() {
2125
dispatch(setContent(SideBarContent.Empty))
2226
})
2327

28+
let instGps: IInstituteGps[] | null = null;
29+
30+
if (data) {
31+
instGps = data.gps;
32+
}
33+
34+
const userLoc = useGpsHook(
35+
Boolean(instGps),
36+
GPS_BUFFER
37+
)
38+
39+
dispatch(floorSet(instGps ? getClosestFloor(instGps, approxGps(userLoc)).floor : 1));
40+
2441
return (
2542
<>
2643
{!isLoading && data ?
@@ -35,8 +52,17 @@ function InstitutesPage() {
3552
name="viewport"
3653
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
3754
</Helmet>
38-
<MapUI instFullName={data.name} firstFloor={data.minFloor} lastFloor={data.maxFloor} />
39-
<ToRenderMap instFullName={data.name} firstFloor={data.minFloor} lastFloor={data.minFloor}/>
55+
<MapUI
56+
instFullName={data.name}
57+
firstFloor={data.minFloor}
58+
lastFloor={data.maxFloor}
59+
/>
60+
<ToRenderMap
61+
instFullName={data.name}
62+
firstFloor={data.minFloor}
63+
lastFloor={data.minFloor}
64+
userLoc={approxGps(userLoc)}
65+
/>
4066
</>:
4167
<>
4268
<Helmet>

src/shared/hooks/GpsHook.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useEffect, useState } from "react";
2+
import { UserGps } from "../../utils/interfaces";
3+
4+
export function useGpsHook(isGpsAv: boolean, bufferSize: number) {
5+
const [userLoc, setUserLoc] = useState(new Array<UserGps>(bufferSize));
6+
7+
/* ON MOUNT - GET CURRENT GPS */
8+
useEffect(() => {
9+
if (navigator.geolocation && isGpsAv) {
10+
navigator.geolocation.getCurrentPosition(
11+
(pos) => {
12+
setUserLoc(userLoc.fill({
13+
latitude: pos.coords.latitude,
14+
longtitude: pos.coords.longitude,
15+
altitude: pos.coords.altitude ? pos.coords.altitude : 268.4
16+
}))
17+
}
18+
)
19+
}
20+
}, []);
21+
22+
/* ON GPS UPDATE */
23+
useEffect(() => {
24+
if (navigator.geolocation && isGpsAv) {
25+
navigator.geolocation.watchPosition(
26+
(pos) => {
27+
userLoc.shift()
28+
userLoc.push({
29+
latitude: pos.coords.latitude,
30+
longtitude: pos.coords.longitude,
31+
altitude: pos.coords.altitude ? pos.coords.altitude : 268.4
32+
})
33+
setUserLoc(userLoc);
34+
},
35+
() => {},
36+
{
37+
enableHighAccuracy: true,
38+
}
39+
)
40+
}
41+
}, [navigator.geolocation]);
42+
43+
return userLoc;
44+
}

src/utils/const.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,19 @@ const QIUCK_TIPS_LIST = [
4444

4545
const PHONE_BREAKPOINT = 1200;
4646

47+
const EQUATORIAL_RADIUS = 6378160;
48+
49+
const POLAR_RADIUS = 6356774;
50+
51+
const GPS_BUFFER = 4;
52+
4753
export {
4854
InstColors,
49-
InstLinks, PHONE_BREAKPOINT, QIUCK_TIPS_LIST
55+
InstLinks,
56+
PHONE_BREAKPOINT,
57+
QIUCK_TIPS_LIST,
58+
EQUATORIAL_RADIUS,
59+
POLAR_RADIUS,
60+
GPS_BUFFER
5061
};
5162

src/utils/gps.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { EQUATORIAL_RADIUS, POLAR_RADIUS } from "./const";
2+
import { IFloorGps, IInstituteGps, UserGps, UserLocation } from "./interfaces";
3+
4+
function approxGps(data: UserGps[]): UserGps {
5+
const sumOfData = data.reduce(
6+
(e, accum) => ({
7+
latitude: accum.latitude + e.latitude,
8+
longtitude: accum.longtitude + e.longtitude,
9+
altitude: accum.altitude + e.altitude
10+
}),
11+
{
12+
latitude: 0,
13+
longtitude: 0,
14+
altitude: 0
15+
}
16+
);
17+
18+
return {
19+
latitude: sumOfData.latitude / data.length,
20+
longtitude: sumOfData.longtitude / data.length,
21+
altitude: sumOfData.altitude / data.length
22+
}
23+
}
24+
25+
function boundGpsToMap(
26+
loc: {x: number, y: number},
27+
mapSize: {width: number, height: number})
28+
: {x: number, y: number} {
29+
return {
30+
x: Math.min(mapSize.width, Math.max(0, loc.x)),
31+
y: Math.min(mapSize.height, Math.max(0, loc.y))
32+
}
33+
}
34+
35+
function deg2rad(x: number): number {
36+
return x * (Math.PI / 180);
37+
}
38+
39+
function getClosestFloor(floors: IInstituteGps[], loc: UserGps): IInstituteGps {
40+
let min_index = 1;
41+
let min_value = Number.MAX_VALUE;
42+
43+
for (let i=0; i<floors.length; i++) {
44+
const dist = Math.abs(floors[i].centre - loc.altitude);
45+
if (dist < min_value) {
46+
min_value = dist;
47+
min_index = i;
48+
}
49+
}
50+
51+
return floors[min_index];
52+
}
53+
54+
function flattenSphericalCoord(loc: UserGps): UserLocation {
55+
const lat_rad = deg2rad(loc.latitude);
56+
const long_rad = deg2rad(loc.longtitude);
57+
// const alt_rad = deg2rad(loc.altitude);
58+
59+
const e2 = (EQUATORIAL_RADIUS**2 - POLAR_RADIUS**2) / EQUATORIAL_RADIUS**2;
60+
const nB = EQUATORIAL_RADIUS / Math.sqrt(1 - (e2 * Math.sin(lat_rad)**2));
61+
62+
return {
63+
x: (nB + loc.altitude) * Math.cos(lat_rad) * Math.cos(long_rad) / 1e+3,
64+
y: (nB + loc.altitude) * Math.cos(lat_rad) * Math.sin(long_rad) / 1e+3,
65+
z: ((Math.pow(POLAR_RADIUS, 2) / Math.pow(EQUATORIAL_RADIUS, 2)) * nB + loc.altitude) * Math.sin(lat_rad)
66+
}
67+
}
68+
69+
function createPredictor(mapGps: IFloorGps): (loc: UserGps) => {x: number, y: number} {
70+
function predictor(loc: UserGps): {x: number, y: number} {
71+
const flatCoord = flattenSphericalCoord(loc);
72+
73+
const linearX = mapGps.linear.x.a + mapGps.linear.x.b1 * flatCoord.x + mapGps.linear.x.b2 * flatCoord.y;
74+
const linearY = mapGps.linear.y.a + mapGps.linear.y.b1 * flatCoord.x + mapGps.linear.y.b2 * flatCoord.y;
75+
76+
let sumOfWeigths = 0;
77+
const weights = mapGps.forces.map((e) => {
78+
const dist = Math.sqrt(Math.pow(e.point.x - linearX, 2) + Math.pow(e.point.y - linearY, 2));
79+
const weight = 1.0 / Math.pow(dist + 1e-12, 2);
80+
sumOfWeigths += weight;
81+
82+
return weight;
83+
});
84+
const normWeigths = weights.map(e => e / sumOfWeigths);
85+
86+
return {
87+
x: linearX + normWeigths.reduce(
88+
(accum, currentValue, index) => accum + currentValue * mapGps.forces[index].force.x,
89+
0.0
90+
),
91+
y: linearY + normWeigths.reduce(
92+
(accum, currentValue, index) => accum + currentValue * mapGps.forces[index].force.y,
93+
0.0
94+
)
95+
}
96+
}
97+
98+
return predictor;
99+
}
100+
101+
export {
102+
approxGps,
103+
boundGpsToMap,
104+
deg2rad,
105+
getClosestFloor,
106+
flattenSphericalCoord,
107+
createPredictor
108+
}

0 commit comments

Comments
 (0)