Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/components/map/parts/tiles-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,33 @@ export const propertyNameMappings: Record<string, Record<number, string>> = {
node_type: nodeTypeNames,
intersection_type: intersectionTypeNames,
};

export const accessFlagNames: Record<number, string> = {
0: 'None',
1: 'Auto',
2: 'Pedestrian',
4: 'Bicycle',
8: 'Truck',
16: 'Emergency',
32: 'Taxi',
64: 'Bus',
128: 'HOV',
256: 'Wheelchair',
512: 'Moped',
1024: 'Motorcycle',
};

export const bikeNetworkFlagNames: Record<number, string> = {
0: 'None',
1: 'National',
2: 'Regional',
4: 'Local',
8: 'Mountain',
};

export const bitmaskPropertyMappings: Record<string, Record<number, string>> = {
'access:fwd': accessFlagNames,
'access:bwd': accessFlagNames,
access: accessFlagNames,
bike_network: bikeNetworkFlagNames,
};
2 changes: 1 addition & 1 deletion src/components/map/parts/tiles-info-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function TilesInfoPopup({ features, onClose }: TilesInfoPopupProps) {
idx % 2 === 0 ? 'bg-muted/30' : 'bg-transparent'
}
>
<td className="py-1.5 px-2 text-muted-foreground whitespace-nowrap">
<td className="py-1.5 px-2 text-muted-foreground whitespace-nowrap font-bold">
{key}
</td>
<td className="py-1.5 px-2 text-right font-medium">
Expand Down
69 changes: 58 additions & 11 deletions src/components/map/parts/tiles-property.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,66 @@ describe('TilesProperty', () => {
});
});

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

it('should show X icon for falsy access values', () => {
const { container } = render(
<TilesProperty propertyKey="access:car" value={false} />
);
expect(container.querySelector('.text-red-600')).toBeInTheDocument();
it('should decode access:bwd bitmask to badges', () => {
// 8 (Truck) + 64 (Bus) = 72
render(<TilesProperty propertyKey="access:bwd" value={72} />);
expect(screen.getByText('Truck')).toBeInTheDocument();
expect(screen.getByText('Bus')).toBeInTheDocument();
expect(screen.queryByText('Auto')).not.toBeInTheDocument();
});

it('should show all access types for full bitmask', () => {
// All flags: 1+2+4+8+16+32+64+128+256+512+1024 = 2047
render(<TilesProperty propertyKey="access:fwd" value={2047} />);
expect(screen.getByText('Auto')).toBeInTheDocument();
expect(screen.getByText('Pedestrian')).toBeInTheDocument();
expect(screen.getByText('Bicycle')).toBeInTheDocument();
expect(screen.getByText('Truck')).toBeInTheDocument();
expect(screen.getByText('Emergency')).toBeInTheDocument();
expect(screen.getByText('Taxi')).toBeInTheDocument();
expect(screen.getByText('Bus')).toBeInTheDocument();
expect(screen.getByText('HOV')).toBeInTheDocument();
expect(screen.getByText('Wheelchair')).toBeInTheDocument();
expect(screen.getByText('Moped')).toBeInTheDocument();
expect(screen.getByText('Motorcycle')).toBeInTheDocument();
});

it('should show None for zero bitmask', () => {
render(<TilesProperty propertyKey="access:fwd" value={0} />);
expect(screen.getByText('None')).toBeInTheDocument();
});
});

describe('bike_network bitmask properties', () => {
it('should decode bike_network bitmask to badges', () => {
// 1 (National) + 4 (Local) = 5
render(<TilesProperty propertyKey="bike_network" value={5} />);
expect(screen.getByText('National')).toBeInTheDocument();
expect(screen.getByText('Local')).toBeInTheDocument();
expect(screen.queryByText('Regional')).not.toBeInTheDocument();
});

it('should show all bike network types for full bitmask', () => {
// All flags: 1+2+4+8 = 15
render(<TilesProperty propertyKey="bike_network" value={15} />);
expect(screen.getByText('National')).toBeInTheDocument();
expect(screen.getByText('Regional')).toBeInTheDocument();
expect(screen.getByText('Local')).toBeInTheDocument();
expect(screen.getByText('Mountain')).toBeInTheDocument();
});

it('should show None for zero bike_network', () => {
render(<TilesProperty propertyKey="bike_network" value={0} />);
expect(screen.getByText('None')).toBeInTheDocument();
});
});

Expand Down
43 changes: 29 additions & 14 deletions src/components/map/parts/tiles-property.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import type { ReactNode } from 'react';
import { ExternalLink, Check, X as XIcon } from 'lucide-react';

import { propertyNameMappings } from './tiles-constants';
import {
bitmaskPropertyMappings,
propertyNameMappings,
} from './tiles-constants';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';

function decodeBitmask(
value: number,
mapping: Record<number, string>
): string[] {
if (value === 0) {
return [mapping[0]!];
}
return Object.entries(mapping)
.filter(([mask]) => value & Number(mask))
.map(([, label]) => label);
}

interface TilesPropertyProps {
propertyKey: string;
value: unknown;
Expand Down Expand Up @@ -33,9 +48,9 @@ export function TilesProperty({
);
}

const mapping = propertyNameMappings[propertyKey];
if (mapping && typeof value === 'number') {
const name = mapping[value] || 'Unknown';
const propNamemapping = propertyNameMappings[propertyKey];
if (propNamemapping && typeof value === 'number') {
const name = propNamemapping[value] || 'Unknown';
return (
<span className="inline-flex items-center gap-1.5">
<Badge variant="outline">{name}</Badge>
Expand All @@ -44,17 +59,17 @@ export function TilesProperty({
);
}

if (propertyKey.startsWith('access:')) {
const bitmaskMapping = bitmaskPropertyMappings[propertyKey];
if (bitmaskMapping && typeof value === 'number') {
const decodedValues = decodeBitmask(value, bitmaskMapping);
return (
<span
className={cn(
'inline-flex items-center justify-center size-5 rounded-full',
value
? 'bg-emerald-500/20 text-emerald-600 dark:text-emerald-400'
: 'bg-red-500/20 text-red-600 dark:text-red-400'
)}
>
{value ? <Check className="size-3" /> : <XIcon className="size-3" />}
<span className="inline-flex flex-col items-end gap-1.5">
{decodedValues.map((label) => (
<Badge variant="outline" key={label}>
{label}
</Badge>
))}
<span>({value})</span>
</span>
);
}
Expand Down