-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios, macos] fixes #6160: allow updating multipoint coordinates #6565
Changes from all commits
086297d
156071a
d85aec1
e7888e1
0349363
07aeddf
6931ab4
a48e1b7
a24482a
6e61b93
608115e
6e607ec
2d46f20
8d92589
a2377ed
d240ba9
18cd62a
ea0c900
b9c10e3
d4cb5a3
fcd5ada
138ae55
7f6049b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,12 @@ | ||
#import "MGLMultiPoint_Private.h" | ||
#import "MGLGeometry_Private.h" | ||
#import "MGLTypes.h" | ||
|
||
#import <mbgl/util/geo.hpp> | ||
|
||
mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) { | ||
if (!cgColor) { | ||
return { 0, 0, 0, 0 }; | ||
} | ||
mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) | ||
{ | ||
if (!cgColor) return { 0, 0, 0, 0 }; | ||
NSCAssert(CGColorGetNumberOfComponents(cgColor) >= 4, @"Color must have at least 4 components"); | ||
const CGFloat *components = CGColorGetComponents(cgColor); | ||
return { (float)components[0], (float)components[1], (float)components[2], (float)components[3] }; | ||
|
@@ -18,28 +18,34 @@ @implementation MGLMultiPoint | |
MGLCoordinateBounds _bounds; | ||
} | ||
|
||
- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords | ||
count:(NSUInteger)count | ||
- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count | ||
{ | ||
self = [super init]; | ||
|
||
if (self) | ||
{ | ||
_count = count; | ||
_coordinates = (CLLocationCoordinate2D *)malloc(_count * sizeof(CLLocationCoordinate2D)); | ||
[self setupWithCoordinates:coords count:count]; | ||
} | ||
|
||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); | ||
return self; | ||
} | ||
|
||
for (NSUInteger i = 0; i < _count; i++) | ||
{ | ||
_coordinates[i] = coords[i]; | ||
bounds.extend(mbgl::LatLng(coords[i].latitude, coords[i].longitude)); | ||
} | ||
- (void)setupWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count | ||
{ | ||
if (_coordinates) free(_coordinates); | ||
|
||
_count = count; | ||
_coordinates = (CLLocationCoordinate2D *)malloc(_count * sizeof(CLLocationCoordinate2D)); | ||
|
||
_bounds = MGLCoordinateBoundsFromLatLngBounds(bounds); | ||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); | ||
|
||
for (NSUInteger i = 0; i < _count; i++) | ||
{ | ||
_coordinates[i] = coords[i]; | ||
bounds.extend(mbgl::LatLng(coords[i].latitude, coords[i].longitude)); | ||
} | ||
|
||
return self; | ||
_bounds = MGLCoordinateBoundsFromLatLngBounds(bounds); | ||
} | ||
|
||
- (void)dealloc | ||
|
@@ -49,41 +55,30 @@ - (void)dealloc | |
|
||
- (CLLocationCoordinate2D)coordinate | ||
{ | ||
if ([self isMemberOfClass:[MGLMultiPoint class]]) | ||
{ | ||
[[NSException exceptionWithName:@"MGLAbstractClassException" | ||
reason:@"MGLMultiPoint is an abstract class" | ||
userInfo:nil] raise]; | ||
} | ||
|
||
assert(_count > 0); | ||
|
||
return CLLocationCoordinate2DMake(_coordinates[0].latitude, _coordinates[0].longitude); | ||
} | ||
|
||
- (NSUInteger)pointCount | ||
{ | ||
if ([self isMemberOfClass:[MGLMultiPoint class]]) | ||
{ | ||
[[NSException exceptionWithName:@"MGLAbstractClassException" | ||
reason:@"MGLMultiPoint is an abstract class" | ||
userInfo:nil] raise]; | ||
} | ||
|
||
return _count; | ||
} | ||
|
||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPointCount | ||
{ | ||
return [NSSet setWithObjects:@"coordinates", nil]; | ||
} | ||
|
||
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range | ||
{ | ||
if ([self isMemberOfClass:[MGLMultiPoint class]]) | ||
if (range.location + range.length > _count) | ||
{ | ||
[[NSException exceptionWithName:@"MGLAbstractClassException" | ||
reason:@"MGLMultiPoint is an abstract class" | ||
userInfo:nil] raise]; | ||
[NSException raise:NSRangeException | ||
format:@"Invalid coordinate range %@ extends beyond current coordinate count of %zu", | ||
NSStringFromRange(range), _count]; | ||
} | ||
|
||
assert(range.location + range.length <= _count); | ||
|
||
NSUInteger index = 0; | ||
|
||
for (NSUInteger i = range.location; i < range.location + range.length; i++) | ||
|
@@ -93,6 +88,43 @@ - (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range | |
} | ||
} | ||
|
||
- (void)replaceCoordinatesInRange:(NSRange)range withCoordinates:(CLLocationCoordinate2D *)coords | ||
{ | ||
if ((coords >= _coordinates && coords < _coordinates + _count) || | ||
(coords + range.length >= _coordinates && coords + range.length < _coordinates + _count)) | ||
{ | ||
[NSException raise:NSRangeException format:@"Reuse of existing coordinates array %p not supported", coords]; | ||
} | ||
else if (range.length == 0) | ||
{ | ||
[NSException raise:NSRangeException format:@"Empty coordinate range %@", NSStringFromRange(range)]; | ||
} | ||
else if (range.location > _count) | ||
{ | ||
[NSException raise:NSRangeException | ||
format:@"Invalid range %@ for existing coordinate count %zu", | ||
NSStringFromRange(range), _count]; | ||
} | ||
|
||
[self willChangeValueForKey:@"coordinates"]; | ||
if (NSMaxRange(range) <= _count) | ||
{ | ||
// replacing existing coordinate(s) | ||
memcpy(_coordinates + range.location, coords, range.length * sizeof(CLLocationCoordinate2D)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will catch the case in which the developer tries to close a polyline: [polyline setCoordinates:polyline.coordinates range:NSMakeRange(polyline.count, 1)]; but not if they try to have it return to the midpoint: [polyline setCoordinates:polyline.coordinates + polyline.pointCount / 2 range:NSMakeRange(polyline.count, 1)]; I think the check should be expanded to something like: (coords >= _coordinates + range.location && coords < pointCount + NSMaxRange(range))
|| (coords + range.length >= pointCount + range.location && coords + range.length < pointCount + NSMaxRange(range)) By the way, in that case, it would be safe to call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. I'm going to avoid There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I got this ok in 7f6049b? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎉 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this check may be off by 1 since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, the comparison is |
||
} | ||
else | ||
{ | ||
// appending new coordinate(s) | ||
NSUInteger newCount = NSMaxRange(range); | ||
CLLocationCoordinate2D *newCoordinates = (CLLocationCoordinate2D *)malloc(newCount * sizeof(CLLocationCoordinate2D)); | ||
memcpy(newCoordinates, _coordinates, fmin(_count, range.location) * sizeof(CLLocationCoordinate2D)); | ||
memcpy(newCoordinates + range.location, coords, range.length * sizeof(CLLocationCoordinate2D)); | ||
[self setupWithCoordinates:newCoordinates count:newCount]; | ||
free(newCoordinates); | ||
} | ||
[self didChangeValueForKey:@"coordinates"]; | ||
} | ||
|
||
- (MGLCoordinateBounds)overlayBounds | ||
{ | ||
return _bounds; | ||
|
@@ -103,15 +135,16 @@ - (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds | |
return MGLLatLngBoundsFromCoordinateBounds(_bounds).intersects(MGLLatLngBoundsFromCoordinateBounds(overlayBounds)); | ||
} | ||
|
||
- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id <MGLMultiPointDelegate>)delegate { | ||
- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id <MGLMultiPointDelegate>)delegate | ||
{ | ||
NSAssert(NO, @"Cannot add an annotation from an instance of %@", NSStringFromClass([self class])); | ||
return mbgl::SymbolAnnotation({mbgl::Point<double>()}); | ||
} | ||
|
||
- (NSString *)description | ||
{ | ||
return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>", | ||
NSStringFromClass([self class]), (void *)self, (unsigned long)_count, MGLStringFromCoordinateBounds(_bounds)]; | ||
NSStringFromClass([self class]), (void *)self, (unsigned long)_count, MGLStringFromCoordinateBounds(_bounds)]; | ||
} | ||
|
||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1795,6 +1795,29 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N | |
} | ||
} | ||
} | ||
else if ([keyPath isEqualToString:@"coordinates"] && [object isKindOfClass:[MGLMultiPoint class]]) | ||
{ | ||
MGLMultiPoint *annotation = object; | ||
MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context; | ||
// We can get here because a subclass registered itself as an observer | ||
// of the coordinates key path of a multipoint annotation but failed | ||
// to handle the change. This check deters us from treating the | ||
// subclass’s context as an annotation tag. If the context happens to | ||
// match a valid annotation tag, the annotation will be unnecessarily | ||
// but safely updated. | ||
if (annotation == [self annotationWithTag:annotationTag]) | ||
{ | ||
// Update the annotation’s backing geometry to match the annotation model object. | ||
_mbglMap->updateAnnotation(annotationTag, [annotation annotationObjectWithDelegate:self]); | ||
|
||
// We don't current support shape multipoint annotation selection, but let's make sure | ||
// deselection is handled just to avoid problems in the future. | ||
if (annotationTag == _selectedAnnotationTag) | ||
{ | ||
[self deselectAnnotation:annotation animated:YES]; | ||
} | ||
} | ||
} | ||
} | ||
|
||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomEnabled | ||
|
@@ -2840,6 +2863,8 @@ - (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations | |
MGLAnnotationContext context; | ||
context.annotation = annotation; | ||
_annotationContextsByAnnotationTag[annotationTag] = context; | ||
|
||
[(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For MGLPolygon, also observe changes to interiorPolygons. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although this could be tail work, since it would require making the interiorPolygons property mutable or at least settable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👉 #6576 |
||
} | ||
else if ( ! [annotation isKindOfClass:[MGLMultiPolyline class]] | ||
|| ![annotation isKindOfClass:[MGLMultiPolygon class]] | ||
|
@@ -3132,6 +3157,10 @@ - (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations | |
{ | ||
[(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag]; | ||
} | ||
else if ([annotation isKindOfClass:[MGLMultiPoint class]]) | ||
{ | ||
[(NSObject *)annotation removeObserver:self forKeyPath:@"coordinates" context:(void *)(NSUInteger)annotationTag]; | ||
} | ||
|
||
_mbglMap->removeAnnotation(annotationTag); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pointCount
can also change. Might be worth implementing+keyPathsForValuesAffectingPointCount:
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ea0c900