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