1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2013-2019 Kai Pastor
4 *
5 * This file is part of OpenOrienteering.
6 *
7 * OpenOrienteering is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * OpenOrienteering is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #ifndef OPENORIENTEERING_OBJECT_H
23 #define OPENORIENTEERING_OBJECT_H
24
25 #include <limits>
26 #include <vector>
27 #include <utility>
28
29 #include <QtGlobal>
30 #include <QHash>
31 #include <QRectF>
32 #include <QString>
33 // IWYU pragma: no_include <QTransform>
34
35 #include "core/map_coord.h"
36 #include "core/path_coord.h"
37 #include "core/virtual_path.h"
38 #include "core/renderables/renderable.h"
39 #include "core/symbols/symbol.h"
40
41 class QTransform;
42 class QXmlStreamReader;
43 class QXmlStreamWriter;
44 // IWYU pragma: no_forward_declare QRectF
45
46 namespace OpenOrienteering {
47
48 class Map;
49 class PointObject;
50 class PathObject;
51 class TextObject;
52 class VirtualCoordVector;
53
54
55 /**
56 * Abstract base class which combines coordinates and a symbol to form an object
57 * (in a map, or inside a point symbol as one of its elements).
58 *
59 * Every object must have a symbol. If the symbol is not known, one of the
60 * "undefined" symbols from the Map class can be used.
61 *
62 * From the object's data, a call to update() will generate the object's "output",
63 * that is a set of renderables and the calculation of the object's extent (bounding box).
64 * The renderables can then be inserted into a map where they are used to display the object.
65 */
66 class Object // clazy:exclude=copyable-polymorphic
67 {
68 friend class ObjectRenderables;
69 friend class OCAD8FileImport;
70 friend class XMLImportExport;
71 public:
72 /** Enumeration of possible object types. */
73 enum Type
74 {
75 /**
76 * A single coordinate, no further coordinates can be added.
77 * For point symbols only.
78 */
79 Point = 0,
80
81 /**
82 * A dynamic list of coordinates.
83 * For line, area and combined symbols.
84 */
85 Path = 1,
86
87 /**
88 * Either one or two coordinates, for single-anchor or box text.
89 * For text symbols only.
90 */
91 Text = 4
92 };
93
94 /** Creates an empty object with the given type and (optional) symbol. */
95 explicit Object(Type type, const Symbol* symbol = nullptr);
96
97 /** Creates an empty object with the given type, symbol, coords and (optional) map. */
98 explicit Object(Type type, const Symbol* symbol, MapCoordVector coords, Map* map = nullptr);
99
100 protected:
101 /**
102 * Constructs a Object, initialized from the given prototype.
103 *
104 * Note that the object is NOT added to a map, and consequently,
105 * the map pointer is initialized to nullptr.
106 */
107 explicit Object(const Object& proto);
108
109 public:
110 /** Destructs the object. */
111 virtual ~Object();
112
113 Object& operator=(const Object& other) = delete;
114
115 virtual void copyFrom(const Object& other);
116
117 /** Creates an identical copy of the object.
118 *
119 * This needs to be implemented in non-abstract subclasses.
120 * Implementation should use the copy constructor to ensure proper initialization.
121 */
122 virtual Object* duplicate() const = 0;
123
124 /**
125 * Checks for equality with another object.
126 *
127 * If compare_symbol is set, also the symbols are compared for having the same properties.
128 * Note that the map property is not compared.
129 */
130 bool equals(const Object* other, bool compare_symbol) const;
131
132 virtual bool validate() const;
133
134 /** Returns the object type determined by the subclass */
135 inline Type getType() const;
136
137 /** Convenience cast to PointObject with type checking */
138 PointObject* asPoint();
139 /** Convenience cast to PointObject with type checking */
140 const PointObject* asPoint() const;
141 /** Convenience cast to PathObject with type checking */
142 PathObject* asPath();
143 /** Convenience cast to PathObject with type checking */
144 const PathObject* asPath() const;
145 /** Convenience cast to TextObject with type checking */
146 TextObject* asText();
147 /** Convenience cast to TextObject with type checking */
148 const TextObject* asText() const;
149
150 /** Saves the object in xml format to the given stream. */
151 void save(QXmlStreamWriter& xml) const;
152 /**
153 * Loads the object in xml format from the given stream.
154 * @param xml The stream to load the object from, must be at the correct tag.
155 * @param map The map in which the object will be inserted.
156 * This value will be assigned to the object's map pointer
157 * It may be nullptr.
158 * @param symbol_dict A dictionary mapping symbol IDs to symbol pointers.
159 * @param symbol If set, this symbol will be assigned to the object, rather
160 * than reading the symbol from the stream.
161 */
162 static Object* load(QXmlStreamReader& xml, Map* map, const SymbolDictionary& symbol_dict, const Symbol* symbol = nullptr);
163
164
165 /**
166 * Returns the rotation for this object (in radians).
167 *
168 * The interpretation of this value depends the object's symbol.
169 */
getRotation()170 qreal getRotation() const { return rotation; }
171
172 /**
173 * Sets the object's rotation (in radians).
174 *
175 * The interpretation of this value depends the object's symbol.
176 * It may have no effect at all.
177 * The value must not be NaN.
178 */
179 void setRotation(qreal new_rotation);
180
181
182 /**
183 * If the output_dirty flag is set, regenerates output and extent, and updates the object's map (if set).
184 *
185 * Returns true if output was dirty.
186 */
187 bool update() const;
188
189 /**
190 * Always regenerates output and extent, and updates the object's map (if set).
191 */
192 void forceUpdate() const;
193
194
195 /** Moves the whole object
196 * @param dx X offset in native map coordinates.
197 * @param dy Y offset in native map coordinates.
198 */
199 void move(qint32 dx, qint32 dy);
200
201 /** Moves the whole object by the given offset. */
202 void move(const MapCoord& offset);
203
204 /** Scales all coordinates, with the given scaling center */
205 virtual void scale(const MapCoordF& center, double factor);
206
207 /** Scales all coordinates, with the center (0, 0).
208 * @param factor_x horizontal scaling factor
209 * @param factor_y vertical scaling factor
210 */
211 virtual void scale(double factor_x, double factor_y);
212
213 /** Rotates the whole object around the center point.
214 * The angle must be given in radians. */
215 void rotateAround(const MapCoordF& center, qreal angle);
216
217 /** Rotates the whole object around the center (0, 0).
218 * The angle must be given in radians. */
219 void rotate(qreal angle);
220
221 /**
222 * Apply a transformation to all coordinates.
223 *
224 * \todo Handle rotation of patterns or text (?)
225 */
226 virtual void transform(const QTransform& t) = 0;
227
228 /**
229 * Checks if the given coord, with the given tolerance, is on this object.
230 *
231 * With extended_selection, the coord is on point objects always
232 * if it is within their extent, otherwise it has to be close to
233 * their midpoint. Returns a Symbol::Type which specifies on which
234 * symbol type the coord is
235 * (important for combined symbols which can have areas and lines).
236 */
237 int isPointOnObject(const MapCoordF& coord, qreal tolerance, bool treat_areas_as_paths, bool extended_selection) const;
238
239 /**
240 * Checks if a path point (excluding curve control points) is included in the given box.
241 */
242 virtual bool intersectsBox(const QRectF& box) const = 0;
243
244 /** Takes ownership of the renderables */
245 void takeRenderables();
246
247 /** Deletes the renderables (and extent), undoing update() */
248 void clearRenderables();
249
250 /** Returns the renderables, read-only */
251 const ObjectRenderables& renderables() const;
252
253 // Getters / Setters
254
255 /**
256 * Returns the raw MapCoordVector of the object.
257 * It's layout and interpretation depends on the object type.
258 */
259 const MapCoordVector& getRawCoordinateVector() const;
260
261 /** Sets the object output's dirty state. */
262 void setOutputDirty(bool dirty = true);
263 /** Returns if the object's output must be regenerated. */
264 bool isOutputDirty() const;
265
266 /**
267 * Changes the object's symbol, returns if successful.
268 *
269 * Some conversions are impossible, for example point to line. Normally,
270 * this method checks if the types of the old and the new symbol are
271 * compatible. If the old symbol pointer is no longer valid, you can
272 * use no_checks to disable this.
273 */
274 bool setSymbol(const Symbol* new_symbol, bool no_checks);
275 /** Returns the object's symbol. */
276 const Symbol* getSymbol() const;
277
278 /** NOTE: The extent is only valid after update() has been called! */
279 const QRectF& getExtent() const;
280
281 /**
282 * Sets the object's map pointer.
283 *
284 * May be nullptr if the object is not in a map.
285 */
286 void setMap(Map* map);
287 /** Returns the object's map pointer. */
288 Map* getMap() const;
289
290 /** Constructs an object of the given type with the given symbol. */
291 static Object* getObjectForType(Type type, const Symbol* symbol = nullptr);
292
293
294 /** Defines a type which maps keys to values, to be used for tagging objects. */
295 typedef QHash<QString, QString> Tags;
296
297 /** Returns a const reference to the object's tags. */
298 const Tags& tags() const;
299
300 /** Replaces the object's tags. */
301 void setTags(const Tags& tags);
302
303 /** Returns the value of the given tag key. */
304 QString getTag(const QString& key) const;
305
306 /** Sets the given tag key to the value. */
307 void setTag(const QString& key, const QString& value);
308
309 /** Removes the given tag key and its value. */
310 void removeTag(const QString& key);
311
312
313 /**
314 * @brief Extends a rectangle to enclose all of the object's control points.
315 */
316 void includeControlPointsRect(QRectF& rect) const;
317
318 protected:
319 virtual void updateEvent() const;
320
321 virtual void createRenderables(ObjectRenderables& output, Symbol::RenderableOptions options) const;
322
323 Type type;
324 const Symbol* symbol = nullptr;
325 MapCoordVector coords;
326 Map* map = nullptr;
327 Tags object_tags;
328
329 private:
330 qreal rotation = 0; ///< The object's rotation (in radians).
331 mutable bool output_dirty = true; // does the output have to be re-generated because of changes?
332 mutable QRectF extent; // only valid after calling update()
333 mutable ObjectRenderables output; // only valid after calling update()
334 };
335
336
337
338 class PathPartVector;
339
340
341 /**
342 * Helper class with information about parts of paths.
343 *
344 * A part is a path segment which is separated from other parts by
345 * a hole point at its end.
346 */
347 class PathPart : public VirtualPath
348 {
349 public:
350 /** Pointer to path object containing this part */
351 PathObject* path = nullptr;
352
353 PathPart(
354 PathObject& path,
355 MapCoordVector::size_type start_index,
356 MapCoordVector::size_type end_index
357 );
358
359 PathPart(
360 const VirtualCoordVector& coords,
361 MapCoordVector::size_type start_index,
362 MapCoordVector::size_type end_index
363 );
364
365 PathPart(
366 PathObject& path,
367 const VirtualPath& virtual_path
368 );
369
370 ~PathPart() = default;
371
372 PathPart& operator=(const PathPart& rhs);
373
374 /**
375 * Closes or opens the sub-path.
376 *
377 * If closed == true and may_use_existing_close_point == false,
378 * a new point is added as closing point even if its coordinates
379 * are identical to the existing last point. Else, the last point
380 * may be reused.
381 */
382 void setClosed(bool closed, bool may_use_existing_close_point = false);
383
384 /**
385 * Closes the subpath, merging the start and end point at their center.
386 *
387 * \see PathPart::setClosed()
388 */
389 void connectEnds();
390
391 /**
392 * Reverses the part's coordinates.
393 *
394 * Reversing the coordinates results in switching the start/end/mid/dash
395 * symbol direction for line symbols.
396 *
397 * \see PathObject::reverse()
398 */
399 void reverse();
400
401 static PathPartVector calculatePathParts(const VirtualCoordVector& coords);
402 };
403
404
405
406 class PathPartVector : public std::vector<PathPart>
407 {
408 public:
409 /**
410 * This is dangerous when copying objects (which own a PathPartVector).
411 *
412 * Objects need to deal with the PathParts explicitly, at least as long as
413 * the PathPart contains distinct references to the object and to the
414 * coordinates.
415 *
416 * Other use cases may consider using std::vector<PathPart>.
417 */
418 PathPartVector& operator=(const PathPartVector&) = delete;
419
420 /**
421 * Returns true if the part's end_index is lower than index.
422 *
423 * This function can be used for doing a binary search on a sorted PathPartVector.
424 *
425 * @see std::lower_bound()
426 */
427 static bool compareEndIndex(const PathPart& part, VirtualPath::size_type index);
428 };
429
430
431
432 /**
433 * Object type which can be used for line, area and combined symbols.
434 * Has a dynamic number of coordinates.
435 *
436 * The coordinates are divided into one or multiple PathParts. A PathPart
437 * is ended by a coordinate with the "hole point" flag. For all types of
438 * flags which can be set, see the MapCoord documentation.
439 */
440 class PathObject : public Object // clazy:exclude=copyable-polymorphic
441 {
442 friend class PathPart;
443
444 public:
445
446 /** Returned by calcAllIntersectionsWith(). */
447 struct Intersection
448 {
449 /** Coordinate of the intersection */
450 MapCoordF coord;
451 /** Part index of intersection */
452 PathPartVector::size_type part_index;
453 /** Length of path until this intersection point */
454 PathCoord::length_type length;
455 /** Part index of intersection in other path */
456 PathPartVector::size_type other_part_index;
457 /** Length of other path until this intersection point */
458 PathCoord::length_type other_length;
459
460 /**
461 * Creates an Intersection at the position specified by factors a and b
462 * between the a0/a1 and b0/b1 PathCoords in the given parts.
463 */
464 static Intersection makeIntersectionAt(
465 double a,
466 double b,
467 const PathCoord& a0,
468 const PathCoord& a1,
469 const PathCoord& b0,
470 const PathCoord& b1,
471 PathPartVector::size_type part_index,
472 PathPartVector::size_type other_part_index );
473 };
474
475 /** std::vector of Intersection with the ability to sort them and remove duplicates. */
476 class Intersections : public std::vector<Intersection>
477 {
478 public:
479 /** Sorts the intersections and removes duplicates. */
480 void normalize();
481 };
482
483
484 /** Constructs a PathObject, optionally assigning a symbol. */
485 explicit PathObject(const Symbol* symbol = nullptr);
486
487 /** Constructs a PathObject, assigning initial coords and optionally the map pointer. */
488 explicit PathObject(const Symbol* symbol, MapCoordVector coords, Map* map = nullptr);
489
490 /**
491 * Constructs a PathObject, assigning initial coords from a single piece of a line.
492 *
493 * "Piece" refers to a single straight or curved arc from the point
494 * identified by parameter piece to the immediate next point on the path.
495 *
496 * If the path is not closed, and piece refers to the last element in the
497 * path (part), then the arc ending in the point referred to by piece is
498 * returned instead.
499 */
500 explicit PathObject(const Symbol* symbol, const PathObject& proto, MapCoordVector::size_type piece);
501
502 protected:
503 /** Constructs a PathObject, initialized from the given prototype. */
504 explicit PathObject(const PathObject& proto);
505
506 public:
507 /**
508 * Constructs a PathObject, initialized from the given part of another object.
509 */
510 explicit PathObject(const PathPart& proto_part);
511
512 /**
513 * Creates a duplicate of the path object.
514 *
515 * Use asPath() on the result to obtain an object of type PathObject.
516 */
517 PathObject* duplicate() const override;
518
519 PathObject& operator=(const PathObject& other) = delete;
520
521 /** Replaces this object's contents by those of the other. */
522 void copyFrom(const Object& other) override;
523
524
525 bool validate() const override;
526
527
528 /** Checks the path for valid flags, and makes corrections as necessary. */
529 void normalize();
530
531
532 bool intersectsBox(const QRectF& box) const override;
533
534
535 // Coordinate access methods
536
537 /** Returns the number of coordinates, including curve handles and close points. */
getCoordinateCount()538 MapCoordVector::size_type getCoordinateCount() const { return coords.size(); }
539
540 /** Returns the i-th coordinate. */
getCoordinate(MapCoordVector::size_type pos)541 MapCoord getCoordinate(MapCoordVector::size_type pos) const
542 {
543 Q_ASSERT(pos < coords.size());
544 return coords[pos];
545 }
546
547 /** Returns a non-const reference to the i-th coordinate.
548 *
549 * Normally you should modify coordinates via PathObject::setCoordinate.
550 * Unlike that function, modifying a coordinate directly via the reference
551 * will not keep the first and last point of a closed path in sync.
552 */
getCoordinateRef(MapCoordVector::size_type pos)553 MapCoord& getCoordinateRef(MapCoordVector::size_type pos)
554 {
555 Q_ASSERT(pos < coords.size());
556 setOutputDirty();
557 return coords[pos];
558 }
559
560 /** Replaces the i-th coordinate with c. */
561 void setCoordinate(MapCoordVector::size_type pos, const MapCoord& c);
562
563 /** Adds the coordinate at the given index. */
564 void addCoordinate(MapCoordVector::size_type pos, const MapCoord& c);
565
566 /** Adds the coordinate at the end, optionally starting a new part.
567 * If starting a new part, make sure that the last coord of the old part
568 * has the hole point flag! */
569 void addCoordinate(const MapCoord& c, bool start_new_part = false);
570
571 /**
572 * Deletes a coordinate from the path.
573 *
574 * When requesting a control point of a bezier arc to be deleted, the other
575 * control point is deleted, too.
576 *
577 * If the number of regular points in the coordinate's part is not more
578 * than two, the whole part is delete from the object.
579 *
580 * @param pos Index of the coordinate to delete.
581 * @param adjust_other_coords If set and the deleted coordinate was joining
582 * two bezier curves, adapts the adjacent curves with a strategy defined
583 * by delete_bezier_point_action. adjust_other_coords does not work
584 * when deleting bezier curve handles!
585 * @param delete_bezier_point_action Must be an enum value from
586 * Settings::DeleteBezierPointAction if adjust_other_coords is set.
587 */
588 void deleteCoordinate(MapCoordVector::size_type pos, bool adjust_other_coords, int delete_bezier_point_action = -1);
589
590 /** Deletes all coordinates of the object. */
591 void clearCoordinates();
592
593 /**
594 * Assigns the given prototype's coordinates subset to this object's coordinates.
595 *
596 * The range must be within one part. Last may be smaller than first iff
597 * the path is closed.
598 */
599 void assignCoordinates(const PathObject& proto, MapCoordVector::size_type first, MapCoordVector::size_type last);
600
601
602 /** Finds the path part containing the given coord index. */
603 PathPartVector::const_iterator findPartForIndex(MapCoordVector::size_type coords_index) const;
604
605 /** Finds the path part containing the given coord index. */
606 PathPartVector::iterator findPartForIndex(MapCoordVector::size_type coords_index);
607
608 /**
609 * Finds the path part containing the given coord index.
610 *
611 * \todo Review where this signature can be replace by the one returning an iterator.
612 */
613 PathPartVector::size_type findPartIndexForIndex(MapCoordVector::size_type coords_index) const;
614
615
616 /**
617 * Returns the path coordinate for the map coordinate with given index.
618 *
619 * @param index Index of normal MapCoord for which to create the PathCoord.
620 */
621 PathCoord findPathCoordForIndex(MapCoordVector::size_type index) const;
622
623
624 /**
625 * Returns true if the given index is a curve handle.
626 */
627 bool isCurveHandle(MapCoordVector::size_type index) const;
628
629
630 /**
631 * Returns the vector of path parts.
632 */
633 const PathPartVector& parts() const;
634
635 /**
636 * Returns the vector of path parts.
637 *
638 * Marks the output as dirty.
639 */
640 PathPartVector& parts();
641
642 /**
643 * Deletes the i-th path part.
644 */
645 void deletePart(PathPartVector::size_type part_index);
646
647 /**
648 * Transforms the coordinates and the pattern origin.
649 */
650 void transform(const QTransform& t) override;
651
652 // Pattern methods
653
654 /**
655 * Returns the rotation of the object pattern. Only has an effect in
656 * combination with a symbol interpreting this value.
657 */
658 qreal getPatternRotation() const;
659
660 /**
661 * Sets the rotation of the object pattern. Only has an effect in
662 * combination with a symbol interpreting this value.
663 */
664 void setPatternRotation(qreal rotation);
665
666 /**
667 * Returns the origin of the object pattern. Only has an effect in
668 * combination with a symbol interpreting this value.
669 */
670 MapCoord getPatternOrigin() const;
671
672 /**
673 * Sets the origin of the object pattern. Only has an effect in
674 * combination with a symbol interpreting this value.
675 */
676 void setPatternOrigin(const MapCoord& origin);
677
678
679 // Operations
680
681 /**
682 * Calculates the closest point on the path to the given coordinate,
683 * returns the squared distance of these points and PathCoord information
684 * for the point on the path.
685 *
686 * This does not need to be an existing path coordinate. This method is
687 * usually called to find the position on the path the user clicked on.
688 * part_index can be set to a valid part index to constrain searching
689 * to this specific path part.
690 *
691 * \todo Convert out_distance_sq to double (so avoiding conversions).
692 * \todo Return PathCoord rather than writing to the provided reference.
693 */
694 void calcClosestPointOnPath(
695 MapCoordF coord,
696 float& out_distance_sq,
697 PathCoord& out_path_coord,
698 MapCoordVector::size_type start_index = 0,
699 MapCoordVector::size_type end_index = std::numeric_limits<PathPartVector::size_type>::max()
700 ) const;
701
702 /**
703 * Calculates the closest control point coordinate to the given coordinate,
704 * returns the squared distance of these points and the index of the control point.
705 *
706 * \todo Convert out_distance_sq to double (so avoiding conversions).
707 * \todo Return index rather than writing to the provided reference.
708 */
709 void calcClosestCoordinate(
710 MapCoordF coord,
711 float& out_distance_sq,
712 MapCoordVector::size_type& out_index) const;
713
714 /**
715 * Splits the path at the position given by path_coord.
716 *
717 * Must not be called while isOutputDirty() returns true.
718 *
719 * Returns the index of the added point.
720 */
721 MapCoordVector::size_type subdivide(const PathCoord& path_coord);
722
723 /**
724 * Splits the path in the curve which starts at the given index.
725 *
726 * The second parameter determines the split position between begin and end
727 * of the curve (0.0 ... 1.0).
728 *
729 * Must not be called while isOutputDirty() returns true.
730 *
731 * @return The index of the added point.
732 */
733 MapCoordVector::size_type subdivide(MapCoordVector::size_type index, float param);
734
735 /**
736 * Returns if connectIfClose() would change something with the given parameters
737 */
738 bool canBeConnected(const PathObject* other, double connect_threshold_sq) const;
739
740 /**
741 * Returns if the objects were connected (if so, you can delete the other object).
742 * If one of the paths has to be reversed, it is done for the "other" path.
743 * Otherwise, the "other" path is not changed.
744 *
745 * \todo Review documentation, container usage,
746 */
747 bool connectIfClose(PathObject* other, double connect_threshold_sq);
748
749 /**
750 * Connects the given parts, optionally merging the end coordinates at the
751 * center position, and copying over the coordinates from other.
752 */
753 void connectPathParts(
754 PathPartVector::size_type part_index,
755 const PathObject* other,
756 PathPartVector::size_type other_part_index,
757 bool prepend,
758 bool merge_ends = true
759 );
760
761 /**
762 * Returns the result of removing the section between begin and end from the path.
763 *
764 * begin and end must belong to the path part with the given part_index.
765 * However, objects with holes, and part_index values greater than 0, are
766 * not supported at the moment.
767 *
768 * Returns an empty vector when nothing remains after removal.
769 */
770 std::vector<PathObject*> removeFromLine(
771 PathPartVector::size_type part_index,
772 PathCoord::length_type clen_begin,
773 PathCoord::length_type clen_end
774 ) const;
775
776 /**
777 * Returns the result of splitting the path at the given inner position.
778 *
779 * Returns an empty vector when the object is not changed by the split.
780 * This happens when the path is not closed and split_pos is the begin or
781 * end of the path, or when the object has got more than a single PathPart.
782 */
783 std::vector<PathObject*> splitLineAt(const PathCoord& split_pos) const;
784
785 /**
786 * Replaces the path with a non-empty range of it starting and ending at the given lengths.
787 *
788 * For open paths, the end length must be greater than the start length.
789 * For closed paths, an end length smaller than or equal to the start length
790 * will cause the resulting path to span the original start/end point.
791 */
792 void changePathBounds(
793 PathPartVector::size_type part_index,
794 PathCoord::length_type start_len,
795 PathCoord::length_type end_len
796 );
797
798 /**
799 * Appends (copies) the coordinates of other to this path.
800 */
801 void appendPath(const PathObject* other);
802
803 /**
804 * Appends (copies) the coordinates of a specific part to this path.
805 *
806 * The other object is determined from the part's path property.
807 */
808 void appendPathPart(const PathPart& part);
809
810 /**
811 * Reverses the object's coordinates, resulting in switching
812 * the start / end / mid / dash symbol direction for line symbols.
813 */
814 void reverse();
815
816 /** Ensures that all parts are closed. Useful for objects with area-only symbols. */
817 void closeAllParts();
818
819 /**
820 * Converts all polygonal sections in this path to splines.
821 * If at least one section is converted, returns true and
822 * returns an undo duplicate if the corresponding pointer is set.
823 */
824 bool convertToCurves(PathObject** undo_duplicate = nullptr);
825
826 /**
827 * Converts the given range of coordinates to a spline by inserting handles.
828 * The range must consist of only polygonal segments before.
829 *
830 * @return The new index of the end of the range.
831 */
832 PathPart::size_type convertRangeToCurves(const PathPart& part, PathPart::size_type start_index, PathPart::size_type end_index);
833
834 /**
835 * Tries to remove points while retaining the path shape as much as possible.
836 * If at least one point is changed, returns true and
837 * returns an undo duplicate if the corresponding pointer is set.
838 */
839 bool simplify(PathObject** undo_duplicate, double threshold);
840
841 /** See Object::isPointOnObject() */
842 int isPointOnPath(
843 MapCoordF coord,
844 qreal tolerance,
845 bool treat_areas_as_paths,
846 bool extended_selection
847 ) const;
848
849 /**
850 * Returns true if the given coordinate is inside the area
851 * defined by this object, which must be closed.
852 */
853 bool isPointInsideArea(const MapCoordF& coord) const;
854
855 /**
856 * Calculates the maximum distance of the given coord ranges of two objects.
857 */
858 double calcMaximumDistanceTo(
859 MapCoordVector::size_type start_index,
860 MapCoordVector::size_type end_index,
861 const PathObject* other,
862 MapCoordVector::size_type other_start_index,
863 MapCoordVector::size_type other_end_index
864 ) const;
865
866 /**
867 * Calculates and adds all intersections with the other path to out.
868 * Note: intersections are not sorted and may contain duplicates!
869 * To clean them up, call clean() on the Intersections object after adding
870 * all intersections with objects you are interested in.
871 */
872 void calcAllIntersectionsWith(const PathObject* other, Intersections& out) const;
873
874 /** Called by Object::update() */
875 void updatePathCoords() const;
876
877 /** Called by Object::load() */
878 void recalculateParts();
879
880 protected:
881 /**
882 * Adjusts the end index of the given part and the start/end indexes of the following parts.
883 *
884 * output_dirty must be set before calling this function.
885 */
886 void partSizeChanged(PathPartVector::iterator part, MapCoordVector::difference_type change);
887
888
889 void prepareDeleteBezierPoint(MapCoordVector::size_type pos, int delete_bezier_point_action);
890
891 /**
892 * Calculates the factors which should be applied to the length of the
893 * remaining bezier curve handle vectors when deleting a point joining
894 * two bezier curves to try to retain the original curves' shape.
895 *
896 * This is a simple version, the result should be optimized with
897 * calcBezierPointDeletionRetainingShapeOptimization().
898 *
899 * p0, p1, p2, q0 make up the first original curve,
900 * q0, q1, q2, q3 make up the second original curve.
901 * out_pfactor is set to the factor to apply to the vector (p1 - p0),
902 * out_qfactor is set to the factor to apply to the vector (q2 - q3),
903 */
904 static void calcBezierPointDeletionRetainingShapeFactors(
905 MapCoord p0,
906 MapCoord p1,
907 MapCoord p2,
908 MapCoord q0,
909 MapCoord q1,
910 MapCoord q2,
911 MapCoord q3,
912 double& out_pfactor,
913 double& out_qfactor
914 );
915
916 /**
917 * Uses nonlinear optimization to improve the first result obtained by
918 * calcBezierPointDeletionRetainingShapeFactors().
919 */
920 static void calcBezierPointDeletionRetainingShapeOptimization(
921 MapCoord p0,
922 MapCoord p1,
923 MapCoord p2,
924 MapCoord q0,
925 MapCoord q1,
926 MapCoord q2,
927 MapCoord q3,
928 double& out_pfactor,
929 double& out_qfactor
930 );
931
932 /**
933 * Is used internally by calcBezierPointDeletionRetainingShapeOptimization()
934 * to calculate the current cost. Evaluates the distance between p0 ... p3
935 * and the reference path.
936 */
937 static float calcBezierPointDeletionRetainingShapeCost(
938 MapCoord p0,
939 MapCoordF p1,
940 MapCoordF p2,
941 MapCoord p3,
942 PathObject* reference
943 );
944
945 /**
946 * Sets coord as the point which closes a part: sets the correct flags
947 * on it and replaces the coord at the given index by it.
948 * TODO: make separate methods? Setting coords exists already.
949 */
950 void setClosingPoint(MapCoordVector::size_type index, const MapCoord& coord);
951
952 void updateEvent() const override;
953
954 void createRenderables(ObjectRenderables& output, Symbol::RenderableOptions options) const override;
955
956 private:
957 /**
958 * Origin shift of the object pattern. Only used if the object
959 * has a symbol which interprets this value.
960 */
961 MapCoord pattern_origin = {};
962
963 /** Path parts list */
964 mutable PathPartVector path_parts;
965 };
966
967
968
969 /** Compares the length of the intersections. */
970 inline
971 bool operator< (const PathObject::Intersection& lhs, const PathObject::Intersection& rhs)
972 {
973 return lhs.length < rhs.length;
974 }
975
976 /** Fuzzy equality check. */
977 inline
978 bool operator== (const PathObject::Intersection& lhs, const PathObject::Intersection& rhs)
979 {
980 // NOTE: coord is not compared, as the intersection is defined by the other params already.
981 const double epsilon = 1e-10;
982 return lhs.part_index == rhs.part_index &&
983 lhs.other_part_index == rhs.other_part_index &&
984 qAbs(lhs.length - rhs.length) <= epsilon &&
985 qAbs(lhs.other_length - rhs.other_length) <= epsilon;
986 }
987
988
989
990 /**
991 * Object type which can only be used for point symbols,
992 * and is also the only object which can be used with them.
993 *
994 * Has exactly one coordinate, and additionally a rotation parameter.
995 */
996 class PointObject : public Object // clazy:exclude=copyable-polymorphic
997 {
998 public:
999 /** Constructs a PointObject, optionally assigning the symbol. */
1000 explicit PointObject(const Symbol* symbol = nullptr);
1001
1002 protected:
1003 /** Constructs a PointObject, initialized from the given prototype. */
1004 explicit PointObject(const PointObject& proto);
1005
1006 public:
1007 /**
1008 * Creates a duplicate of the point.
1009 *
1010 * Use asPoint() on the result to obtain an object of type PointObject.
1011 */
1012 PointObject* duplicate() const override;
1013
1014 PointObject& operator=(const PointObject& other) = delete;
1015
1016 /** Replaces the content of this object by that of another. */
1017 void copyFrom(const Object& other) override;
1018
1019
1020 /** Sets the point's position to a new position given in native map coordinates. */
1021 void setPosition(qint32 x, qint32 y);
1022
1023 /** Changes the point's position. */
1024 void setPosition(const MapCoord& coord);
1025
1026 /** Changes the point's position. */
1027 void setPosition(const MapCoordF& coord);
1028
1029 /** Returns the point's position as MapCoordF. */
1030 MapCoordF getCoordF() const;
1031
1032 /** Returns the point's coordinate. */
1033 MapCoord getCoord() const;
1034
1035
1036 /**
1037 * Transforms the position.
1038 */
1039 void transform(const QTransform& t) override;
1040
1041
1042 bool intersectsBox(const QRectF& box) const override;
1043 };
1044
1045
1046
1047 /**
1048 * A single PathCoord together with the object it belongs to.
1049 *
1050 * This is a convenient structure for passing around as parameter and return value.
1051 */
1052 struct ObjectPathCoord : public PathCoord
1053 {
1054 PathObject* object;
1055
1056 constexpr ObjectPathCoord() noexcept;
1057 constexpr ObjectPathCoord(PathObject* object) noexcept;
1058 constexpr ObjectPathCoord(PathObject* object, const PathCoord& coord) noexcept;
1059 constexpr ObjectPathCoord(PathObject* object, const PathCoord&& coord) noexcept;
1060 constexpr ObjectPathCoord(const ObjectPathCoord&) noexcept = default;
1061 constexpr ObjectPathCoord(ObjectPathCoord&&) noexcept = default;
1062 ObjectPathCoord& operator=(const ObjectPathCoord&) noexcept = default;
1063 ObjectPathCoord& operator=(ObjectPathCoord&&) noexcept = default;
1064
1065 /**
1066 * This constructor sets the PathCoord members according to the given coordinate index.
1067 */
1068 ObjectPathCoord(PathObject* object, MapCoordVector::size_type index);
1069
1070 /**
1071 * Returns true iff the object is not null.
1072 */
1073 constexpr operator bool() const;
1074
1075 /**
1076 * Sets this PathCoord to the point on this path which is the closest to the
1077 * given coordinate.
1078 *
1079 * \return The squared distance of these points.
1080 *
1081 * \see PathObject::calcClosestPointOnPath
1082 */
1083 float findClosestPointTo(const MapCoordF& map_coord);
1084 };
1085
1086
1087
1088 //### Object inline code ###
1089
1090 inline
getType()1091 Object::Type Object::getType() const
1092 {
1093 return type;
1094 }
1095
1096 inline
renderables()1097 const ObjectRenderables& Object::renderables() const
1098 {
1099 return output;
1100 }
1101
1102 inline
getRawCoordinateVector()1103 const MapCoordVector& Object::getRawCoordinateVector() const
1104 {
1105 return coords;
1106 }
1107
1108 inline
setOutputDirty(bool dirty)1109 void Object::setOutputDirty(bool dirty)
1110 {
1111 output_dirty = dirty;
1112 }
1113
1114 inline
isOutputDirty()1115 bool Object::isOutputDirty() const
1116 {
1117 return output_dirty;
1118 }
1119
1120 inline
getSymbol()1121 const Symbol* Object::getSymbol() const
1122 {
1123 return symbol;
1124 }
1125
1126 inline
getExtent()1127 const QRectF& Object::getExtent() const
1128 {
1129 return extent;
1130 }
1131
1132 inline
setMap(Map * map)1133 void Object::setMap(Map* map)
1134 {
1135 this->map = map;
1136 setOutputDirty();
1137 }
1138
1139 inline
getMap()1140 Map* Object::getMap() const
1141 {
1142 return map;
1143 }
1144
1145 inline
tags()1146 const Object::Tags& Object::tags() const
1147 {
1148 return object_tags;
1149 }
1150
1151 inline
getTag(const QString & key)1152 QString Object::getTag(const QString& key) const
1153 {
1154 return object_tags.value(key);
1155 }
1156
1157
1158
1159 //### PathPart inline code ###
1160
1161 inline
PathPart(const VirtualCoordVector & coords,MapCoordVector::size_type start_index,MapCoordVector::size_type end_index)1162 PathPart::PathPart(
1163 const VirtualCoordVector& coords,
1164 MapCoordVector::size_type start_index,
1165 MapCoordVector::size_type end_index)
1166 : VirtualPath(coords, start_index, end_index)
1167 {
1168 // nothing else
1169 }
1170
1171 inline
PathPart(PathObject & path,MapCoordVector::size_type start_index,MapCoordVector::size_type end_index)1172 PathPart::PathPart(
1173 PathObject& path,
1174 MapCoordVector::size_type start_index,
1175 MapCoordVector::size_type end_index )
1176 : VirtualPath(path.getRawCoordinateVector(), start_index, end_index)
1177 , path(&path)
1178 {
1179 // nothing else
1180 }
1181
1182 inline
1183 PathPart& PathPart::operator=(const PathPart& rhs)
1184 {
1185 Q_ASSERT(path == rhs.path);
1186 VirtualPath::operator=(rhs);
1187 return *this;
1188 }
1189
1190
1191
1192 //### PathObject inline code ###
1193
1194 inline
parts()1195 const PathPartVector& PathObject::parts() const
1196 {
1197 return path_parts;
1198 }
1199
1200 inline
parts()1201 PathPartVector& PathObject::parts()
1202 {
1203 setOutputDirty();
1204 return path_parts;
1205 }
1206
1207 inline
getPatternRotation()1208 qreal PathObject::getPatternRotation() const
1209 {
1210 return getRotation();
1211 }
1212
1213 inline
getPatternOrigin()1214 MapCoord PathObject::getPatternOrigin() const
1215 {
1216 return pattern_origin;
1217 }
1218
1219
1220
1221 //### ObjectPathCoord inline code ###
1222
1223 inline
ObjectPathCoord()1224 constexpr ObjectPathCoord::ObjectPathCoord() noexcept
1225 : ObjectPathCoord { nullptr }
1226 {
1227 // nothing else
1228 }
1229
1230
1231 inline
ObjectPathCoord(PathObject * object)1232 constexpr ObjectPathCoord::ObjectPathCoord(PathObject* object) noexcept
1233 : object { object }
1234 {
1235 // nothing else
1236 }
1237
1238
1239 inline
ObjectPathCoord(PathObject * object,const PathCoord & coord)1240 constexpr ObjectPathCoord::ObjectPathCoord(PathObject* object, const PathCoord& coord) noexcept
1241 : PathCoord { coord }
1242 , object { object }
1243 {
1244 // nothing else
1245 }
1246
1247
1248 inline
ObjectPathCoord(PathObject * object,const PathCoord && coord)1249 constexpr ObjectPathCoord::ObjectPathCoord(PathObject* object, const PathCoord&& coord) noexcept
1250 : PathCoord { std::move(coord) }
1251 , object { object }
1252 {
1253 // nothing else
1254 }
1255
1256
1257 inline
ObjectPathCoord(PathObject * object,MapCoordVector::size_type index)1258 ObjectPathCoord::ObjectPathCoord(PathObject* object, MapCoordVector::size_type index)
1259 : PathCoord { object->findPathCoordForIndex(index) }
1260 , object { object }
1261 {
1262 // nothing else
1263 }
1264
1265
1266 inline
findClosestPointTo(const MapCoordF & map_coord)1267 float ObjectPathCoord::findClosestPointTo(const MapCoordF& map_coord)
1268 {
1269 float distance_sq;
1270 object->calcClosestPointOnPath(map_coord, distance_sq, *this);
1271 return distance_sq;
1272
1273 }
1274
1275
1276 inline
1277 constexpr ObjectPathCoord::operator bool() const
1278 {
1279 return static_cast<bool>(object);
1280 }
1281
1282 } // namespace OpenOrienteering
1283
1284
1285 #endif
1286