chore: initialize insar management system v2
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
-- Migration: PostGIS helper functions and views for InSAR workflows
|
||||
-- Note: keep this file UTF-8 without BOM to avoid SQL parser issues.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
|
||||
-- =====================================================
|
||||
-- Function: find_dinsar_pairs
|
||||
-- Purpose : Find candidate D-InSAR pairs with spatial/time/overlap constraints
|
||||
-- =====================================================
|
||||
CREATE OR REPLACE FUNCTION find_dinsar_pairs(
|
||||
p_time_baseline_min INTEGER,
|
||||
p_time_baseline_max INTEGER,
|
||||
p_spatial_baseline_max_meters NUMERIC,
|
||||
p_overlap_threshold NUMERIC,
|
||||
p_start_date TEXT DEFAULT NULL,
|
||||
p_aoi_geom GEOMETRY DEFAULT NULL,
|
||||
p_require_orbit_data BOOLEAN DEFAULT TRUE,
|
||||
p_require_same_imaging_mode BOOLEAN DEFAULT TRUE,
|
||||
p_require_same_polarization BOOLEAN DEFAULT TRUE,
|
||||
p_aoi_overlap_threshold NUMERIC DEFAULT NULL
|
||||
)
|
||||
RETURNS TABLE (
|
||||
master_id INTEGER,
|
||||
slave_id INTEGER,
|
||||
master_imaging_date TEXT,
|
||||
slave_imaging_date TEXT,
|
||||
time_baseline_days INTEGER,
|
||||
spatial_baseline_meters NUMERIC,
|
||||
overlap_ratio NUMERIC
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH candidate_pairs AS (
|
||||
SELECT
|
||||
m.id AS master_id,
|
||||
s.id AS slave_id,
|
||||
m.imaging_date::text AS master_imaging_date,
|
||||
s.imaging_date::text AS slave_imaging_date,
|
||||
ABS(to_date(s.imaging_date, 'YYYYMMDD') - to_date(m.imaging_date, 'YYYYMMDD')) AS time_baseline_days,
|
||||
ST_DistanceSphere(ST_Centroid(m.geom), ST_Centroid(s.geom))::numeric AS spatial_baseline_meters,
|
||||
(
|
||||
ST_Area(ST_Intersection(m.geom, s.geom)::geography) /
|
||||
NULLIF(GREATEST(ST_Area(m.geom::geography), ST_Area(s.geom::geography)), 0)
|
||||
)::numeric AS overlap_ratio
|
||||
FROM radar_data m
|
||||
JOIN radar_data s ON ST_Intersects(m.geom, s.geom)
|
||||
WHERE m.id < s.id
|
||||
AND m.orbit_direction = s.orbit_direction
|
||||
AND m.satellite = s.satellite
|
||||
AND (NOT p_require_orbit_data OR (m.has_orbit_data = true AND s.has_orbit_data = true))
|
||||
AND (
|
||||
NOT p_require_same_imaging_mode OR (
|
||||
m.imaging_mode IS NOT NULL AND m.imaging_mode <> ''
|
||||
AND s.imaging_mode IS NOT NULL AND s.imaging_mode <> ''
|
||||
AND m.imaging_mode = s.imaging_mode
|
||||
)
|
||||
)
|
||||
AND (
|
||||
NOT p_require_same_polarization OR (
|
||||
m.polarization IS NOT NULL AND m.polarization <> ''
|
||||
AND s.polarization IS NOT NULL AND s.polarization <> ''
|
||||
AND m.polarization = s.polarization
|
||||
)
|
||||
)
|
||||
AND (p_start_date IS NULL OR (m.imaging_date >= p_start_date AND s.imaging_date >= p_start_date))
|
||||
AND (p_aoi_geom IS NULL OR (ST_Intersects(m.geom, p_aoi_geom) AND ST_Intersects(s.geom, p_aoi_geom)))
|
||||
AND (
|
||||
p_aoi_geom IS NULL OR p_aoi_overlap_threshold IS NULL OR (
|
||||
ST_Area(ST_Intersection(m.geom, p_aoi_geom)::geography) /
|
||||
NULLIF(ST_Area(p_aoi_geom::geography), 0) >= p_aoi_overlap_threshold
|
||||
AND ST_Area(ST_Intersection(s.geom, p_aoi_geom)::geography) /
|
||||
NULLIF(ST_Area(p_aoi_geom::geography), 0) >= p_aoi_overlap_threshold
|
||||
)
|
||||
)
|
||||
AND (m.imaging_date ~ '^[0-9]{8}$' AND s.imaging_date ~ '^[0-9]{8}$')
|
||||
)
|
||||
SELECT
|
||||
cp.master_id,
|
||||
cp.slave_id,
|
||||
cp.master_imaging_date,
|
||||
cp.slave_imaging_date,
|
||||
cp.time_baseline_days,
|
||||
cp.spatial_baseline_meters,
|
||||
cp.overlap_ratio
|
||||
FROM candidate_pairs cp
|
||||
WHERE cp.time_baseline_days BETWEEN p_time_baseline_min AND p_time_baseline_max
|
||||
AND cp.spatial_baseline_meters <= p_spatial_baseline_max_meters
|
||||
AND cp.overlap_ratio >= p_overlap_threshold
|
||||
ORDER BY cp.overlap_ratio DESC;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =====================================================
|
||||
-- Function: calculate_coverage_overlap
|
||||
-- Purpose : Compute overlap area and ratio between two images
|
||||
-- =====================================================
|
||||
CREATE OR REPLACE FUNCTION calculate_coverage_overlap(
|
||||
p_image1_id INTEGER,
|
||||
p_image2_id INTEGER
|
||||
)
|
||||
RETURNS TABLE (
|
||||
overlap_area NUMERIC,
|
||||
overlap_ratio NUMERIC,
|
||||
intersection_geom GEOMETRY
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
ST_Area(ST_Intersection(r1.geom, r2.geom)::geography) AS overlap_area,
|
||||
ST_Area(ST_Intersection(r1.geom, r2.geom)::geography) /
|
||||
NULLIF(GREATEST(ST_Area(r1.geom::geography), ST_Area(r2.geom::geography)), 0) AS overlap_ratio,
|
||||
ST_Intersection(r1.geom, r2.geom) AS intersection_geom
|
||||
FROM radar_data r1
|
||||
CROSS JOIN radar_data r2
|
||||
WHERE r1.id = p_image1_id AND r2.id = p_image2_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =====================================================
|
||||
-- Function: find_common_overlap_area
|
||||
-- Purpose : Find common overlap area among a set of images
|
||||
-- =====================================================
|
||||
CREATE OR REPLACE FUNCTION find_common_overlap_area(
|
||||
p_image_ids INTEGER[]
|
||||
)
|
||||
RETURNS TABLE (
|
||||
common_area NUMERIC,
|
||||
common_geom GEOMETRY
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_first_geom GEOMETRY;
|
||||
v_result_geom GEOMETRY;
|
||||
BEGIN
|
||||
IF p_image_ids IS NULL OR array_length(p_image_ids, 1) IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
SELECT geom INTO v_first_geom
|
||||
FROM radar_data
|
||||
WHERE id = p_image_ids[1];
|
||||
|
||||
v_result_geom := v_first_geom;
|
||||
|
||||
FOR i IN 2..array_length(p_image_ids, 1) LOOP
|
||||
SELECT ST_Intersection(v_result_geom, geom) INTO v_result_geom
|
||||
FROM radar_data
|
||||
WHERE id = p_image_ids[i];
|
||||
|
||||
IF v_result_geom IS NULL OR ST_IsEmpty(v_result_geom) THEN
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
IF v_result_geom IS NULL OR ST_IsEmpty(v_result_geom) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
ST_Area(v_result_geom::geography) AS common_area,
|
||||
v_result_geom AS common_geom;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =====================================================
|
||||
-- View: radar_pairs_view (helper view)
|
||||
-- =====================================================
|
||||
CREATE OR REPLACE VIEW radar_pairs_view AS
|
||||
SELECT
|
||||
row_number() OVER (ORDER BY m.id, s.id) AS id,
|
||||
m.id AS master_id,
|
||||
s.id AS slave_id,
|
||||
m.imaging_date AS master_date,
|
||||
s.imaging_date AS slave_date,
|
||||
CASE
|
||||
WHEN m.imaging_date ~ '^[0-9]{8}$' AND s.imaging_date ~ '^[0-9]{8}$'
|
||||
THEN ABS(to_date(s.imaging_date, 'YYYYMMDD') - to_date(m.imaging_date, 'YYYYMMDD'))
|
||||
ELSE NULL
|
||||
END AS time_baseline_days,
|
||||
ST_DistanceSphere(ST_Centroid(m.geom), ST_Centroid(s.geom)) AS spatial_baseline_meters,
|
||||
m.geom AS geom1,
|
||||
s.geom AS geom2,
|
||||
m.geom AS master_geom,
|
||||
s.geom AS slave_geom,
|
||||
ST_Area(ST_Intersection(m.geom, s.geom)::geography) AS overlap_area
|
||||
FROM radar_data m
|
||||
JOIN radar_data s ON ST_Intersects(m.geom, s.geom)
|
||||
WHERE m.id < s.id;
|
||||
|
||||
-- =====================================================
|
||||
-- Function: optimize_task_selection
|
||||
-- Purpose : Select pairs with spatial coverage diversity
|
||||
-- =====================================================
|
||||
CREATE OR REPLACE FUNCTION optimize_task_selection(
|
||||
p_pair_ids INTEGER[],
|
||||
p_penalty_factor NUMERIC DEFAULT 0.3
|
||||
)
|
||||
RETURNS TABLE (
|
||||
selected_pair_id INTEGER,
|
||||
score NUMERIC
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_selected_ids INTEGER[];
|
||||
v_total_geom GEOMETRY := NULL;
|
||||
v_pair RECORD;
|
||||
v_inter_geom GEOMETRY;
|
||||
v_new_area NUMERIC;
|
||||
v_overlap_area NUMERIC;
|
||||
v_score NUMERIC;
|
||||
BEGIN
|
||||
FOR v_pair IN
|
||||
SELECT id, geom1, geom2, overlap_area
|
||||
FROM radar_pairs_view
|
||||
WHERE id = ANY(p_pair_ids)
|
||||
ORDER BY overlap_area DESC
|
||||
LOOP
|
||||
v_inter_geom := ST_Intersection(v_pair.geom1, v_pair.geom2);
|
||||
|
||||
IF v_total_geom IS NULL THEN
|
||||
v_total_geom := v_inter_geom;
|
||||
v_selected_ids := array_append(v_selected_ids, v_pair.id);
|
||||
ELSE
|
||||
v_new_area := ST_Area(ST_Difference(v_inter_geom, v_total_geom));
|
||||
v_overlap_area := ST_Area(ST_Intersection(v_inter_geom, v_total_geom));
|
||||
v_score := v_new_area - (v_overlap_area * p_penalty_factor);
|
||||
|
||||
IF v_score > 0 THEN
|
||||
v_total_geom := ST_Union(v_total_geom, v_inter_geom);
|
||||
v_selected_ids := array_append(v_selected_ids, v_pair.id);
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN QUERY
|
||||
SELECT unnest(v_selected_ids)::INTEGER AS selected_pair_id, 0 AS score;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =====================================================
|
||||
-- Function: find_hazard_points_in_area
|
||||
-- Purpose : Find hazard points within a geometry
|
||||
-- =====================================================
|
||||
CREATE OR REPLACE FUNCTION find_hazard_points_in_area(
|
||||
p_area_geom GEOMETRY
|
||||
)
|
||||
RETURNS TABLE (
|
||||
id INTEGER,
|
||||
tybh TEXT,
|
||||
hazard_type TEXT,
|
||||
hazard_name TEXT,
|
||||
city TEXT,
|
||||
county TEXT,
|
||||
longitude NUMERIC,
|
||||
latitude NUMERIC
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
hp.id,
|
||||
hp.tybh,
|
||||
hp.hazard_type,
|
||||
hp.hazard_name,
|
||||
hp.city,
|
||||
hp.county,
|
||||
hp.longitude,
|
||||
hp.latitude
|
||||
FROM hazard_points hp
|
||||
WHERE ST_Covers(p_area_geom, hp.geom);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =====================================================
|
||||
-- Table + Function: spatial query logs
|
||||
-- =====================================================
|
||||
CREATE TABLE IF NOT EXISTS spatial_query_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
query_type TEXT,
|
||||
execution_time_ms NUMERIC,
|
||||
result_count INTEGER,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION log_spatial_query(
|
||||
p_query_type TEXT,
|
||||
p_execution_time_ms NUMERIC,
|
||||
p_result_count INTEGER
|
||||
)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO spatial_query_logs (query_type, execution_time_ms, result_count)
|
||||
VALUES (p_query_type, p_execution_time_ms, p_result_count);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- =====================================================
|
||||
-- Indexes (redundant with ORM, kept for reference)
|
||||
-- =====================================================
|
||||
CREATE INDEX IF NOT EXISTS idx_radar_data_geom ON radar_data USING GIST (geom);
|
||||
CREATE INDEX IF NOT EXISTS idx_dinsar_results_geom ON dinsar_results USING GIST (geom);
|
||||
CREATE INDEX IF NOT EXISTS idx_hazard_points_geom ON hazard_points USING GIST (geom);
|
||||
|
||||
-- =====================================================
|
||||
-- Optional grants (adjust to your app user)
|
||||
-- =====================================================
|
||||
-- GRANT EXECUTE ON FUNCTION find_dinsar_pairs TO your_app_user;
|
||||
-- GRANT EXECUTE ON FUNCTION calculate_coverage_overlap TO your_app_user;
|
||||
-- GRANT SELECT ON radar_pairs_view TO your_app_user;
|
||||
Reference in New Issue
Block a user