1use std::cmp::Ordering;
4use std::collections::BinaryHeap;
5use std::f64;
6
7pub const COORD_PRECISION: f64 = 1e-1; #[derive(Debug, Copy, Clone)]
16pub struct Point {
17 pub x: f64,
18 pub y: f64,
19}
20
21impl PartialEq for Point {
22 fn eq(&self, other: &Point) -> bool {
23 (self.x - other.x).abs() < COORD_PRECISION &&
24 (self.y - other.y).abs() < COORD_PRECISION
25 }
26}
27
28impl Point {
29 fn euclidean_distance_line_string(&self, line_string: &LineString) -> f64 {
30 if line_string_contains_point(line_string, *self) || line_string.points.is_empty() {
32 return 0.0;
33 }
34
35 line_string.points.windows(2)
36 .map(|line| line_segment_distance(*self, line[0], line[1]))
37 .fold(f64::MAX, |accum, val| accum.min(val))
38 }
39}
40
41fn line_segment_distance(point: Point, start: Point, end: Point) -> f64
42{
43 if start == end {
44 return euclidean_length_2(point, start);
45 }
46 let dx = dx(start, end);
47 let dy = dy(start, end);
48 let r = ((point.x - start.x) * dx + (point.y - start.y) * dy) / (dx.powi(2) + dy.powi(2));
49
50 if r <= 0.0 {
51 euclidean_length_2(point, start)
52 } else if r >= 1.0 {
53 euclidean_length_2(point, end)
54 } else {
55 let s = ((start.y - point.y) * dx - (start.x - point.x) * dy) / (dx * dx + dy * dy);
56 s.abs() * dx.hypot(dy)
57 }
58}
59
60fn line_string_contains_point(line_string: &LineString, point: Point) -> bool {
61 if line_string.points.is_empty() {
63 return false;
64 }
65
66 if line_string.points.iter().any(|p| point_contains_point(*p, point)) {
68 return true;
69 }
70
71 for line in line_string.points.windows(2) {
72 let start = line[0];
73 let end = line[1];
74 if {
75 (start.y == end.y)
76 && (start.y == point.y)
77 && (point.x > start.x.min(end.x))
78 && (point.x < start.x.max(end.x))
79 } || {
80 (start.x == end.x)
81 && (start.x == point.x)
82 && (point.y > start.y.min(end.y))
83 && (point.y < start.y.max(end.y))
84 } {
85 return true;
86 }
87 }
88
89 false
90}
91
92fn point_contains_point(a: Point, b: Point) -> bool {
93 euclidean_length_2(a, b) < COORD_PRECISION
94}
95
96#[derive(Debug, Clone)]
97pub struct LineString {
98 pub points: Vec<Point>,
99}
100
101fn centroid_points(start: Point, end: Point) -> Point {
102 let two = 2.0;
103 let x = start.x + dx(start, end) / two;
104 let y = start.y + dy(start, end) / two;
105 Point { x, y }
106}
107
108impl LineString {
109 pub fn area(&self) -> f64 {
110 get_linestring_area(&self)
111 }
112
113 pub fn centroid(&self) -> Option<Point> {
114 if self.points.is_empty() {
118 return None;
119 }
120
121 if self.points.len() == 1 {
122 Some(self.points[0])
123 } else {
124 let (sum_x, sum_y, total_length) =
125 self.points.windows(2)
126 .fold((0.0_f64, 0.0_f64, 0.0_f64), |accum, line| {
127 let segment_len = euclidean_length_2(line[0], line[1]);
128 let line_center = centroid_points(line[0], line[1]);
129 (
130 accum.0 + segment_len * line_center.x,
131 accum.1 + segment_len * line_center.y,
132 accum.2 + segment_len,
133 )
134 });
135 Some(Point { x: sum_x / total_length, y: sum_y / total_length })
136 }
137 }
138}
139
140fn dx(a: Point, b: Point) -> f64 {
141 b.x - a.x
142}
143
144fn dy(a: Point, b: Point) -> f64 {
145 b.y - a.y
146}
147
148fn euclidean_length_2(a: Point, b: Point) -> f64 {
149 dx(a, b).hypot(dy(a, b))
150}
151
152#[derive(Debug, Clone)]
153pub struct Polygon {
154 pub exterior: LineString,
155 pub interiors: Vec<LineString>,
156}
157
158#[derive(PartialEq, Eq)]
159enum PointPosition {
160 Inside,
161 Outside,
162 OnBoundary,
163}
164
165impl Polygon {
166 fn contains(&self, p: &Point) -> bool {
167 match get_position(*p, &self.exterior) {
168 PointPosition::OnBoundary | PointPosition::Outside => false,
169 _ => self
170 .interiors
171 .iter()
172 .all(|ls| get_position(*p, ls) == PointPosition::Outside),
173 }
174 }
175
176 fn area(&self) -> f64 {
177 let area_exterior = get_linestring_area(&self.exterior);
178 let area_interior: f64 = self.interiors.iter().map(|line| get_linestring_area(line)).sum();
179 area_exterior - area_interior
180 }
181
182 fn centroid(&self) -> Option<Point> {
193
194 if self.exterior.points.is_empty() {
195 return None;
196 } else if self.exterior.points.len() == 1 {
197 return Some(self.exterior.points[0]);
198 }
199
200 let external_centroid = simple_polygon_centroid(&self.exterior)?;
201
202 if !self.interiors.is_empty() {
203 let external_area = simple_polygon_area(&self.exterior).abs();
204 let (totals_x, totals_y, internal_area) = self
206 .interiors
207 .iter()
208 .filter_map(|ring| {
209 let area = simple_polygon_area(ring).abs();
210 let centroid = simple_polygon_centroid(ring)?;
211 Some((centroid.x * area, centroid.y * area, area))
212 })
213 .fold((0.0_f64, 0.0_f64, 0.0_f64), |accum, val| {
214 (accum.0 + val.0, accum.1 + val.1, accum.2 + val.2)
215 });
216
217 return Some(Point {
218 x: ((external_centroid.x * external_area) - totals_x) / (external_area - internal_area),
219 y: ((external_centroid.y * external_area) - totals_y) / (external_area - internal_area),
220 });
221 }
222
223 Some(external_centroid)
224 }
225
226 fn bounding_rect(&self) -> Option<Rect> {
227 get_bounding_rect(&self.exterior)
228 }
229}
230
231
232struct Rect {
233 pub min: Point,
234 pub max: Point,
235}
236
237fn get_bounding_rect(line_string: &LineString) -> Option<Rect> {
238 if line_string.points.is_empty() {
239 return None;
240 }
241 let first_point = line_string.points[0];
242
243 let mut min_x = first_point.x;
244 let mut max_x = first_point.x;
245 let mut min_y = first_point.y;
246 let mut max_y = first_point.y;
247
248 for point in line_string.points.iter() {
249 min_x = min_x.min(point.x);
250 max_x = max_x.max(point.x);
251 min_y = min_y.min(point.y);
252 max_y = max_y.max(point.y);
253 }
254
255 Some(Rect {
256 min: Point { x: min_x, y: min_y },
257 max: Point { x: max_x, y: max_y },
258 })
259}
260
261fn simple_polygon_area(line_string: &LineString) -> f64 {
262 if line_string.points.is_empty() || line_string.points.len() == 1 {
263 return 0.0;
264 }
265
266 line_string.points.windows(2).map(|line| determinant(line[0], line[1])).sum::<f64>() / 2.0_f64
267}
268
269fn get_linestring_area(line_string: &LineString) -> f64 {
270 line_string.points.windows(2).map(|line| {
271 determinant(line[0], line[1])
272 }).sum()
273}
274
275fn determinant(start: Point, end: Point) -> f64 {
276 start.x * end.y - start.y * end.x
277}
278
279fn simple_polygon_centroid(line_string: &LineString) -> Option<Point> {
280 let area = get_linestring_area(line_string);
281 if area == 0.0 {
282 return line_string.centroid();
284 }
285 let (sum_x, sum_y) = line_string.points.windows(2)
286 .fold((0.0_f64, 0.0_f64), |accum, line| {
287 let start = line[0];
288 let end = line[1];
289 let tmp = determinant(start, end);
290 (
291 accum.0 + ((end.x + start.x) * tmp),
292 accum.1 + ((end.y + start.y) * tmp),
293 )
294 });
295
296 let six = 6.0;
297 Some(Point { x: sum_x / (six * area), y: sum_y / (six * area) })
298}
299
300fn get_position(p: Point, linestring: &LineString) -> PointPosition {
302
303 if linestring.points.is_empty() {
308 return PointPosition::Outside;
309 }
310 if line_string_contains_point(&linestring, p) {
312 return PointPosition::OnBoundary;
313 }
314
315 let mut xints = 0.0_f64;
316 let mut crossings = 0;
317
318 for line in linestring.points.windows(2) {
319 let start = line[0];
320 let end = line[1];
321 if p.y > start.y.min(end.y)
322 && p.y <= start.y.max(end.y)
323 && p.x <= start.x.max(end.x)
324 {
325 if start.y != end.y {
326 xints = (p.y - start.y) * (end.x - start.x)
327 / (end.y - start.y)
328 + start.x;
329 }
330 if (start.x == end.x) || (p.x <= xints) {
331 crossings += 1;
332 }
333 }
334 }
335
336 if crossings % 2 == 1 {
337 PointPosition::Inside
338 } else {
339 PointPosition::Outside
340 }
341}
342
343
344#[derive(Debug)]
346struct Qcell {
347 centroid: Point,
349 extent: f64,
351 distance: f64,
353 max_distance: f64,
355}
356
357impl Qcell {
358 fn new(x: f64, y: f64, h: f64, distance: f64, max_distance: f64) -> Qcell {
359 Qcell {
360 centroid: Point { x, y },
361 extent: h,
362 distance,
363 max_distance,
364 }
365 }
366}
367
368impl PartialOrd for Qcell {
369 fn partial_cmp(&self, other: &Qcell) -> Option<Ordering> {
370 Some(self.cmp(other))
371 }
372}
373
374impl Ord for Qcell {
375 fn cmp(&self, other: &Qcell) -> std::cmp::Ordering {
376 self.max_distance.partial_cmp(&other.max_distance).unwrap()
377 }
378}
379
380impl PartialEq for Qcell {
381 fn eq(&self, other: &Qcell) -> bool {
382 (self.max_distance - other.max_distance).abs() < COORD_PRECISION
383 }
384}
385
386impl Eq for Qcell { }
387
388fn signed_distance(x: f64, y: f64, polygon: &Polygon) -> f64 {
391 let point = Point { x, y };
392 let inside = polygon.contains(&point);
393 let distance = point.euclidean_distance_line_string(&polygon.exterior);
395 if inside {
396 distance
397 } else {
398 -distance
399 }
400}
401
402fn add_quad(
404 mpq: &mut BinaryHeap<Qcell>,
405 cell: &Qcell,
406 new_height: f64,
407 polygon: &Polygon,
408) {
409 let two = 2.0_f64;
410 let centroid_x = cell.centroid.x;
411 let centroid_y = cell.centroid.y;
412 for combo in &[
413 (centroid_x - new_height, centroid_y - new_height),
414 (centroid_x + new_height, centroid_y - new_height),
415 (centroid_x - new_height, centroid_y + new_height),
416 (centroid_x + new_height, centroid_y + new_height),
417 ] {
418 let mut new_dist = signed_distance(combo.0, combo.1, polygon);
419 mpq.push(Qcell::new(
420 combo.0,
421 combo.1,
422 new_height,
423 new_dist,
424 new_dist + new_height * two.sqrt(),
425 ));
426 }
427}
428
429pub fn polylabel(polygon: &Polygon, tolerance: f64) -> Point {
461
462 if polygon.area() < 0.0 {
464 return Point { x: 0.0, y: 0.0 };
465 }
466
467 let two = 2.0_f64;
468
469 let centroid = polygon.centroid().unwrap();
471 let bbox = polygon.bounding_rect().unwrap();
472 let width = bbox.max.x - bbox.min.x;
473 let height = bbox.max.y - bbox.min.y;
474 let cell_size = width.min(height);
475
476 if cell_size == 0.0 {
478 return Point { x: bbox.min.x, y: bbox.min.y };
479 }
480
481 let mut h = cell_size / two;
482 let distance = signed_distance(centroid.x, centroid.y, polygon);
483 let max_distance = distance + 0.0 * two.sqrt();
484
485 let mut best_cell = Qcell::new(
486 centroid.x,
487 centroid.y,
488 0.0,
489 distance,
490 max_distance,
491 );
492
493 let bbox_cell_dist = signed_distance(
495 bbox.min.x + width / two,
496 bbox.min.y + height / two,
497 polygon,
498 );
499 let bbox_cell = Qcell {
500 centroid: Point { x: bbox.min.x + width / two, y: bbox.min.y + height / two },
501 extent: 0.0,
502 distance: bbox_cell_dist,
503 max_distance: bbox_cell_dist + 0.0 * two.sqrt(),
504 };
505
506 if bbox_cell.distance > best_cell.distance {
507 best_cell = bbox_cell;
508 }
509
510 let mut cell_queue: BinaryHeap<Qcell> = BinaryHeap::new();
512 let mut x = bbox.min.x;
514 let mut y;
515
516 while x < bbox.max.x {
517 y = bbox.min.y;
518 while y < bbox.max.y {
519 let latest_dist = signed_distance(x + h, y + h, polygon);
520 cell_queue.push(Qcell {
521 centroid: Point { x: x + h, y: y + h },
522 extent: h,
523 distance: latest_dist,
524 max_distance: latest_dist + h * two.sqrt(),
525 });
526 y = y + cell_size;
527 }
528 x = x + cell_size;
529 }
530
531
532 while !cell_queue.is_empty() {
534 let cell = cell_queue.pop().unwrap();
535 if cell.distance > best_cell.distance {
537 best_cell.centroid = Point { x: cell.centroid.x, y: cell.centroid.y };
538 best_cell.extent = cell.extent;
539 best_cell.distance = cell.distance;
540 best_cell.max_distance = cell.max_distance;
541 }
542 if cell.max_distance - best_cell.distance <= tolerance {
544 continue;
545 }
546 h = cell.extent / two;
548 add_quad(&mut cell_queue, &cell, h, polygon);
549 }
550
551 best_cell.centroid
553}