Machine Coding Problem

Geofencing Library

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

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.

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 ===");
    }
}