Skip to main content

Map Implementation

Map Provider

  • Uses react-native-maps with Google provider on Android, Apple Maps on iOS
  • iOS: mapType="mutedStandard" for a subdued look
  • Android: Custom JSON map style (grey/silver palette, POI labels hidden)

Custom SVG Markers (Primary Tier)

Each high-priority place renders as a custom SVG pin marker (MarkerIcon component) matching the web version's design:

  • Same SVG path data (viewBox 0 0 29 31)
  • White circle background for contrast against the map
  • Pin color: COLORS.primary (teal), selected: red (#B91C1C)
  • Selected markers scale to 1.3x
  • Place name rendered below the pin as a <Text> with white text shadow highlight
  • Names truncated to 14 characters with ellipsis
  • Staggered opacity fade-in animation (30ms delay between each marker)
  • tracksViewChanges={true} to support the fade-in animation
  • pointerEvents="none" on all child views inside Marker (container, MarkerIcon views, and text labels) to prevent them from intercepting marker taps

Marker Tap Handling

Marker taps are handled via the native onMarkerPress event on MapView rather than per-Marker onPress callbacks. When a marker is tapped, the handler uses a placesById lookup map (built from the places array) to resolve the tapped place by its marker identifier.

Selected Marker Behavior

When a place is selected:

  • Primary markers get a red fill (#B91C1C), 1.3x scale, and zIndex 999
  • Secondary dots are promoted to full MarkerIcon pins with red fill when selected
  • Marker keys include selection state (${id}-sel / ${id}-def) to force native re-render on selection change

Secondary Dot Markers

Lower-priority places that don't fit as primary markers are shown as small dots:

  • 14px teal circles with 2px white border
  • Drop shadow for depth
  • Staggered opacity fade-in (15ms delay, starting after primary markers finish)
  • Tapping a dot selects the place and shows its detail in the bottom sheet

Two-Tier Overlap Removal (replaces clustering)

Instead of react-native-map-clustering, the app uses a priority-based two-tier deduplication algorithm (tieredMarkers() function):

Primary pass

  1. Sort all places by priority score: verified (+100) > promoted (+50) > self-reported (+10) > rating
  2. Iterate sorted list; for each place, compute approximate pixel distance to all already-placed primary pins
  3. If within 50px radius (OVERLAP_PX_PRIMARY), skip the lower-priority pin
  4. Result: clean set of non-overlapping SVG pin markers

Secondary pass

  1. Take remaining places not in the primary set
  2. For each, check pixel distance against all placed markers (both primary and secondary)
  3. If within 10px radius (OVERLAP_PX_SECONDARY), skip
  4. Cap at 200 dots (MAX_SECONDARY_DOTS)
  5. Result: dense field of small dots filling gaps between primary pins

Pixel distance is approximated from lat/lng deltas relative to the current map region's latitudeDelta/longitudeDelta, assuming a ~400px map width.

Bottom Sheet

  • @gorhom/bottom-sheet with three snap points: 12% (peek), 45% (half), 85% (full)
  • enableOverDrag={false} to prevent over-scrolling
  • List mode: Shows a scrollable BottomSheetFlatList of PlaceCard components. Tapping a card calls selectPlace() (inline drawer, no navigation).
  • Detail mode: When selectedPlace is set, shows inline place detail with "← Back to results" header, place info, ACS attributes, and "Write a Review" CTA.
  • Sheet tracks its index via onChange; when fully expanded (index 2 / 85%), a floating "Back to map" pill button appears at center-bottom.

Floating Controls

  • Search bar — Top of screen, shows the EM logo icon, a divider, search icon, and placeholder text. Tapping opens the search modal.
  • Zoom buttons — Right side, +/− buttons that halve or double the region deltas with animation.
  • Location button — Below zoom buttons, requests GPS and animates to user's position.
  • "Back to map" pill — Center-bottom, only visible when sheet is at 85%. Collapses sheet to 12%.

Location

  • expo-location for GPS permission and current position
  • "My location" floating button animates the map to the user's coordinates (0.06 degree deltas)