Authored by Thomas Rasch

o Fun project: Tile source for a coordinate grid (needs some improvement)

//
// RMCoordinateGridSource.h
// MapView
//
// Copyright (c) 2008-2012, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import "RMAbstractMercatorTileSource.h"
#import "RMProjection.h"
typedef enum : short {
GridModeGeographic, // 47˚ 33'
GridModeGeographicDecimal, // 47.56
GridModeUTM // 32T 5910
} CoordinateGridMode;
// UTM grid is missing for now
@interface RMCoordinateGridSource : RMAbstractMercatorTileSource
@property (nonatomic, assign) CoordinateGridMode gridMode;
@property (nonatomic, retain) UIColor *gridColor;
@property (nonatomic, assign) CGFloat gridLineWidth;
@property (nonatomic, retain) UIColor *minorLabelColor;
@property (nonatomic, retain) UIFont *minorLabelFont;
@property (nonatomic, retain) UIColor *majorLabelColor;
@property (nonatomic, retain) UIFont *majorLabelFont;
@end
... ...
//
// RMCoordinateGridSource.m
// MapView
//
// Copyright (c) 2008-2012, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import "RMCoordinateGridSource.h"
#import "RMTileCache.h"
#define kTileSidePadding 25.0 // px
static double coordinateGridSpacing[19] = {
45.0, // 0
45.0, // 1
45.0, // 2
10.0, // 3
5.0, // 4
5.0, // 5
2.0, // 6
1.0, // 7
1.0, // 8
0.5, // 9
0.25, // 10
(1.0 / 60.0) * 5.0, // 11
(1.0 / 60.0) * 3.0, // 12
(1.0 / 60.0) * 2.0, // 13
(1.0 / 60.0), // 14
(1.0 / 60.0), // 15
(1.0 / 60.0), // 16
(1.0 / 60.0), // 17
(1.0 / 60.0), // 18
};
static double coordinateGridSpacingDecimal[19] = {
45.0, // 0
45.0, // 1
45.0, // 2
10.0, // 3
5.0, // 4
5.0, // 5
2.0, // 6
1.0, // 7
1.0, // 8
0.5, // 9
0.25, // 10
0.10, // 11
0.05, // 12
0.05, // 13
0.01, // 14
0.01, // 15
0.01, // 16
0.01, // 17
0.01, // 18
};
@implementation RMCoordinateGridSource
@synthesize gridColor = _gridColor;
@synthesize gridLineWidth = _gridLineWidth;
@synthesize gridMode = _labelingMode;
@synthesize minorLabelColor = _minorLabelColor;
@synthesize minorLabelFont = _minorLabelFont;
@synthesize majorLabelColor = _majorLabelColor;
@synthesize majorLabelFont = _majorLabelFont;
- (id)init
{
if (!(self = [super init]))
return nil;
self.minZoom = 5;
self.maxZoom = 18;
self.gridColor = [UIColor colorWithWhite:0.1 alpha:0.6];
self.gridLineWidth = 2.0;
self.gridMode = GridModeGeographicDecimal;
self.minorLabelColor = self.majorLabelColor = [UIColor colorWithWhite:0.1 alpha:0.6];
self.minorLabelFont = [UIFont boldSystemFontOfSize:14.0];
self.majorLabelFont = [UIFont boldSystemFontOfSize:11.0];
return self;
}
- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache
{
if (tile.zoom < 0 || tile.zoom > 18)
return nil;
UIImage *image = nil;
tile = [[self mercatorToTileProjection] normaliseTile:tile];
image = [tileCache cachedImage:tile withCacheKey:[self uniqueTilecacheKey]];
if (image)
return image;
// Implementation
RMProjectedRect planetBounds = self.projection.planetBounds;
double scale = (1<<tile.zoom);
double tileMetersPerPixel = planetBounds.size.width / (self.tileSideLength * scale);
double paddedTileSideLength = self.tileSideLength + (2.0 * kTileSidePadding);
CGPoint bottomLeft = CGPointMake((tile.x * self.tileSideLength) - kTileSidePadding,
((scale - tile.y - 1) * self.tileSideLength) - kTileSidePadding);
RMProjectedRect normalizedProjectedRect;
normalizedProjectedRect.origin.x = (bottomLeft.x * tileMetersPerPixel) - fabs(planetBounds.origin.x);
normalizedProjectedRect.origin.y = (bottomLeft.y * tileMetersPerPixel) - fabs(planetBounds.origin.y);
normalizedProjectedRect.size.width = paddedTileSideLength * tileMetersPerPixel;
normalizedProjectedRect.size.height = paddedTileSideLength * tileMetersPerPixel;
CLLocationCoordinate2D southWest = [self.projection projectedPointToCoordinate:
RMProjectedPointMake(normalizedProjectedRect.origin.x,
normalizedProjectedRect.origin.y)];
CLLocationCoordinate2D northEast = [self.projection projectedPointToCoordinate:
RMProjectedPointMake(normalizedProjectedRect.origin.x + normalizedProjectedRect.size.width,
normalizedProjectedRect.origin.y + normalizedProjectedRect.size.height)];
double gridSpacing;
switch (self.gridMode)
{
case GridModeGeographic: {
gridSpacing = coordinateGridSpacing[tile.zoom];
break;
}
case GridModeGeographicDecimal:
case GridModeUTM:
default: {
gridSpacing = coordinateGridSpacingDecimal[tile.zoom];
break;
}
}
double coordinatesLatitudeSpan = northEast.latitude - southWest.latitude,
coordinatesLongitudeSpan = northEast.longitude - southWest.longitude;
double bottom = floor(southWest.latitude / gridSpacing) * gridSpacing,
top = floor(northEast.latitude / gridSpacing) * gridSpacing;
double left = ceil(southWest.longitude / gridSpacing) * gridSpacing,
right = ceil(northEast.longitude / gridSpacing) * gridSpacing;
// Draw the tile
UIGraphicsBeginImageContext(CGSizeMake(paddedTileSideLength, paddedTileSideLength));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, self.gridColor.CGColor);
CGContextSetLineWidth(context, self.gridLineWidth);
// Grid lines
for (double row = top; row >= bottom; row -= gridSpacing)
{
CGFloat yCoordinate = paddedTileSideLength - (((row - southWest.latitude) / coordinatesLatitudeSpan) * paddedTileSideLength);
CGContextMoveToPoint(context, 0.0, yCoordinate);
CGContextAddLineToPoint(context, paddedTileSideLength, yCoordinate);
}
for (double column = left; column <= right; column += gridSpacing)
{
CGFloat xCoordinate = ((column - southWest.longitude) / coordinatesLongitudeSpan) * paddedTileSideLength;
CGContextMoveToPoint(context, xCoordinate, 0.0);
CGContextAddLineToPoint(context, xCoordinate, paddedTileSideLength);
}
CGContextStrokePath(context);
// Labels
for (double row = top; row >= bottom; row -= gridSpacing)
{
CGFloat yCoordinate = paddedTileSideLength - (((row - southWest.latitude) / coordinatesLatitudeSpan) * paddedTileSideLength);
for (double column = (left - (gridSpacing/2.0)); column <= (right + (gridSpacing / 2.0)); column += gridSpacing)
{
CGFloat xCoordinate = ((column - southWest.longitude) / coordinatesLongitudeSpan) * paddedTileSideLength;
NSString *label1 = nil, *label2 = nil;
double degrees = (row > 0.0 ? floor(row) : ceil(row));
double fraction = (row > 0.0 ? row - floor(row) : ceil(row) - row);
switch (self.gridMode)
{
case GridModeGeographic: {
label1 = [NSString stringWithFormat:@"%.0f˚", degrees];
label2 = [NSString stringWithFormat:@"%02.0f'", fraction * 60.0];
break;
}
case GridModeGeographicDecimal:
case GridModeUTM:
default: {
label1 = [NSString stringWithFormat:@"%.0f", degrees];
label2 = [NSString stringWithFormat:@"%02.0f", fraction * 100.0];
}
}
// potential problem: some of these functions are not thread safe, so the app will crash
// if any other thread uses these functions outside of the @synchronized
@synchronized (self)
{
CGSize label1Size = [label1 sizeWithFont:self.majorLabelFont];
CGSize label2Size = [label2 sizeWithFont:self.minorLabelFont];
CGFloat upperBorder = yCoordinate - MAX((label1Size.height / 2.0), (label2Size.height / 2.0));
CGRect labelBackgroundRect = CGRectMake(xCoordinate - label1Size.width - 3.0, upperBorder - 1.0, label1Size.width + label2Size.width + 8.0, MAX(label1Size.height, label2Size.height) + 2.0);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
UIRectFill(labelBackgroundRect);
CGContextSetFillColorWithColor(context, self.majorLabelColor.CGColor);
[label1 drawAtPoint:CGPointMake(xCoordinate - label1Size.width - 1.0, upperBorder) withFont:self.majorLabelFont];
CGContextSetFillColorWithColor(context, self.minorLabelColor.CGColor);
[label2 drawAtPoint:CGPointMake(xCoordinate + 1.0, upperBorder) withFont:self.minorLabelFont];
}
}
}
for (double column=left; column<=right; column += gridSpacing)
{
CGFloat xCoordinate = ((column - southWest.longitude) / coordinatesLongitudeSpan) * paddedTileSideLength;
for (double row = (top + (gridSpacing/2.0)); row >= (bottom - (gridSpacing/2.0)); row -= gridSpacing)
{
CGFloat yCoordinate = paddedTileSideLength - (((row - southWest.latitude) / coordinatesLatitudeSpan) * paddedTileSideLength);
NSString *label1 = nil, *label2 = nil;
double degrees = (column > 0.0 ? floor(column) : ceil(column));
double fraction = (column > 0.0 ? column - floor(column) : ceil(column) - column);
switch (self.gridMode)
{
case GridModeGeographic: {
label1 = [NSString stringWithFormat:@"%.0f˚", degrees];
label2 = [NSString stringWithFormat:@"%02.0f'", fraction * 60.0];
break;
}
case GridModeGeographicDecimal:
case GridModeUTM:
default: {
label1 = [NSString stringWithFormat:@"%.0f", degrees];
label2 = [NSString stringWithFormat:@"%02.0f", fraction * 100.0];
}
}
@synchronized (self)
{
CGSize label1Size = [label1 sizeWithFont:self.majorLabelFont];
CGSize label2Size = [label2 sizeWithFont:self.minorLabelFont];
CGFloat upperBorder = yCoordinate - MAX((label1Size.height / 2.0), (label2Size.height / 2.0));
CGRect labelBackgroundRect = CGRectMake(xCoordinate - label1Size.width - 3.0, upperBorder - 1.0, label1Size.width + label2Size.width + 8.0, MAX(label1Size.height, label2Size.height) + 2.0);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
UIRectFill(labelBackgroundRect);
CGContextSetFillColorWithColor(context, self.majorLabelColor.CGColor);
[label1 drawAtPoint:CGPointMake(xCoordinate - label1Size.width - 1.0, upperBorder) withFont:self.majorLabelFont];
CGContextSetFillColorWithColor(context, self.minorLabelColor.CGColor);
[label2 drawAtPoint:CGPointMake(xCoordinate + 1.0, upperBorder) withFont:self.minorLabelFont];
}
}
}
// Image
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], CGRectMake(kTileSidePadding, kTileSidePadding, self.tileSideLength, self.tileSideLength));
image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
if (image)
[tileCache addImage:image forTile:tile withCacheKey:[self uniqueTilecacheKey]];
return image;
}
- (NSString *)uniqueTilecacheKey
{
return @"RMCoordinateGrid";
}
- (NSString *)shortName
{
return @"Coordinate grid";
}
- (NSString *)longDescription
{
return [self shortName];
}
- (NSString *)shortAttribution
{
return @"n/a";
}
- (NSString *)longAttribution
{
return @"n/a";
}
@end
... ...
... ... @@ -47,6 +47,8 @@
16128CF6148D295300C23C0E /* RMOpenSeaMapSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 16128CF4148D295300C23C0E /* RMOpenSeaMapSource.m */; };
161E563A1594664E00B00BB6 /* RMOpenSeaMapLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 161E56381594664E00B00BB6 /* RMOpenSeaMapLayer.h */; };
161E563B1594664E00B00BB6 /* RMOpenSeaMapLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 161E56391594664E00B00BB6 /* RMOpenSeaMapLayer.m */; };
1656665515A1DF7900EF3DC7 /* RMCoordinateGridSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 1656665315A1DF7900EF3DC7 /* RMCoordinateGridSource.h */; };
1656665615A1DF7900EF3DC7 /* RMCoordinateGridSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1656665415A1DF7900EF3DC7 /* RMCoordinateGridSource.m */; };
16EC85D2133CA6C300219947 /* RMAbstractMercatorTileSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 16EC85CC133CA6C300219947 /* RMAbstractMercatorTileSource.h */; };
16EC85D3133CA6C300219947 /* RMAbstractMercatorTileSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 16EC85CD133CA6C300219947 /* RMAbstractMercatorTileSource.m */; };
16EC85D4133CA6C300219947 /* RMAbstractWebMapSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 16EC85CE133CA6C300219947 /* RMAbstractWebMapSource.h */; };
... ... @@ -204,6 +206,8 @@
16128CF4148D295300C23C0E /* RMOpenSeaMapSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMOpenSeaMapSource.m; sourceTree = "<group>"; };
161E56381594664E00B00BB6 /* RMOpenSeaMapLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMOpenSeaMapLayer.h; sourceTree = "<group>"; };
161E56391594664E00B00BB6 /* RMOpenSeaMapLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMOpenSeaMapLayer.m; sourceTree = "<group>"; };
1656665315A1DF7900EF3DC7 /* RMCoordinateGridSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMCoordinateGridSource.h; sourceTree = "<group>"; };
1656665415A1DF7900EF3DC7 /* RMCoordinateGridSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMCoordinateGridSource.m; sourceTree = "<group>"; };
16EC85CC133CA6C300219947 /* RMAbstractMercatorTileSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMAbstractMercatorTileSource.h; sourceTree = "<group>"; };
16EC85CD133CA6C300219947 /* RMAbstractMercatorTileSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMAbstractMercatorTileSource.m; sourceTree = "<group>"; };
16EC85CE133CA6C300219947 /* RMAbstractWebMapSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMAbstractWebMapSource.h; sourceTree = "<group>"; };
... ... @@ -366,6 +370,8 @@
16EC85CB133CA69A00219947 /* Map sources */ = {
isa = PBXGroup;
children = (
1656665315A1DF7900EF3DC7 /* RMCoordinateGridSource.h */,
1656665415A1DF7900EF3DC7 /* RMCoordinateGridSource.m */,
D1437B33122869E400888DAE /* RMDBMapSource.h */,
D1437B32122869E400888DAE /* RMDBMapSource.m */,
1607499314E120A100D535F5 /* RMGenericMapSource.h */,
... ... @@ -698,6 +704,7 @@
16F98C961590CFF000FF90CE /* RMShape.h in Headers */,
16FBF07615936BF1004ECAD1 /* RMTileSourcesContainer.h in Headers */,
161E563A1594664E00B00BB6 /* RMOpenSeaMapLayer.h in Headers */,
1656665515A1DF7900EF3DC7 /* RMCoordinateGridSource.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
... ... @@ -919,6 +926,7 @@
16F98C971590CFF000FF90CE /* RMShape.m in Sources */,
16FBF07715936BF1004ECAD1 /* RMTileSourcesContainer.m in Sources */,
161E563B1594664E00B00BB6 /* RMOpenSeaMapLayer.m in Sources */,
1656665615A1DF7900EF3DC7 /* RMCoordinateGridSource.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
... ...
... ... @@ -16,6 +16,7 @@
#import "RMProjection.h"
#import "RMAnnotation.h"
#import "RMQuadTree.h"
#import "RMCoordinateGridSource.h"
@implementation MainViewController
{
... ... @@ -98,6 +99,7 @@
// mapView.enableDragging = YES;
// mapView.debugTiles = YES;
// [mapView setConstraintsSouthWest:CLLocationCoordinate2DMake(47.0, 10.0) northEast:CLLocationCoordinate2DMake(48.0, 11.0)];
// [mapView addTileSource:[[[RMCoordinateGridSource alloc] init] autorelease]];
UIImage *clusterMarkerImage = [UIImage imageNamed:@"marker-blue.png"];
mapView.clusterMarkerSize = clusterMarkerImage.size;
... ...