Skip to content

Commit bf73361

Browse files
authored
Merge pull request #1206 from data-for-change/dev
merge dev into master
2 parents 32df185 + f004866 commit bf73361

3 files changed

Lines changed: 93 additions & 7 deletions

File tree

src/components/molecules/LocationMap.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React, { FC } from 'react';
2-
import { IPoint, IPointAccident } from 'models/Point';
2+
import { IPoint, IPointAccidentWithPosition } from 'models/Point';
33
import { MostSevereAccidentsMarker } from 'components/atoms';
4-
import { ClockPosition } from 'models/ClockPosition';
54
import Map from './map/Map';
65
import { CSSProperties } from '@material-ui/core/styles/withStyles';
76

@@ -11,9 +10,9 @@ interface IProps {
1110
}
1211

1312
const LocationMap: FC<IProps> = ({ items }) => {
14-
const markers = items.map((x: IPointAccident, i: number) => {
13+
const markers = items.map((x: IPointAccidentWithPosition, i: number) => {
1514
if (x.latitude !== null && x.longitude !== null) {
16-
const tooltipOffset = i % 2 === 0 ? ClockPosition.RIGHT : ClockPosition.LEFT;
15+
const tooltipOffset = x.labelClockPosition;
1716
return (
1817
<div key={i}>
1918
<MostSevereAccidentsMarker data={x} tooltipOffset={tooltipOffset} />

src/components/molecules/widgets/MostSevereAccidentsMapWidget.tsx

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC } from 'react';
1+
import React, { FC, useMemo } from 'react';
22
import LocationMap from 'components/molecules/LocationMap';
33
import { IWidgetMostSevereAccidentsData } from 'models/WidgetData';
44
import { Box } from '@material-ui/core';
@@ -8,11 +8,59 @@ import { makeStyles } from '@material-ui/core/styles';
88
import { useTranslation } from 'react-i18next';
99
import { aggregatePoints } from 'utils/map.utils';
1010
import { IAggregatePointAccident } from 'models/Point';
11+
import { ClockPosition } from 'models/ClockPosition';
12+
13+
14+
function linearRegression(points: { longitude: number; latitude: number }[]) {
15+
const n = points.length;
16+
if (n < 2) return null;
17+
const sumX = points.reduce((sum, p) => sum + p.longitude, 0);
18+
const sumY = points.reduce((sum, p) => sum + p.latitude, 0);
19+
const sumXY = points.reduce((sum, p) => sum + p.longitude * p.latitude, 0);
20+
const sumXX = points.reduce((sum, p) => sum + p.longitude * p.longitude, 0);
21+
const denominator = n * sumXX - sumX * sumX;
22+
if (denominator === 0) {
23+
// All points have the same longitude (vertical line)
24+
return null;
25+
}
26+
const slope = (n * sumXY - sumX * sumY) / denominator;
27+
const intercept = (sumY - slope * sumX) / n;
28+
return { slope, intercept };
29+
}
30+
31+
32+
function getLabelClockPosition(
33+
slope: number | null,
34+
mod: number
35+
): ClockPosition {
36+
let degrees: number;
37+
if (slope === null || !isFinite(slope)) {
38+
degrees = mod === 0 ? 0 : 180;
39+
} else {
40+
const baseAngle = Math.atan(slope);
41+
const perpAngle = baseAngle + (mod === 0 ? Math.PI / 2 : -Math.PI / 2);
42+
degrees = ((perpAngle * 180) / Math.PI + 360) % 360; // convert to [0, 360)
43+
}
44+
const snappedAngle = Math.round(degrees / 45) * 45 % 360;
45+
const angleToClockPosition: { [angle: number]: ClockPosition } = {
46+
0: ClockPosition.RIGHT,
47+
45: ClockPosition.TOPRIGHT,
48+
90: ClockPosition.TOP,
49+
135: ClockPosition.TOPLEFT,
50+
180: ClockPosition.LEFT,
51+
225: ClockPosition.BOTTOMLEFT,
52+
270: ClockPosition.BOTTOM,
53+
315: ClockPosition.BOTTOMRIGHT,
54+
};
55+
return angleToClockPosition[snappedAngle];
56+
}
1157

1258
interface IProps {
1359
data: IWidgetMostSevereAccidentsData;
1460
sizeOptions: number;
1561
}
62+
63+
1664
const useStyles = makeStyles({
1765
carLogo: {
1866
height: '20px',
@@ -40,16 +88,49 @@ const useStyles = makeStyles({
4088
},
4189
});
4290

91+
92+
function getSortedItemsAlongLine(
93+
items: IAggregatePointAccident[],
94+
slope: number | null
95+
): IAggregatePointAccident[] {
96+
if (slope === null || !isFinite(slope)) {
97+
return [...items].sort((a, b) => a.latitude - b.latitude);
98+
}
99+
const dx = 1;
100+
const dy = slope;
101+
102+
return [...items].sort((a, b) => {
103+
const projA = a.longitude * dx + a.latitude * dy;
104+
const projB = b.longitude * dx + b.latitude * dy;
105+
return projA - projB;
106+
});
107+
}
108+
109+
43110
const MostSevereAccidentsMapWidget: FC<IProps> = ({ data }) => {
44111
const classes = useStyles();
45112
const { items } = data;
46113
const { t } = useTranslation();
47-
const aggregatedItems : IAggregatePointAccident[] = aggregatePoints(items);
114+
const aggregatedItems: IAggregatePointAccident[] = useMemo(
115+
() => aggregatePoints(items),
116+
[items]
117+
);
118+
const itemsWithLabelPosition = useMemo(() => {
119+
const regression = linearRegression(aggregatedItems);
120+
const slope = regression ? regression.slope : null;
121+
const sortedItems = getSortedItemsAlongLine(aggregatedItems, slope);
122+
return sortedItems.map((item, idx) => ({
123+
...item,
124+
labelClockPosition: getLabelClockPosition(slope, idx % 2),
125+
}));
126+
}, [aggregatedItems]);
48127

49128
return (
50129
<Box height={'100%'}>
51130
<Box className={classes.mapBox}>
52-
<LocationMap items={aggregatedItems} />
131+
{itemsWithLabelPosition.length > 0 && (
132+
<LocationMap key={itemsWithLabelPosition.map(i => i.accident_timestamp).join(',')} items={itemsWithLabelPosition} />
133+
)}
53134
</Box>
54135
<Box className={classes.iconsContainer}>
55136
<Box className={classes.singleIcon}>

src/models/Point.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ export interface IPointAccident extends IPoint {
99
accident_timestamp: string;
1010
}
1111

12+
export interface IPointAccidentWithPosition extends IPoint {
13+
accident_severity: SeverityTypes;
14+
accident_timestamp: string;
15+
labelClockPosition: number;
16+
}
17+
1218
export interface IAggregatePointAccident extends IPoint{
1319
accident_severity: SeverityTypes;
1420
accident_timestamp: string[];

0 commit comments

Comments
 (0)