# Spatial Data Integration with sf

    library(linkeR)
    library(shiny)
    library(leaflet)
    library(DT)
    library(sf)

# Introduction

The `linkeR` package provides seamless integration with `sf` spatial objects, automatically extracting coordinates from geometry columns for use in leaflet maps. This vignette demonstrates how to work with spatial data in linked dashboards.

## Key Features

- Automatic coordinate extraction: linkeR automatically extracts longitude/latitude from sf geometry columns
- Preserves geometry: The original geometry column is kept for advanced spatial operations
- Mixed data types: Link sf objects with regular data frames
- Multi-geometry support: Works with POINT, POLYGON, and LINESTRING geometries

## Basic SF Integration

Here’s a simple example using sf point data:

    library(shiny)
    library(leaflet)
    library(DT)
    library(linkeR)
    library(sf)

    # Create sample sf point data
    create_sample_sf_data <- function() {
      # Create point geometries
      points <- st_sfc(
        st_point(c(-111.89, 40.76)),  # Salt Lake City
        st_point(c(-111.97, 41.22)),  # Ogden
        st_point(c(-111.66, 40.23)),  # Provo
        crs = 4326  # WGS84
      )

      # Create sf object
      sf_data <- st_sf(
        id = c("LOC_001", "LOC_002", "LOC_003"),
        name = c("Salt Lake City", "Ogden", "Provo"),
        population = c(200000, 87000, 116000),
        geometry = points
      )

      return(sf_data)
    }

    # Create corresponding table data
    create_table_data <- function() {
      data.frame(
        id = c("LOC_001", "LOC_002", "LOC_003"),
        name = c("Salt Lake City", "Ogden", "Provo"),
        county = c("Salt Lake", "Weber", "Utah"),
        established = c(1847, 1851, 1849),
        area_sq_mi = c(111.1, 26.6, 44.2)
      )
    }

    ui <- fluidPage(
      titlePanel("SF Integration Demo"),

      fluidRow(
        column(6,
          h4("Spatial Data (SF Object)"),
          leafletOutput("location_map")
        ),
        column(6,
          h4("City Information"),
          DTOutput("city_table")
        )
      ),

      fluidRow(
        column(12,
          h4("Selection Details"),
          verbatimTextOutput("selection_info")
        )
      )
    )

    server <- function(input, output, session) {

      # Create reactive sf data
      sf_data <- reactive({
        create_sample_sf_data()
      })

      # Create reactive table data
      table_data <- reactive({
        create_table_data()
      })

      # Render leaflet map
      output$location_map <- renderLeaflet({
        data <- sf_data()

        # For initial rendering, extract coordinates manually
        coords <- st_coordinates(data)

        leaflet() %>%
          addTiles() %>%
          addCircleMarkers(
            lng = coords[, 1],
            lat = coords[, 2],
            layerId = data$id,  # Critical for linking!
            radius = 8,
            popup = ~paste("City:", data$name)
          ) %>%
          fitBounds(
            lng1 = min(coords[, 1]) - 0.1,
            lat1 = min(coords[, 2]) - 0.1,
            lng2 = max(coords[, 1]) + 0.1,
            lat2 = max(coords[, 2]) + 0.1
          )
      })

      # Render data table
      output$city_table <- renderDT({
        datatable(
          table_data(),
          selection = "single",
          rownames = FALSE,
          options = list(pageLength = 5)
        )
      })

      # Link sf object with regular data frame
      registry <- link_plots(
        session,
        location_map = sf_data,    # SF object - coordinates auto-extracted!
        city_table = table_data,   # Regular data frame
        shared_id_column = "id"
      )

      # Display selection information
      output$selection_info <- renderText({
        selection <- registry$get_selection()
        if (!is.null(selection$selected_id)) {
          selected_sf <- sf_data()[sf_data()$id == selection$selected_id, ]
          selected_table <- table_data()[table_data()$id == selection$selected_id, ]

          paste0(
            "Selected: ", selected_sf$name, "\n",
            "Source: ", selection$source, "\n",
            "Population: ", format(selected_sf$population, big.mark = ","), "\n",
            "County: ", selected_table$county, "\n",
            "Established: ", selected_table$established
          )
        } else {
          "No selection"
        }
      })
    }

    shinyApp(ui, server)

## Advanced SF Integration with Custom Handlers

You can create custom click handlers that work with both the extracted coordinates and the original geometry:

    # Custom click handler that uses both coordinates and geometry
    custom_sf_handler <- function(map_proxy, selected_data, session) {
      if (!is.null(selected_data)) {
        # Use extracted coordinates for map operations
        longitude <- selected_data$longitude
        latitude <- selected_data$latitude

        # Create rich popup content
        popup_content <- paste0(
          "<div style='min-width: 200px;'>",
          "<h4>", selected_data$name, "</h4>",
          "<p><strong>Population:</strong> ", format(selected_data$population, big.mark = ","), "</p>",
          "<p><strong>Coordinates:</strong> ", round(longitude, 4), ", ", round(latitude, 4), "</p>",
          "<p><em>Data from SF object</em></p>",
          "</div>"
        )

        # Update map view and add popup
        map_proxy %>%
          leaflet::setView(lng = longitude, lat = latitude, zoom = 12) %>%
          leaflet::clearPopups() %>%
          leaflet::addPopups(
            lng = longitude,
            lat = latitude,
            popup = popup_content
          )

        # If you have the original geometry, you could also add buffers, etc.
        # This demonstrates how you can access both coordinate and geometry data

      } else {
        # Handle deselection
        map_proxy %>% leaflet::clearPopups()
      }
    }

    # Use the custom handler in link_plots
    registry <- link_plots(
      session,
      location_map = sf_data,
      city_table = table_data,
      shared_id_column = "id",
      leaflet_click_handler = custom_sf_handler
    )

## Working with Different Geometry Types

linkeR supports various geometry types by extracting appropriate coordinates:

    # Example with polygon data
    create_polygon_sf_data <- function() {
      # Create polygon geometries (city boundaries)
      polygon1 <- st_polygon(list(cbind(
        c(-111.95, -111.85, -111.85, -111.95, -111.95),
        c(40.72, 40.72, 40.80, 40.80, 40.72)
      )))

      polygon2 <- st_polygon(list(cbind(
        c(-112.05, -111.95, -111.95, -112.05, -112.05),
        c(41.18, 41.18, 41.26, 41.26, 41.18)
      )))

      polygons <- st_sfc(polygon1, polygon2, crs = 4326)

      # Create sf object with polygon geometries
      sf_polygons <- st_sf(
        id = c("ZONE_001", "ZONE_002"),
        name = c("Downtown SLC", "Downtown Ogden"),
        area_sq_km = c(25.5, 18.2),
        geometry = polygons
      )

      return(sf_polygons)
    }

    # linkeR automatically extracts centroids for non-POINT geometries
    polygon_data <- reactive({
      create_polygon_sf_data()
    })

    # This works the same way - linkeR handles the geometry type automatically
    registry <- link_plots(
      session,
      zone_map = polygon_data,     # Polygon sf object
      zone_table = table_data,     # Regular data frame
      shared_id_column = "id"
    )

## Benefits of SF Integration

### 1. Automatic Coordinate Extraction

- No need to manually extract coordinates from geometry columns
- Works with POINT, POLYGON, and LINESTRING geometries
- Handles coordinate reference system transformations

### 2. Preserved Geometry

- Original geometry column is maintained
- Enables advanced spatial operations in custom handlers
- Supports complex spatial workflows

### 3. Mixed Data Types

- Link sf objects with regular data frames seamlessly
- Consistent behavior across different data sources
- Flexible dashboard design

### 4. Developer-Friendly

- Simple API - just pass your sf object to `link_plots()`
- Extensive error handling and validation
- Clear documentation and examples

## Troubleshooting

### Common Issues:

“Required columns missing”: Ensure your sf object has valid geometries that can be converted to coordinates.

“Non-POINT geometries detected”: This is normal - linkeR automatically uses centroids for polygons and lines. There is potential for more advanced support in the future.

Performance issues: Large sf objects can impact performance. Consider simplifying geometries or using spatial indexing.

### Debugging SF Integration:

    # Check your sf object structure
    sf_data <- your_sf_object()
    print(st_geometry_type(sf_data))  # Check geometry types
    print(st_crs(sf_data))           # Check coordinate reference system
    print(names(sf_data))            # Check column names

    # Test coordinate extraction
    coords <- st_coordinates(sf_data)
    print(head(coords))              # Verify coordinates are numeric

## Next Steps

- See `vignette("getting-started")` for basic linkeR usage
- See `vignette("custom-behaviors")` for advanced click handlers
- See `vignette("multiple-components")` for complex dashboard examples
- Run `system.file("examples", "sf_app.R", package = "linkeR")` for a complete sf demonstration

The sf integration in linkeR makes it easy to create interactive spatial dashboards without manual coordinate management, while preserving the full power of sf for advanced spatial operations.