diff --git a/src/components/map/parts/tiles-constants.ts b/src/components/map/parts/tiles-constants.ts index f9e69d7..b708358 100644 --- a/src/components/map/parts/tiles-constants.ts +++ b/src/components/map/parts/tiles-constants.ts @@ -110,3 +110,33 @@ export const propertyNameMappings: Record> = { node_type: nodeTypeNames, intersection_type: intersectionTypeNames, }; + +export const accessFlagNames: Record = { + 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 = { + 0: 'None', + 1: 'National', + 2: 'Regional', + 4: 'Local', + 8: 'Mountain', +}; + +export const bitmaskPropertyMappings: Record> = { + 'access:fwd': accessFlagNames, + 'access:bwd': accessFlagNames, + access: accessFlagNames, + bike_network: bikeNetworkFlagNames, +}; diff --git a/src/components/map/parts/tiles-info-popup.tsx b/src/components/map/parts/tiles-info-popup.tsx index 54243e8..cb5590b 100644 --- a/src/components/map/parts/tiles-info-popup.tsx +++ b/src/components/map/parts/tiles-info-popup.tsx @@ -58,7 +58,7 @@ export function TilesInfoPopup({ features, onClose }: TilesInfoPopupProps) { idx % 2 === 0 ? 'bg-muted/30' : 'bg-transparent' } > - + {key} diff --git a/src/components/map/parts/tiles-property.spec.tsx b/src/components/map/parts/tiles-property.spec.tsx index c9e3d54..2ba4e7e 100644 --- a/src/components/map/parts/tiles-property.spec.tsx +++ b/src/components/map/parts/tiles-property.spec.tsx @@ -104,19 +104,66 @@ describe('TilesProperty', () => { }); }); - describe('access properties', () => { - it('should show check icon for truthy access values', () => { - const { container } = render( - - ); - 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(); + 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( - - ); - expect(container.querySelector('.text-red-600')).toBeInTheDocument(); + it('should decode access:bwd bitmask to badges', () => { + // 8 (Truck) + 64 (Bus) = 72 + render(); + 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(); + 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(); + expect(screen.getByText('None')).toBeInTheDocument(); + }); + }); + + describe('bike_network bitmask properties', () => { + it('should decode bike_network bitmask to badges', () => { + // 1 (National) + 4 (Local) = 5 + render(); + 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(); + 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(); + expect(screen.getByText('None')).toBeInTheDocument(); }); }); diff --git a/src/components/map/parts/tiles-property.tsx b/src/components/map/parts/tiles-property.tsx index a8b247a..5c06647 100644 --- a/src/components/map/parts/tiles-property.tsx +++ b/src/components/map/parts/tiles-property.tsx @@ -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 +): 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; @@ -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 ( {name} @@ -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 ( - - {value ? : } + + {decodedValues.map((label) => ( + + {label} + + ))} + ({value}) ); }