geospatial-postgis-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Geospatial Patterns - PostGIS for laneweaverTMS

地理空间模式 - 为laneweaverTMS使用PostGIS

When to Use This Skill

何时使用该技能

Use when:
  • Creating geofence boundaries around facilities
  • Calculating distances between points (truck to facility, origin to destination)
  • Detecting geofence entry/exit events for tracking
  • Building ETA calculations or routing features
  • Querying fleet positions and historical tracks
  • Implementing spatial indexes for location queries
  • Integrating with mapping frontends (Mapbox, Leaflet)
适用于以下场景:
  • 为设施创建地理围栏边界
  • 计算点位间距离(卡车到设施、起点到终点)
  • 检测地理围栏的进入/退出事件以实现追踪
  • 构建ETA计算或路由功能
  • 查询车队位置和历史轨迹
  • 为位置查询实现空间索引
  • 与地图前端集成(Mapbox、Leaflet)

PostGIS Fundamentals

PostGIS基础

Geography vs Geometry Types

Geography与Geometry类型

Use
geography
for real-world distance calculations
:
TypeUse CaseDistance UnitEarth Curvature
geography
GPS coordinates, long distancesMetersAccounts for curvature
geometry
Local/planar operations, contains checksProjection unitsIgnores curvature
sql
-- Geography: accurate distances in meters for GPS data
SELECT ST_Distance(
    ST_MakePoint(-87.6298, 41.8781)::geography,  -- Chicago
    ST_MakePoint(-122.4194, 37.7749)::geography  -- San Francisco
) / 1609.34 AS distance_miles;
-- Returns: ~1856 miles (accurate)

-- Geometry: faster but less accurate for large distances
SELECT ST_Distance(
    ST_SetSRID(ST_MakePoint(-87.6298, 41.8781), 4326),
    ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)
) AS distance_degrees;
-- Returns: degrees (must convert, less accurate for long distances)
在进行真实世界距离计算时使用
geography
类型
:
类型适用场景距离单位地球曲率处理
geography
GPS坐标、长距离计算Meters考虑地球曲率
geometry
本地/平面操作、包含性检查投影单位忽略地球曲率
sql
-- Geography: 对GPS数据计算精确的米制距离
SELECT ST_Distance(
    ST_MakePoint(-87.6298, 41.8781)::geography,  -- Chicago
    ST_MakePoint(-122.4194, 37.7749)::geography  -- San Francisco
) / 1609.34 AS distance_miles;
-- Returns: ~1856 miles (accurate)

-- Geometry: 速度更快但长距离计算精度较低
SELECT ST_Distance(
    ST_SetSRID(ST_MakePoint(-87.6298, 41.8781), 4326),
    ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)
) AS distance_degrees;
-- Returns: degrees (must convert, less accurate for long distances)

SRID 4326 (WGS84)

SRID 4326(WGS84坐标系)

All GPS coordinates use SRID 4326 (World Geodetic System 1984):
sql
-- Creating a point from GPS coordinates
ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)

-- Creating a geography point (preferred for distance calculations)
ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography

-- Example: Create point for a facility location
ST_SetSRID(ST_MakePoint(-87.6298, 41.8781), 4326)::geography
Important: PostGIS uses (longitude, latitude) order, NOT (latitude, longitude).
所有GPS坐标均使用SRID 4326(1984世界大地坐标系):
sql
-- 从GPS坐标创建点
ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)

-- 创建geography类型的点(推荐用于距离计算)
ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography

-- 示例:为设施位置创建点
ST_SetSRID(ST_MakePoint(-87.6298, 41.8781), 4326)::geography
重要提示: PostGIS使用(经度,纬度)顺序,而非(纬度,经度)。

Geofence Table Design

地理围栏表设计

Table Structure

表结构

sql
-- Migration: Create geofences table

CREATE TABLE public.geofences (
    -- Primary key
    id UUID DEFAULT gen_random_uuid() NOT NULL,

    -- Identification
    name TEXT NOT NULL,
    description TEXT,

    -- Relationship to facility (optional - standalone geofences allowed)
    facility_id UUID REFERENCES facilities(id) ON DELETE SET NULL,

    -- Spatial boundary - geography type for accurate distance calculations
    boundary geography(Polygon, 4326) NOT NULL,

    -- For circular geofences, store radius for reference
    radius_miles NUMERIC(10,2),

    -- Geofence type for business logic
    geofence_type TEXT NOT NULL DEFAULT 'facility',

    -- Active flag for enabling/disabling
    is_active BOOLEAN NOT NULL DEFAULT true,

    -- Standard audit columns
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    created_by INT4,
    updated_by INT4,
    deleted_at TIMESTAMPTZ,
    deleted_by INT4,

    CONSTRAINT geofences_pkey PRIMARY KEY (id),
    CONSTRAINT chk_geofences_type CHECK (
        geofence_type IN ('facility', 'city', 'region', 'custom')
    )
);

COMMENT ON TABLE public.geofences IS
    'Geographic boundaries for tracking events (arrival, departure, dwell time)';

COMMENT ON COLUMN public.geofences.boundary IS
    'Polygon boundary in WGS84 (SRID 4326). Use geography type for accurate distance calculations';

COMMENT ON COLUMN public.geofences.radius_miles IS
    'For circular geofences, the original radius used to generate the boundary polygon';
sql
-- 迁移脚本:创建geofences表

CREATE TABLE public.geofences (
    -- 主键
    id UUID DEFAULT gen_random_uuid() NOT NULL,

    -- 标识信息
    name TEXT NOT NULL,
    description TEXT,

    -- 与设施的关联关系(可选 - 允许独立地理围栏)
    facility_id UUID REFERENCES facilities(id) ON DELETE SET NULL,

    -- 空间边界 - 使用geography类型以实现精确的距离计算
    boundary geography(Polygon, 4326) NOT NULL,

    -- 对于圆形地理围栏,存储参考半径
    radius_miles NUMERIC(10,2),

    -- 用于业务逻辑的地理围栏类型
    geofence_type TEXT NOT NULL DEFAULT 'facility',

    -- 启用/禁用的状态标识
    is_active BOOLEAN NOT NULL DEFAULT true,

    -- 标准审计列
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
    created_by INT4,
    updated_by INT4,
    deleted_at TIMESTAMPTZ,
    deleted_by INT4,

    CONSTRAINT geofences_pkey PRIMARY KEY (id),
    CONSTRAINT chk_geofences_type CHECK (
        geofence_type IN ('facility', 'city', 'region', 'custom')
    )
);

COMMENT ON TABLE public.geofences IS
    '用于追踪事件(到达、离开、停留时间)的地理边界';

COMMENT ON COLUMN public.geofences.boundary IS
    'WGS84坐标系(SRID 4326)下的多边形边界。使用geography类型以实现精确的距离计算';

COMMENT ON COLUMN public.geofences.radius_miles IS
    '对于圆形地理围栏,用于生成边界多边形的原始半径';

Creating Circular Geofences

创建圆形地理围栏

sql
-- Create a circular geofence around a facility
INSERT INTO geofences (name, facility_id, boundary, radius_miles, geofence_type)
SELECT
    f.name || ' Geofence',
    f.id,
    -- ST_Buffer creates a circle; convert miles to meters (1 mile = 1609.34 meters)
    ST_Buffer(
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography,
        0.5 * 1609.34  -- 0.5 mile radius in meters
    ),
    0.5,
    'facility'
FROM facilities f
WHERE f.id = $1;
sql
-- 为设施创建圆形地理围栏
INSERT INTO geofences (name, facility_id, boundary, radius_miles, geofence_type)
SELECT
    f.name || ' Geofence',
    f.id,
    -- ST_Buffer创建圆形;将英里转换为米(1英里=1609.34米)
    ST_Buffer(
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography,
        0.5 * 1609.34  -- 0.5英里半径(米制)
    ),
    0.5,
    'facility'
FROM facilities f
WHERE f.id = $1;

Creating Polygon Geofences

创建多边形地理围栏

sql
-- Create a custom polygon geofence (e.g., terminal yard)
INSERT INTO geofences (name, boundary, geofence_type)
VALUES (
    'Terminal Yard A',
    ST_GeomFromText(
        'POLYGON((-87.630 41.878, -87.628 41.878, -87.628 41.876, -87.630 41.876, -87.630 41.878))',
        4326
    )::geography,
    'custom'
);
sql
-- 创建自定义多边形地理围栏(例如:码头堆场)
INSERT INTO geofences (name, boundary, geofence_type)
VALUES (
    'Terminal Yard A',
    ST_GeomFromText(
        'POLYGON((-87.630 41.878, -87.628 41.878, -87.628 41.876, -87.630 41.876, -87.630 41.878))',
        4326
    )::geography,
    'custom'
);

Spatial Indexes

空间索引

Critical: Index All Spatial Columns

关键操作:为所有空间列创建索引

sql
-- Migration: Add spatial indexes to geofences table

-- GIST index on geofence boundaries (required for spatial queries)
CREATE INDEX idx_geofences_boundary
    ON public.geofences USING GIST(boundary);

-- Standard indexes
CREATE INDEX idx_geofences_facility_id
    ON public.geofences(facility_id)
    WHERE facility_id IS NOT NULL;

CREATE INDEX idx_geofences_deleted_at
    ON public.geofences(deleted_at)
    WHERE deleted_at IS NULL;

CREATE INDEX idx_geofences_is_active
    ON public.geofences(is_active)
    WHERE is_active = true;
sql
-- 迁移脚本:为geofences表添加空间索引

-- 为地理围栏边界创建GIST索引(空间查询必需)
CREATE INDEX idx_geofences_boundary
    ON public.geofences USING GIST(boundary);

-- 标准索引
CREATE INDEX idx_geofences_facility_id
    ON public.geofences(facility_id)
    WHERE facility_id IS NOT NULL;

CREATE INDEX idx_geofences_deleted_at
    ON public.geofences(deleted_at)
    WHERE deleted_at IS NULL;

CREATE INDEX idx_geofences_is_active
    ON public.geofences(is_active)
    WHERE is_active = true;

Index for load_cognition Location Queries

为load_cognition表创建位置查询索引

sql
-- Create index on load_cognition for location-based queries
-- Note: Creates expression index since location is stored as lat/lon columns

CREATE INDEX idx_load_cognition_location
    ON public.load_cognition USING GIST(
        ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography
    );

-- Alternative: Partial index for valid coordinates only
CREATE INDEX idx_load_cognition_location_valid
    ON public.load_cognition USING GIST(
        ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography
    )
    WHERE latitude IS NOT NULL
      AND longitude IS NOT NULL
      AND latitude BETWEEN -90 AND 90
      AND longitude BETWEEN -180 AND 180;
sql
-- 为load_cognition表创建基于位置查询的索引
-- 注意:由于位置存储为lat/lon列,因此创建表达式索引

CREATE INDEX idx_load_cognition_location
    ON public.load_cognition USING GIST(
        ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography
    );

-- 替代方案:仅为有效坐标创建部分索引
CREATE INDEX idx_load_cognition_location_valid
    ON public.load_cognition USING GIST(
        ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography
    )
    WHERE latitude IS NOT NULL
      AND longitude IS NOT NULL
      AND latitude BETWEEN -90 AND 90
      AND longitude BETWEEN -180 AND 180;

Index for facilities Table

为facilities表创建索引

sql
-- Create spatial index on facilities for proximity queries
CREATE INDEX idx_facilities_location
    ON public.facilities USING GIST(
        ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography
    )
    WHERE latitude IS NOT NULL AND longitude IS NOT NULL;
sql
-- 为facilities表创建空间索引以支持邻近查询
CREATE INDEX idx_facilities_location
    ON public.facilities USING GIST(
        ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography
    )
    WHERE latitude IS NOT NULL AND longitude IS NOT NULL;

Common Spatial Queries

常见空间查询

ST_Distance: Calculate Distance Between Points

ST_Distance:计算点位间距离

sql
-- Distance from truck to destination facility (in miles)
SELECT
    lc.load_id,
    ST_Distance(
        ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
    ) / 1609.34 AS distance_miles
FROM load_cognition lc
JOIN loads l ON lc.load_id = l.id
JOIN stops s ON l.id = s.load_id AND s.stop_type = 'destination'
JOIN facilities f ON s.facility_id = f.id
WHERE lc.load_id = $1
ORDER BY lc.cognition_time DESC
LIMIT 1;
sql
-- 计算卡车到目的地设施的距离(英里)
SELECT
    lc.load_id,
    ST_Distance(
        ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
    ) / 1609.34 AS distance_miles
FROM load_cognition lc
JOIN loads l ON lc.load_id = l.id
JOIN stops s ON l.id = s.load_id AND s.stop_type = 'destination'
JOIN facilities f ON s.facility_id = f.id
WHERE lc.load_id = $1
ORDER BY lc.cognition_time DESC
LIMIT 1;

ST_DWithin: Find Points Within Radius

ST_DWithin:查找指定半径内的点位

sql
-- Find all trucks within 50 miles of a facility
SELECT
    lc.load_id,
    l.load_number,
    lc.driver_name,
    ST_Distance(
        ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
    ) / 1609.34 AS distance_miles
FROM load_cognition lc
JOIN loads l ON lc.load_id = l.id
CROSS JOIN (
    SELECT longitude, latitude FROM facilities WHERE id = $1
) f
WHERE ST_DWithin(
    ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
    ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography,
    50 * 1609.34  -- 50 miles in meters
)
AND lc.cognition_time > now() - INTERVAL '1 hour'
ORDER BY distance_miles;
sql
-- 查找设施50英里范围内的所有卡车
SELECT
    lc.load_id,
    l.load_number,
    lc.driver_name,
    ST_Distance(
        ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
    ) / 1609.34 AS distance_miles
FROM load_cognition lc
JOIN loads l ON lc.load_id = l.id
CROSS JOIN (
    SELECT longitude, latitude FROM facilities WHERE id = $1
) f
WHERE ST_DWithin(
    ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
    ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography,
    50 * 1609.34  -- 50英里(米制)
)
AND lc.cognition_time > now() - INTERVAL '1 hour'
ORDER BY distance_miles;

ST_Contains: Check if Point is Inside Polygon

ST_Contains:检查点位是否在多边形内

sql
-- Check if truck is inside any active geofence
SELECT g.id, g.name, g.facility_id
FROM geofences g
WHERE ST_Contains(
    g.boundary::geometry,
    ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)
)
AND g.is_active = true
AND g.deleted_at IS NULL;
sql
-- 检查卡车是否处于任何活跃的地理围栏内
SELECT g.id, g.name, g.facility_id
FROM geofences g
WHERE ST_Contains(
    g.boundary::geometry,
    ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)
)
AND g.is_active = true
AND g.deleted_at IS NULL;

ST_Buffer: Create Radius Around Point

ST_Buffer:为点位创建半径区域

sql
-- Create a 10-mile buffer zone around current truck position
SELECT ST_Buffer(
    ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)::geography,
    10 * 1609.34  -- 10 miles in meters
) AS buffer_zone;

-- Find facilities within buffer
SELECT f.id, f.name, f.city, f.state
FROM facilities f
WHERE ST_DWithin(
    ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography,
    ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)::geography,
    10 * 1609.34
)
AND f.deleted_at IS NULL;
sql
-- 为当前卡车位置创建10英里的缓冲区
SELECT ST_Buffer(
    ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)::geography,
    10 * 1609.34  -- 10英里(米制)
) AS buffer_zone;

-- 查找缓冲区内的设施
SELECT f.id, f.name, f.city, f.state
FROM facilities f
WHERE ST_DWithin(
    ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography,
    ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)::geography,
    10 * 1609.34
)
AND f.deleted_at IS NULL;

Geofence Event Detection

地理围栏事件检测

Check Geofence Entry

检查地理围栏进入

sql
-- Function to check if a position is inside any geofence
CREATE OR REPLACE FUNCTION public.check_geofence_entry(
    p_longitude NUMERIC,
    p_latitude NUMERIC
)
RETURNS TABLE (
    geofence_id UUID,
    geofence_name TEXT,
    facility_id UUID,
    geofence_type TEXT
)
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = 'public'
AS $$
BEGIN
    RETURN QUERY
    SELECT
        g.id,
        g.name,
        g.facility_id,
        g.geofence_type
    FROM geofences g
    WHERE ST_Contains(
        g.boundary::geometry,
        ST_SetSRID(ST_MakePoint(p_longitude, p_latitude), 4326)
    )
    AND g.is_active = true
    AND g.deleted_at IS NULL;
END;
$$;

COMMENT ON FUNCTION public.check_geofence_entry(NUMERIC, NUMERIC) IS
    'Returns all active geofences containing the given GPS coordinates';
sql
-- 检查位置是否处于任何地理围栏内的函数
CREATE OR REPLACE FUNCTION public.check_geofence_entry(
    p_longitude NUMERIC,
    p_latitude NUMERIC
)
RETURNS TABLE (
    geofence_id UUID,
    geofence_name TEXT,
    facility_id UUID,
    geofence_type TEXT
)
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = 'public'
AS $$
BEGIN
    RETURN QUERY
    SELECT
        g.id,
        g.name,
        g.facility_id,
        g.geofence_type
    FROM geofences g
    WHERE ST_Contains(
        g.boundary::geometry,
        ST_SetSRID(ST_MakePoint(p_longitude, p_latitude), 4326)
    )
    AND g.is_active = true
    AND g.deleted_at IS NULL;
END;
$$;

COMMENT ON FUNCTION public.check_geofence_entry(NUMERIC, NUMERIC) IS
    '返回包含给定GPS坐标的所有活跃地理围栏';

Geofence Event Table

地理围栏事件表

sql
-- Table to track geofence entry/exit events
CREATE TABLE public.geofence_events (
    id UUID DEFAULT gen_random_uuid() NOT NULL,

    load_id UUID NOT NULL REFERENCES loads(id) ON DELETE CASCADE,
    geofence_id UUID NOT NULL REFERENCES geofences(id) ON DELETE CASCADE,

    event_type TEXT NOT NULL,  -- 'entry', 'exit', 'dwell'
    event_time TIMESTAMPTZ NOT NULL DEFAULT now(),

    -- Position at time of event
    latitude NUMERIC(10,7),
    longitude NUMERIC(11,7),

    -- Dwell time tracking (for exit events)
    entry_time TIMESTAMPTZ,
    dwell_minutes INTEGER,

    -- Standard audit columns
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,

    CONSTRAINT geofence_events_pkey PRIMARY KEY (id),
    CONSTRAINT chk_geofence_events_type CHECK (
        event_type IN ('entry', 'exit', 'dwell')
    )
);

CREATE INDEX idx_geofence_events_load_id ON geofence_events(load_id);
CREATE INDEX idx_geofence_events_geofence_id ON geofence_events(geofence_id);
CREATE INDEX idx_geofence_events_event_time ON geofence_events(event_time);
sql
-- 用于追踪地理围栏进入/退出事件的表
CREATE TABLE public.geofence_events (
    id UUID DEFAULT gen_random_uuid() NOT NULL,

    load_id UUID NOT NULL REFERENCES loads(id) ON DELETE CASCADE,
    geofence_id UUID NOT NULL REFERENCES geofences(id) ON DELETE CASCADE,

    event_type TEXT NOT NULL,  -- 'entry', 'exit', 'dwell'
    event_time TIMESTAMPTZ NOT NULL DEFAULT now(),

    -- 事件发生时的位置
    latitude NUMERIC(10,7),
    longitude NUMERIC(11,7),

    -- 停留时间追踪(针对退出事件)
    entry_time TIMESTAMPTZ,
    dwell_minutes INTEGER,

    -- 标准审计列
    created_at TIMESTAMPTZ DEFAULT now() NOT NULL,

    CONSTRAINT geofence_events_pkey PRIMARY KEY (id),
    CONSTRAINT chk_geofence_events_type CHECK (
        event_type IN ('entry', 'exit', 'dwell')
    )
);

CREATE INDEX idx_geofence_events_load_id ON geofence_events(load_id);
CREATE INDEX idx_geofence_events_geofence_id ON geofence_events(geofence_id);
CREATE INDEX idx_geofence_events_event_time ON geofence_events(event_time);

Detect Entry/Exit Pattern

检测进入/退出模式

sql
-- Function to process location update and detect geofence events
CREATE OR REPLACE FUNCTION public.process_location_geofence(
    p_load_id UUID,
    p_longitude NUMERIC,
    p_latitude NUMERIC,
    p_timestamp TIMESTAMPTZ DEFAULT now()
)
RETURNS SETOF geofence_events
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = 'public'
AS $$
DECLARE
    v_current_geofences UUID[];
    v_previous_geofences UUID[];
    v_entered_geofence UUID;
    v_exited_geofence UUID;
    v_entry_time TIMESTAMPTZ;
    v_event geofence_events;
BEGIN
    -- Get current geofences containing this position
    SELECT ARRAY_AGG(g.id) INTO v_current_geofences
    FROM geofences g
    WHERE ST_Contains(
        g.boundary::geometry,
        ST_SetSRID(ST_MakePoint(p_longitude, p_latitude), 4326)
    )
    AND g.is_active = true
    AND g.deleted_at IS NULL;

    -- Get previously active geofences for this load
    SELECT ARRAY_AGG(DISTINCT ge.geofence_id) INTO v_previous_geofences
    FROM geofence_events ge
    WHERE ge.load_id = p_load_id
      AND ge.event_type = 'entry'
      AND NOT EXISTS (
          SELECT 1 FROM geofence_events ge2
          WHERE ge2.load_id = p_load_id
            AND ge2.geofence_id = ge.geofence_id
            AND ge2.event_type = 'exit'
            AND ge2.event_time > ge.event_time
      );

    -- Handle NULL arrays
    v_current_geofences := COALESCE(v_current_geofences, ARRAY[]::UUID[]);
    v_previous_geofences := COALESCE(v_previous_geofences, ARRAY[]::UUID[]);

    -- Detect entries (in current but not in previous)
    FOR v_entered_geofence IN
        SELECT UNNEST(v_current_geofences)
        EXCEPT
        SELECT UNNEST(v_previous_geofences)
    LOOP
        INSERT INTO geofence_events (load_id, geofence_id, event_type, event_time, latitude, longitude)
        VALUES (p_load_id, v_entered_geofence, 'entry', p_timestamp, p_latitude, p_longitude)
        RETURNING * INTO v_event;
        RETURN NEXT v_event;
    END LOOP;

    -- Detect exits (in previous but not in current)
    FOR v_exited_geofence IN
        SELECT UNNEST(v_previous_geofences)
        EXCEPT
        SELECT UNNEST(v_current_geofences)
    LOOP
        -- Get entry time for dwell calculation
        SELECT ge.event_time INTO v_entry_time
        FROM geofence_events ge
        WHERE ge.load_id = p_load_id
          AND ge.geofence_id = v_exited_geofence
          AND ge.event_type = 'entry'
        ORDER BY ge.event_time DESC
        LIMIT 1;

        INSERT INTO geofence_events (
            load_id, geofence_id, event_type, event_time,
            latitude, longitude, entry_time, dwell_minutes
        )
        VALUES (
            p_load_id, v_exited_geofence, 'exit', p_timestamp,
            p_latitude, p_longitude, v_entry_time,
            EXTRACT(EPOCH FROM (p_timestamp - v_entry_time)) / 60
        )
        RETURNING * INTO v_event;
        RETURN NEXT v_event;
    END LOOP;

    RETURN;
END;
$$;

COMMENT ON FUNCTION public.process_location_geofence(UUID, NUMERIC, NUMERIC, TIMESTAMPTZ) IS
    'Processes location update and generates geofence entry/exit events';
sql
-- 处理位置更新并检测地理围栏事件的函数
CREATE OR REPLACE FUNCTION public.process_location_geofence(
    p_load_id UUID,
    p_longitude NUMERIC,
    p_latitude NUMERIC,
    p_timestamp TIMESTAMPTZ DEFAULT now()
)
RETURNS SETOF geofence_events
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = 'public'
AS $$
DECLARE
    v_current_geofences UUID[];
    v_previous_geofences UUID[];
    v_entered_geofence UUID;
    v_exited_geofence UUID;
    v_entry_time TIMESTAMPTZ;
    v_event geofence_events;
BEGIN
    -- 获取当前位置所属的地理围栏
    SELECT ARRAY_AGG(g.id) INTO v_current_geofences
    FROM geofences g
    WHERE ST_Contains(
        g.boundary::geometry,
        ST_SetSRID(ST_MakePoint(p_longitude, p_latitude), 4326)
    )
    AND g.is_active = true
    AND g.deleted_at IS NULL;

    -- 获取该货物之前活跃的地理围栏
    SELECT ARRAY_AGG(DISTINCT ge.geofence_id) INTO v_previous_geofences
    FROM geofence_events ge
    WHERE ge.load_id = p_load_id
      AND ge.event_type = 'entry'
      AND NOT EXISTS (
          SELECT 1 FROM geofence_events ge2
          WHERE ge2.load_id = p_load_id
            AND ge2.geofence_id = ge.geofence_id
            AND ge2.event_type = 'exit'
            AND ge2.event_time > ge.event_time
      );

    -- 处理NULL数组
    v_current_geofences := COALESCE(v_current_geofences, ARRAY[]::UUID[]);
    v_previous_geofences := COALESCE(v_previous_geofences, ARRAY[]::UUID[]);

    -- 检测进入事件(当前在围栏内但之前不在)
    FOR v_entered_geofence IN
        SELECT UNNEST(v_current_geofences)
        EXCEPT
        SELECT UNNEST(v_previous_geofences)
    LOOP
        INSERT INTO geofence_events (load_id, geofence_id, event_type, event_time, latitude, longitude)
        VALUES (p_load_id, v_entered_geofence, 'entry', p_timestamp, p_latitude, p_longitude)
        RETURNING * INTO v_event;
        RETURN NEXT v_event;
    END LOOP;

    -- 检测退出事件(之前在围栏内但当前不在)
    FOR v_exited_geofence IN
        SELECT UNNEST(v_previous_geofences)
        EXCEPT
        SELECT UNNEST(v_current_geofences)
    LOOP
        -- 获取进入时间以计算停留时间
        SELECT ge.event_time INTO v_entry_time
        FROM geofence_events ge
        WHERE ge.load_id = p_load_id
          AND ge.geofence_id = v_exited_geofence
          AND ge.event_type = 'entry'
        ORDER BY ge.event_time DESC
        LIMIT 1;

        INSERT INTO geofence_events (
            load_id, geofence_id, event_type, event_time,
            latitude, longitude, entry_time, dwell_minutes
        )
        VALUES (
            p_load_id, v_exited_geofence, 'exit', p_timestamp,
            p_latitude, p_longitude, v_entry_time,
            EXTRACT(EPOCH FROM (p_timestamp - v_entry_time)) / 60
        )
        RETURNING * INTO v_event;
        RETURN NEXT v_event;
    END LOOP;

    RETURN;
END;
$$;

COMMENT ON FUNCTION public.process_location_geofence(UUID, NUMERIC, NUMERIC, TIMESTAMPTZ) IS
    '处理位置更新并生成地理围栏进入/退出事件';

ETA Calculations

ETA计算

Basic Distance-Based ETA

基于距离的基础ETA

sql
-- Calculate simple ETA based on distance and average speed
CREATE OR REPLACE FUNCTION public.calculate_eta(
    p_current_lon NUMERIC,
    p_current_lat NUMERIC,
    p_dest_lon NUMERIC,
    p_dest_lat NUMERIC,
    p_avg_speed_mph NUMERIC DEFAULT 50
)
RETURNS TIMESTAMPTZ
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = 'public'
AS $$
DECLARE
    v_distance_miles NUMERIC;
    v_hours NUMERIC;
BEGIN
    -- Calculate distance in miles
    v_distance_miles := ST_Distance(
        ST_SetSRID(ST_MakePoint(p_current_lon, p_current_lat), 4326)::geography,
        ST_SetSRID(ST_MakePoint(p_dest_lon, p_dest_lat), 4326)::geography
    ) / 1609.34;

    -- Calculate travel time
    v_hours := v_distance_miles / p_avg_speed_mph;

    RETURN now() + (v_hours || ' hours')::INTERVAL;
END;
$$;

COMMENT ON FUNCTION public.calculate_eta(NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC) IS
    'Calculates ETA based on straight-line distance and average speed. For accurate routing, integrate with external routing API';
sql
-- 根据距离和平均速度计算简单ETA
CREATE OR REPLACE FUNCTION public.calculate_eta(
    p_current_lon NUMERIC,
    p_current_lat NUMERIC,
    p_dest_lon NUMERIC,
    p_dest_lat NUMERIC,
    p_avg_speed_mph NUMERIC DEFAULT 50
)
RETURNS TIMESTAMPTZ
LANGUAGE plpgsql
SECURITY INVOKER
SET search_path = 'public'
AS $$
DECLARE
    v_distance_miles NUMERIC;
    v_hours NUMERIC;
BEGIN
    -- 计算英里距离
    v_distance_miles := ST_Distance(
        ST_SetSRID(ST_MakePoint(p_current_lon, p_current_lat), 4326)::geography,
        ST_SetSRID(ST_MakePoint(p_dest_lon, p_dest_lat), 4326)::geography
    ) / 1609.34;

    -- 计算行驶时间
    v_hours := v_distance_miles / p_avg_speed_mph;

    RETURN now() + (v_hours || ' hours')::INTERVAL;
END;
$$;

COMMENT ON FUNCTION public.calculate_eta(NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC) IS
    '基于直线距离和平均速度计算ETA。若需精确路由,请集成外部路由API';

ETA for Load with Stops

带停靠点的货物ETA

sql
-- Calculate ETA to next stop for a load
SELECT
    l.load_number,
    s.stop_sequence,
    f.name AS facility_name,
    ST_Distance(
        ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
    ) / 1609.34 AS distance_miles,
    now() + (
        (ST_Distance(
            ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
            ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
        ) / 1609.34) / 50  -- Assume 50 mph average
        || ' hours'
    )::INTERVAL AS estimated_arrival
FROM loads l
JOIN stops s ON l.id = s.load_id
JOIN facilities f ON s.facility_id = f.id
JOIN LATERAL (
    SELECT longitude, latitude
    FROM load_cognition
    WHERE load_id = l.id
    ORDER BY cognition_time DESC
    LIMIT 1
) lc ON true
WHERE l.id = $1
  AND s.actual_arrival IS NULL  -- Not yet arrived
ORDER BY s.stop_sequence
LIMIT 1;
sql
-- 计算货物到下一个停靠点的ETA
SELECT
    l.load_number,
    s.stop_sequence,
    f.name AS facility_name,
    ST_Distance(
        ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
        ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
    ) / 1609.34 AS distance_miles,
    now() + (
        (ST_Distance(
            ST_SetSRID(ST_MakePoint(lc.longitude, lc.latitude), 4326)::geography,
            ST_SetSRID(ST_MakePoint(f.longitude, f.latitude), 4326)::geography
        ) / 1609.34) / 50  -- 假设平均速度50英里/小时
        || ' hours'
    )::INTERVAL AS estimated_arrival
FROM loads l
JOIN stops s ON l.id = s.load_id
JOIN facilities f ON s.facility_id = f.id
JOIN LATERAL (
    SELECT longitude, latitude
    FROM load_cognition
    WHERE load_id = l.id
    ORDER BY cognition_time DESC
    LIMIT 1
) lc ON true
WHERE l.id = $1
  AND s.actual_arrival IS NULL  -- 尚未到达
ORDER BY s.stop_sequence
LIMIT 1;

PGRouting Integration Points

PGRouting集成要点

For accurate routing with road networks:
sql
-- PGRouting requires a road network table
-- This is typically populated from OpenStreetMap data

-- Example: Calculate route distance using Dijkstra algorithm
-- Note: Requires pgr_createTopology and road network data

-- SELECT
--     sum(cost) AS total_distance,
--     ST_Union(geom) AS route_geometry
-- FROM pgr_dijkstra(
--     'SELECT id, source, target, cost FROM roads',
--     $start_node, $end_node,
--     directed := false
-- ) AS route
-- JOIN roads ON route.edge = roads.id;

-- For production ETA calculations, consider:
-- 1. External routing APIs (OSRM, Google Directions, Here)
-- 2. Pre-calculated route distances in lanes table
-- 3. Mileage tables from PC*MILER or similar
若需基于道路网络的精确路由:
sql
-- PGRouting需要道路网络表
-- 通常从OpenStreetMap数据中填充

-- 示例:使用Dijkstra算法计算路由距离
-- 注意:需要pgr_createTopology和道路网络数据

-- SELECT
--     sum(cost) AS total_distance,
--     ST_Union(geom) AS route_geometry
-- FROM pgr_dijkstra(
--     'SELECT id, source, target, cost FROM roads',
--     $start_node, $end_node,
--     directed := false
-- ) AS route
-- JOIN roads ON route.edge = roads.id;

-- 生产环境ETA计算建议:
-- 1. 外部路由API(OSRM、Google Directions、Here)
-- 2. 在lanes表中预计算路由距离
-- 3. 使用PC*MILER等里程表

Real-Time Tracking Patterns

实时追踪模式

load_cognition Table Structure

load_cognition表结构

The
load_cognition
table stores GPS tracking data:
sql
-- Key columns in load_cognition
-- id UUID
-- load_id UUID (FK to loads)
-- driver_name TEXT
-- latitude NUMERIC(10,7)
-- longitude NUMERIC(11,7)
-- cognition_time TIMESTAMPTZ
-- speed_mph NUMERIC
-- heading NUMERIC
-- source TEXT (e.g., 'eld', 'mobile', 'manual')
load_cognition
表存储GPS追踪数据:
sql
-- load_cognition表的关键列
-- id UUID
-- load_id UUID(关联loads表的外键)
-- driver_name TEXT
-- latitude NUMERIC(10,7)
-- longitude NUMERIC(11,7)
-- cognition_time TIMESTAMPTZ
-- speed_mph NUMERIC
-- heading NUMERIC
-- source TEXT(例如:'eld', 'mobile', 'manual')

Query Current Fleet Positions

查询当前车队位置

sql
-- Get current position of all active loads
SELECT DISTINCT ON (l.id)
    l.id AS load_id,
    l.load_number,
    l.load_status,
    lc.driver_name,
    lc.latitude,
    lc.longitude,
    lc.speed_mph,
    lc.cognition_time,
    -- Calculate time since last update
    EXTRACT(EPOCH FROM (now() - lc.cognition_time)) / 60 AS minutes_since_update
FROM loads l
JOIN load_cognition lc ON l.id = lc.load_id
WHERE l.load_status IN ('dispatched', 'at_origin', 'in_transit', 'at_destination')
  AND l.deleted_at IS NULL
  AND lc.latitude IS NOT NULL
  AND lc.longitude IS NOT NULL
ORDER BY l.id, lc.cognition_time DESC;
sql
-- 获取所有活跃货物的当前位置
SELECT DISTINCT ON (l.id)
    l.id AS load_id,
    l.load_number,
    l.load_status,
    lc.driver_name,
    lc.latitude,
    lc.longitude,
    lc.speed_mph,
    lc.cognition_time,
    -- 计算上次更新至今的时间
    EXTRACT(EPOCH FROM (now() - lc.cognition_time)) / 60 AS minutes_since_update
FROM loads l
JOIN load_cognition lc ON l.id = lc.load_id
WHERE l.load_status IN ('dispatched', 'at_origin', 'in_transit', 'at_destination')
  AND l.deleted_at IS NULL
  AND lc.latitude IS NOT NULL
  AND lc.longitude IS NOT NULL
ORDER BY l.id, lc.cognition_time DESC;

Historical Track Query

历史轨迹查询

sql
-- Get tracking history for a load (for route visualization)
SELECT
    lc.latitude,
    lc.longitude,
    lc.cognition_time,
    lc.speed_mph,
    lc.heading
FROM load_cognition lc
WHERE lc.load_id = $1
  AND lc.latitude IS NOT NULL
  AND lc.longitude IS NOT NULL
ORDER BY lc.cognition_time ASC;
sql
-- 获取货物的历史轨迹(用于路线可视化)
SELECT
    lc.latitude,
    lc.longitude,
    lc.cognition_time,
    lc.speed_mph,
    lc.heading
FROM load_cognition lc
WHERE lc.load_id = $1
  AND lc.latitude IS NOT NULL
  AND lc.longitude IS NOT NULL
ORDER BY lc.cognition_time ASC;

Track as GeoJSON LineString

轨迹转换为GeoJSON LineString

sql
-- Get tracking history as GeoJSON for map display
SELECT json_build_object(
    'type', 'Feature',
    'properties', json_build_object(
        'load_id', $1::text,
        'point_count', count(*)
    ),
    'geometry', json_build_object(
        'type', 'LineString',
        'coordinates', json_agg(
            json_build_array(longitude, latitude)
            ORDER BY cognition_time
        )
    )
) AS geojson
FROM load_cognition
WHERE load_id = $1
  AND latitude IS NOT NULL
  AND longitude IS NOT NULL;
sql
-- 将历史轨迹转换为GeoJSON以用于地图展示
SELECT json_build_object(
    'type', 'Feature',
    'properties', json_build_object(
        'load_id', $1::text,
        'point_count', count(*)
    ),
    'geometry', json_build_object(
        'type', 'LineString',
        'coordinates', json_agg(
            json_build_array(longitude, latitude)
            ORDER BY cognition_time
        )
    )
) AS geojson
FROM load_cognition
WHERE load_id = $1
  AND latitude IS NOT NULL
  AND longitude IS NOT NULL;

Frontend Mapping Integration

前端地图集成

GeoJSON Output for Mapbox/Leaflet

为Mapbox/Leaflet输出GeoJSON

sql
-- Facilities as GeoJSON FeatureCollection
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', json_agg(
        json_build_object(
            'type', 'Feature',
            'id', f.id,
            'properties', json_build_object(
                'name', f.name,
                'city', f.city,
                'state', f.state,
                'facility_type', f.facility_type
            ),
            'geometry', json_build_object(
                'type', 'Point',
                'coordinates', json_build_array(f.longitude, f.latitude)
            )
        )
    )
) AS geojson
FROM facilities f
WHERE f.latitude IS NOT NULL
  AND f.longitude IS NOT NULL
  AND f.deleted_at IS NULL;
sql
-- 将设施转换为GeoJSON FeatureCollection
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', json_agg(
        json_build_object(
            'type', 'Feature',
            'id', f.id,
            'properties', json_build_object(
                'name', f.name,
                'city', f.city,
                'state', f.state,
                'facility_type', f.facility_type
            ),
            'geometry', json_build_object(
                'type', 'Point',
                'coordinates', json_build_array(f.longitude, f.latitude)
            )
        )
    )
) AS geojson
FROM facilities f
WHERE f.latitude IS NOT NULL
  AND f.longitude IS NOT NULL
  AND f.deleted_at IS NULL;

Geofence Boundaries as GeoJSON

地理围栏边界转换为GeoJSON

sql
-- Export geofences as GeoJSON for map overlay
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', json_agg(
        json_build_object(
            'type', 'Feature',
            'id', g.id,
            'properties', json_build_object(
                'name', g.name,
                'geofence_type', g.geofence_type,
                'facility_id', g.facility_id
            ),
            'geometry', ST_AsGeoJSON(g.boundary::geometry)::json
        )
    )
) AS geojson
FROM geofences g
WHERE g.is_active = true
  AND g.deleted_at IS NULL;
sql
-- 将地理围栏导出为GeoJSON以用于地图叠加层
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', json_agg(
        json_build_object(
            'type', 'Feature',
            'id', g.id,
            'properties', json_build_object(
                'name', g.name,
                'geofence_type', g.geofence_type,
                'facility_id', g.facility_id
            ),
            'geometry', ST_AsGeoJSON(g.boundary::geometry)::json
        )
    )
) AS geojson
FROM geofences g
WHERE g.is_active = true
  AND g.deleted_at IS NULL;

Fleet Positions as GeoJSON

车队位置转换为GeoJSON

sql
-- Current fleet positions for real-time map
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', (
        SELECT json_agg(
            json_build_object(
                'type', 'Feature',
                'id', t.load_id,
                'properties', json_build_object(
                    'load_number', t.load_number,
                    'load_status', t.load_status,
                    'driver_name', t.driver_name,
                    'speed_mph', t.speed_mph,
                    'last_update', t.cognition_time
                ),
                'geometry', json_build_object(
                    'type', 'Point',
                    'coordinates', json_build_array(t.longitude, t.latitude)
                )
            )
        )
        FROM (
            SELECT DISTINCT ON (l.id)
                l.id AS load_id,
                l.load_number,
                l.load_status,
                lc.driver_name,
                lc.latitude,
                lc.longitude,
                lc.speed_mph,
                lc.cognition_time
            FROM loads l
            JOIN load_cognition lc ON l.id = lc.load_id
            WHERE l.load_status IN ('dispatched', 'at_origin', 'in_transit', 'at_destination')
              AND l.deleted_at IS NULL
              AND lc.latitude IS NOT NULL
            ORDER BY l.id, lc.cognition_time DESC
        ) t
    )
) AS geojson;
sql
-- 用于实时地图的当前车队位置
SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', (
        SELECT json_agg(
            json_build_object(
                'type', 'Feature',
                'id', t.load_id,
                'properties', json_build_object(
                    'load_number', t.load_number,
                    'load_status', t.load_status,
                    'driver_name', t.driver_name,
                    'speed_mph', t.speed_mph,
                    'last_update', t.cognition_time
                ),
                'geometry', json_build_object(
                    'type', 'Point',
                    'coordinates', json_build_array(t.longitude, t.latitude)
                )
            )
        )
        FROM (
            SELECT DISTINCT ON (l.id)
                l.id AS load_id,
                l.load_number,
                l.load_status,
                lc.driver_name,
                lc.latitude,
                lc.longitude,
                lc.speed_mph,
                lc.cognition_time
            FROM loads l
            JOIN load_cognition lc ON l.id = lc.load_id
            WHERE l.load_status IN ('dispatched', 'at_origin', 'in_transit', 'at_destination')
              AND l.deleted_at IS NULL
              AND lc.latitude IS NOT NULL
            ORDER BY l.id, lc.cognition_time DESC
        ) t
    )
) AS geojson;

LayerChart Integration

LayerChart集成

For geographic visualizations in the frontend, use the
layerchart
plugin:
  • Choropleth: State/region heat maps for load volume or revenue
  • Bubble Map: Facility markers sized by load count
  • GeoPath: Route visualization with tracking data
  • GeoTile: Background map tiles for context
Reference the
layerchart
skill for component patterns and data transformation utilities.
若需在前端实现地理可视化,使用
layerchart
插件:
  • Choropleth:按州/区域的货物量或收入热力图
  • Bubble Map:按货物量调整大小的设施标记
  • GeoPath:使用追踪数据实现路线可视化
  • GeoTile:用于上下文展示的背景地图瓦片
参考
layerchart
技能获取组件模式和数据转换工具。

PostGIS Functions Quick Reference

PostGIS函数速查

FunctionPurposeExample
ST_MakePoint(lon, lat)
Create point from coordinates
ST_MakePoint(-87.6298, 41.8781)
ST_SetSRID(geom, srid)
Assign coordinate system
ST_SetSRID(point, 4326)
ST_Distance(a, b)
Distance between geometries (meters for geography)
ST_Distance(a::geography, b::geography)
ST_DWithin(a, b, dist)
True if within distance
ST_DWithin(a, b, 80467)
(50 miles)
ST_Contains(poly, point)
True if polygon contains point
ST_Contains(geofence, truck_pos)
ST_Buffer(geom, dist)
Create buffer zone
ST_Buffer(point::geography, 1609.34)
(1 mile)
ST_AsGeoJSON(geom)
Export as GeoJSON
ST_AsGeoJSON(boundary)
ST_GeomFromGeoJSON(json)
Import from GeoJSON
ST_GeomFromGeoJSON($1)
ST_Union(geom)
Merge geometries
ST_Union(route_segments)
ST_Centroid(geom)
Center point of geometry
ST_Centroid(state_boundary)
函数用途示例
ST_MakePoint(lon, lat)
从坐标创建点
ST_MakePoint(-87.6298, 41.8781)
ST_SetSRID(geom, srid)
分配坐标系
ST_SetSRID(point, 4326)
ST_Distance(a, b)
计算几何体间距离(geography类型返回米制)
ST_Distance(a::geography, b::geography)
ST_DWithin(a, b, dist)
判断是否在指定距离内
ST_DWithin(a, b, 80467)
(50英里)
ST_Contains(poly, point)
判断多边形是否包含点
ST_Contains(geofence, truck_pos)
ST_Buffer(geom, dist)
创建缓冲区
ST_Buffer(point::geography, 1609.34)
(1英里)
ST_AsGeoJSON(geom)
导出为GeoJSON
ST_AsGeoJSON(boundary)
ST_GeomFromGeoJSON(json)
从GeoJSON导入
ST_GeomFromGeoJSON($1)
ST_Union(geom)
合并几何体
ST_Union(route_segments)
ST_Centroid(geom)
获取几何体中心点
ST_Centroid(state_boundary)

Unit Conversion Reference

单位转换参考

FromToMultiply By
MilesMeters1609.34
MetersMiles0.000621371
KilometersMiles0.621371
MilesKilometers1.60934
转换系数
英里1609.34
英里0.000621371
千米英里0.621371
英里千米1.60934

Implementation Checklist

实施检查清单

PostGIS Setup:
[ ] PostGIS extension enabled (CREATE EXTENSION postgis)
[ ] PGRouting extension enabled if routing needed (CREATE EXTENSION pgrouting)
[ ] SRID 4326 used for all GPS coordinate data
[ ] Geography type used for distance calculations

Geofences:
[ ] Geofences table with geography(Polygon, 4326) boundary column
[ ] GIST index on boundary column
[ ] Functions for geofence entry/exit detection
[ ] Event table for tracking geofence transitions

Spatial Indexes:
[ ] GIST index on all geography/geometry columns
[ ] Expression index on load_cognition for location queries
[ ] Partial indexes for valid coordinate rows only

Tracking:
[ ] load_cognition captures lat/lon with each update
[ ] Efficient queries for current fleet position
[ ] Historical track queries return ordered points
[ ] GeoJSON output for frontend consumption

Frontend Integration:
[ ] GeoJSON endpoints for facilities, geofences, fleet positions
[ ] LayerChart components for geographic visualizations
[ ] Real-time position updates via Supabase realtime subscriptions
PostGIS 配置:
[ ] 已启用PostGIS扩展(CREATE EXTENSION postgis)
[ ] 若需要路由功能,启用PGRouting扩展(CREATE EXTENSION pgrouting)
[ ] 所有GPS坐标数据使用SRID 4326
[ ] 距离计算使用Geography类型

地理围栏:
[ ] 包含geography(Polygon, 4326)类型boundary列的geofences表
[ ] 为boundary列创建GIST索引
[ ] 实现地理围栏进入/退出检测的函数
[ ] 用于追踪地理围栏转换事件的事件表

空间索引:
[ ] 为所有geography/geometry列创建GIST索引
[ ] 为load_cognition表创建基于位置查询的表达式索引
[ ] 仅为有效坐标行创建部分索引

追踪功能:
[ ] load_cognition表在每次更新时捕获经纬度
[ ] 高效查询当前车队位置
[ ] 历史轨迹查询返回有序的点位
[ ] 提供供前端使用的GeoJSON输出

前端集成:
[ ] 提供设施、地理围栏、车队位置的GeoJSON接口
[ ] 使用LayerChart组件实现地理可视化
[ ] 通过Supabase实时订阅实现位置实时更新

Related Skills

相关技能

  • supabase:laneweaver-database-design
    : Table conventions, audit columns, migrations
  • layerchart
    : Geographic visualization components for frontend
  • load-lifecycle-patterns
    : Load tracking status transitions

Remember: Use
geography
type for real-world distance calculations with GPS data. Always use SRID 4326 for GPS coordinates. Index all spatial columns with GIST indexes for query performance.
  • supabase:laneweaver-database-design
    :表规范、审计列、迁移脚本
  • layerchart
    :用于前端的地理可视化组件
  • load-lifecycle-patterns
    :货物追踪状态转换

注意:在使用GPS数据进行真实世界距离计算时,使用
geography
类型。所有GPS坐标始终使用SRID 4326。为所有空间列创建GIST索引以提升查询性能。