Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
18 changes: 0 additions & 18 deletions LFHeatMap.podspec

This file was deleted.

85 changes: 28 additions & 57 deletions LFHeatMap/LFHeatMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,75 +6,46 @@
*/

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#import <Mapbox-iOS-SDK/Mapbox.h>

extern inline CGRect CGRectContainingPoints(NSArray *points);
extern inline CGPoint CGPointOffset(CGPoint point, CGFloat xOffset, CGFloat yOffset);

@interface RMHeatmapAnnotation : RMAnnotation

@property (nonatomic, strong) UIImage *heatmapImage;

@end

@interface RMHeatmapMarker : RMMarker

- (instancetype)initWithHeatmapAnnotation:(RMHeatmapAnnotation *)heatmapAnnotation;

@end

@interface LFHeatMap : NSObject
{
{
}

/**
Generates a heat map image for the specified map view.

There should be a one-to-one correspondence between the location and weight elements.
A nil weight parameter implies an even weight distribution.

@params
mapView: Map view representing the heat map area.
boost: heat boost value
locations: array of CLLocation objects representing the data points
weights: array of NSNumber integer objects representing the weight of each point

@returns
UIImage object representing the heatmap for the map region.
*/
+ (UIImage *)heatMapForMapView:(MKMapView *)mapView
boost:(float)boost
locations:(NSArray *)locations
weights:(NSArray *)weights;

/**
Generates a heat map image for the specified rectangle.
Generates a heat map annotation for the specified map view.

There should be a one-to-one correspondence between the location and weight elements.
A nil weight parameter implies an even weight distribution.

@params
@rect: region frame
boost: heat boost value
points: array of NSValue CGPoint objects representing the data points
weights: array of NSNumber integer objects representing the weight of each point

@returns
UIImage object representing the heatmap for the specified region.
*/
+ (UIImage *)heatMapWithRect:(CGRect)rect
boost:(float)boost
points:(NSArray *)points
weights:(NSArray *)weights;

/**
Generates a heat map image for the specified rectangle.

There should be a one-to-one correspondence between the location and weight elements.
A nil weight parameter implies an even weight distribution.
@param mapView Map view representing the heat map area.
@param boost heat boost value
@param locations array of CLLocation objects representing the data points
@param weights array of NSNumber integer objects representing the weight of each point

@params
@rect: region frame
boost: heat boost value
points: array of NSValue CGPoint objects representing the data points
weights: array of NSNumber integer objects representing the weight of each point
weightsAdjustmentEnabled: set YES for weight balancing and normalization
groupingEnabled: set YES for tighter visual grouping of dense areas
@warning If the heatmap image generated is too big, this method will return nil

@returns
UIImage object representing the heatmap for the specified region.
@return RMHeatmapAnnotation object representing the heatmap ready to be added to the map
*/
+ (UIImage *)heatMapWithRect:(CGRect)rect
boost:(float)boost
points:(NSArray *)points
weights:(NSArray *)weights
weightsAdjustmentEnabled:(BOOL)weightsAdjustmentEnabled
groupingEnabled:(BOOL)groupingEnabled;
+ (RMHeatmapAnnotation *)heatMapAnnotationForMapView:(RMMapView *)mapView
boost:(float)boost
locations:(NSArray *)locations
weights:(NSArray *)weights;


@end
Expand Down
127 changes: 121 additions & 6 deletions LFHeatMap/LFHeatMap.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,90 @@
*/

#import "LFHeatMap.h"
@import CoreLocation.CLLocation;

/**
* Calculates the smallest CGRect containing the provided points
* @param points An Array of NSValue containing CGPoint structs
* @discussion If no points are provided, this method return CGRectNull
*/
inline CGRect CGRectContainingPoints(NSArray *points) {

if (points.count == 0) return CGRectNull;

CGPoint (^pointAtIndex)(NSUInteger index) = ^CGPoint(NSUInteger index) {

NSValue *value = points[index];

return [value CGPointValue];
};

CGFloat greatestXValue = pointAtIndex(0).x;
CGFloat greatestYValue = pointAtIndex(0).y;
CGFloat smallestXValue = pointAtIndex(0).x;
CGFloat smallestYValue = pointAtIndex(0).y;

for(int i = 1; i < points.count; i++)
{
CGPoint point = pointAtIndex(i);
greatestXValue = MAX(greatestXValue, point.x);
greatestYValue = MAX(greatestYValue, point.y);
smallestXValue = MIN(smallestXValue, point.x);
smallestYValue = MIN(smallestYValue, point.y);
}

CGRect rect;
rect.origin = CGPointMake(smallestXValue, smallestYValue);
rect.size.width = greatestXValue - smallestXValue;
rect.size.height = greatestYValue - smallestYValue;

return rect;
}

inline CGPoint CGPointOffset(CGPoint point, CGFloat xOffset, CGFloat yOffset) {

return CGPointMake(point.x + xOffset, point.y + yOffset);

}

@implementation RMHeatmapAnnotation

- (instancetype)initWithMapView:(RMMapView *)aMapView
coordinate:(CLLocationCoordinate2D)aCoordinate
title:(NSString *)aTitle
heatmapImage:(UIImage *)heatmapImage
{
NSParameterAssert(heatmapImage);

self = [super initWithMapView:aMapView coordinate:aCoordinate andTitle:aTitle];

if (!self) return nil;

self.heatmapImage = heatmapImage;

return self;
}

- (instancetype)initWithMapView:(RMMapView *)aMapView coordinate:(CLLocationCoordinate2D)aCoordinate andTitle:(NSString *)aTitle
{
@throw [NSException exceptionWithName:@"Invalid initializer" reason:@"Called invalid initializer on class RMHeatmapAnnotation" userInfo:nil];
}

@end

@implementation RMHeatmapMarker

- (instancetype)initWithHeatmapAnnotation:(RMHeatmapAnnotation *)heatmapAnnotation
{
NSParameterAssert(heatmapAnnotation);
self = [super initWithUIImage:heatmapAnnotation.heatmapImage];

if (!self) return nil;

return self;
}

@end

@implementation LFHeatMap

Expand Down Expand Up @@ -141,10 +224,10 @@ inline static int isqrt(int x)
}
}

+ (UIImage *)heatMapForMapView:(MKMapView *)mapView
boost:(float)boost
locations:(NSArray *)locations
weights:(NSArray *)weights
+ (RMHeatmapAnnotation *)heatMapAnnotationForMapView:(RMMapView *)mapView
boost:(float)boost
locations:(NSArray *)locations
weights:(NSArray *)weights
{
if (!mapView || !locations)
return nil;
Expand All @@ -153,11 +236,43 @@ + (UIImage *)heatMapForMapView:(MKMapView *)mapView
for (NSInteger i = 0; i < [locations count]; i++)
{
CLLocation *location = [locations objectAtIndex:i];
CGPoint point = [mapView convertCoordinate:location.coordinate toPointToView:mapView];
CGPoint point = [mapView coordinateToPixel:location.coordinate];
[points addObject:[NSValue valueWithCGPoint:point]];
}

return [LFHeatMap heatMapWithRect:mapView.frame boost:boost points:points weights:weights];
CGRect containerRect = CGRectContainingPoints(points);
CGPoint centerPoint = CGPointMake(CGRectGetMidX(containerRect), CGRectGetMidY(containerRect));
CLLocationCoordinate2D heatmapCenterLocation = [mapView pixelToCoordinate:centerPoint];

CGFloat xOffset = -containerRect.origin.x;
CGFloat yOffset = -containerRect.origin.y;

NSMutableArray *translatedPoints = [[NSMutableArray alloc] initWithCapacity:points.count];
CGRect translatedContainerRect = CGRectOffset(containerRect, xOffset, yOffset);

CGFloat imagePadding = 40.0f;

CGRect translatedContainerRectWithPadding = CGRectInset(translatedContainerRect, -imagePadding, -imagePadding);

for (NSValue *value in points)
{
CGPoint translatedPoint = CGPointOffset([value CGPointValue], xOffset, yOffset);
[translatedPoints addObject:[NSValue valueWithCGPoint:translatedPoint]];
}

if (translatedContainerRectWithPadding.size.width > 2048 || translatedContainerRectWithPadding.size.height > 2048)
{
NSLog(@"LFHeatMap: resulting heatmap is too big, reduce zoom");
return nil;
}

UIImage *image = [LFHeatMap heatMapWithRect:translatedContainerRectWithPadding boost:boost points:translatedPoints weights:weights];
RMHeatmapAnnotation *heatmapAnnotation = [[RMHeatmapAnnotation alloc] initWithMapView:mapView
coordinate:heatmapCenterLocation
title:@"Heatmap"
heatmapImage:image];

return heatmapAnnotation;
}

+ (UIImage *)heatMapWithRect:(CGRect)rect
Expand Down
Loading