Skip to content

Commit 53963f2

Browse files
authored
fix: access* and bike_network bitmask (#327)
1 parent 185225b commit 53963f2

File tree

4 files changed

+118
-26
lines changed

4 files changed

+118
-26
lines changed

src/components/map/parts/tiles-constants.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,33 @@ export const propertyNameMappings: Record<string, Record<number, string>> = {
110110
node_type: nodeTypeNames,
111111
intersection_type: intersectionTypeNames,
112112
};
113+
114+
export const accessFlagNames: Record<number, string> = {
115+
0: 'None',
116+
1: 'Auto',
117+
2: 'Pedestrian',
118+
4: 'Bicycle',
119+
8: 'Truck',
120+
16: 'Emergency',
121+
32: 'Taxi',
122+
64: 'Bus',
123+
128: 'HOV',
124+
256: 'Wheelchair',
125+
512: 'Moped',
126+
1024: 'Motorcycle',
127+
};
128+
129+
export const bikeNetworkFlagNames: Record<number, string> = {
130+
0: 'None',
131+
1: 'National',
132+
2: 'Regional',
133+
4: 'Local',
134+
8: 'Mountain',
135+
};
136+
137+
export const bitmaskPropertyMappings: Record<string, Record<number, string>> = {
138+
'access:fwd': accessFlagNames,
139+
'access:bwd': accessFlagNames,
140+
access: accessFlagNames,
141+
bike_network: bikeNetworkFlagNames,
142+
};

src/components/map/parts/tiles-info-popup.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function TilesInfoPopup({ features, onClose }: TilesInfoPopupProps) {
5858
idx % 2 === 0 ? 'bg-muted/30' : 'bg-transparent'
5959
}
6060
>
61-
<td className="py-1.5 px-2 text-muted-foreground whitespace-nowrap">
61+
<td className="py-1.5 px-2 text-muted-foreground whitespace-nowrap font-bold">
6262
{key}
6363
</td>
6464
<td className="py-1.5 px-2 text-right font-medium">

src/components/map/parts/tiles-property.spec.tsx

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,19 +104,66 @@ describe('TilesProperty', () => {
104104
});
105105
});
106106

107-
describe('access properties', () => {
108-
it('should show check icon for truthy access values', () => {
109-
const { container } = render(
110-
<TilesProperty propertyKey="access:car" value={true} />
111-
);
112-
expect(container.querySelector('.text-emerald-600')).toBeInTheDocument();
107+
describe('access bitmask properties', () => {
108+
it('should decode access:fwd bitmask to badges', () => {
109+
// 1 (Auto) + 2 (Pedestrian) + 4 (Bicycle) = 7
110+
render(<TilesProperty propertyKey="access:fwd" value={7} />);
111+
expect(screen.getByText('Auto')).toBeInTheDocument();
112+
expect(screen.getByText('Pedestrian')).toBeInTheDocument();
113+
expect(screen.getByText('Bicycle')).toBeInTheDocument();
113114
});
114115

115-
it('should show X icon for falsy access values', () => {
116-
const { container } = render(
117-
<TilesProperty propertyKey="access:car" value={false} />
118-
);
119-
expect(container.querySelector('.text-red-600')).toBeInTheDocument();
116+
it('should decode access:bwd bitmask to badges', () => {
117+
// 8 (Truck) + 64 (Bus) = 72
118+
render(<TilesProperty propertyKey="access:bwd" value={72} />);
119+
expect(screen.getByText('Truck')).toBeInTheDocument();
120+
expect(screen.getByText('Bus')).toBeInTheDocument();
121+
expect(screen.queryByText('Auto')).not.toBeInTheDocument();
122+
});
123+
124+
it('should show all access types for full bitmask', () => {
125+
// All flags: 1+2+4+8+16+32+64+128+256+512+1024 = 2047
126+
render(<TilesProperty propertyKey="access:fwd" value={2047} />);
127+
expect(screen.getByText('Auto')).toBeInTheDocument();
128+
expect(screen.getByText('Pedestrian')).toBeInTheDocument();
129+
expect(screen.getByText('Bicycle')).toBeInTheDocument();
130+
expect(screen.getByText('Truck')).toBeInTheDocument();
131+
expect(screen.getByText('Emergency')).toBeInTheDocument();
132+
expect(screen.getByText('Taxi')).toBeInTheDocument();
133+
expect(screen.getByText('Bus')).toBeInTheDocument();
134+
expect(screen.getByText('HOV')).toBeInTheDocument();
135+
expect(screen.getByText('Wheelchair')).toBeInTheDocument();
136+
expect(screen.getByText('Moped')).toBeInTheDocument();
137+
expect(screen.getByText('Motorcycle')).toBeInTheDocument();
138+
});
139+
140+
it('should show None for zero bitmask', () => {
141+
render(<TilesProperty propertyKey="access:fwd" value={0} />);
142+
expect(screen.getByText('None')).toBeInTheDocument();
143+
});
144+
});
145+
146+
describe('bike_network bitmask properties', () => {
147+
it('should decode bike_network bitmask to badges', () => {
148+
// 1 (National) + 4 (Local) = 5
149+
render(<TilesProperty propertyKey="bike_network" value={5} />);
150+
expect(screen.getByText('National')).toBeInTheDocument();
151+
expect(screen.getByText('Local')).toBeInTheDocument();
152+
expect(screen.queryByText('Regional')).not.toBeInTheDocument();
153+
});
154+
155+
it('should show all bike network types for full bitmask', () => {
156+
// All flags: 1+2+4+8 = 15
157+
render(<TilesProperty propertyKey="bike_network" value={15} />);
158+
expect(screen.getByText('National')).toBeInTheDocument();
159+
expect(screen.getByText('Regional')).toBeInTheDocument();
160+
expect(screen.getByText('Local')).toBeInTheDocument();
161+
expect(screen.getByText('Mountain')).toBeInTheDocument();
162+
});
163+
164+
it('should show None for zero bike_network', () => {
165+
render(<TilesProperty propertyKey="bike_network" value={0} />);
166+
expect(screen.getByText('None')).toBeInTheDocument();
120167
});
121168
});
122169

src/components/map/parts/tiles-property.tsx

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
import type { ReactNode } from 'react';
22
import { ExternalLink, Check, X as XIcon } from 'lucide-react';
33

4-
import { propertyNameMappings } from './tiles-constants';
4+
import {
5+
bitmaskPropertyMappings,
6+
propertyNameMappings,
7+
} from './tiles-constants';
58
import { cn } from '@/lib/utils';
69
import { Badge } from '@/components/ui/badge';
710

11+
function decodeBitmask(
12+
value: number,
13+
mapping: Record<number, string>
14+
): string[] {
15+
if (value === 0) {
16+
return [mapping[0]!];
17+
}
18+
return Object.entries(mapping)
19+
.filter(([mask]) => value & Number(mask))
20+
.map(([, label]) => label);
21+
}
22+
823
interface TilesPropertyProps {
924
propertyKey: string;
1025
value: unknown;
@@ -33,9 +48,9 @@ export function TilesProperty({
3348
);
3449
}
3550

36-
const mapping = propertyNameMappings[propertyKey];
37-
if (mapping && typeof value === 'number') {
38-
const name = mapping[value] || 'Unknown';
51+
const propNamemapping = propertyNameMappings[propertyKey];
52+
if (propNamemapping && typeof value === 'number') {
53+
const name = propNamemapping[value] || 'Unknown';
3954
return (
4055
<span className="inline-flex items-center gap-1.5">
4156
<Badge variant="outline">{name}</Badge>
@@ -44,17 +59,17 @@ export function TilesProperty({
4459
);
4560
}
4661

47-
if (propertyKey.startsWith('access:')) {
62+
const bitmaskMapping = bitmaskPropertyMappings[propertyKey];
63+
if (bitmaskMapping && typeof value === 'number') {
64+
const decodedValues = decodeBitmask(value, bitmaskMapping);
4865
return (
49-
<span
50-
className={cn(
51-
'inline-flex items-center justify-center size-5 rounded-full',
52-
value
53-
? 'bg-emerald-500/20 text-emerald-600 dark:text-emerald-400'
54-
: 'bg-red-500/20 text-red-600 dark:text-red-400'
55-
)}
56-
>
57-
{value ? <Check className="size-3" /> : <XIcon className="size-3" />}
66+
<span className="inline-flex flex-col items-end gap-1.5">
67+
{decodedValues.map((label) => (
68+
<Badge variant="outline" key={label}>
69+
{label}
70+
</Badge>
71+
))}
72+
<span>({value})</span>
5873
</span>
5974
);
6075
}

0 commit comments

Comments
 (0)