📍 GPS Coverage Map

Scroll down for video of capture and the capture graph code!

🎬 Capture Video

📄 Capture Graph Code

Client-Side Capture Procedure

def location_survey_procedure() -> cgp.ProcedureSequence:
    """Client-side capture procedure for the GPS survey."""
    root = cgp.GetRootDirectory()
    session = root.new_session("LocationSurvey")

    # Show instructions
    instructions = cgp.show_instructions(
        title="Location Survey",
        text="Walk to the guided location and record a 360° spin video.",
    )

    # Guide user to server-assigned location
    target_location = root.get_location_file().load(
        or_else=cgp.NullProcedure(return_type=cgp.PLocation)
    )
    check_location = cgp.GuideToLocation(
        target=target_location, threshold_meters=LOCATION_SIGMA_M
    )

    # Capture panoramic video
    video = cgp.CaptureVideo(label="Record 360° Spin Video")
    save_video = session.new_file("spin_video").save(video)

    return cgp.ProcedureSequence(
        label="Location Survey Capture",
        procedures=[instructions, check_location, save_video],
    )

Server-Side Location Scheduler (Void-and-Cluster)

def __capture_location__(user_id, target, persistence) -> cg.Location:
    """Compute optimal location using void-and-cluster on GPS coordinates."""
    sessions = target.LocationSurvey
    distance_fn = cgsh.distance.combine(
        location=cgsh.distance.location(sigma_m=10.0)
    )

    store = cgsh.organize.BatchedDistanceStore(
        persistence=persistence,
        sessions=sessions,
        distance_fn=distance_fn,
        threshold=1.0,
    )

    if store.is_empty():
        # Generate candidates on grid within bounds
        candidates = cg.List([])
        candidates.location = cgsh.forecast.locations_area(
            bounds=BOUNDS, resolution_meters=1.0,
        )

        # Select batch using void-and-cluster (maximize distance)
        slots = cgsh.select_sessions(
            potential_sessions=candidates,
            previous_sessions=sessions,
            distance_fn=distance_fn,
            energy_fn=lambda d: -d,
            energy_mode="max",
            selections=8,
        )
        store.set_batch(list(slots))

    slot = store.request_slot(user_id)
    return slot.location if slot else BOUNDS[0]