I've recently worked on a similar problem as this.
My methodology was as follows:
- Find nearest line to the input point(s)
- Get a small subsection of this line
- Rotate the subsection 90 degrees around a point on the line closest to the input point (allow us to cut and or create point at intersection)
To this methodology we can add a way to cut get a point when these two lines intersect...
For that we can use some thing like this:
ST_CollectionExtract(ST_Intersection(a.geom, b.geom), 1)
I was working with a table of many input points but it should be fine for just one point (we need a geom and id column in the netowrk and point tables).
Full code here:
--1 Closest line to point(s)
CREATE TABLE schema.test_closestline AS
SELECT pid ,lid, distance_m, a.geom
FROM
(SELECT q.id pid, a.id lid, ROUND(ST_Distance(q.geom, a.geom)::NUMERIC, 2) as distance_m,
ROW_NUMBER() OVER (PARTITION BY q.id ORDER BY ST_DISTANCE(a.geom,q.geom)) as row_number,
a.geom
FROM schema.road_network a
INNER JOIN schema.input_points q ON
ST_DWITHIN(a.geom,q.geom,100) -- use a sensible distance to restrict the result set without losing records
) a
WHERE a.row_number = 1
DROP TABLE IF EXISTS schema.test_blade_rotate;
--2/3 get Small line segment at right 90 degress to line, crossing at closest point to input point
CREATE TABLE schema.test_blade_rotate AS
SELECT
line.id, ST_Rotate(line.geom, 1.5708, ST_Centroid(line.geom)) geom --1.5708 radians = 90 degrees
FROM
(
SELECT paired.pid id,
CASE
WHEN ( round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2) + 1 = 1 ) THEN ST_LineSubstring(paired.lgeom, round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2), round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2)+ 0.01)
WHEN ( round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2) + 1 = 2) THEN ST_LineSubstring(paired.lgeom, round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2) - 0.01, round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2))
ELSE ST_LineSubstring(paired.lgeom,(round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2) - 0.01), (round((ST_LineLocatePoint(paired.lgeom,paired.pgeom))::numeric,2)+0.01))
END AS geom
FROM
(SELECT line.pid, (ST_DUMP(line.geom)).geom lgeom, (ST_DUMP(point.geom)).geom pgeom
FROM
schema.test_closestline as line,
schema.input_points as point
WHERE line.pid = point.id) as paired
) as line;
--4 POINT FROM INTERSECT OF TWO LINES:
CREATE TABLE schema.closest_point AS
SELECT
b.id, ST_CollectionExtract(ST_Intersection(a.geom, b.geom), 1) as geom
FROM schema.road_network a, schema.test_blade_rotate b
WHERE ST_DWithin(...)
) with a decent value to preselect only those geometries that are within range to the point.