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.