Skip to content

Commit

Permalink
feat: refactor core loo compression and filtering methods into their …
Browse files Browse the repository at this point in the history
…own library files (#1259)

Also fix the method of reducing down the loos array according to the enabled filters
  • Loading branch information
ob6160 authored and Rupert Redington committed Dec 5, 2021
1 parent de6f629 commit 8f88da8
Show file tree
Hide file tree
Showing 13 changed files with 174 additions and 143 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
56 changes: 3 additions & 53 deletions src/api/resolvers.js → src/api/resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { stringifyAndCompressLoos } from '../lib/loo';

const { Loo, Report, Area, MapGeo } = require('./db');
const { GraphQLDateTime } = require('graphql-iso-date');
const without = require('lodash/without');
Expand Down Expand Up @@ -116,59 +118,7 @@ const resolvers = {
],
],
});

const FILTER_NO_PAYMENT = 0b00000001;
const FILTER_ALL_GENDER = 0b00000010;
const FILTER_AUTOMATIC = 0b00000100;
const FILTER_ACCESSIBLE = 0b00001000;
const FILTER_BABY_CHNG = 0b00010000;
const FILTER_RADAR = 0b00100000;
let count = 0;
const genLooFilterMask = (loo) => {
const noPayment =
loo.properties.noPayment === undefined ||
loo.properties.noPayment === false
? 0
: FILTER_NO_PAYMENT;
const allGender =
loo.properties.allGender === undefined ||
loo.properties.allGender === false
? 0
: FILTER_ALL_GENDER;

const automatic =
loo.properties.automatic === undefined ||
loo.properties.automatic === false
? 0
: FILTER_AUTOMATIC;
const accessible =
loo.properties.accessible === undefined ||
loo.properties.accessible === false
? 0
: FILTER_ACCESSIBLE;
const babyChange =
loo.properties.babyChange === undefined ||
loo.properties.babyChange === false
? 0
: FILTER_BABY_CHNG;
const radar =
loo.properties.radar === undefined || loo.properties.radar === false
? 0
: FILTER_RADAR;

return (
noPayment | allGender | automatic | accessible | babyChange | radar
);
};

const mapped = loos.map((loo) => {
return `${loo.id}|${loo.properties.geometry.coordinates[0].toFixed(
4
)}|${loo.properties.geometry.coordinates[1].toFixed(
4
)}|${genLooFilterMask(loo)}`;
});
return mapped;
return stringifyAndCompressLoos(loos);
},
areas: async (parent, args) => {
const data = await Area.find({}, { name: 1, type: 1 }).exec();
Expand Down
117 changes: 35 additions & 82 deletions src/components/LooMap/Markers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import { useMap } from 'react-leaflet';
import { useUkLooMarkersQuery } from '../../api-client/graphql';
import config, { Filter } from '../../config';
import { useMapState } from '../MapState';
import { FILTER_TYPE, getAppliedFiltersAsFilterTypes } from '../../lib/filter';
import {
filterCompressedLooByAppliedFilters,
parseCompressedLoo,
} from '../../lib/loo';
const KEY_ENTER = 13;

const mcg = L.markerClusterGroup({
Expand All @@ -17,109 +22,57 @@ const mcg = L.markerClusterGroup({
chunkInterval: 500,
});

function parseCompressedLoo(str) {
const l = str.split('|');
return {
id: l[0],
location: {
lng: l[1],
lat: l[2],
},
filterBitmask: parseInt(l[3], 10),
};
}

enum FILTER_TYPE {
NO_PAYMENT = 0b00000001,
ALL_GENDER = 0b00000010,
AUTOMATIC = 0b00000100,
ACCESSIBLE = 0b00001000,
BABY_CHNG = 0b00010000,
RADAR = 0b00100000,
}

const Markers = () => {
const router = useRouter();

const [mapState] = useMapState();
const [mapState, setMapState] = useMapState();
const { filters } = mapState;
const { data } = useUkLooMarkersQuery();

const [appliedFilters, setAppliedFilters] = useState<Array<FILTER_TYPE>>([]);
const [appliedFilterTypes, setAppliedFilterTypes] = useState<
Array<FILTER_TYPE>
>([]);

useEffect(() => {
// get the filter objects from config for the filters applied by the user
const applied: Array<Filter> = config.filters.filter(
(filter) => filters[filter.id]
);

const bitmask = applied.reduce((p, c) => {
switch (c.id) {
case 'accessible':
p.push(FILTER_TYPE.ACCESSIBLE);
break;
case 'allGender':
p.push(FILTER_TYPE.ALL_GENDER);
break;
case 'automatic':
p.push(FILTER_TYPE.AUTOMATIC);
break;
case 'babyChange':
p.push(FILTER_TYPE.BABY_CHNG);
break;
case 'noPayment':
p.push(FILTER_TYPE.NO_PAYMENT);
break;
case 'radar':
p.push(FILTER_TYPE.RADAR);
break;
}
return p;
}, [] as Array<FILTER_TYPE>);

const appliedFilterTypes = getAppliedFiltersAsFilterTypes(applied);
window.setTimeout(() => {
setAppliedFilters(bitmask);
setAppliedFilterTypes(appliedFilterTypes);
}, 200);
}, [filters]);

const filteredLooGroups = useMemo(() => {
if (!data?.ukLooMarkers) {
return [];
}
return data.ukLooMarkers
.filter((compressed) => {
const toilet = parseCompressedLoo(compressed);
for (let i = 0; i < appliedFilters.length; i++) {
const filter = appliedFilters[i];
if (toilet.filterBitmask & filter) {
return false;
}
}
return true;

const parsedAndFilteredMarkers = data?.ukLooMarkers
.map(parseCompressedLoo)
.filter((compressedLoo) =>
filterCompressedLooByAppliedFilters(compressedLoo, appliedFilterTypes)
);

return parsedAndFilteredMarkers.map((toilet) => {
return L.marker(new L.LatLng(toilet.location.lat, toilet.location.lng), {
zIndexOffset: 0,
icon: new ToiletMarkerIcon({
toiletId: toilet.id,
}),
alt: 'Public Toilet',
keyboard: false,
})
.map((compressed) => {
const toilet = parseCompressedLoo(compressed);
return L.marker(
new L.LatLng(toilet.location.lat, toilet.location.lng),
{
zIndexOffset: 0,
icon: new ToiletMarkerIcon({
toiletId: toilet.id,
}),
alt: 'Public Toilet',
keyboard: false,
}
)
.on('click', () => {
.on('click', () => {
router.push(`/loos/${toilet.id}`);
})
.on('keydown', (event: { originalEvent: { keyCode: number } }) => {
if (event.originalEvent.keyCode === KEY_ENTER) {
router.push(`/loos/${toilet.id}`);
})
.on('keydown', (event: { originalEvent: { keyCode: number } }) => {
if (event.originalEvent.keyCode === KEY_ENTER) {
router.push(`/loos/${toilet.id}`);
}
});
});
}, [data, router, appliedFilters]);
}
});
});
}, [appliedFilterTypes, data?.ukLooMarkers, router]);

const map = useMap();
useEffect(() => {
Expand Down
17 changes: 9 additions & 8 deletions src/components/MapState.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React, { useEffect } from 'react';
import React, { Dispatch, useEffect } from 'react';
import { Loo } from '../api-client/graphql';
import config, { Filter, FILTERS_KEY } from '../config';

const MapStateContext = React.createContext(null);
const MapStateContext =
React.createContext<[MapState, Dispatch<MapState>]>(null);

interface MapState {
center: {
center?: {
lat: number;
lng: number;
};
zoom: number;
radius: number;
geolocation: any;
loos: Loo[];
filters: Filter[];
zoom?: number;
radius?: number;
geolocation?: any;
loos?: Loo[];
filters?: Filter[];
}

const reducer = (state: MapState, newState: MapState) => {
Expand Down
65 changes: 65 additions & 0 deletions src/lib/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Loo } from '../api-client/graphql';
import { Filter } from '../config';
import { LooProperties } from './loo';

export enum FILTER_TYPE {
NO_PAYMENT = 0b00000001,
ALL_GENDER = 0b00000010,
AUTOMATIC = 0b00000100,
ACCESSIBLE = 0b00001000,
BABY_CHNG = 0b00010000,
RADAR = 0b00100000,
}

export const genLooFilterBitmask = (loo: { properties: LooProperties }) => {
const {
noPayment = 0,
allGender = 0,
automatic = 0,
accessible = 0,
babyChange = 0,
radar = 0,
} = loo.properties;

const noPaymentValue = noPayment ? FILTER_TYPE.NO_PAYMENT : 0;
const allGenderValue = allGender ? FILTER_TYPE.ALL_GENDER : 0;
const automaticValue = automatic ? FILTER_TYPE.AUTOMATIC : 0;
const accessibleValue = accessible ? FILTER_TYPE.ACCESSIBLE : 0;
const babyChangeValue = babyChange ? FILTER_TYPE.BABY_CHNG : 0;
const radarValue = radar ? FILTER_TYPE.RADAR : 0;

return (
noPaymentValue |
allGenderValue |
automaticValue |
accessibleValue |
babyChangeValue |
radarValue
);
};

export const getAppliedFiltersAsFilterTypes = (filters: Filter[]) => {
return filters.reduce((p, c) => {
switch (c.id) {
case 'accessible':
p.push(FILTER_TYPE.ACCESSIBLE);
break;
case 'allGender':
p.push(FILTER_TYPE.ALL_GENDER);
break;
case 'automatic':
p.push(FILTER_TYPE.AUTOMATIC);
break;
case 'babyChange':
p.push(FILTER_TYPE.BABY_CHNG);
break;
case 'noPayment':
p.push(FILTER_TYPE.NO_PAYMENT);
break;
case 'radar':
p.push(FILTER_TYPE.RADAR);
break;
}
return p;
}, [] as Array<FILTER_TYPE>);
};
62 changes: 62 additions & 0 deletions src/lib/loo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Loo } from '../api-client/graphql';
import { FILTER_TYPE, genLooFilterBitmask } from './filter';

export type LooProperties = Omit<Loo, '__typename'> & {
geometry: {
coordinates: [number, number];
};
};

type CompressedLooString = `${string}|${string}|${string}|${number}`;

type CompressedLooObject = {
id: string;
location: {
lng: number;
lat: number;
};
filterBitmask: number;
};

export const stringifyAndCompressLoos = (
loos: { id?: string; properties: LooProperties }[]
): CompressedLooString[] =>
loos.map((loo) => {
const id = loo.id;
const lat = loo.properties.geometry.coordinates[0].toFixed(4);
const lng = loo.properties.geometry.coordinates[1].toFixed(4);
const filterMask = genLooFilterBitmask(loo);
return `${id}|${lat}|${lng}|${filterMask}` as CompressedLooString;
});

export const parseCompressedLoo = (
compressedLoo: CompressedLooString
): CompressedLooObject => {
const loo = compressedLoo.split('|');
return {
id: loo[0],
location: {
lng: parseFloat(loo[1]),
lat: parseFloat(loo[2]),
},
filterBitmask: parseInt(loo[3], 10),
};
};

export const filterCompressedLooByAppliedFilters = (
compressedLoo: CompressedLooObject,
appliedFilters: Array<FILTER_TYPE>
) => {
if (appliedFilters.length === 0) {
return true;
}

// Check that the loo passes all of the applied filters.
const passesAll = appliedFilters.reduce((previousValue, currentFilter) => {
const currentFilterInMask =
(compressedLoo.filterBitmask & currentFilter) !== 0;
return previousValue && currentFilterInMask;
}, true);

return passesAll;
};

0 comments on commit 8f88da8

Please sign in to comment.