Machine Coding Problem

Geofencing Library

macoAllgeopoint-in-polygon-tests
Commonly Asked By:UberLyftDoorDash

Requirements & System Scope

Functional Scope (In-Scope)

  • Dynamic GeoZone Creation: Generates multi-vertex polygons complete with spatial bounding box attributes.
  • Ray-Casting Containment Validator: Implements point-in-polygon ray crossing geometry to precisely determine zone boundaries.
  • Spatial Index Pre-Filtering: Optimizes lookup checks from O(N) to candidate overlaps of O(K) using pre-computed bounding boxes.
  • Stateful ENTER/EXIT Crossings: Tracks user location changes over time, firing events exactly as state boundaries are crossed.

Explicit Boundaries (Out-of-Scope)

  • Physical Map Canvas Rendering: Excludes front-end maps, visualizing polygons and paths with structured logs.
  • H3/S2 Hexagonal Discretization: Models boundaries with standard polygons to focus on ray-casting mathematics and local spatial index models.

Class Diagram & Entity Relationships

Geo-filtering processes, Ray-Casting checks, and transition workflows:

Loading...
  • GeoZone: Contains polygon vertices and a fast bounding box pre-filter coordinate window.
  • SpatialIndex: Maintains registered zones, returning candidate lists for location points.
  • GeofenceMonitor: Manages user active zone maps and builds ENTER/EXIT event timelines.

Design Patterns & SOLID Principles

  • Proxy Pattern (Bounding Box Guard): acts as a fast check proxy in front of the ray-casting algorithm, bypassing expensive mathematical calculations for distant polygons.
  • State Transition Observer Pattern: Listens for location coordinates and translates changes into geofence crossings.
  • Single Responsibility Principle (SRP): Isolates coordinates representation, polygon bounding boxes, crossing geometry, index lookups, and lifecycle state changes.

Core Execution Workflows

Ray-Casting Mathematics & Crossing Diffs

  1. Fast Candidate Filtering:
    1. Receive user location update (lat, lng).
    2. Search the SpatialIndex. For each zone, verify if: minLat <= lat <= maxLat and minLng <= lng <= maxLng.
  2. Point-in-Polygon Ray Casting:
    1. Cast a horizontal ray to the right from the test point.
    2. For each polygon edge, check if the ray intersects the segment.
    3. Tally intersections: if the total number of crossings is odd, the point is inside the polygon; if even, it is outside.
  3. Zone Transition Eviction:
    1. Compare current zones with previous records.
    2. Generate ENTER alerts for new active overlaps and EXIT alerts for evicted ones.

Concurrency & Thread Safety Strategy

Handling spatial indexing under dynamic, multi-threaded zone additions and updates:

  • Thread-Safe Indexing: Enforces strict synchronization boundaries around spatial zone registries, preventing index corruption during query sweeps.
  • Concurrent State Lookups: Utilizes thread-safe memory stores for tracking active user locations, ensuring multiple incoming device updates do not conflict.

Complete Clean Code Blueprint

Production reference implementations demonstrating spatial indices, ray-casting point-in-polygon containment tests, and entry/exit monitors in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

class Point {
    private final double lat;
    private final double lng;

    public Point(double lat, double lng) {
        this.lat = lat;
        this.lng = lng;
    }

    public double getLat() { return lat; }
    public double getLng() { return lng; }
}

class BoundingBox {
    private final double minLat;
    private final double maxLat;
    private final double minLng;
    private final double maxLng;

    public BoundingBox(List<Point> polygon) {
        double minLa = Double.MAX_VALUE;
        double maxLa = Double.MIN_VALUE;
        double minLn = Double.MAX_VALUE;
        double maxLn = Double.MIN_VALUE;

        for (Point p : polygon) {
            if (p.getLat() < minLa) minLa = p.getLat();
            if (p.getLat() > maxLa) maxLa = p.getLat();
            if (p.getLng() < minLn) minLn = p.getLng();
            if (p.getLng() > maxLn) maxLn = p.getLng();
        }
        this.minLat = minLa;
        this.maxLat = maxLa;
        this.minLng = minLn;
        this.maxLng = maxLn;
    }

    public boolean contains(Point p) {
        return p.getLat() >= minLat && p.getLat() <= maxLat &&
               p.getLng() >= minLng && p.getLng() <= maxLng;
    }
}

class GeoZone {
    private final String zoneId;
    private final String name;
    private final List<Point> polygon;
    private final BoundingBox boundingBox;

    public GeoZone(String zoneId, String name, List<Point> polygon) {
        this.zoneId = zoneId;
        this.name = name;
        this.polygon = new ArrayList<>(polygon);
        this.boundingBox = new BoundingBox(polygon);
    }

    public String getZoneId() { return zoneId; }
    public String getName() { return name; }
    public List<Point> getPolygon() { return polygon; }
    public BoundingBox getBoundingBox() { return boundingBox; }
}

class PointInPolygonTester {
    public static boolean contains(Point point, GeoZone zone) {
        List<Point> poly = zone.getPolygon();
        int n = poly.size();
        if (n < 3) return false;

        boolean inside = false;
        double px = point.getLng();
        double py = point.getLat();

        for (int i = 0, j = n - 1; i < n; j = i++) {
            Point vi = poly.get(i);
            Point vj = poly.get(j);

            double vix = vi.getLng();
            double viy = vi.getLat();
            double vjx = vj.getLng();
            double vjy = vj.getLat();

            // Ray-Casting Point-in-Polygon validation
            boolean intersect = ((viy > py) != (vjy > py))
                && (px < (vjx - vix) * (py - viy) / (vjy - viy) + vix);
            
            if (intersect) {
                inside = !inside;
            }
        }
        return inside;
    }
}

class SpatialIndex {
    private final List<GeoZone> zones = new ArrayList<>();

    public synchronized void addZone(GeoZone zone) {
        zones.add(zone);
    }

    public synchronized void removeZone(String zoneId) {
        zones.removeIf(z -> z.getZoneId().equals(zoneId));
    }

    // Bounding Box Pre-Filter narrows candidates from N to O(K)
    public synchronized List<GeoZone> getCandidates(Point point) {
        List<GeoZone> candidates = new ArrayList<>();
        for (GeoZone zone : zones) {
            if (zone.getBoundingBox().contains(point)) {
                candidates.add(zone);
            }
        }
        return candidates;
    }
}

enum EventType {
    ENTER, EXIT
}

class GeofenceEvent {
    private final String userId;
    private final String zoneId;
    private final EventType type;
    private final long timestampMs;

    public GeofenceEvent(String userId, String zoneId, EventType type) {
        this.userId = userId;
        this.zoneId = zoneId;
        this.type = type;
        this.timestampMs = System.currentTimeMillis();
    }

    public String getUserId() { return userId; }
    public String getZoneId() { return zoneId; }
    public EventType getType() { return type; }
    public long getTimestampMs() { return timestampMs; }
}

class GeofenceMonitor {
    private final SpatialIndex spatialIndex;
    private final ConcurrentHashMap<String, Set<String>> userActiveZonesMap = new ConcurrentHashMap<>();

    public GeofenceMonitor(SpatialIndex spatialIndex) {
        this.spatialIndex = spatialIndex;
    }

    public synchronized List<GeofenceEvent> updateLocation(String userId, Point point) {
        List<GeofenceEvent> events = new ArrayList<>();

        // 1. Fast bounding-box pre-filtering
        List<GeoZone> candidates = spatialIndex.getCandidates(point);

        // 2. Ray-casting point-in-polygon containment check
        Set<String> newActiveZones = new HashSet<>();
        for (GeoZone candidate : candidates) {
            if (PointInPolygonTester.contains(point, candidate)) {
                newActiveZones.add(candidate.getZoneId());
            }
        }

        // 3. Diff old vs new zones to track transitions
        Set<String> previousActiveZones = userActiveZonesMap.getOrDefault(userId, Collections.emptySet());

        // Emit ENTER
        for (String zoneId : newActiveZones) {
            if (!previousActiveZones.contains(zoneId)) {
                events.add(new GeofenceEvent(userId, zoneId, EventType.ENTER));
            }
        }

        // Emit EXIT
        for (String zoneId : previousActiveZones) {
            if (!newActiveZones.contains(zoneId)) {
                events.add(new GeofenceEvent(userId, zoneId, EventType.EXIT));
            }
        }

        // Update local session register
        if (newActiveZones.isEmpty()) {
            userActiveZonesMap.remove(userId);
        } else {
            userActiveZonesMap.put(userId, newActiveZones);
        }

        return events;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== JAVA GEOFENCING LIBRARY SIMULATION ===");
        SpatialIndex index = new SpatialIndex();

        List<Point> polygon = Arrays.asList(
            new Point(10.0, 10.0),
            new Point(10.0, 20.0),
            new Point(20.0, 20.0),
            new Point(20.0, 10.0)
        );
        GeoZone zone = new GeoZone("zone-1", "HQ", polygon);
        index.addZone(zone);

        GeofenceMonitor monitor = new GeofenceMonitor(index);

        System.out.println("User is outside zone. Updating location to (5, 5)...");
        List<GeofenceEvent> events = monitor.updateLocation("user-1", new Point(5.0, 5.0));
        System.out.println("Events: " + events.size());

        System.out.println("User moves inside zone. Updating location to (15, 15)...");
        events = monitor.updateLocation("user-1", new Point(15.0, 15.0));
        for (GeofenceEvent e : events) {
            System.out.println("Event: " + e.getType() + " zone: " + e.getZoneId());
        }

        System.out.println("User moves outside zone again. Updating location to (25, 25)...");
        events = monitor.updateLocation("user-1", new Point(25.0, 25.0));
        for (GeofenceEvent e : events) {
            System.out.println("Event: " + e.getType() + " zone: " + e.getZoneId());
        }
        System.out.println("=== END OF JAVA SIMULATION ===");
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...