Android SDK for Robot Geofencing and Safety-Zone Management
When autonomous mobile devices navigate human environments, keeping them out of hazardous areas (keep-out zones, stairwells, restricted storage) is critical for industrial safety compliance (ISO 3691-4).
This tutorial demonstrates how to implement a spatial geofencing engine in Kotlin using spatial indexing, ray-casting point-in-polygon (PIP) algorithms, and instant safety interlocks.
1. Safety Zone Taxonomy
+-------------------------------------------------------------+
| Operating Environment |
| +-------------------------------------------------------+ |
| | Keep-Out Zone (Polygon) | |
| | [Robots Prohibited - Trigger E-Stop / Speed 0.0] | |
| +-------------------------------------------------------+ |
| |
| +-------------------------------------------------------+ |
| | Warning Zone (Polygon) | |
| | [Speed Limited to <= 0.3 m/s] | |
| +-------------------------------------------------------+ |
+-------------------------------------------------------------+
2. Spatial Data Structures
package com.vmodal.sdk.geofence.model
data class Point2D(val x: Double, val y: Double)
enum class ZoneType {
KEEP_OUT, // Hard stop zone
SPEED_RESTRICTED, // Soft slow-down zone
AUTHORIZED_ONLY // Requires special operator permission
}
data class SafetyZone(
val zoneId: String,
val name: String,
val type: ZoneType,
val vertices: List<Point2D>,
val maxSpeedLimitMps: Double = 0.0
)
3. Implementing Ray-Casting Point-in-Polygon Engine
The Ray-Casting algorithm determines whether a given 2D coordinate $(x, y)$ resides inside an arbitrary polygon by drawing a ray to infinity and counting edge intersections.
package com.vmodal.sdk.geofence.geometry
import com.vmodal.sdk.geofence.model.Point2D
class PolygonGeometryEvaluator {
/**
* Standard Ray-Casting Algorithm for Point-in-Polygon
*/
fun isPointInsidePolygon(point: Point2D, polygon: List<Point2D>): Boolean {
if (polygon.size < 3) return false
var inside = false
var j = polygon.size - 1
for (i in polygon.indices) {
val pi = polygon[i]
val pj = polygon[j]
val intersect = ((pi.y > point.y) != (pj.y > point.y)) &&
(point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x)
if (intersect) inside = !inside
j = i
}
return inside
}
}
4. Building the Geofence Evaluation Engine
package com.vmodal.sdk.geofence
import com.vmodal.sdk.geofence.geometry.PolygonGeometryEvaluator
import com.vmodal.sdk.geofence.model.Point2D
import com.vmodal.sdk.geofence.model.SafetyZone
import com.vmodal.sdk.geofence.model.ZoneType
sealed class SafetyEvaluationResult {
object SafeState : SafetyEvaluationResult()
data class SpeedLimitEnforced(val maxSpeed: Double, val zoneName: String) : SafetyEvaluationResult()
data class EmergencyStopRequired(val violatedZoneId: String, val zoneName: String) : SafetyEvaluationResult()
}
class GeofenceSafetyManager {
private val activeZones = mutableListOf<SafetyZone>()
private val evaluator = PolygonGeometryEvaluator()
fun updateZones(zones: List<SafetyZone>) {
synchronized(activeZones) {
activeZones.clear()
activeZones.addAll(zones)
}
}
fun evaluateRobotPosition(robotPosition: Point2D): SafetyEvaluationResult {
synchronized(activeZones) {
for (zone in activeZones) {
if (evaluator.isPointInsidePolygon(robotPosition, zone.vertices)) {
return when (zone.type) {
ZoneType.KEEP_OUT -> SafetyEvaluationResult.EmergencyStopRequired(
violatedZoneId = zone.zoneId,
zoneName = zone.name
)
ZoneType.SPEED_RESTRICTED -> SafetyEvaluationResult.SpeedLimitEnforced(
maxSpeed = zone.maxSpeedLimitMps,
zoneName = zone.name
)
ZoneType.AUTHORIZED_ONLY -> SafetyEvaluationResult.EmergencyStopRequired(
violatedZoneId = zone.zoneId,
zoneName = "${zone.name} (Unauthorized Access)"
)
}
}
}
}
return SafetyEvaluationResult.SafeState
}
}
5. End-to-End Test Execution
import com.vmodal.sdk.geofence.GeofenceSafetyManager
import com.vmodal.sdk.geofence.SafetyEvaluationResult
import com.vmodal.sdk.geofence.model.Point2D
import com.vmodal.sdk.geofence.model.SafetyZone
import com.vmodal.sdk.geofence.model.ZoneType
fun main() {
val geofenceManager = GeofenceSafetyManager()
// Define a Keep-Out zone around a open stairwell
val stairwellZone = SafetyZone(
zoneId = "ZONE_HAZARD_01",
name = "Main Stairwell Pit",
type = ZoneType.KEEP_OUT,
vertices = listOf(
Point2D(10.0, 10.0),
Point2D(15.0, 10.0),
Point2D(15.0, 15.0),
Point2D(10.0, 15.0)
)
)
geofenceManager.updateZones(listOf(stairwellZone))
val safePose = Point2D(5.0, 5.0)
val breachPose = Point2D(12.0, 12.0)
println("Evaluating Pose 1 (5,5): ${geofenceManager.evaluateRobotPosition(safePose)}")
println("Evaluating Pose 2 (12,12): ${geofenceManager.evaluateRobotPosition(breachPose)}")
}
Conclusion
Enforcing spatial integrity using in-memory point-in-polygon evaluations gives Android-driven robots an instant, deterministic safety layer to satisfy strict operational requirements.
Useful Links
- Website: www.v-modal.com
- SDK Flutter: v-modal/vmodal_sdk_flutter
- SDK Android: v-modal/vmodal_sdk_android
- Discord: https://discord.gg/K72z28KUx











