1 /**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * Copyright 2014 Nicklas Avén
22 *
23 **********************************************************************/
24
25 #include "effectivearea.h"
26 #include "qgspoint.h"
27
initiate_minheap(int npoints)28 static MINHEAP initiate_minheap( int npoints )
29 {
30 MINHEAP tree;
31 tree.key_array = ( areanode ** )lwalloc( npoints * sizeof( void * ) );
32 tree.maxSize = npoints;
33 tree.usedSize = 0;
34 return tree;
35 }
36
destroy_minheap(MINHEAP tree)37 static void destroy_minheap( MINHEAP tree )
38 {
39 lwfree( tree.key_array );
40 }
41
42 /**
43 * Calculate the area of a triangle in 2d
44 */
triarea2d(const QgsPoint & P1,const QgsPoint & P2,const QgsPoint & P3)45 static double triarea2d( const QgsPoint &P1, const QgsPoint &P2, const QgsPoint &P3 )
46 {
47 return std::fabs( 0.5 * ( ( P1.x() - P2.x() ) * ( P3.y() - P2.y() ) - ( P1.y() - P2.y() ) * ( P3.x() - P2.x() ) ) );
48 }
49
50 /**
51 * Calculate the area of a triangle in 3d space
52 */
triarea3d(const QgsPoint & P1,const QgsPoint & P2,const QgsPoint & P3)53 static double triarea3d( const QgsPoint &P1, const QgsPoint &P2, const QgsPoint &P3 )
54 {
55 //LWDEBUG( 2, "Entered triarea3d" );
56 double ax, bx, ay, by, az, bz, cx, cy, cz, area;
57
58 ax = P1.x() - P2.x();
59 bx = P3.x() - P2.x();
60 ay = P1.y() - P2.y();
61 by = P3.y() - P2.y();
62 az = P1.z() - P2.z();
63 bz = P3.z() - P2.z();
64
65 cx = ay * bz - az * by;
66 cy = az * bx - ax * bz;
67 cz = ax * by - ay * bx;
68
69 area = std::fabs( 0.5 * ( std::sqrt( cx * cx + cy * cy + cz * cz ) ) );
70 return area;
71 }
72
73 /**
74 * We create the minheap by ordering the minheap array by the areas in the areanode structs that the minheap keys refer to
75 */
cmpfunc(const void * a,const void * b)76 static int cmpfunc( const void *a, const void *b )
77 {
78 const double v1 = ( *( areanode ** )a )->area;
79 const double v2 = ( *( areanode ** )b )->area;
80
81 /* qsort gives unpredictable results when comparing identical values.
82 * If two values are the same we force returning the last point in the point array.
83 * That way we get the same ordering on different machines and platforms
84 */
85 if ( v1 == v2 )
86 return ( *( areanode ** )a ) - ( *( areanode ** )b );
87 else
88 return ( v1 > v2 ) ? 1 : -1;
89 }
90
91 /**
92 * Sift Down
93 */
down(MINHEAP * tree,areanode * arealist,int parent)94 static void down( MINHEAP *tree, areanode *arealist, int parent )
95 {
96 //LWDEBUG( 2, "Entered down" );
97 areanode **treearray = tree->key_array;
98 const int left = parent * 2 + 1;
99 const int right = left + 1;
100 areanode *tmp = nullptr;
101 int swap = parent;
102 double leftarea = 0;
103 double rightarea = 0;
104 const double parentarea = ( ( areanode * )treearray[parent] )->area;
105
106 if ( left < tree->usedSize )
107 {
108 leftarea = ( ( areanode * )treearray[left] )->area;
109 if ( parentarea > leftarea ) swap = left;
110 }
111 if ( right < tree->usedSize )
112 {
113 rightarea = ( ( areanode * )treearray[right] )->area;
114 if ( rightarea < parentarea && rightarea < leftarea ) swap = right;
115 }
116 if ( swap > parent )
117 {
118 // OK, we have to swap something
119 tmp = treearray[parent];
120 treearray[parent] = treearray[swap];
121 // Update reference
122 ( ( areanode * )treearray[parent] )->treeindex = parent;
123 treearray[swap] = tmp;
124 // Update reference
125 ( ( areanode * )treearray[swap] )->treeindex = swap;
126 if ( swap < tree->usedSize ) down( tree, arealist, swap );
127 }
128 }
129
130 /**
131 * Sift Up
132 */
up(MINHEAP * tree,areanode * arealist,int c)133 static void up( MINHEAP *tree, areanode *arealist, int c )
134 {
135 //LWDEBUG( 2, "Entered up" );
136 areanode *tmp = nullptr;
137
138 Q_UNUSED( arealist )
139
140 areanode **treearray = tree->key_array;
141 int parent = ( c - 1 ) / 2;
142
143 while ( ( ( areanode * )treearray[c] )->area < ( ( areanode * )treearray[parent] )->area )
144 {
145 // OK, we have to swap
146 tmp = treearray[parent];
147 treearray[parent] = treearray[c];
148 // Update reference
149 ( ( areanode * )treearray[parent] )->treeindex = parent;
150 treearray[c] = tmp;
151 // Update reference
152 ( ( areanode * )treearray[c] )->treeindex = c;
153 c = parent;
154 parent = ( c - 1 ) / 2;
155 }
156 }
157
158 /**
159 * Gets a reference to the point with the smallest effective area from the root of the min heap
160 */
minheap_pop(MINHEAP * tree,areanode * arealist)161 static areanode *minheap_pop( MINHEAP *tree, areanode *arealist )
162 {
163 //LWDEBUG( 2, "Entered minheap_pop" );
164 areanode *res = tree->key_array[0];
165
166 // put last value first
167 tree->key_array[0] = tree->key_array[( tree->usedSize ) - 1];
168 ( ( areanode * )tree->key_array[0] )->treeindex = 0;
169
170 tree->usedSize--;
171 down( tree, arealist, 0 );
172 return res;
173 }
174
175 /**
176 * The member of the minheap at index idx is changed. Update the tree and make restore the heap property
177 */
minheap_update(MINHEAP * tree,areanode * arealist,int idx)178 static void minheap_update( MINHEAP *tree, areanode *arealist, int idx )
179 {
180 areanode **treearray = tree->key_array;
181 const int parent = ( idx - 1 ) / 2;
182
183 if ( ( ( areanode * )treearray[idx] )->area < ( ( areanode * )treearray[parent] )->area )
184 up( tree, arealist, idx );
185 else
186 down( tree, arealist, idx );
187 }
188
189 /**
190 * To get the effective area, we have to check what area a point results in when all smaller areas are eliminated
191 */
tune_areas(EFFECTIVE_AREAS * ea,int avoid_collaps,int set_area,double trshld)192 static void tune_areas( EFFECTIVE_AREAS *ea, int avoid_collaps, int set_area, double trshld )
193 {
194 //LWDEBUG( 2, "Entered tune_areas" );
195 QgsPoint P1;
196 QgsPoint P2;
197 QgsPoint P3;
198 double area;
199 int go_on = 1;
200 double check_order_min_area = 0;
201
202 const int npoints = ea->inpts.size();
203 int i;
204 int current, before_current, after_current;
205
206 MINHEAP tree = initiate_minheap( npoints );
207
208 // Add all keys (index in initial_arealist) into minheap array
209 for ( i = 0; i < npoints; i++ )
210 {
211 tree.key_array[i] = ea->initial_arealist + i;
212 //LWDEBUGF( 2, "add nr %d, with area %lf, and %lf", i, ea->initial_arealist[i].area, tree.key_array[i]->area );
213 }
214 tree.usedSize = npoints;
215
216 // order the keys by area, small to big
217 qsort( tree.key_array, npoints, sizeof( void * ), cmpfunc );
218
219 // We have to put references to our tree in our point-list
220 for ( i = 0; i < npoints; i++ )
221 {
222 ( ( areanode * )tree.key_array[i] )->treeindex = i;
223 //LWDEBUGF( 4, "Check ordering qsort gives, area=%lf and belong to point %d", (( areanode* )tree.key_array[i] )->area, tree.key_array[i] - ea->initial_arealist );
224 }
225
226 // OK, now we have a minHeap, just need to keep it
227 i = 0;
228 while ( go_on )
229 {
230 // Get a reference to the point with the currently smallest effective area
231 current = minheap_pop( &tree, ea->initial_arealist ) - ea->initial_arealist;
232
233 // We have found the smallest area. That is the resulting effective area for the "current" point
234 if ( i < npoints - avoid_collaps )
235 ea->res_arealist[current] = ea->initial_arealist[current].area;
236 else
237 ea->res_arealist[current] = FLT_MAX;
238
239 if ( ea->res_arealist[current] < check_order_min_area )
240 lwerror( "Oh no, this is a bug. For some reason the minHeap returned our points in the wrong order. Please file a ticket in PostGIS ticket system, or send a mail at the mailing list. Returned area = %lf, and last area = %lf", ea->res_arealist[current], check_order_min_area );
241
242 check_order_min_area = ea->res_arealist[current];
243
244 // The found smallest area point is now regarded as eliminated and we have to recalculate the area the adjacent (ignoring earlier eliminated points) points gives
245
246 // Find point before and after
247 before_current = ea->initial_arealist[current].prev;
248 after_current = ea->initial_arealist[current].next;
249
250 P2 = ea->inpts.at( before_current );
251 P3 = ea->inpts.at( after_current );
252
253 // Check if point before current point is the first in the point array.
254 if ( before_current > 0 )
255 {
256 P1 = ea->inpts.at( ea->initial_arealist[before_current].prev );
257
258 if ( ea->is3d )
259 area = triarea3d( P1, P2, P3 );
260 else
261 area = triarea2d( P1, P2, P3 );
262
263 ea->initial_arealist[before_current].area = FP_MAX( area, ea->res_arealist[current] );
264 minheap_update( &tree, ea->initial_arealist, ea->initial_arealist[before_current].treeindex );
265 }
266 if ( after_current < npoints - 1 ) // Check if point after current point is the last in the point array.
267 {
268 P1 = P2;
269 P2 = P3;
270 P3 = ea->inpts.at( ea->initial_arealist[after_current].next );
271
272 if ( ea->is3d )
273 area = triarea3d( P1, P2, P3 );
274 else
275 area = triarea2d( P1, P2, P3 );
276
277 ea->initial_arealist[after_current].area = FP_MAX( area, ea->res_arealist[current] );
278 minheap_update( &tree, ea->initial_arealist, ea->initial_arealist[after_current].treeindex );
279 }
280
281 // rearrange the nodes so the eliminated point will be ignored on the next run
282 ea->initial_arealist[before_current].next = ea->initial_arealist[current].next;
283 ea->initial_arealist[after_current ].prev = ea->initial_arealist[current].prev;
284
285 // Check if we are finished
286 if ( ( !set_area && ea->res_arealist[current] > trshld ) || ( ea->initial_arealist[0].next == ( npoints - 1 ) ) )
287 go_on = 0;
288
289 i++;
290 }
291 destroy_minheap( tree );
292 }
293
294 /**
295 * We calculate the effective area for the first time
296 */
ptarray_calc_areas(EFFECTIVE_AREAS * ea,int avoid_collaps,int set_area,double trshld)297 void ptarray_calc_areas( EFFECTIVE_AREAS *ea, int avoid_collaps, int set_area, double trshld )
298 {
299 //LWDEBUG( 2, "Entered ptarray_calc_areas" );
300 int i;
301 const int npoints = ea->inpts.size();
302 double area;
303
304 QgsPoint P1;
305 QgsPoint P2;
306 QgsPoint P3;
307 P1 = ea->inpts.at( 0 );
308 P2 = ea->inpts.at( 1 );
309
310 // The first and last point shall always have the maximum effective area. We use float max to not make trouble for bbox
311 ea->initial_arealist[0].area = ea->initial_arealist[npoints - 1].area = FLT_MAX;
312 ea->res_arealist[0] = ea->res_arealist[npoints - 1] = FLT_MAX;
313
314 ea->initial_arealist[0].next = 1;
315 ea->initial_arealist[0].prev = 0;
316
317 for ( i = 1; i < ( npoints ) - 1; i++ )
318 {
319 ea->initial_arealist[i].next = i + 1;
320 ea->initial_arealist[i].prev = i - 1;
321 P3 = ea->inpts.at( i + 1 );
322
323 if ( ea->is3d )
324 area = triarea3d( P1, P2, P3 );
325 else
326 area = triarea2d( P1, P2, P3 );
327
328 //LWDEBUGF( 4, "Write area %lf to point %d on address %p", area, i, &( ea->initial_arealist[i].area ) );
329 ea->initial_arealist[i].area = area;
330 P1 = P2;
331 P2 = P3;
332 }
333 ea->initial_arealist[npoints - 1].next = npoints - 1;
334 ea->initial_arealist[npoints - 1].prev = npoints - 2;
335
336 for ( i = 1; i < ( npoints ) - 1; i++ )
337 {
338 ea->res_arealist[i] = FLT_MAX;
339 }
340 tune_areas( ea, avoid_collaps, set_area, trshld );
341 }
342