/MapView/Map/RMFractalTileProjection.m
Objective C | 201 lines | 128 code | 40 blank | 33 comment | 11 complexity | b1f571d9dfe056107c44297ebd5adb0c MD5 | raw file
1// 2// RMFractalTileProjection.m 3// 4// Copyright (c) 2008-2009, Route-Me Contributors 5// All rights reserved. 6// 7// Redistribution and use in source and binary forms, with or without 8// modification, are permitted provided that the following conditions are met: 9// 10// * Redistributions of source code must retain the above copyright notice, this 11// list of conditions and the following disclaimer. 12// * Redistributions in binary form must reproduce the above copyright notice, 13// this list of conditions and the following disclaimer in the documentation 14// and/or other materials provided with the distribution. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26// POSSIBILITY OF SUCH DAMAGE. 27 28#import "RMFractalTileProjection.h" 29#import "RMMercatorToScreenProjection.h" 30#import "RMProjection.h" 31#import <math.h> 32 33@implementation RMFractalTileProjection 34 35@synthesize maxZoom, minZoom; 36@synthesize tileSideLength; 37@synthesize planetBounds; 38 39-(id) initFromProjection:(RMProjection*)projection tileSideLength:(NSUInteger)aTileSideLength maxZoom: (NSUInteger) aMaxZoom minZoom: (NSUInteger) aMinZoom 40{ 41 if (![super init]) 42 return nil; 43 44 // We don't care about the rest of the projection... just the bounds is important. 45 planetBounds = [projection planetBounds]; 46 47 if (planetBounds.size.width == 0.0f || planetBounds.size.height == 0.0f) 48 { 49 /// \bug magic string literals 50 @throw [NSException exceptionWithName:@"RMUnknownBoundsException" 51 reason:@"RMFractalTileProjection was initialised with a projection with unknown bounds" 52 userInfo:nil]; 53 } 54 55 tileSideLength = aTileSideLength; 56 maxZoom = aMaxZoom; 57 minZoom = aMinZoom; 58 59 scaleFactor = log2(planetBounds.size.width / tileSideLength); 60 61 return self; 62} 63 64- (void) setTileSideLength: (NSUInteger) aTileSideLength 65{ 66 tileSideLength = aTileSideLength; 67 68 scaleFactor = log2(planetBounds.size.width / tileSideLength); 69} 70 71- (void) setMinZoom: (NSUInteger) aMinZoom 72{ 73 minZoom = aMinZoom; 74} 75 76- (void) setMaxZoom: (NSUInteger) aMaxZoom 77{ 78 maxZoom = aMaxZoom; 79} 80 81- (float) normaliseZoom: (float) zoom 82{ 83 float normalised_zoom = roundf(zoom); 84 85 if (normalised_zoom > maxZoom) 86 normalised_zoom = maxZoom; 87 if (normalised_zoom < minZoom) 88 normalised_zoom = minZoom; 89 90 return normalised_zoom; 91} 92 93- (float) limitFromNormalisedZoom: (float) zoom 94{ 95 return exp2f(zoom); 96} 97 98- (RMTile) normaliseTile: (RMTile) tile 99{ 100 // The mask contains a 1 for every valid x-coordinate bit. 101 uint32_t mask = 1; 102 for (int i = 0; i < tile.zoom; i++) 103 mask <<= 1; 104 105 mask -= 1; 106 107 tile.x &= mask; 108 109 // If the tile's y coordinate is off the screen 110 if (tile.y & (~mask)) 111 { 112 return RMTileDummy(); 113 } 114 115 return tile; 116} 117 118- (RMProjectedPoint) constrainPointHorizontally: (RMProjectedPoint) aPoint 119{ 120 while (aPoint.easting < planetBounds.origin.easting) 121 aPoint.easting += planetBounds.size.width; 122 while (aPoint.easting > (planetBounds.origin.easting + planetBounds.size.width)) 123 aPoint.easting -= planetBounds.size.width; 124 125 return aPoint; 126} 127 128- (RMTilePoint) projectInternal: (RMProjectedPoint)aPoint normalisedZoom:(float)zoom limit:(float) limit 129{ 130 RMTilePoint tile; 131 RMProjectedPoint newPoint = [self constrainPointHorizontally:aPoint]; 132 133 double x = (newPoint.easting - planetBounds.origin.easting) / planetBounds.size.width * limit; 134 // Unfortunately, y is indexed from the bottom left.. hence we have to translate it. 135 double y = (double)limit * ((planetBounds.origin.northing - newPoint.northing) / planetBounds.size.height + 1); 136 137 tile.tile.x = (uint32_t)x; 138 tile.tile.y = (uint32_t)y; 139 tile.tile.zoom = zoom; 140 tile.offset.x = (float)x - tile.tile.x; 141 tile.offset.y = (float)y - tile.tile.y; 142 143 return tile; 144} 145 146- (RMTilePoint) project: (RMProjectedPoint)aPoint atZoom:(float)zoom 147{ 148 float normalised_zoom = [self normaliseZoom:zoom]; 149 float limit = [self limitFromNormalisedZoom:normalised_zoom]; 150 151 return [self projectInternal:aPoint normalisedZoom:normalised_zoom limit:limit]; 152} 153 154- (RMTileRect) projectRect: (RMProjectedRect)aRect atZoom:(float)zoom 155{ 156 /// \bug assignment of float to int, WTF? 157 int normalised_zoom = [self normaliseZoom:zoom]; 158 float limit = [self limitFromNormalisedZoom:normalised_zoom]; 159 160 RMTileRect tileRect; 161 // The origin for projectInternal will have to be the top left instead of the bottom left. 162 RMProjectedPoint topLeft = aRect.origin; 163 topLeft.northing += aRect.size.height; 164 tileRect.origin = [self projectInternal:topLeft normalisedZoom:normalised_zoom limit:limit]; 165 166 tileRect.size.width = aRect.size.width / planetBounds.size.width * limit; 167 tileRect.size.height = aRect.size.height / planetBounds.size.height * limit; 168 169 return tileRect; 170} 171 172-(RMTilePoint) project: (RMProjectedPoint)aPoint atScale:(float)scale 173{ 174 return [self project:aPoint atZoom:[self calculateZoomFromScale:scale]]; 175} 176-(RMTileRect) projectRect: (RMProjectedRect)aRect atScale:(float)scale 177{ 178 return [self projectRect:aRect atZoom:[self calculateZoomFromScale:scale]]; 179} 180 181-(RMTileRect) project: (RMMercatorToScreenProjection*)screen; 182{ 183 return [self projectRect:[screen projectedBounds] atScale:[screen metersPerPixel]]; 184} 185 186-(float) calculateZoomFromScale: (float) scale 187{ // zoom = log2(bounds.width/tileSideLength) - log2(s) 188 return scaleFactor - log2(scale); 189} 190 191-(float) calculateNormalisedZoomFromScale: (float) scale 192{ 193 return [self normaliseZoom:[self calculateZoomFromScale:scale]]; 194} 195 196-(float) calculateScaleFromZoom: (float) zoom 197{ 198 return planetBounds.size.width / tileSideLength / exp2(zoom); 199} 200 201@end