PolylineSimplifyer::getSqSegDist()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 25
rs 9.4555
cc 5
nc 4
nop 3
1
<?php
2
/**
3
 * Class PolylineSimplifyer
4
 *
5
 * @created      04.10.2019
6
 * @author       smiley <[email protected]>
7
 * @copyright    2019 smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\GeoJSON;
12
13
use function array_fill, array_pop, array_push, array_values, count, is_array;
14
15
/**
16
 * @see https://github.com/mourner/simplify-js
17
 */
18
class PolylineSimplifyer{
19
20
	protected array $coords;
21
22
	/**
23
	 * PolylineSimplifyer constructor.
24
	 *
25
	 * @throws \chillerlan\GeoJSON\GeoJSONException
26
	 */
27
	public function __construct(array $polylineCoords){
28
29
		if(count($polylineCoords) < 2){
30
			throw new GeoJSONException('not enough points');
31
		}
32
33
		$this->coords = array_values($polylineCoords);
34
35
		foreach($this->coords as $i => $coord){
36
37
			if(!is_array($coord) || count($coord) < 2){
38
				throw new GeoJSONException('invalid coords found');
39
			}
40
41
			$this->coords[$i] = array_values($coord);
42
		}
43
44
	}
45
46
	/**
47
	 * @throws \chillerlan\GeoJSON\GeoJSONException
48
	 */
49
	public function simplify(float $tolerance = 1, bool $highestQuality = false):array{
50
		$sqTolerance = $tolerance * $tolerance;
51
		$points      = $highestQuality ? $this->coords : $this->simplifyRadialDist($sqTolerance);
52
53
		return $this->simplifyDouglasPeucker($points, $sqTolerance);
54
	}
55
56
	/**
57
	 * basic distance-based simplification
58
	 */
59
	protected function simplifyRadialDist(float $sqTolerance):array{
60
		$prevPoint = $this->coords[0];
61
		$newPoints = [$prevPoint];
62
		$point     = null;
63
		$len       = count($this->coords);
64
65
		for($i = 1; $i < $len; $i++){
66
			$point = $this->coords[$i];
67
68
			if($this->getSqDist($point, $prevPoint) > $sqTolerance){
69
				$newPoints[] = $point;
70
				$prevPoint   = $point;
71
			}
72
		}
73
74
		if($prevPoint !== $point){
75
			$newPoints[] = $point;
76
		}
77
78
		return $newPoints;
79
	}
80
81
	/**
82
	 * square distance between 2 points
83
	 */
84
	protected function getSqDist(array $p1, array $p2):float{
85
		$dx = $p1[0] - $p2[0];
86
		$dy = $p1[1] - $p2[1];
87
88
		return $dx * $dx + $dy * $dy;
89
	}
90
91
	/**
92
	 * simplification using optimized Douglas-Peucker algorithm with recursion elimination
93
	 */
94
	protected function simplifyDouglasPeucker(array $points, float $sqTolerance):array{
95
		$len       = count($points);
96
		$markers   = array_fill(0, $len - 1, null);
97
		$first     = 0;
98
		$last      = $len - 1;
99
		$stack     = [];
100
		$newPoints = [];
101
		$index     = 0;
102
103
		$markers[$first] = $markers[$last] = 1;
104
105
		while($last){
106
107
			$maxSqDist = 0;
108
109
			for($i = $first + 1; $i < $last; $i++){
110
				$sqDist = $this->getSqSegDist($points[$i], $points[$first], $points[$last]);
111
112
				if($sqDist > $maxSqDist){
113
					$index     = $i;
114
					$maxSqDist = $sqDist;
115
				}
116
			}
117
118
			if($maxSqDist > $sqTolerance){
119
				$markers[$index] = 1;
120
				array_push($stack, $first, $index, $index, $last);
121
			}
122
123
			$last  = array_pop($stack);
124
			$first = array_pop($stack);
125
		}
126
127
		foreach($points as $i => $point){
128
			if($markers[$i]){
129
				$newPoints[] = $point;
130
			}
131
		}
132
133
		return $newPoints;
134
	}
135
136
	/**
137
	 * square distance from a point to a segment
138
	 */
139
	protected function getSqSegDist(array $p, array $p1, array $p2):float{
140
		$x  = $p1[0];
141
		$y  = $p1[1];
142
		$dx = $p2[0] - $x;
143
		$dy = $p2[1] - $y;
144
145
		if((int)$dx !== 0 || (int)$dy !== 0){
146
147
			$t = (($p[0] - $x) * $dx + ($p[1] - $y) * $dy) / ($dx * $dx + $dy * $dy);
148
149
			if($t > 1){
150
				$x = $p2[0];
151
				$y = $p2[1];
152
153
			}
154
			elseif($t > 0){
155
				$x += $dx * $t;
156
				$y += $dy * $t;
157
			}
158
		}
159
160
		$dx = $p[0] - $x;
161
		$dy = $p[1] - $y;
162
163
		return $dx * $dx + $dy * $dy;
164
	}
165
166
}
167