1 #include "FeatureManipulations.h"
2
3 #include "Global.h"
4 #include "Coord.h"
5 #include "DocumentCommands.h"
6 #include "FeatureCommands.h"
7 #include "WayCommands.h"
8 #include "RelationCommands.h"
9 #include "NodeCommands.h"
10 #include "Document.h"
11 #include "Features.h"
12 #include "PropertiesDock.h"
13 #include "Projection.h"
14 #include "ImportExportOSC.h"
15
16 #include "Utils.h"
17 #include "LineF.h"
18
19 #include <QtCore/QString>
20 #include <QMessageBox>
21
22 #include <algorithm>
23
24 static void mergeNodes(Document* theDocument, CommandList* theList, Node *node1, Node *node2);
25
isNear(Node * a,Node * b)26 static bool isNear(Node* a, Node* b)
27 {
28 // For now, only if exactly same position
29 // Future: use distance threshold?
30 return a->position() == b->position();
31 }
32
canJoin(Way * R1,Way * R2)33 bool canJoin(Way* R1, Way* R2)
34 {
35 if ( (R1->size() == 0) || (R2->size() == 0) )
36 return true;
37 Node* Start1 = R1->getNode(0);
38 Node* End1 = R1->getNode(R1->size()-1);
39 Node* Start2 = R2->getNode(0);
40 Node* End2 = R2->getNode(R2->size()-1);
41 return (Start1 == Start2) ||
42 (Start1 == End2) ||
43 (End1 == Start2) ||
44 (End1 == End2) ||
45 isNear(Start1, Start2) ||
46 isNear(Start1, End2) ||
47 isNear(End1, Start2) ||
48 isNear(End1, End2);
49 }
50
canBreak(Way * R1,Way * R2)51 bool canBreak(Way* R1, Way* R2)
52 {
53 if ( (R1->size() == 0) || (R2->size() == 0) )
54 return false;
55 for (int i=0; i<R1->size(); i++)
56 for (int j=0; j<R2->size(); j++)
57 if (R1->get(i) == R2->get(j))
58 return true;
59 return false;
60 }
61
canJoinRoads(PropertiesDock * theDock)62 bool canJoinRoads(PropertiesDock* theDock)
63 {
64 QHash<Coord,int> ends;
65 for (int i=0; i<theDock->selectionSize(); ++i)
66 if (Way* R = CAST_WAY(theDock->selection(i))) {
67 if (R->isClosed()) continue;
68 if (!R->size()) continue;
69 Coord start = R->getNode(0)->position();
70 Coord end = R->getNodes().back()->position();
71 ++ends[start];
72 ++ends[end];
73 }
74
75 return ends.values().contains(2);
76 }
77
canBreakRoads(PropertiesDock * theDock)78 bool canBreakRoads(PropertiesDock* theDock)
79 {
80 QList<Way*> Input;
81 for (int i=0; i<theDock->selectionSize(); ++i)
82 if (Way* R = CAST_WAY(theDock->selection(i)))
83 Input.push_back(R);
84 for (int i=0; i<Input.size(); ++i)
85 for (int j=i+1; j<Input.size(); ++j)
86 if (canBreak(Input[i],Input[j]))
87 return true;
88 return false;
89 }
90
canDetachNodes(PropertiesDock * theDock)91 bool canDetachNodes(PropertiesDock* theDock)
92 {
93 QList<Way*> Roads, Result;
94 QList<Node*> Points;
95 for (int i=0; i<theDock->selectionSize(); ++i)
96 if (Way* R = CAST_WAY(theDock->selection(i)))
97 Roads.push_back(R);
98 else if (Node* Pt = CAST_NODE(theDock->selection(i)))
99 Points.push_back(Pt);
100
101 if (Roads.size() > 1 && Points.size() > 1)
102 return false;
103
104 if (Roads.size() == 0 && Points.size()) {
105 for (int i=0; i<Points.size(); ++i) {
106 Way * R = Way::GetSingleParentRoad(Points[i]);
107 if (R)
108 return true;
109 }
110 return false;
111 }
112
113 if (Roads.size() && Points.size() == 1)
114 return true;
115
116 if (Roads.size() == 1 && Points.size())
117 return true;
118
119 return false;
120 }
121
reversePoints(Document * theDocument,CommandList * theList,Way * R)122 void reversePoints(Document* theDocument, CommandList* theList, Way* R)
123 {
124 Layer *layer=theDocument->getDirtyOrOriginLayer(R->layer());
125 for (int i=R->size()-2; i>=0; --i)
126 {
127 Node* Pt = R->getNode(i);
128 theList->add(new WayRemoveNodeCommand(R,i,layer));
129 theList->add(new WayAddNodeCommand(R,Pt,layer));
130 }
131 }
132
133
distanceFrom(QLineF ab,const Coord & c)134 static qreal distanceFrom(QLineF ab, const Coord& c)
135 {
136 // distance in metres from c to line segment ab
137 qreal ab_len2 = ab.dx() * ab.dx() + ab.dy() * ab.dy();
138 QPointF dp;
139 if (ab_len2) {
140 QLineF ac(ab.p1(), c);
141 qreal u = (ac.dx() * ab.dx() + ac.dy() * ab.dy()) / ab_len2;
142 if (u < 0.0) u = 0.0;
143 else if (u > 1.0) u = 1.0;
144 dp = ab.pointAt(u);
145 } else {
146 dp = ab.p1();
147 }
148 Coord dc(dp);
149 return dc.distanceFrom(c);
150 }
151
simplifyWay(Document * doc,Layer * layer,CommandList * theList,Way * w,int start,int end,qreal threshold)152 void simplifyWay(Document *doc, Layer *layer, CommandList *theList, Way *w, int start, int end, qreal threshold)
153 {
154 // Douglas-Peucker reduction of uninteresting points in way, subject to maximum error in metres
155
156 // TODO Performance: Use path-hull algorithm described at http://www.cs.ubc.ca/cgi-bin/tr/1992/TR-92-07.ps
157 if (end - start <= 1)
158 // no removable nodes
159 return;
160
161 QLineF segment(w->getNode(start)->position(), w->getNode(end)->position());
162 qreal maxdist = -1;
163 int maxpos = 0;
164 for (int i = start+1; i < end; i++) {
165 qreal d = distanceFrom(segment, w->getNode(i)->position());
166 if (d > maxdist) {
167 maxdist = d;
168 maxpos = i;
169 }
170 }
171 // maxdist is in kilometres
172 if (maxpos && maxdist * 1000 > threshold) {
173 simplifyWay(doc, layer, theList, w, maxpos, end, threshold);
174 simplifyWay(doc, layer, theList, w, start, maxpos, threshold);
175 } else {
176 for (int i = end - 1; i > start; i--) {
177 Feature *n = w->get(i);
178 if (!doc->isDownloadedSafe(n->boundingBox()) && n->hasOSMId())
179 continue;
180 theList->add(new WayRemoveNodeCommand(w, i, layer));
181 theList->add(new RemoveFeatureCommand(doc, n));
182 }
183 }
184 }
185 QSet<QString> uninterestingKeys;
isNodeInteresting(Node * n)186 bool isNodeInteresting(Node *n)
187 {
188 for (int i = 0; i < n->tagSize(); i++)
189 if (!uninterestingKeys.contains(n->tagKey(i)))
190 return true;
191 return false;
192 }
193
simplifyRoads(Document * theDocument,CommandList * theList,PropertiesDock * theDock,qreal threshold)194 void simplifyRoads(Document* theDocument, CommandList* theList, PropertiesDock* theDock, qreal threshold)
195 {
196 if (uninterestingKeys.isEmpty())
197 uninterestingKeys << "source";
198
199 for (int i = 0; i < theDock->selectionSize(); ++i)
200 if (Way* w = CAST_WAY(theDock->selection(i))) {
201 Layer *layer = theDocument->getDirtyOrOriginLayer(w->layer());
202 int end = w->size() - 1;
203 for (int i = end; i >= 0; i--) {
204 Node *n = w->getNode(i);
205 if (i == 0 || n->sizeParents() > 1 || isNodeInteresting(n)) {
206 simplifyWay(theDocument, layer, theList, w, i, end, threshold);
207 end = i;
208 }
209 }
210 }
211 }
212
appendPoints(Document * theDocument,CommandList * L,Way * Dest,Way * Src,bool prepend,bool reverse)213 static void appendPoints(Document* theDocument, CommandList* L, Way* Dest, Way* Src, bool prepend, bool reverse)
214 {
215 Layer *layer = theDocument->getDirtyOrOriginLayer(Dest->layer());
216 int srclen = Src->size();
217 int destpos = prepend ? 0 : Dest->size();
218 for (int i=1; i<srclen; ++i) {
219 int j = (reverse ? srclen-i : i) - (prepend != reverse ? 1 : 0);
220 Node* Pt = Src->getNode(j);
221 if (!Pt->layer() || layer != Pt->layer()) {
222 L->add(new AddFeatureCommand(layer, Pt, false));
223 }
224 L->add(new WayAddNodeCommand(Dest, Pt, destpos++, layer));
225 }
226 }
227
join(Document * theDocument,CommandList * L,Way * R1,Way * R2)228 static Way* join(Document* theDocument, CommandList* L, Way* R1, Way* R2)
229 {
230 QList<Feature*> Alternatives;
231 if (R1->size() == 0)
232 {
233 Feature::mergeTags(theDocument,L,R2,R1);
234 L->add(new RemoveFeatureCommand(theDocument,R1,Alternatives));
235 return R2;
236 }
237 if (R2->size() == 0)
238 {
239 Feature::mergeTags(theDocument,L,R1,R2);
240 L->add(new RemoveFeatureCommand(theDocument,R2,Alternatives));
241 return R1;
242 }
243 Node* Start1 = R1->getNode(0);
244 Node* End1 = R1->getNode(R1->size()-1);
245 Node* Start2 = R2->getNode(0);
246 Node* End2 = R2->getNode(R2->size()-1);
247
248 bool prepend = false; // set true if R2 meets beginning of R1
249 bool reverse = false; // set true if R2 is opposite direction to R1
250
251 if (End1 == Start2) {
252 // nothing to do, but skip the other tests
253 } else if (End1 == End2) {
254 reverse = true;
255 } else if (Start1 == Start2) {
256 prepend = true;
257 reverse = true;
258 } else if (Start1 == End2) {
259 prepend = true;
260 } else if (isNear(End1, Start2)) {
261 mergeNodes(theDocument, L, End1, Start2);
262 } else if (isNear(End1, End2)) {
263 mergeNodes(theDocument, L, End1, End2);
264 reverse = true;
265 } else if (isNear(Start1, Start2)) {
266 mergeNodes(theDocument, L, Start1, Start2);
267 prepend = true;
268 reverse = true;
269 } else if (isNear(Start1, End2)) {
270 mergeNodes(theDocument, L, Start1, End2);
271 prepend = true;
272 }
273
274 appendPoints(theDocument, L, R1, R2, prepend, reverse);
275 Feature::mergeTags(theDocument,L,R1,R2);
276 L->add(new RemoveFeatureCommand(theDocument,R2,Alternatives));
277
278 // Auto-merge nearly-closed ways
279 Node *StartResult = R1->getNode(0);
280 Node *EndResult = R1->getNode(R1->size()-1);
281 if (StartResult != EndResult && isNear(StartResult, EndResult))
282 mergeNodes(theDocument, L, StartResult, EndResult);
283 return R1;
284 }
285
286 template<typename T>
replaceItem(QList<T> & list,T oldItem,T newItem)287 static void replaceItem(QList<T> &list, T oldItem, T newItem)
288 {
289 int i = list.indexOf(oldItem);
290 if (i >= 0)
291 list.replace(i, newItem);
292 }
293
joinRoads(Document * theDocument,CommandList * theList,PropertiesDock * theDock)294 void joinRoads(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
295 {
296 QList<Feature*> selection = theDock->selection();
297 QHash<Coord,QList<Way*> > ends;
298
299 foreach (Feature *F, selection)
300 if (Way* R = CAST_WAY(F))
301 if (!R->isClosed()) {
302 Coord start = R->getNode(0)->position();
303 ends[start] << R;
304 Coord end = R->getNodes().back()->position();
305 ends[end] << R;
306 }
307
308 foreach (Coord c, ends.keys()) {
309 QList<Way*> ways = ends[c];
310 if (ways.count() != 2) continue;
311 Way *R1 = ways[0];
312 Way *R2 = ways[1];
313 if (R1 == R2) {
314 Node *firstNode = R1->getNode(0);
315 Node *lastNode = R1->getNode(R1->size()-1);
316 if (firstNode != lastNode && R1->size() >= 4 && firstNode->position() == lastNode->position()) {
317 // almost-area; close it properly
318 mergeNodes(theDocument, theList, R1->getNode(0), R1->getNode(R1->size()-1));
319 }
320 } else {
321 R1 = join(theDocument, theList, R1, R2);
322 // replace R2 with R1 for subsequent operations
323 replaceItem(ends[R2->getNode(0)->position()], R2, R1);
324 replaceItem(ends[R2->getNodes().back()->position()], R2, R1);
325 selection.removeOne(R2);
326 }
327 }
328 theDock->setSelection(selection);
329 }
330
handleWaysplitSpecialRestriction(Document * theDocument,CommandList * theList,Way * FirstPart,Way * NextPart,Relation * theRel)331 static bool handleWaysplitSpecialRestriction(Document* theDocument, CommandList* theList, Way* FirstPart, Way* NextPart, Relation* theRel)
332 {
333 if (theRel->tagValue("type","") != "restriction")
334 return false;
335
336 for (int k=0; k < theRel->size(); k++)
337 {
338 // check for via
339 if (theRel->getRole(k) == "via")
340 {
341 if (theRel->get(k) == FirstPart)
342 {
343 // whoops, we just split a via way, just add the new way, too
344 theList->add(new RelationAddFeatureCommand(theRel, theRel->getRole(k), NextPart, k+1, theDocument->getDirtyOrOriginLayer(FirstPart->layer())));
345 // we just added a member, so get over it
346 k++;
347 }
348 else if ((theRel->get(k))->getType() == IFeature::Point)
349 {
350 // this seems to be a via node, check if it is member the nextPart
351 if (NextPart->find(theRel->get(k)) < NextPart->size())
352 {
353 // yes it is, so remove First Part and add Next Part to it
354 int idx = theRel->find(FirstPart);
355 theList->add(new RelationAddFeatureCommand(theRel, theRel->getRole(idx), NextPart, idx+1, theDocument->getDirtyOrOriginLayer(FirstPart->layer())));
356 theList->add(new RelationRemoveFeatureCommand(theRel, idx, theDocument->getDirtyOrOriginLayer(FirstPart->layer())));
357 }
358 }
359 else if ((theRel->get(k))->getType() & IFeature::LineString)
360 {
361 // this is a way, check the nodes
362 Way* W = CAST_WAY(theRel->get(k));
363 for (int l=0; l<W->size(); l++)
364 {
365 // check if this node is member the nextPart
366 if (NextPart->find(W->get(l)) < NextPart->size())
367 {
368 // yes it is, so remove First Part and add Next Part to it
369 int idx = theRel->find(FirstPart);
370 theList->add(new RelationAddFeatureCommand(theRel, theRel->getRole(idx), NextPart, idx+1, theDocument->getDirtyOrOriginLayer(FirstPart->layer())));
371 theList->add(new RelationRemoveFeatureCommand(theRel, idx, theDocument->getDirtyOrOriginLayer(FirstPart->layer())));
372 break;
373 }
374 }
375 }
376 }
377 }
378 return true;
379 }
380
handleWaysplitRelations(Document * theDocument,CommandList * theList,Way * FirstPart,Way * NextPart)381 static void handleWaysplitRelations(Document* theDocument, CommandList* theList, Way* FirstPart, Way* NextPart)
382 {
383 /* since we may delete First Part from some Relations here, we first build a list of the relations to check */
384 QList<Relation*> checkList;
385
386 for (int j=0; j < FirstPart->sizeParents(); j++) {
387 checkList.append(CAST_RELATION(FirstPart->getParent(j)));
388 }
389
390 for (int j=0; j < checkList.count(); j++) {
391 Relation* L = checkList.at(j);
392 if (!handleWaysplitSpecialRestriction(theDocument, theList, FirstPart, NextPart, L))
393 {
394 int idx = L->find(FirstPart);
395 theList->add(new RelationAddFeatureCommand(L, L->getRole(idx), NextPart, idx+1, theDocument->getDirtyOrOriginLayer(FirstPart->layer())));
396 }
397 }
398 }
399
400
splitRoad(Document * theDocument,CommandList * theList,Way * In,const QList<Node * > & Points,QList<Way * > & Result)401 static void splitRoad(Document* theDocument, CommandList* theList, Way* In, const QList<Node*>& Points, QList<Way*>& Result)
402 {
403 int pos = 0;
404 if (In->isClosed()) { // Special case: If area, rotate the area so that the start node is the first point of splitting
405
406 QList<Node*> Target;
407 for (int i=0; i < Points.size(); i++)
408 if ((pos = In->find(Points[i])) != In->size()) {
409 for (int j=pos+1; j<In->size(); ++j)
410 Target.push_back(In->getNode(j));
411 for (int j=1; j<= pos; ++j)
412 Target.push_back(In->getNode(j));
413 break;
414 }
415 if (pos == In->size())
416 return;
417
418 if (Points.size() == 1) // Special case: For a 1 point area splitting, de-close the road, i.e. duplicate the selected node
419 {
420 Node* N = g_backend.allocNode(theDocument->getDirtyOrOriginLayer(In->layer()), *(In->getNode(pos)));
421 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(In->layer()),N,true));
422
423 Target.prepend(N);
424 } else // otherwise, just close the modified area
425 Target.prepend(In->getNode(pos));
426
427 // Now, reconstruct the road/area
428 while (In->size())
429 theList->add(new WayRemoveNodeCommand(In,(int)0,theDocument->getDirtyOrOriginLayer(In->layer())));
430
431 for (int i=0; i<Target.size(); ++i)
432 theList->add(new WayAddNodeCommand(In,Target[i],theDocument->getDirtyOrOriginLayer(In->layer())));
433
434 if (Points.size() == 1) { // For 1-point, we are done
435 Result.push_back(In);
436 return;
437 }
438 }
439
440 Way* FirstPart = In;
441 bool FirstWayValid = false;
442 for (int i=1; (i+1)<FirstPart->size(); ++i)
443 {
444 if (std::find(Points.begin(),Points.end(),FirstPart->get(i)) != Points.end() and FirstPart->get(i) != FirstPart->get(i+1) \
445 and (FirstWayValid or FirstPart->get(0) != FirstPart->get(i))) {
446 Way* NextPart = g_backend.allocWay(theDocument->getDirtyOrOriginLayer(In->layer()));
447 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(In->layer()),NextPart,true));
448 copyTags(NextPart,FirstPart);
449 theList->add(new WayAddNodeCommand(NextPart, FirstPart->getNode(i), theDocument->getDirtyOrOriginLayer(In->layer())));
450 while ( (i+1) < FirstPart->size() )
451 {
452 theList->add(new WayAddNodeCommand(NextPart, FirstPart->getNode(i+1), theDocument->getDirtyOrOriginLayer(In->layer())));
453 theList->add(new WayRemoveNodeCommand(FirstPart,i+1,theDocument->getDirtyOrOriginLayer(In->layer())));
454 }
455 handleWaysplitRelations(theDocument, theList, FirstPart, NextPart);
456
457 FirstWayValid = true;
458 Result.push_back(FirstPart);
459 FirstPart = NextPart;
460 i=0;
461 }
462 }
463 Result.push_back(FirstPart);
464
465 }
466
splitRoads(Document * theDocument,CommandList * theList,PropertiesDock * theDock)467 void splitRoads(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
468 {
469 QList<Way*> Roads, Result;
470 QList<Node*> Points;
471 for (int i=0; i<theDock->selectionSize(); ++i)
472 if (Way* R = CAST_WAY(theDock->selection(i)))
473 Roads.push_back(R);
474 else if (Node* Pt = CAST_NODE(theDock->selection(i)))
475 Points.push_back(Pt);
476
477 if (Roads.size() == 0 && Points.size() == 1)
478 {
479 Way * R = Way::GetSingleParentRoadInner(Points[0]);
480 if (R)
481 Roads.push_back(R);
482 }
483
484 for (int i=0; i<Roads.size(); ++i)
485 splitRoad(theDocument, theList,Roads[i],Points, Result);
486 theDock->setSelection(Result);
487 }
488
489 /* Split road by the two nodes and return the way joining them, if there is one. */
cutoutRoad(Document * theDocument,CommandList * theList,PropertiesDock *,Node * N1,Node * N2)490 Way *cutoutRoad(Document* theDocument, CommandList* theList, PropertiesDock* /* theDock */, Node *N1, Node *N2) {
491 QList<Way*> Roads, Result;
492 QList<Node*> Points;
493
494 Way *R1 = Way::GetSingleParentRoadInner( N1 );
495 Way *R2 = Way::GetSingleParentRoadInner( N2 );
496
497 if (R1)
498 Roads.push_back(R1);
499 if (R2)
500 Roads.push_back(R2);
501
502 Points.push_back(N1);
503 Points.push_back(N2);
504
505 for (int i=0; i<Roads.size(); ++i)
506 splitRoad(theDocument, theList, Roads[i], Points, Result);
507
508 for (int i = 0; i < Result.size(); ++i) {
509 if (Result[i]->isExtrimity(N1) && Result[i]->isExtrimity(N2))
510 return Result[i];
511 }
512 return NULL;
513 }
514
breakRoad(Document * theDocument,CommandList * theList,Way * R,Node * Pt)515 static void breakRoad(Document* theDocument, CommandList* theList, Way* R, Node* Pt)
516 {
517 for (int i=0; i<R->size(); ++i) {
518 if (R->get(i) == Pt) {
519 Node* New = g_backend.allocNode(theDocument->getDirtyOrOriginLayer(R->layer()), *Pt);
520 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(R->layer()),New,true));
521 copyTags(New,Pt);
522 theList->add(new WayRemoveNodeCommand(R,i,theDocument->getDirtyOrOriginLayer(R->layer())));
523 theList->add(new WayAddNodeCommand(R,New,i,theDocument->getDirtyOrOriginLayer(R->layer())));
524 }
525 }
526 if (!Pt->sizeParents())
527 theList->add(new RemoveFeatureCommand(theDocument,Pt));
528 }
529
breakRoads(Document * theDocument,CommandList * theList,PropertiesDock * theDock)530 void breakRoads(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
531 {
532 QList<Way*> Roads, Result;
533 QList<Node*> Points;
534 for (int i=0; i<theDock->selectionSize(); ++i)
535 if (Way* R = CAST_WAY(theDock->selection(i)))
536 Roads.push_back(R);
537 else if (Node* Pt = CAST_NODE(theDock->selection(i)))
538 Points.push_back(Pt);
539
540 if (Roads.size() == 0 && Points.size() == 1)
541 {
542 for (int i=0; i<Points[0]->sizeParents() ; ++i) {
543 Way * R = CAST_WAY(Points[0]->getParent(i));
544 if (R && !R->isDeleted())
545 Roads.push_back(R);
546 }
547 }
548
549 if (Roads.size() == 1 && Points.size() ) {
550 splitRoad(theDocument, theList,Roads[0],Points, Result);
551 if (Roads[0]->area() > 0.0) {
552 for (int i=0; i<Points.size(); ++i)
553 breakRoad(theDocument, theList, Roads[0],Points[i]);
554 } else {
555 Roads = Result;
556 }
557 }
558
559 for (int i=0; i<Roads.size(); ++i)
560 for (int j=0; j<Roads[i]->size(); ++j)
561 for (int k=i+1; k<Roads.size(); ++k)
562 breakRoad(theDocument, theList, Roads[k],CAST_NODE(Roads[i]->get(j)));
563 }
564
canCreateJunction(PropertiesDock * theDock)565 bool canCreateJunction(PropertiesDock* theDock)
566 {
567 return createJunction(NULL, NULL, theDock, false);
568 }
569
createJunction(Document * theDocument,CommandList * theList,PropertiesDock * theDock,bool doIt)570 int createJunction(Document* theDocument, CommandList* theList, PropertiesDock* theDock, bool doIt)
571 {
572 //TODO test that the junction do not already exists!
573
574 QList<Way*> Roads, Result;
575 for (int i=0; i<theDock->selectionSize(); ++i)
576 if (Way* R = CAST_WAY(theDock->selection(i)))
577 Roads.push_back(R);
578
579 if (Roads.size() < 2)
580 return 0;
581
582 Way* R1 = Roads[0];
583 Way* R2 = Roads[1];
584
585 return Way::createJunction(theDocument, theList, R1, R2, doIt);
586 }
587
588 #define STREET_NUMBERS_LENGTH .0000629
589 #define STREET_NUMBERS_ANGLE 30.0
590
createStreetNumbers(Document * theDocument,CommandList * theList,Way * theRoad,bool Left)591 void createStreetNumbers(Document* theDocument, CommandList* theList, Way* theRoad, bool Left)
592 {
593 QString streetName = theRoad->tagValue("name", "");
594 QLineF l, l2, nv;
595
596 Node* N;
597 Way* R = g_backend.allocWay(theDocument->getDirtyOrOriginLayer());
598 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(),R,true));
599 theList->add(new SetTagCommand(R, "addr:interpolation", ""));
600 theList->add(new SetTagCommand(R, "addr:street", streetName));
601 QPointF prevPoint;
602
603 for (int j=0; j < theRoad->size(); j++) {
604 if (j == 0) {
605 l2 = QLineF(theRoad->getNode(j)->position(), theRoad->getNode(j+1)->position());
606 l = l2;
607 l.setAngle(l2.angle() + 180.);
608 prevPoint = l.p2();
609 } else
610 if (j == theRoad->size()-1) {
611 l = QLineF(theRoad->getNode(j)->position(), theRoad->getNode(j-1)->position());
612 l2 = l;
613 l2.setAngle(l.angle() + 180.);
614 } else {
615 l = QLineF(theRoad->getNode(j)->position(), theRoad->getNode(j-1)->position());
616 l2 = QLineF(theRoad->getNode(j)->position(), theRoad->getNode(j+1)->position());
617 }
618 nv = l.normalVector().unitVector();
619
620 qreal theAngle = (l.angle() - l2.angle());
621 if (theAngle < 0.0) theAngle = 360. + theAngle;
622 theAngle /= 2.;
623 nv.setAngle(l2.angle() + theAngle);
624 nv.setLength(STREET_NUMBERS_LENGTH/sin(angToRad(theAngle)));
625 if (Left)
626 nv.setAngle(nv.angle() + 180.0);
627
628 QLineF lto(prevPoint, nv.p2());
629 lto.setLength(lto.length()+STREET_NUMBERS_LENGTH);
630 QPointF pto;
631
632 bool intersectedTo = false;
633 for (int k=0; k < theRoad->getNode(j)->sizeParents(); ++k) {
634 Way* I = CAST_WAY(theRoad->getNode(j)->getParent(k));
635 if (!I || I == theRoad || I->isDeleted())
636 continue;
637
638 for (int m=0; m < I->size()-1; ++m) {
639 QLineF l3 = QLineF(I->getNode(m)->position(), I->getNode(m+1)->position());
640 QPointF theIntersection;
641 if (lto.intersect(l3, &theIntersection) == QLineF::BoundedIntersection) {
642 intersectedTo = true;
643 QLineF lt = QLineF(prevPoint, theIntersection);
644 if (lt.length() < lto.length())
645 lto = lt;
646 }
647 }
648 }
649
650 if (j != 0) {
651 QLineF lfrom = QLineF(nv.p2(), prevPoint);
652 lfrom.setLength(lfrom.length()*2.);
653 QPointF pfrom;
654
655 bool intersectedFrom = false;
656 for (int k=0; k < theRoad->getNode(j-1)->sizeParents(); ++k) {
657 Way* I = CAST_WAY(theRoad->getNode(j-1)->getParent(k));
658 if (!I || I == theRoad || I->isDeleted())
659 continue;
660
661 for (int m=0; m < I->size()-1; ++m) {
662 QLineF l3 = QLineF(I->getNode(m)->position(), I->getNode(m+1)->position());
663 QPointF theIntersection;
664 if (lfrom.intersect(l3, &theIntersection) == QLineF::BoundedIntersection) {
665 intersectedFrom = true;
666 QLineF lt = QLineF(nv.p2(), theIntersection);
667 if (lt.length() < lfrom.length())
668 lfrom = lt;
669 }
670 }
671 }
672 if (intersectedFrom) {
673 lfrom.setLength(lfrom.length() - STREET_NUMBERS_LENGTH);
674 pfrom = lfrom.p2();
675
676 R = g_backend.allocWay(theDocument->getDirtyOrOriginLayer());
677 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(),R,true));
678 theList->add(new SetTagCommand(R, "addr:interpolation", ""));
679 theList->add(new SetTagCommand(R, "addr:street", streetName));
680
681 N = g_backend.allocNode(theDocument->getDirtyOrOriginLayer(), Coord(pfrom));
682 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(),N,true));
683 theList->add(new WayAddNodeCommand(R, N, theDocument->getDirtyOrOriginLayer(R->layer())));
684 theList->add(new SetTagCommand(N, "addr:housenumber", ""));
685 } else {
686 pfrom = prevPoint;
687 }
688 }
689
690 if (intersectedTo) {
691 if (j != 0) {
692 lto.setLength(lto.length() - STREET_NUMBERS_LENGTH);
693 pto = lto.p2();
694
695 N = g_backend.allocNode(theDocument->getDirtyOrOriginLayer(), Coord(pto));
696 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(),N,true));
697 theList->add(new WayAddNodeCommand(R, N, theDocument->getDirtyOrOriginLayer(R->layer())));
698 theList->add(new SetTagCommand(N, "addr:housenumber", ""));
699 }
700 } else {
701 if (theAngle < 85. || theAngle > 95. || j== 0 || j == theRoad->size()-1) {
702 N = g_backend.allocNode(theDocument->getDirtyOrOriginLayer(), Coord(nv.p2()));
703 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(),N,true));
704 theList->add(new WayAddNodeCommand(R, N, theDocument->getDirtyOrOriginLayer(R->layer())));
705 theList->add(new SetTagCommand(N, "addr:housenumber", ""));
706 }
707
708 pto = nv.p2();
709 }
710 prevPoint = nv.p2();
711 }
712 }
713
addStreetNumbers(Document * theDocument,CommandList * theList,PropertiesDock * theDock)714 void addStreetNumbers(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
715 {
716 QList<Way*> Roads;
717 for (int i=0; i<theDock->selectionSize(); ++i)
718 if (Way* R = CAST_WAY(theDock->selection(i)))
719 Roads.push_back(R);
720
721 if (Roads.isEmpty())
722 return;
723
724 QList<Way*>::const_iterator it = Roads.constBegin();
725 for (;it != Roads.constEnd(); ++it) {
726 if((*it)->size() < 2)
727 continue;
728
729 createStreetNumbers(theDocument, theList, (*it), false);
730 createStreetNumbers(theDocument, theList, (*it), true);
731 }
732
733 if (Roads.size() == 1)
734 theList->setFeature(Roads.at(0));
735 }
736
alignNodes(Document * theDocument,CommandList * theList,PropertiesDock * theDock)737 void alignNodes(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
738 {
739 if (theDock->selectionSize() < 3) //thre must be at least 3 nodes to align something
740 return;
741
742 //We build a list of selected nodes
743 QList<Node*> Nodes;
744 for (int i=0; i<theDock->selectionSize(); ++i)
745 if (Node* N = CAST_NODE(theDock->selection(i)))
746 Nodes.push_back(N);
747
748 //we check that we have at least 3 nodes and the first two can give a line
749 if(Nodes.size() < 3)
750 return;
751 if(Nodes[0]->position() == Nodes[1]->position())
752 return;
753
754 //we do the alignement
755 Coord pos(0,0);
756 const Coord p1(Nodes[0]->position());
757 const Coord p2(Nodes[1]->position()-p1);
758 const qreal slope = angle(p2);
759 for (int i=2; i<Nodes.size(); ++i) {
760 pos=Nodes[i]->position()-p1;
761 rotate(pos,-slope);
762 pos.setY(0);
763 rotate(pos,slope);
764 pos=pos+p1;
765 theList->add(new MoveNodeCommand( Nodes[i], pos, theDocument->getDirtyOrOriginLayer(Nodes[i]->layer()) ));
766 }
767 }
768
bingExtract(Document * theDocument,CommandList * theList,PropertiesDock * theDock,CoordBox vp)769 void bingExtract(Document* theDocument, CommandList* theList, PropertiesDock* theDock, CoordBox vp)
770 {
771 //We build a list of selected nodes
772 QList<Node*> Nodes;
773 for (int i=0; i<theDock->selectionSize(); ++i)
774 if (Node* N = CAST_NODE(theDock->selection(i)))
775 Nodes.push_back(N);
776
777 //we check that we have at least 2 nodes
778 if(Nodes.size() < 2)
779 return;
780 if(Nodes[0]->position() == Nodes[1]->position())
781 return;
782
783 //http://3667a17de9b94ccf8fd278f9de62dae4.cloudapp.net/
784 QString u("http://magicshop.cloudapp.net/DetectRoad.svc/detect/?");
785 u.append(QString("pt1=%1,%2").arg(COORD2STRING(Nodes[0]->position().y())).arg(COORD2STRING(Nodes[0]->position().x())));
786 u.append(QString("&pt2=%1,%2").arg(COORD2STRING(Nodes[1]->position().y())).arg(COORD2STRING(Nodes[1]->position().x())));
787 u.append(QString("&bbox=%1,%2,%3,%4")
788 .arg(COORD2STRING(vp.top()))
789 .arg(COORD2STRING(vp.left()))
790 .arg(COORD2STRING(vp.bottom()))
791 .arg(COORD2STRING(vp.right()))
792 );
793
794 QString sXml;
795 if (!Utils::sendBlockingNetRequest(QUrl(u), sXml)) {
796 QMessageBox::critical(0, QApplication::tr("Bing Road Detect"), QApplication::tr("Cannot get output."));
797 return;
798 }
799 qDebug() << sXml;
800
801 //Import the OSC
802 Document* newDoc;
803 QDomDocument xmlDoc;
804 xmlDoc.setContent(sXml);
805 if (!(newDoc = Document::getDocumentFromXml(&xmlDoc))) {
806 QMessageBox::critical(0, QApplication::tr("Bing Road Detect"), QApplication::tr("No valid data."));
807 return;
808 }
809
810 if (!newDoc->size()) {
811 QMessageBox::critical(0, QApplication::tr("Bing Road Detect"), QApplication::tr("Cannot parse output."));
812 } else {
813 DrawingLayer* newLayer = new DrawingLayer( "Bing Road Extract" );
814 newLayer->setUploadable(false);
815 theDocument->add(newLayer);
816 QList<Feature*> theFeats = theDocument->mergeDocument(newDoc, newLayer, theList);
817
818 // Merge in initial nodes
819 Node * N0 = NULL;
820 Node * N1 = NULL;
821 qreal Best0 = 180., Best1 = 180.;
822
823 foreach (Feature* F, theFeats) {
824 if (CHECK_NODE(F)) {
825 Node* N = STATIC_CAST_NODE(F);
826 qreal B = distance(Nodes[0]->position(), N->position());
827 if (B < Best0) {
828 N0 = N;
829 Best0 = B;
830 }
831 B = distance(Nodes[1]->position(), N->position());
832 if (B < Best1) {
833 N1 = N;
834 Best1 = B;
835 }
836 }
837 }
838 if (N0)
839 mergeNodes(theDocument, theList, Nodes[0], N0);
840 if (N1)
841 mergeNodes(theDocument, theList, Nodes[1], N1);
842 }
843 delete newDoc;
844 }
845
spreadNodes(Document * theDocument,CommandList * theList,PropertiesDock * theDock)846 void spreadNodes(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
847 {
848 // There must be at least 3 nodes to align something
849 if (theDock->selectionSize() < 3)
850 return;
851
852 // We build a list of selected nodes
853 // Sort by distance along the line between the first two nodes
854 QList<Node*> Nodes;
855 QList<float> Metrics;
856 Coord p;
857 Coord delta;
858 for (int i=0; i<theDock->selectionSize(); ++i) {
859 if (Node* N = CAST_NODE(theDock->selection(i))) {
860 Coord pos(N->position());
861 if (Nodes.size() == 0) {
862 p = pos;
863 Nodes.push_back(N);
864 Metrics.push_back(0.0f);
865 } else if (Nodes.size() == 1) {
866 delta = pos - p;
867 // First two nodes must form a line
868 if (delta.isNull())
869 return;
870 Nodes.push_back(N);
871 Metrics.push_back(delta.x()*delta.x() + delta.y()*delta.y());
872 } else {
873 pos = pos - p;
874 qreal metric = pos.x()*delta.x() + pos.y()*delta.y();
875 // This could be done more efficiently with a binary search
876 for (int j = 0; j < Metrics.size(); ++j) {
877 if (metric < Metrics[j]) {
878 Nodes.insert(j, N);
879 Metrics.insert(j, metric);
880 goto inserted;
881 }
882 }
883 Nodes.push_back(N);
884 Metrics.push_back(metric);
885 inserted:
886 ;
887 }
888 }
889 }
890
891 // We check that we have at least 3 nodes
892 if(Nodes.size() < 3)
893 return;
894
895 // Do the spreading between the extremes
896 p = Nodes[0]->position();
897 delta = (Nodes[Nodes.size()-1]->position() - p) / (Nodes.size()-1);
898
899 for (int i=1; i<Nodes.size()-1; ++i) {
900 p = p + delta;
901 theList->add(new MoveNodeCommand( Nodes[i], p, theDocument->getDirtyOrOriginLayer(Nodes[i]->layer()) ));
902 }
903 }
904
mergeNodes(Document * theDocument,CommandList * theList,Node * node1,Node * node2)905 static void mergeNodes(Document* theDocument, CommandList* theList, Node *node1, Node *node2)
906 {
907 QList<Feature*> alt;
908 alt.append(node1);
909 Feature::mergeTags(theDocument, theList, node1, node2);
910 theList->add(new RemoveFeatureCommand(theDocument, node2, alt));
911 }
912
mergeNodes(Document * theDocument,CommandList * theList,PropertiesDock * theDock)913 void mergeNodes(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
914 {
915 if (theDock->selectionSize() <= 1)
916 return;
917 QList<Node*> Nodes;
918 QList<Feature*> alt;
919 for (int i=0; i<theDock->selectionSize(); ++i)
920 if (Node* N = CAST_NODE(theDock->selection(i)))
921 Nodes.push_back(N);
922 Node* merged = Nodes[0];
923 alt.push_back(merged);
924 for (int i=1; i<Nodes.size(); ++i) {
925 Feature::mergeTags(theDocument, theList, merged, Nodes[i]);
926 theList->add(new RemoveFeatureCommand(theDocument, Nodes[i], alt));
927 }
928 }
929
detachNode(Document * theDocument,CommandList * theList,PropertiesDock * theDock)930 void detachNode(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
931 {
932 QList<Way*> Roads, Result;
933 QList<Node*> Points;
934 for (int i=0; i<theDock->selectionSize(); ++i)
935 if (Way* R = CAST_WAY(theDock->selection(i)))
936 Roads.push_back(R);
937 else if (Node* Pt = CAST_NODE(theDock->selection(i)))
938 Points.push_back(Pt);
939
940 if (Roads.size() > 1 && Points.size() > 1)
941 return;
942
943 if (Roads.size() == 0 && Points.size())
944 {
945 for (int i=0; i<Points.size(); ++i) {
946 Way * R = Way::GetSingleParentRoad(Points[i]);
947 if (R)
948 theList->add(new WayRemoveNodeCommand(R, Points[i],
949 theDocument->getDirtyOrOriginLayer(R)));
950 }
951 }
952
953 if (Roads.size() > 1 && Points.size() == 1)
954 {
955 for (int i=0; i<Roads.size(); ++i) {
956 if (Roads[i]->find(Points[0]) < Roads[i]->size())
957 theList->add(new WayRemoveNodeCommand(Roads[i], Points[0],
958 theDocument->getDirtyOrOriginLayer(Roads[i])));
959 }
960 }
961
962 if (Roads.size() == 1 && Points.size())
963 {
964 for (int i=0; i<Points.size(); ++i) {
965 if (Roads[0]->find(Points[i]) < Roads[0]->size())
966 theList->add(new WayRemoveNodeCommand(Roads[0], Points[i],
967 theDocument->getDirtyOrOriginLayer(Roads[0])));
968 }
969 }
970 }
971
commitFeatures(Document * theDocument,CommandList * theList,PropertiesDock * theDock)972 void commitFeatures(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
973 {
974 QSet<Feature*> Features;
975 QQueue<Feature*> ToAdd;
976
977 for (int i=0; i<theDock->selectionSize(); ++i)
978 if (!theDock->selection(i)->isUploadable() && !theDock->selection(i)->isSpecial())
979 ToAdd.enqueue(theDock->selection(i));
980
981 while (!ToAdd.isEmpty()) {
982 Feature *feature = ToAdd.dequeue();
983 Features.insert(feature);
984 for (int j=0; j < feature->size(); ++j) {
985 Feature *member = feature->get(j);
986 if (!Features.contains(member)) {
987 ToAdd.enqueue(member);
988 }
989 }
990 }
991
992 if (Features.size()) {
993 Layer *layer = theDocument->getDirtyLayer();
994 foreach (Feature *feature, Features)
995 theList->add(new AddFeatureCommand(layer,feature,true));
996 }
997 }
998
addRelationMember(Document * theDocument,CommandList * theList,PropertiesDock * theDock)999 void addRelationMember(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
1000 {
1001 Relation* theRelation = NULL;
1002 QList<Feature*> Features;
1003 for (int i=0; i<theDock->selectionSize(); ++i)
1004 if ((theDock->selection(i)->getClass() == "Relation") && !theRelation)
1005 theRelation = CAST_RELATION(theDock->selection(i));
1006 else
1007 Features.push_back(theDock->selection(i));
1008
1009 if (!(theRelation && Features.size())) return;
1010
1011 for (int i=0; i<Features.size(); ++i) {
1012 theList->add(new RelationAddFeatureCommand(theRelation, "", Features[i], theDocument->getDirtyOrOriginLayer(theRelation->layer())));
1013 }
1014 }
1015
removeRelationMember(Document * theDocument,CommandList * theList,PropertiesDock * theDock)1016 void removeRelationMember(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
1017 {
1018 Relation* theRelation = NULL;
1019 QList<Feature*> Features;
1020 for (int i=0; i<theDock->selectionSize(); ++i)
1021 if ((theDock->selection(i)->getClass() == "Relation") && !theRelation)
1022 theRelation = CAST_RELATION(theDock->selection(i));
1023 else
1024 Features.push_back(theDock->selection(i));
1025
1026 if (!theRelation && Features.size() == 1)
1027 theRelation = Feature::GetSingleParentRelation(Features[0]);
1028 if (!(theRelation && Features.size())) return;
1029
1030 int idx;
1031 for (int i=0; i<Features.size(); ++i) {
1032 if ((idx = theRelation->find(Features[i])) != theRelation->size())
1033 theList->add(new RelationRemoveFeatureCommand(theRelation, idx, theDocument->getDirtyOrOriginLayer(theRelation->layer())));
1034 }
1035 }
1036
addToMultipolygon(Document * theDocument,CommandList * theList,PropertiesDock * theDock)1037 void addToMultipolygon(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
1038 {
1039 Relation* theRelation = NULL;
1040 QList<Way*> theWays;
1041 for (int i=0; i<theDock->selectionSize(); ++i) {
1042 if (!theRelation && CHECK_RELATION(theDock->selection(i)) && theDock->selection(i)->tagValue("type", "") == "multipolygon")
1043 theRelation = STATIC_CAST_RELATION(theDock->selection(i));
1044 if (CHECK_WAY(theDock->selection(i)))
1045 theWays << STATIC_CAST_WAY(theDock->selection(i));
1046 }
1047
1048 if (!theWays.size())
1049 return;
1050
1051 if (theRelation) {
1052 for (int i=0; i<theRelation->size(); ++i) {
1053 if (CHECK_WAY(theRelation->get(i)) && !theWays.contains(STATIC_CAST_WAY(theRelation->get(i)))) {
1054 theWays << STATIC_CAST_WAY(theRelation->get(i));
1055 }
1056 }
1057 }
1058
1059 Way* outer = theWays[0];
1060 for (int i=1; i<theWays.size(); ++i) {
1061 if (outer->boundingBox().contains(theWays[i]->boundingBox()))
1062 continue;
1063 if (theWays[i]->boundingBox().contains(outer->boundingBox()))
1064 outer = theWays[i];
1065 else
1066 if (theWays[i]->boundingBox().intersects(outer->boundingBox()))
1067 outer = NULL;
1068 }
1069 if (outer) {
1070 if (!theRelation) {
1071 theRelation = g_backend.allocRelation(theDocument->getDirtyOrOriginLayer());
1072 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(),theRelation,true));
1073 } else {
1074 for (int i=0; i<theRelation->size(); ++i)
1075 theList->add(new RelationRemoveFeatureCommand(theRelation, 0));
1076 }
1077 theRelation->setTag("type", "multipolygon");
1078 } else
1079 return;
1080
1081 theList->add(new RelationAddFeatureCommand(theRelation, "outer", outer));
1082 for (int i=0; i<theWays.size(); ++i) {
1083 if (theWays[i] != outer) {
1084 theList->add(new RelationAddFeatureCommand(theRelation, "inner", theWays[i]));
1085 }
1086 }
1087 }
1088
1089 /* Subdivide theRoad between index and index+1 into divisions segments.
1090 * divisions-1 new nodes are created starting at index index+1.
1091 */
subdivideRoad(Document * theDocument,CommandList * theList,Way * theRoad,unsigned int index,unsigned int divisions)1092 static void subdivideRoad(Document* theDocument, CommandList* theList,
1093 Way* theRoad, unsigned int index, unsigned int divisions)
1094 {
1095 Node* N0 = theRoad->getNode(index);
1096 Node* N1 = theRoad->getNode(index+1);
1097 Coord nodeBase = N0->position();
1098 Coord nodeDelta = (N1->position() - nodeBase) / divisions;
1099 for (unsigned int i = 1; i < divisions; ++i) {
1100 nodeBase = nodeBase + nodeDelta;
1101 Node* newNode = g_backend.allocNode(theDocument->getDirtyOrOriginLayer(theRoad), nodeBase);
1102 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(theRoad), newNode, true));
1103 theList->add(new WayAddNodeCommand(theRoad, newNode, index + i, theDocument->getDirtyOrOriginLayer(theRoad)));
1104 }
1105 }
1106
canSubdivideRoad(PropertiesDock * theDock,Way ** outTheRoad,unsigned int * outEdge)1107 bool canSubdivideRoad(PropertiesDock* theDock, Way** outTheRoad, unsigned int* outEdge)
1108 {
1109 // Get the selected way and nodes
1110 Way* theRoad = NULL;
1111 Node* theNodes[2] = { NULL, NULL };
1112 for (int i = 0; i < theDock->selectionSize(); ++i) {
1113 if ((theDock->selection(i)->getClass() == "Way") && !theRoad)
1114 theRoad = CAST_WAY(theDock->selection(i));
1115 else if (theDock->selection(i)->getClass() == "Node") {
1116 if (!theNodes[0])
1117 theNodes[0] = CAST_NODE(theDock->selection(i));
1118 else if (!theNodes[1])
1119 theNodes[1] = CAST_NODE(theDock->selection(i));
1120 }
1121 }
1122
1123 // If the way has only two nodes, use them
1124 if (theRoad && theRoad->size() == 2) {
1125 theNodes[0] = theRoad->getNode(0);
1126 theNodes[1] = theRoad->getNode(1);
1127 // Now this would just be silly
1128 if (theNodes[0] == theNodes[1])
1129 return false;
1130 }
1131
1132 // A way and 2 nodes
1133 if (!theRoad || !theNodes[0] || !theNodes[1])
1134 return false;
1135
1136 // Nodes must be adjacent in the way
1137 int numNodes = theRoad->size();
1138 int nodeIndex0 = -1;
1139 for (int i = 0; i < numNodes-1; ++i) {
1140 Node* N0 = theRoad->getNode(i);
1141 Node* N1 = theRoad->getNode(i+1);
1142 if ((N0 == theNodes[0] && N1 == theNodes[1]) ||
1143 (N0 == theNodes[1] && N1 == theNodes[0])) {
1144 nodeIndex0 = i;
1145 break;
1146 }
1147 }
1148
1149 if (nodeIndex0 < 0)
1150 return false;
1151
1152 if (outTheRoad)
1153 *outTheRoad = theRoad;
1154 if (outEdge)
1155 *outEdge = nodeIndex0;
1156
1157 return true;
1158 }
1159
subdivideRoad(Document * theDocument,CommandList * theList,PropertiesDock * theDock,unsigned int divisions)1160 void subdivideRoad(Document* theDocument, CommandList* theList, PropertiesDock* theDock, unsigned int divisions)
1161 {
1162 // Subdividing into 1 division is no-op
1163 if (divisions < 2)
1164 return;
1165
1166 // Get the selected way and nodes
1167 Way* theRoad;
1168 unsigned int edge;
1169 if (!canSubdivideRoad(theDock, &theRoad, &edge))
1170 return;
1171 subdivideRoad(theDocument, theList, theRoad, edge, divisions);
1172 }
1173
1174 /* Remove nodes between theNodes in theArea into a separate way newArea.
1175 * newArea's first node will be theNodes[0], and it's last nodes will be
1176 * theNodes[1] and theNodes[0].
1177 */
splitArea(Document * theDocument,CommandList * theList,Way * theArea,unsigned int nodes[2],Way ** outNewArea)1178 static bool splitArea(Document* theDocument, CommandList* theList,
1179 Way* theArea, unsigned int nodes[2], Way** outNewArea)
1180 {
1181 // Make sure nodes are in order
1182 if (nodes[0] > nodes[1])
1183 qSwap(nodes[0], nodes[1]);
1184
1185 // And not next to one another
1186 if (nodes[0] + 1 == nodes[1] ||
1187 (nodes[0] == 0 && nodes[1] == (unsigned int)theArea->size() - 2)) {
1188 qDebug() << "Nodes must not be adjacent";
1189 return false;
1190 }
1191
1192 Node* theNodes[2];
1193 for (int i = 0; i < 2; ++i)
1194 theNodes[i] = theArea->getNode(nodes[i]);
1195
1196 // Extract nodes between nodes[0] and nodes[1] into a separate area
1197 // and remove the nodes from the original area
1198 Way* newArea = g_backend.allocWay(theDocument->getDirtyOrOriginLayer(theArea));
1199 theList->add(new AddFeatureCommand(theDocument->getDirtyOrOriginLayer(theArea), newArea, true));
1200 copyTags(newArea, theArea);
1201 theList->add(new WayAddNodeCommand(newArea, theNodes[0], theDocument->getDirtyOrOriginLayer(theArea)));
1202 for (unsigned int i = nodes[0]+1; i < nodes[1]; ++i) {
1203 theList->add(new WayAddNodeCommand(newArea, theArea->getNode(nodes[0]+1),
1204 theDocument->getDirtyOrOriginLayer(theArea)));
1205 theList->add(new WayRemoveNodeCommand(theArea, theArea->getNode(nodes[0]+1),
1206 theDocument->getDirtyOrOriginLayer(theArea)));
1207 }
1208 theList->add(new WayAddNodeCommand(newArea, theNodes[1], theDocument->getDirtyOrOriginLayer(theArea)));
1209 theList->add(new WayAddNodeCommand(newArea, theNodes[0], theDocument->getDirtyOrOriginLayer(theArea)));
1210 handleWaysplitRelations(theDocument, theList, theArea, newArea);
1211
1212 if (outNewArea)
1213 *outNewArea = newArea;
1214
1215 return true;
1216 }
1217
1218 // Cycle the node index in an area so it's >= 0, < size-1 (never the last node).
cycleNodeIndex(Way * area,int index)1219 static int cycleNodeIndex(Way* area, int index)
1220 {
1221 if (index >= area->size()-1)
1222 index -= area->size()-1;
1223 else if (index < 0)
1224 index += area->size()-1;
1225 return index;
1226 }
1227
1228 // Find the index of a node in an area as close as possible to expected.
1229 // Return -1 if node isn't in area. This is useful for tracing around shared
1230 // nodes of two areas, where we want the indexes to be continuous so we can
1231 // detect where the edges depart from one another.
nodeIndexInArea(Way * area,Node * node,int expected)1232 static int nodeIndexInArea(Way* area, Node* node, int expected)
1233 {
1234 // parent list is gonna be shorter, so check this first
1235 for (int i = 0; i < node->sizeParents(); ++i)
1236 if (node->getParent(i) == area) {
1237 // now we know it's on the list, find the index
1238 if (area->getNode(expected) == node)
1239 return expected;
1240 int forward = expected;
1241 int backward = expected;
1242 for (int j = 0; j < (area->size()-1)/2; ++j) {
1243 forward = cycleNodeIndex(area, forward + 1);
1244 if (area->getNode(forward) == node)
1245 return forward;
1246 backward = cycleNodeIndex(area, backward - 1);
1247 if (area->getNode(backward) == node)
1248 return backward;
1249 }
1250 break;
1251 }
1252 return -1;
1253 }
1254
1255 // Determine if two area node indexes are adjacent, and in a particular
1256 // direction if direction is 1 or -1. If they are adjacent, direction is set to
1257 // 1 or -1. This is useful tracing the shared nodes between two areas as the
1258 // direction is initialised to 0 and determined on the first check, and then
1259 // enforced on later checks.
nodeIndexesAdjacent(Way * way,unsigned int n1,unsigned int n2,int * direction)1260 static bool nodeIndexesAdjacent(Way* way, unsigned int n1, unsigned int n2, int* direction)
1261 {
1262 int dir = n2 - n1;
1263 if (abs(dir) > 1) {
1264 // wrap?
1265 if (!n1)
1266 n1 = way->size() - 1;
1267 else if (!n2)
1268 n2 = way->size() - 1;
1269 else
1270 return false;
1271 dir = n2 - n1;
1272 }
1273 if (abs(dir) != 1)
1274 return false;
1275
1276 // in the correct direction?
1277 if (!*direction) {
1278 *direction = dir;
1279 return true;
1280 } else
1281 return *direction == dir;
1282 }
1283
1284 // Join selected areas which are sharing edges. Multiple nodes adjacent in both
1285 // areas must be shared for them to be joined.
1286 // FIXME This does not currently produce multipolygons when the areas form an
1287 // enclosed area. Instead the edge will run around the outside, cut into the
1288 // middle and run around the inner part, and then cut back out the same way.
joinAreas(Document * theDocument,CommandList * theList,PropertiesDock * theDock)1289 void joinAreas(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
1290 {
1291 // Collect the set of selected areas
1292 QSet<Way*> areas;
1293 for (int i = 0; i < theDock->selectionSize(); ++i)
1294 if (theDock->selection(i)->getClass() == "Way") {
1295 Way* area = CAST_WAY(theDock->selection(i));
1296 if (area->isClosed())
1297 areas << area;
1298 }
1299
1300 findNextJoin:
1301 // Go through areas in the set
1302 while (areas.size() > 1) {
1303 Way* area = *areas.begin();
1304 // Go through nodes shared between multiple features
1305 for (int i = 0; i < area->size() - 1; ++i) {
1306 Node* node = area->getNode(i);
1307 if (node->sizeParents() <= 1)
1308 continue;
1309 // Go through parents of the node that are areas in our set
1310 for (int p1 = 0; p1 < node->sizeParents(); ++p1) {
1311 if (!(node->getParent(p1)->getType() & IFeature::Polygon))
1312 continue;
1313 Way* otherArea = CAST_WAY(node->getParent(p1));
1314 if (otherArea == area || !areas.contains(otherArea))
1315 continue;
1316 // Look for the first shared mutually adjacent node.
1317 for (int n = 0; n < otherArea->size()-1; ++n) {
1318 if (otherArea->getNode(n) != node)
1319 continue;
1320 int otherFirst = n;
1321 int otherLast = otherFirst;
1322 int otherDirection = 0;
1323 int first = i;
1324 int count = 1;
1325 // We want node i to be the first shared node, so if the
1326 // one before it is also shared and adjacent we can safely
1327 // ignore it as we'll hit it again later
1328 int lastOtherIndex = nodeIndexInArea(otherArea, area->getNode(cycleNodeIndex(area, i-1)), n);
1329 if (lastOtherIndex != -1 && nodeIndexesAdjacent(otherArea, n, lastOtherIndex, &otherDirection))
1330 continue;
1331 // Continue forwards to see how many nodes are shared with
1332 // the other area and adjacent. The first nodeIndexesAdjacent
1333 // will set otherDirection and later nodes have to keep
1334 // going in that direction.
1335 for (int j = 1; j < area->size(); ++j) {
1336 int jIndex = (i + j) % (area->size()-1);
1337 int otherIndex = nodeIndexInArea(otherArea, area->getNode(jIndex), cycleNodeIndex(otherArea, otherLast + otherDirection));
1338 // is next node in same area?
1339 if (otherIndex == -1)
1340 break;
1341 // must be adjacent to last node (and in same direction)
1342 if (!nodeIndexesAdjacent(otherArea, otherLast, otherIndex, &otherDirection))
1343 break;
1344 otherLast = otherIndex;
1345 ++count;
1346 }
1347 if (count < 2)
1348 continue;
1349
1350 // If all of area's nodes are shared (area is enclosed
1351 // inside otherArea), then it is better to swap area and
1352 // otherArea so that area is the one that gets removed.
1353 if (count == area->size()) {
1354 qSwap(area, otherArea);
1355 qSwap(first, otherFirst);
1356 }
1357
1358 // Remove nodes along the shared edge from area. No need to
1359 // remove from otherArea, it'll get removed anyway.
1360 for (int del = 0; del < count-2; ++del) {
1361 int delIndex = cycleNodeIndex(area, first+1);
1362 theList->add(new WayRemoveNodeCommand(area, delIndex, theDocument->getDirtyOrOriginLayer(area->layer())));
1363 if (first > delIndex)
1364 --first;
1365 }
1366
1367 // If otherArea is completely enclosed then the end nodes
1368 // of the shared edge are the same and are now adjacent, so
1369 // remove one of them.
1370 if (count == otherArea->size())
1371 theList->add(new WayRemoveNodeCommand(area, first, theDocument->getDirtyOrOriginLayer(area->layer())));
1372 // Seal the crack left over from an enclosed otherArea
1373 // until the nodes on either side of 'first' diverge.
1374 while (area->size() > 3 &&
1375 area->getNode(cycleNodeIndex(area, first-1)) == area->getNode(cycleNodeIndex(area, first+1))) {
1376 theList->add(new WayRemoveNodeCommand(area, first, theDocument->getDirtyOrOriginLayer(area->layer())));
1377 theList->add(new WayRemoveNodeCommand(area, first, theDocument->getDirtyOrOriginLayer(area->layer())));
1378 first = cycleNodeIndex(area, first-1);
1379 }
1380 // Add the remaining nodes of otherArea that aren't shared
1381 // to area.
1382 for (int add = 1; add < otherArea->size()-count; ++add) {
1383 int index = cycleNodeIndex(otherArea, otherFirst - otherDirection*add);
1384 theList->add(new WayAddNodeCommand(area, otherArea->getNode(index), ++first, theDocument->getDirtyOrOriginLayer(area->layer())));
1385 }
1386 // Merge tags and remove otherArea.
1387 Feature::mergeTags(theDocument, theList, area, otherArea);
1388 otherArea->deleteChildren(theDocument, theList);
1389 theList->add(new RemoveFeatureCommand(theDocument, otherArea));
1390 areas.remove(otherArea);
1391 // Remove otherArea from selection.
1392 QList<Feature*> sel = theDock->selection();
1393 sel.removeOne(otherArea);
1394 theDock->setSelection(sel);
1395 // Restart the process again.
1396 goto findNextJoin;
1397 }
1398 }
1399 }
1400 areas.remove(area);
1401 }
1402 }
1403
canSplitArea(PropertiesDock * theDock,Way ** outTheArea,unsigned int outNodes[2])1404 bool canSplitArea(PropertiesDock* theDock, Way** outTheArea, unsigned int outNodes[2])
1405 {
1406 if (theDock->selectionSize() != 3)
1407 return false;
1408
1409 // Get the selected way and nodes
1410 Way* theArea = NULL;
1411 Node* theNodes[2] = { NULL, NULL };
1412 for (int i = 0; i < theDock->selectionSize(); ++i) {
1413 if ((theDock->selection(i)->getClass() == "Way") && !theArea)
1414 theArea = CAST_WAY(theDock->selection(i));
1415 else if (theDock->selection(i)->getClass() == "Node") {
1416 if (!theNodes[0])
1417 theNodes[0] = CAST_NODE(theDock->selection(i));
1418 else if (!theNodes[1])
1419 theNodes[1] = CAST_NODE(theDock->selection(i));
1420 }
1421 }
1422
1423 // A way and 2 nodes
1424 if (!theArea || !theNodes[0] || !theNodes[1])
1425 return false;
1426
1427 // Way must be closed
1428 if (!theArea->isClosed())
1429 return false;
1430
1431 // Nodes must belong to way
1432 unsigned int numNodes = theArea->size();
1433 unsigned int nodeIndex[2];
1434 for (int i = 0; i < 2; ++i) {
1435 nodeIndex[i] = theArea->find(theNodes[i]);
1436 if (nodeIndex[i] >= numNodes)
1437 return false;
1438 }
1439
1440 // Make sure nodes are in order
1441 if (nodeIndex[0] > nodeIndex[1])
1442 qSwap(nodeIndex[0], nodeIndex[1]);
1443
1444 // And not next to one another
1445 if (nodeIndex[0] + 1 == nodeIndex[1] ||
1446 (nodeIndex[0] == 0 && nodeIndex[1] == (unsigned int)theArea->size() - 2))
1447 return false;
1448
1449
1450 if (outTheArea)
1451 *outTheArea = theArea;
1452 if (outNodes) {
1453 outNodes[0] = nodeIndex[0];
1454 outNodes[1] = nodeIndex[1];
1455 }
1456
1457 return true;
1458 }
1459
splitArea(Document * theDocument,CommandList * theList,PropertiesDock * theDock)1460 void splitArea(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
1461 {
1462 Way* theArea;
1463 unsigned int nodes[2];
1464 if (!canSplitArea(theDock, &theArea, nodes))
1465 return;
1466
1467 Way* newArea;
1468 if (!splitArea(theDocument, theList, theArea, nodes, &newArea))
1469 return;
1470
1471 theDock->setSelection(QList<Way*>() << theArea << newArea);
1472 }
1473
terraceArea(Document * theDocument,CommandList * theList,Way * theArea,unsigned int sides[2],unsigned int divisions,int startNode,QList<Feature * > * outAreas)1474 static void terraceArea(Document* theDocument, CommandList* theList,
1475 Way* theArea, unsigned int sides[2],
1476 unsigned int divisions, int startNode,
1477 QList<Feature*>* outAreas)
1478 {
1479 // We're adding nodes so ordering is important
1480 if (sides[0] > sides[1])
1481 qSwap(sides[0], sides[1]);
1482
1483 bool reverse = (startNode >= 0 && ((5 + startNode - sides[1]) & 2));
1484
1485 // Subdivide both sides
1486 subdivideRoad(theDocument, theList, theArea, sides[1], divisions);
1487 subdivideRoad(theDocument, theList, theArea, sides[0], divisions);
1488
1489 // Split apart
1490 for (unsigned int i = sides[0] + divisions - 1; i > sides[0]; --i) {
1491 Way* newArea;
1492 unsigned int nodes[2] = { i, i + 3 };
1493 splitArea(theDocument, theList, theArea, nodes, &newArea);
1494 if (newArea && outAreas) {
1495 if (reverse)
1496 outAreas->push_front(newArea);
1497 else
1498 outAreas->push_back(newArea);
1499 }
1500 }
1501 if (outAreas) {
1502 if (reverse)
1503 outAreas->push_front(theArea);
1504 else
1505 outAreas->push_back(theArea);
1506 }
1507 }
1508
canTerraceArea(PropertiesDock * theDock,Way ** outTheArea,int * startNode)1509 bool canTerraceArea(PropertiesDock* theDock, Way** outTheArea, int* startNode)
1510 {
1511 // Get the selected area
1512 Way* theArea = NULL;
1513 Node* theNode = NULL;
1514 for (int i = 0; i < theDock->selectionSize(); ++i)
1515 if ((theDock->selection(i)->getClass() == "Way") && !theArea) {
1516 theArea = CAST_WAY(theDock->selection(i));
1517 } else if (startNode && theDock->selection(i)->getClass() == "Node") {
1518 theNode = CAST_NODE(theDock->selection(i));
1519 }
1520 if (!theArea || !theArea->isClosed())
1521 return false;
1522
1523 if (startNode) {
1524 if (theNode)
1525 *startNode = theArea->find(theNode);
1526 else
1527 *startNode = -1;
1528 }
1529
1530 // Only work with 4 edges for now
1531 if (theArea->size() != 5)
1532 return false;
1533
1534 if (outTheArea)
1535 *outTheArea = theArea;
1536
1537 return true;
1538 }
1539
terraceArea(Document * theDocument,CommandList * theList,PropertiesDock * theDock,unsigned int divisions)1540 void terraceArea(Document* theDocument, CommandList* theList, PropertiesDock* theDock, unsigned int divisions)
1541 {
1542 Way* theArea;
1543 int startNode;
1544 if (!canTerraceArea(theDock, &theArea, &startNode))
1545 return;
1546
1547 qreal longestLen = 0.0f;
1548 unsigned int sides[2];
1549 sides[0] = 0;
1550 for (int i = 0; i < theArea->size()-1; ++i) {
1551 const Coord p1(theArea->getNode(i)->position());
1552 const Coord p2(theArea->getNode(i+1)->position());
1553 const qreal len = p1.distanceFrom(p2);
1554 if (len > longestLen) {
1555 longestLen = len;
1556 sides[0] = i;
1557 }
1558 }
1559 sides[1] = (sides[0] + 2) % (theArea->size() - 1);
1560
1561 QList<Feature*> areas;
1562 terraceArea(theDocument, theList, theArea, sides, divisions, startNode, &areas);
1563
1564 theDock->setSelection(areas);
1565 }
1566
1567 // Remove repeated (adjacent) nodes in ways.
removeRepeatsInRoads(Document * theDocument,CommandList * theList,PropertiesDock * theDock)1568 static void removeRepeatsInRoads(Document* theDocument, CommandList* theList, PropertiesDock* theDock)
1569 {
1570 for (int i = 0; i < theDock->selectionSize(); ++i) {
1571 if (theDock->selection(i)->getClass() == "Way") {
1572 Way *way = CAST_WAY(theDock->selection(i));
1573 Node *last = 0;
1574 for (int j = 0; j < way->size();) {
1575 Node *node = way->getNode(j);
1576 if (node == last) {
1577 theList->add(new WayRemoveNodeCommand(way, j, theDocument->getDirtyOrOriginLayer(way->layer())));
1578 continue;
1579 }
1580 last = node;
1581 ++j;
1582 }
1583 }
1584 }
1585 }
1586
1587 // Align a set of midpoints in a specific direction
axisAlignAlignMidpoints(QVector<QPointF> & midpoints,const QVector<qreal> weights,const QPointF & dir,int begin,int end)1588 static void axisAlignAlignMidpoints(QVector<QPointF> &midpoints, const QVector<qreal> weights, const QPointF &dir, int begin, int end)
1589 {
1590 // get weighted average midpoint
1591 const int n = midpoints.size();
1592 QPointF avg(0.0, 0.0);
1593 qreal total_weight = 0.0;
1594 for (int j = begin; j < end; ++j) {
1595 int i = j % n;
1596 avg += weights[i]*midpoints[i];
1597 total_weight += weights[i];
1598 }
1599 avg /= total_weight;
1600
1601 // project midpoints onto dir line through avg
1602 for (int j = begin; j < end; ++j) {
1603 int i = j % n;
1604 QPointF rel_mid = midpoints[i] - avg;
1605 qreal dot = dir.x()*rel_mid.x() + dir.y()*rel_mid.y();
1606 // assumes dir is normalised
1607 midpoints[i] = avg + dir*dot;
1608 }
1609 }
1610
1611 // Axis align uses binary fixed point for angles
1612 static const int angle_shift = 20;
1613
1614 // returns true on success
axisAlignGetWays(PropertiesDock * theDock,QList<Way * > & theWays)1615 static bool axisAlignGetWays(/* in */ PropertiesDock *theDock,
1616 /* out */ QList<Way *> &theWays)
1617 {
1618 QList<Feature *> selection = theDock->selection();
1619 // we can't use foreach since we append to selection inside the loop
1620 for (; !selection.empty(); selection.pop_front()) {
1621 Feature *F = selection.first();
1622 if (F->getClass() == "Way")
1623 theWays << CAST_WAY(F);
1624 else if (F->getClass() == "Relation")
1625 // expand relation members onto the end of the selection list
1626 for (int i = 0; i < F->size(); ++i)
1627 selection << F->get(i);
1628 }
1629 return !theWays.empty();
1630 }
1631
1632 // base the decision purely on what types of features are selected
canAxisAlignRoads(PropertiesDock * theDock)1633 bool canAxisAlignRoads(PropertiesDock* theDock)
1634 {
1635 QList<Way *> theWays;
1636 return axisAlignGetWays(theDock, theWays);
1637 }
1638
1639 // returns true on success
axisAlignPreprocess(PropertiesDock * theDock,const Projection & proj,int axes,QList<Way * > & theWays,QVector<int> & edge_angles,QVector<int> & edge_axis,QVector<qreal> & edge_weight,qreal & total_weight,QVector<QPointF> & midpoints)1640 static bool axisAlignPreprocess(/* in */ PropertiesDock *theDock, const Projection &proj, int axes,
1641 /* out */ QList<Way *> &theWays, QVector<int> &edge_angles, QVector<int> &edge_axis,
1642 QVector<qreal> &edge_weight, qreal &total_weight, QVector<QPointF> &midpoints)
1643 {
1644 if (!axisAlignGetWays(theDock, theWays))
1645 return false;
1646
1647 // manipulate angles as fixed point values with a unit of the angle between axes
1648 int nedges = 0;
1649 foreach (Way *theWay, theWays)
1650 nedges += theWay->size()-1;
1651 edge_angles.resize(nedges);
1652 edge_axis.resize(nedges);
1653 edge_weight.resize(nedges);
1654 midpoints.resize(nedges);
1655 total_weight = 0.0;
1656
1657 // Look at nodes and find angles etc
1658 int i = 0;
1659 foreach (Way *theWay, theWays) {
1660 Node *n1;
1661 Node *n2 = theWay->getNode(0);
1662 QPointF p1;
1663 QPointF p2 = proj.project(n2->position());
1664 for (int j = 0; j < theWay->size()-1; ++j, ++i) {
1665 n1 = n2;
1666 n2 = theWay->getNode(j + 1);
1667 p1 = p2;
1668 p2 = proj.project(n2->position());
1669 if (n1 == n2 || p1 == p2) {
1670 qWarning() << "ERROR: duplicate nodes found during axis align in" << theWay->id().numId;
1671 return false;
1672 }
1673 midpoints[i] = (p1 + p2) * 0.5;
1674 // weight towards longer edges rather than lots of smaller edges
1675 edge_weight[i] = n1->position().distanceFrom(n2->position());
1676 edge_weight[i] *= edge_weight[i];
1677 total_weight += edge_weight[i];
1678 // calculate angle of edge
1679 qreal ang = QLineF(p1, p2).angle();
1680 edge_angles[i] = ang * ((axes << angle_shift) / 360.0);
1681 if (edge_angles[i] < 0) {
1682 edge_angles[i] += axes<<angle_shift;
1683 }
1684 edge_axis[i] = -1;
1685 }
1686 }
1687 return true;
1688 }
1689
1690 // QVectors must be same size
1691 // return true on success
axisAlignCluster(const QVector<int> & edge_angles,const QVector<qreal> & edge_weight,qreal total_weight,unsigned int axes,int & theta,QVector<int> & edge_axis)1692 static bool axisAlignCluster(/* in */ const QVector<int> &edge_angles, const QVector<qreal> &edge_weight,
1693 qreal total_weight, unsigned int axes,
1694 /* out */ int &theta, QVector<int> &edge_axis)
1695 {
1696 // Use a variant of K-Means Clustering with regular spaced clusters to find
1697 // the best angle for the first axis.
1698 const int nedges = edge_angles.size();
1699 bool changed;
1700 theta = 0;
1701 // This should always terminate pretty quickly, but for robustness it's better to warn than hang.
1702 unsigned int safety = 100;
1703 while (--safety) {
1704 // reassign edges to axes, stopping when nothing changes
1705 changed = false;
1706 for (int i = 0; i < nedges; ++i) {
1707 int angle = edge_angles[i] - theta;
1708 int new_axis = ((angle >> (angle_shift - 1)) + 1) >> 1;
1709 if (new_axis >= (int)axes)
1710 new_axis -= axes;
1711 else if (new_axis < 0)
1712 new_axis += axes;
1713 if (new_axis != edge_axis[i]) {
1714 edge_axis[i] = new_axis;
1715 changed = true;
1716 }
1717 }
1718 if (!changed)
1719 break;
1720 // adjust angle of first axis by weighted mean of angles between edges and their assigned axes
1721 qreal dtheta = 0;
1722 for (int i = 0; i < nedges; ++i) {
1723 int diff = (edge_angles[i] - theta - (edge_axis[i] << angle_shift)) % (1 << angle_shift);
1724 if (diff > ((1<<angle_shift)>>1))
1725 diff -= (1<<angle_shift);
1726 else if (diff < -((1<<angle_shift)>>1))
1727 diff += (1<<angle_shift);
1728 dtheta += edge_weight[i]*diff;
1729 }
1730 dtheta /= total_weight;
1731 theta += dtheta;
1732 }
1733 if (!safety)
1734 qWarning() << "WARNING: align axes clustering loop exceeded safety limit";
1735
1736 return safety;
1737 }
1738
1739 // QVectors must be same size
axisAlignCalcVariance(const QVector<int> & edge_angles,const QVector<int> & edge_axis,int theta)1740 static qreal axisAlignCalcVariance(const QVector<int> &edge_angles, const QVector<int> &edge_axis, int theta)
1741 {
1742 const int nedges = edge_angles.size();
1743 qreal variance = 0.0;
1744
1745 for (int i = 0; i < nedges; ++i) {
1746 int diff = (edge_angles[i] - theta - (edge_axis[i] << angle_shift)) % (1 << angle_shift);
1747 if (diff > ((1<<angle_shift)>>1))
1748 diff -= (1<<angle_shift);
1749 else if (diff < -((1<<angle_shift)>>1))
1750 diff += (1<<angle_shift);
1751 qreal diff_f = (double)diff / ((1<<angle_shift)>>1);
1752 variance += diff_f*diff_f;
1753 }
1754 variance /= nedges;
1755 return variance;
1756 }
1757
1758 // Guesses the number of axes in a shape.
1759 // returns 0 on failure.
axisAlignGuessAxes(PropertiesDock * theDock,const Projection & proj,unsigned int max_axes)1760 unsigned int axisAlignGuessAxes(PropertiesDock* theDock, const Projection &proj, unsigned int max_axes)
1761 {
1762 QList<Way *> theWays;
1763 QVector<int> edge_angles;
1764 QVector<int> edge_axis;
1765 QVector<qreal> edge_weight;
1766 QVector<QPointF> midpoints;
1767 qreal total_weight;
1768 qreal min_var;
1769 int min_var_axes = 0;
1770 for (unsigned int axes = 3; axes <= max_axes; ++axes) {
1771 if (!axisAlignPreprocess(theDock, proj, axes,
1772 theWays, edge_angles, edge_axis, edge_weight, total_weight, midpoints))
1773 return 0;
1774 int theta;
1775 axisAlignCluster(edge_angles, edge_weight, total_weight, axes, theta, edge_axis);
1776 qreal var = axisAlignCalcVariance(edge_angles, edge_axis, theta);
1777 // prefer a lower number of axes
1778 var *= sqrt((float)axes);
1779 // we want the number of axes with the lowest weighted variance
1780 if (!min_var_axes || var < min_var) {
1781 min_var = var;
1782 min_var_axes = axes;
1783 }
1784 }
1785
1786 return min_var_axes;
1787 }
1788
1789 // maximum number of iterations
1790 #define AXIS_ALIGN_MAX_ITS 10000
1791 // threshold of (node movement/distance from midpoint)^2
1792 #define AXIS_ALIGN_FAR_THRESHOLD (1e-6) // 1/1000th ^2
1793
1794 // don't add theList to history if result isn't success
axisAlignRoads(Document * theDocument,CommandList * theList,PropertiesDock * theDock,const Projection & proj,unsigned int axes)1795 AxisAlignResult axisAlignRoads(Document* theDocument, CommandList* theList, PropertiesDock* theDock, const Projection &proj, unsigned int axes)
1796 {
1797 // Make sure nodes aren't pointlessly repeated or angles would be undefined
1798 removeRepeatsInRoads(theDocument, theList, theDock);
1799
1800 // manipulate angles as fixed point values with a unit of the angle between axes
1801 QList<Way *> theWays;
1802 QVector<int> edge_angles;
1803 QVector<int> edge_axis;
1804 QVector<qreal> edge_weight;
1805 QVector<QPointF> midpoints;
1806 qreal total_weight;
1807 if (!axisAlignPreprocess(theDock, proj, axes,
1808 theWays, edge_angles, edge_axis, edge_weight, total_weight, midpoints)) {
1809 // should never happen, repeated nodes already removed
1810 Q_ASSERT(0);
1811 theList->undo();
1812 return AxisAlignFail;
1813 }
1814
1815 int theta;
1816 axisAlignCluster(edge_angles, edge_weight, total_weight, axes, theta, edge_axis);
1817
1818 // Calculate axis direction vectors
1819 QVector<QPointF> axis_vectors(axes);
1820 for (unsigned int i = 0; i < axes; ++i) {
1821 int angle = theta + (i<<angle_shift);
1822 axis_vectors[i].setX(cos(M_PI*2/(axes<<angle_shift)*angle));
1823 axis_vectors[i].setY(-sin(M_PI*2/(axes<<angle_shift)*angle));
1824 }
1825
1826 // If adjacent edges are in the same axis we can't intersect them to find
1827 // where the node should go. Align midpoints of adjacent edges on the same
1828 // axis so that we can just project the nodes onto the axis and make the
1829 // result axis aligned. Note that this does not handle adjacent edges which
1830 // have opposite axes and are therefore parallel (when axes is even).
1831 int i = 0;
1832 foreach (Way *theWay, theWays) {
1833 int start = i;
1834 int n = theWay->size()-1;
1835 int last_axis = -1;
1836 int first_in_seq = -1;
1837 int excess = 0;
1838 for (; i < start + n; ++i) {
1839 int axis = edge_axis[i];
1840 if (axis != last_axis) {
1841 if (first_in_seq == -1) {
1842 // If edges sharing axis cross the join between ends, we'll
1843 // come back to it anyway.
1844 excess = i;
1845 if (i != 0 || !theWay->isClosed() || edge_axis[start+n-1] != axis)
1846 first_in_seq = i;
1847 } else {
1848 if (i - first_in_seq > 1) {
1849 axisAlignAlignMidpoints(midpoints, edge_weight, axis_vectors[last_axis], first_in_seq, i);
1850 }
1851 first_in_seq = i;
1852 }
1853 last_axis = axis;
1854 }
1855 }
1856 if (n + excess - first_in_seq > 1)
1857 axisAlignAlignMidpoints(midpoints, edge_weight, axis_vectors[last_axis], first_in_seq, n+excess);
1858 }
1859
1860 // Get the positions of each unique node
1861 QHash<Node *, QPointF> node_pos;
1862 bool dups = false;
1863 foreach (Way *theWay, theWays) {
1864 for (int i = 0; i < theWay->size(); ++i) {
1865 Node *N = theWay->getNode(i);
1866 if (!dups && node_pos.contains(N) && !(theWay->isClosed() && i == theWay->size()-1))
1867 dups = true;
1868 node_pos[N] = proj.project(N->position());
1869 }
1870 }
1871
1872 // If nodes are repeated then they will be moved more than once, so we
1873 // iterate and allow the nodes to converge to a point.
1874 qreal last_movement = -1.0;
1875 qreal movement = 0.0;
1876 for (int it = 0; ; ++it) {
1877 i = 0;
1878 bool moved_far = false;
1879 // Move nodes (only in node_pos) onto the intersection of the neighbouring
1880 // edge's axes through their midpoints. Where neighbouring edges are on the
1881 // same axis (and so there is no intersection), project directly onto the
1882 // axis through the mean midpoint.
1883 foreach (Way *theWay, theWays) {
1884 int index0, index1, max;
1885 int start = i;
1886 int n = theWay->size()-1;
1887 if (theWay->isClosed()) {
1888 index1 = start+n-1;
1889 max = n;
1890 } else {
1891 index1 = -1;
1892 max = theWay->size();
1893 }
1894 for (int j = 0; j < max; ++j) {
1895 index0 = index1;
1896 index1 = ((j < n) ? i++ : -1);
1897 Node *N = theWay->getNode(j);
1898 QPointF old_pos = node_pos[N];
1899 QPointF new_pos;
1900 QPointF mid;
1901 if (index0 >= 0 && index1 >= 0 && edge_axis[index0] != edge_axis[index1]) {
1902 // axes different, so probably safe to intersect
1903 QLineF l0(midpoints[index0], midpoints[index0] + axis_vectors[edge_axis[index0]]);
1904 QLineF l1(midpoints[index1], midpoints[index1] + axis_vectors[edge_axis[index1]]);
1905 if (l0.intersect(l1, &new_pos) == QLineF::NoIntersection) {
1906 theList->undo();
1907 return AxisAlignSharpAngles;
1908 }
1909
1910 if (dups && !moved_far)
1911 mid = midpoints[index0];
1912 } else {
1913 // Axes are the same so there's no intersection point. Just
1914 // project onto axis through average midpoint.
1915 int index = ((index0 >= 0) ? index0 : index1);
1916 QPointF midpoint = midpoints[index];
1917 if (index != index1 && index1 >= 0)
1918 midpoint = (midpoint + midpoints[index1]) / 2;
1919 QPointF rel_pos = old_pos - midpoint;
1920 QPointF dir = axis_vectors[edge_axis[index]];
1921 qreal dot = dir.x()*rel_pos.x() + dir.y()*rel_pos.y();
1922 new_pos = midpoint + dir*dot;
1923
1924 if (dups && !moved_far)
1925 mid = midpoints[index];
1926 }
1927 if (dups) {
1928 // If we're iterating, only move the node half the distance
1929 // towards the new position, so that different edges can
1930 // compete with one another to move the node.
1931 new_pos = (new_pos + old_pos) / 2;
1932
1933 // find how far the node has moved (squared to avoid a sqrt)
1934 QPointF delta = new_pos - old_pos;
1935 qreal moved_sq = delta.x()*delta.x() + delta.y()*delta.y();
1936 movement += moved_sq;
1937
1938 // has the node moved "far"?
1939 if (!moved_far) {
1940 delta = new_pos - mid;
1941 qreal size_sq = delta.x()*delta.x() + delta.y()*delta.y();
1942 if (moved_sq > size_sq * AXIS_ALIGN_FAR_THRESHOLD)
1943 moved_far = true;
1944 }
1945 }
1946 node_pos[N] = new_pos;
1947 }
1948 }
1949
1950 // We're done when no nodes have moved very far.
1951 if (!moved_far)
1952 dups = false;
1953 if (!dups || it >= AXIS_ALIGN_MAX_ITS)
1954 break;
1955 // Every so often check that the movement has decreased since last
1956 // time. If it hasn't then it's likely that the ways are impossible to
1957 // align.
1958 if ((it & 0xf) == 0xf) {
1959 if (last_movement >= 0.0 && movement >= last_movement)
1960 break;
1961 last_movement = movement;
1962 movement = 0.0;
1963 }
1964
1965 // tweak midpoints
1966 i = 0;
1967 foreach (Way *theWay, theWays) {
1968 Node *n2 = theWay->getNode(0);
1969 QPointF p1;
1970 QPointF p2 = node_pos[n2];
1971 for (int j = 0; j < theWay->size()-1; ++j, ++i) {
1972 n2 = theWay->getNode(j + 1);
1973 p1 = p2;
1974 p2 = node_pos[n2];
1975 midpoints[i] = (p1 + p2) * 0.5;
1976 }
1977 }
1978 }
1979 // dups is set to false when converged
1980 if (dups) {
1981 theList->undo();
1982 return AxisAlignFail;
1983 }
1984 // Commit the changes
1985 foreach (Way *theWay, theWays) {
1986 for (int i = 0; i < theWay->size(); ++i) {
1987 Node *N = theWay->getNode(i);
1988 theList->add(new MoveNodeCommand(N, proj.inverse(node_pos[N]), theDocument->getDirtyOrOriginLayer(N->layer())));
1989 }
1990 }
1991 return AxisAlignSuccess;
1992 }
1993