Source code for vissim2gmns.func_lib.link_projection

'''
##############################################################
# Created Date: Thursday, April 9th 2026
# Contact Info: luoxiangyong01@gmail.com
# Author/Copyright: Mr. Xiangyong Luo
##############################################################
'''

from loguru import logger
from shapely import wkt
from shapely.geometry import Point, LineString, MultiLineString
from shapely.ops import linemerge
from pyproj import Geod


[docs] def point_on_wkt_line_by_distance( link_geom: str | LineString | MultiLineString, distance_in_meters: float ) -> Point: """Generate a WGS84 point on a line at a given distance from the start. Args: link_geom (str | LineString | MultiLineString): WKT string or Shapely geometry representing a LINESTRING or mergeable MULTILINESTRING. distance_in_meters (float): Distance along the line in meters from the start point. Notes: - link_geom can be provided as a WKT string or as a Shapely LineString or MultiLineString object. If a MultiLineString is provided, it will be merged into a single LineString if possible. - If the distance is negative, it will be treated as 0 (the start point). - If the distance exceeds the total length of the line, the last point of the line will be returned. - The function uses geodesic calculations to account for the curvature of the Earth, ensuring accurate distance measurements in WGS84 coordinates. Example: >>> import vissim2gmns as vg >>> link_wkt = "LINESTRING (-111.935 33.421, -111.934 33.422, -111.933 33.423)" >>> distance_in_meters = 120 >>> # Get the point on the line at the specified distance. >>> pt = vg.point_on_wkt_line_by_distance(link_wkt, distance_in_meters) >>> print(pt) # POINT (lon lat) >>> print(pt.wkt) # WKT format >>> print(pt.x, pt.y) # longitude, latitude Returns: Point: A Shapely Point object representing the location on the line at the specified distance. Raises: ValueError: If the input WKT is not a valid LINESTRING or mergeable MULTILINESTRING, or if the distance is negative or exceeds the line length. """ if distance_in_meters < 0: distance_in_meters = 0 logger.warning("Distance cannot be negative. Set to 0.") geom = wkt.loads(link_geom) if isinstance(link_geom, str) else link_geom # Support LINESTRING and mergeable MULTILINESTRING if isinstance(geom, MultiLineString): geom = linemerge(geom) if not isinstance(geom, LineString): raise ValueError( "Input WKT must be a LINESTRING or mergeable MULTILINESTRING.") coords = list(geom.coords) if len(coords) < 2: raise ValueError("Line must contain at least two coordinates.") geod = Geod(ellps="WGS84") # Distance = 0, return the first point if distance_in_meters == 0: x0, y0 = coords[0] return Point(x0, y0) accumulated = 0.0 for i in range(len(coords) - 1): lon1, lat1 = coords[i] lon2, lat2 = coords[i + 1] az12, az21, seg_len = geod.inv(lon1, lat1, lon2, lat2) if accumulated + seg_len >= distance_in_meters: remaining = distance_in_meters - accumulated lon, lat, _ = geod.fwd(lon1, lat1, az12, remaining) return Point(lon, lat) accumulated += seg_len # If distance exceeds total line length, return the last point return Point(coords[-1])
if __name__ == "__main__": link_wkt = "LINESTRING (-111.935 33.421, -111.934 33.422, -111.933 33.423)" distance_in_meters = 120 pt = point_on_wkt_line_by_distance(link_wkt, distance_in_meters) print(pt) # POINT (lon lat) print(pt.wkt) # WKT format print(pt.x, pt.y) # longitude, latitude