Buffer a linestring by creating a corridor (parallel offset on both sides + end caps).
(line: &[[f64; 2]], meters: f64, segments: usize)
| 126 | |
| 127 | /// Buffer a linestring by creating a corridor (parallel offset on both sides + end caps). |
| 128 | fn buffer_linestring(line: &[[f64; 2]], meters: f64, segments: usize) -> Geometry { |
| 129 | if line.is_empty() { |
| 130 | return Geometry::Polygon { |
| 131 | coordinates: vec![vec![]], |
| 132 | }; |
| 133 | } |
| 134 | if line.len() == 1 { |
| 135 | return buffer_point(line[0][0], line[0][1], meters, segments); |
| 136 | } |
| 137 | |
| 138 | // Build the offset polygon: left side forward, right side backward, with |
| 139 | // semicircular end caps. |
| 140 | let mut left_side = Vec::new(); |
| 141 | let mut right_side = Vec::new(); |
| 142 | |
| 143 | for i in 0..line.len() - 1 { |
| 144 | let (nl, nr) = offset_segment(line[i], line[i + 1], meters); |
| 145 | left_side.push(nl.0); |
| 146 | left_side.push(nl.1); |
| 147 | right_side.push(nr.0); |
| 148 | right_side.push(nr.1); |
| 149 | } |
| 150 | |
| 151 | // Build polygon: left side forward → end cap → right side backward → start cap. |
| 152 | let mut ring = Vec::new(); |
| 153 | ring.extend_from_slice(&left_side); |
| 154 | |
| 155 | // End cap (semicircle around last point). |
| 156 | let last = line[line.len() - 1]; |
| 157 | let cap_segments = segments / 2; |
| 158 | let end_dir = direction(line[line.len() - 2], last); |
| 159 | for i in 0..=cap_segments { |
| 160 | let angle = end_dir - std::f64::consts::FRAC_PI_2 |
| 161 | + std::f64::consts::PI * (i as f64) / (cap_segments as f64); |
| 162 | let dlat = meters / 110_540.0; |
| 163 | let dlng = meters / (111_320.0 * last[1].to_radians().cos().max(0.001)); |
| 164 | ring.push([last[0] + dlng * angle.cos(), last[1] + dlat * angle.sin()]); |
| 165 | } |
| 166 | |
| 167 | // Right side backward. |
| 168 | for pt in right_side.iter().rev() { |
| 169 | ring.push(*pt); |
| 170 | } |
| 171 | |
| 172 | // Start cap (semicircle around first point). |
| 173 | let first = line[0]; |
| 174 | let start_dir = direction(first, line[1]); |
| 175 | for i in 0..=cap_segments { |
| 176 | let angle = start_dir |
| 177 | + std::f64::consts::FRAC_PI_2 |
| 178 | + std::f64::consts::PI * (i as f64) / (cap_segments as f64); |
| 179 | let dlat = meters / 110_540.0; |
| 180 | let dlng = meters / (111_320.0 * first[1].to_radians().cos().max(0.001)); |
| 181 | ring.push([first[0] + dlng * angle.cos(), first[1] + dlat * angle.sin()]); |
| 182 | } |
| 183 | |
| 184 | // Close. |
| 185 | if let Some(&first_pt) = ring.first() { |
no test coverage detected