Saving current working state before proceeding to Stage 2. Includes: - Backend: Python-based QC validator with shapefile processing - Frontend: Drag-and-drop file upload interface - Sample files for testing - Documentation and revision history 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
126 lines
3.6 KiB
Go
126 lines
3.6 KiB
Go
package qc
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"verofy-backend/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func SiteConnectivityRoute(router *gin.Engine, db *gorm.DB, schema string) {
|
|
router.GET("/api/qc/site-connectivity", func(c *gin.Context) {
|
|
mapID := c.Query("map_id")
|
|
zone := c.Query("zone")
|
|
maxDistanceStr := c.Query("max_distance")
|
|
|
|
if mapID == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "map_id is required"})
|
|
return
|
|
}
|
|
|
|
// Default max distance is 50 meters
|
|
maxDistance := 50.0
|
|
if maxDistanceStr != "" {
|
|
if dist, err := strconv.ParseFloat(maxDistanceStr, 64); err == nil {
|
|
maxDistance = dist
|
|
}
|
|
}
|
|
|
|
// Get all sites for the market (and zone if specified)
|
|
var sites []models.SitesGeoJSON
|
|
siteQuery := db.Table(fmt.Sprintf("%s.sites", schema)).
|
|
Select("gid, id, mapprojectid, name, address1, city, state, zip, ST_AsGeoJSON(geom)::json AS geometry")
|
|
|
|
if mapID != "" {
|
|
siteQuery = siteQuery.Where("mapprojectid = ?", mapID)
|
|
}
|
|
|
|
siteQuery.Find(&sites)
|
|
|
|
// Get all segments for connectivity analysis
|
|
var segments []models.SegmentGeoJSON
|
|
segmentQuery := db.Table(fmt.Sprintf("%s.segment2", schema)).
|
|
Select("id_0, mapid, segment_type, segment_status, id, protection_status, qc_flag, ST_AsGeoJSON(geom)::json AS geometry")
|
|
|
|
if mapID != "" {
|
|
segmentQuery = segmentQuery.Where("mapid = ?", mapID)
|
|
}
|
|
if zone != "" {
|
|
segmentQuery = segmentQuery.Where("group_1 = ?", zone)
|
|
}
|
|
|
|
segmentQuery.Find(&segments)
|
|
|
|
// Analyze connectivity for each site
|
|
results := []map[string]interface{}{}
|
|
connectedCount := 0
|
|
disconnectedCount := 0
|
|
|
|
for _, site := range sites {
|
|
if len(site.Geometry) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Use PostGIS to find the nearest segment within max distance
|
|
var nearestDistance float64
|
|
nearestQuery := fmt.Sprintf(`
|
|
SELECT ST_Distance(
|
|
ST_Transform(sites.geom, 3857),
|
|
ST_Transform(segments.geom, 3857)
|
|
) as distance
|
|
FROM %s.sites sites, %s.segment2 segments
|
|
WHERE sites.gid = ? AND segments.mapid = ?
|
|
ORDER BY ST_Distance(
|
|
ST_Transform(sites.geom, 3857),
|
|
ST_Transform(segments.geom, 3857)
|
|
)
|
|
LIMIT 1
|
|
`, schema, schema)
|
|
|
|
db.Raw(nearestQuery, site.GID, mapID).Scan(&nearestDistance)
|
|
|
|
isConnected := nearestDistance <= maxDistance
|
|
status := "connected"
|
|
if !isConnected {
|
|
status = "disconnected"
|
|
disconnectedCount++
|
|
} else {
|
|
connectedCount++
|
|
}
|
|
|
|
// Update the site's connectivity status in the database
|
|
updateQuery := fmt.Sprintf("UPDATE %s.sites SET connectivity_status = ?, connectivity_distance = ? WHERE gid = ?", schema)
|
|
db.Exec(updateQuery, status, nearestDistance, site.GID)
|
|
|
|
siteResult := map[string]interface{}{
|
|
"site_id": site.GID,
|
|
"site_name": site.Name,
|
|
"mapprojectid": site.MapProjectID,
|
|
"is_connected": isConnected,
|
|
"nearest_distance": nearestDistance,
|
|
"connectivity_status": status,
|
|
"geometry": site.Geometry,
|
|
"address": site.Address1,
|
|
"city": site.City,
|
|
"state": site.State,
|
|
}
|
|
|
|
results = append(results, siteResult)
|
|
}
|
|
|
|
response := map[string]interface{}{
|
|
"total_sites": len(sites),
|
|
"connected_sites": connectedCount,
|
|
"disconnected_sites": disconnectedCount,
|
|
"connectivity_rate": float64(connectedCount) / float64(len(sites)) * 100,
|
|
"max_distance_meters": maxDistance,
|
|
"results": results,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
})
|
|
}
|