PageRenderTime 233ms CodeModel.GetById 166ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 0ms

/MapView/Map/RMTileImageSet.m

http://github.com/route-me/route-me
Objective C | 548 lines | 404 code | 81 blank | 63 comment | 70 complexity | 14a80c12aa30c431e883cd134e41eb1b MD5 | raw file
  1//
  2//  RMTileImageSet.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 "RMTileImageSet.h"
 29#import "RMTileImage.h"
 30#import "RMPixel.h"
 31#import "RMTileSource.h"
 32
 33// For notification strings
 34#import "RMTileLoader.h"
 35
 36#import "RMMercatorToTileProjection.h"
 37
 38@implementation RMTileImageSet
 39
 40@synthesize delegate, tileDepth;
 41
 42-(id) initWithDelegate: (id) _delegate
 43{
 44	if (![super init])
 45		return nil;
 46
 47	tileSource = nil;
 48	self.delegate = _delegate;
 49	images = [[NSMutableSet alloc] init];
 50	[[NSNotificationCenter defaultCenter]
 51     addObserver:self
 52     selector:@selector(tileImageLoaded:)
 53     name:RMMapImageLoadedNotification
 54     object:nil
 55     ];
 56	return self;
 57}
 58
 59-(void) dealloc
 60{
 61	[[NSNotificationCenter defaultCenter] removeObserver:self];
 62	[self removeAllTiles];
 63	[images release];
 64	[super dealloc];
 65}
 66
 67-(void) removeTile: (RMTile) tile
 68{
 69	RMTileImage *img;
 70
 71	NSAssert(!RMTileIsDummy(tile), @"attempted to remove dummy tile");
 72	if (RMTileIsDummy(tile))
 73	{
 74		RMLog(@"attempted to remove dummy tile...??");
 75		return;
 76	}
 77
 78	RMTileImage *dummyTile = [RMTileImage dummyTile:tile];
 79	img = [images member:dummyTile];
 80	if (!img) {
 81		return;
 82	}
 83
 84	if ([delegate respondsToSelector: @selector(tileRemoved:)])
 85	{
 86		[delegate tileRemoved:tile];
 87	}
 88
 89	[[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageRemovedFromScreenNotification object:img];
 90	[images removeObject:dummyTile];
 91}
 92
 93-(void) removeAllTiles
 94{
 95    @synchronized(self){
 96        for (RMTileImage * img in [images allObjects]) {
 97            [self removeTile: img.tile];
 98        }
 99    }
100}
101
102- (void) setTileSource: (id<RMTileSource>)newTileSource
103{
104	[self removeAllTiles];
105
106	tileSource = newTileSource;
107}
108
109-(void) addTile: (RMTile) tile WithImage: (RMTileImage *)image At: (CGRect) screenLocation
110{
111	BOOL tileNeeded;
112
113	tileNeeded = YES;
114    @synchronized(self){
115        for (RMTileImage *img in images)
116        {
117            if (![img isLoaded])
118            {
119                continue;
120            }
121            if ([self isTile:tile worseThanTile:img.tile])
122            {
123                tileNeeded = NO;
124                break;
125            }
126        }
127    }
128	if (!tileNeeded) {
129		return;
130	}
131
132	if ([image isLoaded]) {
133		[self removeTilesWorseThan:image];
134	}
135
136	image.screenLocation = screenLocation;
137	[images addObject:image];
138
139	if (!RMTileIsDummy(image.tile))
140	{
141		if([delegate respondsToSelector:@selector(tileAdded:WithImage:)])
142		{
143			[delegate tileAdded:tile WithImage:image];
144		}
145        
146		[[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageAddedToScreenNotification object:image];
147	}
148}
149
150-(void) addTile: (RMTile) tile At: (CGRect) screenLocation
151{
152	//	RMLog(@"addTile: %d %d", tile.x, tile.y);
153
154	RMTileImage *dummyTile = [RMTileImage dummyTile:tile];
155	RMTileImage *tileImage = [images member:dummyTile];
156
157	if (tileImage != nil)
158	{
159		[tileImage setScreenLocation:screenLocation];
160		[images addObject:dummyTile];
161	}
162	else
163	{
164		RMTileImage *image = [tileSource tileImage:tile];
165		if (image != nil)
166			[self addTile:tile WithImage:image At:screenLocation];
167	}
168}
169
170// Add tiles inside rect protected to bounds. Return rectangle containing bounds
171// extended to full tile loading area
172-(CGRect) addTiles: (RMTileRect)rect ToDisplayIn:(CGRect)bounds
173{
174    //	RMLog(@"addTiles: %d %d - %f %f", rect.origin.tile.x, rect.origin.tile.y, rect.size.width, rect.size.height);
175
176	RMTile t;
177	float pixelsPerTile = bounds.size.width / rect.size.width;
178	RMTileRect roundedRect = RMTileRectRound(rect);
179	// The number of tiles we'll load in the vertical and horizontal directions
180	int tileRegionWidth = (int)roundedRect.size.width;
181	int tileRegionHeight = (int)roundedRect.size.height;
182	id<RMMercatorToTileProjection> proj = [tileSource mercatorToTileProjection];
183	short minimumZoom = [tileSource minZoom], alternateMinimum;
184
185	// Now we translate the loaded region back into screen space for loadedBounds.
186	CGRect newLoadedBounds;
187	newLoadedBounds.origin.x = bounds.origin.x - (rect.origin.offset.x * pixelsPerTile);
188	newLoadedBounds.origin.y = bounds.origin.y - (rect.origin.offset.y * pixelsPerTile);	
189	newLoadedBounds.size.width = tileRegionWidth * pixelsPerTile;
190	newLoadedBounds.size.height = tileRegionHeight * pixelsPerTile;
191
192	alternateMinimum = zoom - tileDepth - 1;
193	if (minimumZoom < alternateMinimum)
194	{
195		minimumZoom = alternateMinimum;
196	}
197
198	for (;;)
199	{
200		CGRect screenLocation;
201		screenLocation.size.width = pixelsPerTile;
202		screenLocation.size.height = pixelsPerTile;
203		t.zoom = rect.origin.tile.zoom;
204
205		for (t.x = roundedRect.origin.tile.x; t.x < roundedRect.origin.tile.x + tileRegionWidth; t.x++)
206		{
207			for (t.y = roundedRect.origin.tile.y; t.y < roundedRect.origin.tile.y + tileRegionHeight; t.y++)
208			{
209				RMTile normalisedTile = [proj normaliseTile: t];
210
211				if (RMTileIsDummy(normalisedTile))
212					continue;
213
214				// this regrouping of terms is better for calculation precision (issue 128)		
215				screenLocation.origin.x = bounds.origin.x + (t.x - rect.origin.tile.x - rect.origin.offset.x) * pixelsPerTile;		
216				screenLocation.origin.y = bounds.origin.y + (t.y - rect.origin.tile.y - rect.origin.offset.y) * pixelsPerTile;
217
218				[self addTile:normalisedTile At:screenLocation];
219			}
220		}
221
222		// adjust rect for next zoom level down until we're at minimum
223		if (--rect.origin.tile.zoom <= minimumZoom)
224			break;
225		if (rect.origin.tile.x & 1)
226			rect.origin.offset.x += 1.0;
227		if (rect.origin.tile.y & 1)
228			rect.origin.offset.y += 1.0;
229		rect.origin.tile.x /= 2;
230		rect.origin.tile.y /= 2;
231		rect.size.width *= 0.5;
232		rect.size.height *= 0.5;
233		rect.origin.offset.x *= 0.5;
234		rect.origin.offset.y *= 0.5;
235		pixelsPerTile = bounds.size.width / rect.size.width;
236		roundedRect = RMTileRectRound(rect);
237		// The number of tiles we'll load in the vertical and horizontal directions
238		tileRegionWidth = (int)roundedRect.size.width;
239		tileRegionHeight = (int)roundedRect.size.height;
240	}
241
242	return newLoadedBounds;
243}
244
245-(RMTileImage*) imageWithTile: (RMTile) tile
246{
247	RMTileImage *dummyTile = [RMTileImage dummyTile:tile];
248
249	return [images member:dummyTile];
250}
251
252-(NSUInteger) count
253{
254	return [images count];
255}
256
257- (void)moveBy: (CGSize) delta
258{
259    @synchronized(self){
260        for (RMTileImage *image in images)
261        {
262            [image moveBy: delta];
263        }
264    }
265}
266
267- (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center
268{
269    @synchronized(self){
270        for (RMTileImage *image in images)
271        {
272            [image zoomByFactor:zoomFactor near:center];
273        }
274    }
275}
276
277/*
278 - (void) drawRect:(CGRect) rect
279 {
280 for (RMTileImage *image in images)
281 {
282 [image draw];
283 }
284 }
285 */
286
287- (void) printDebuggingInformation
288{
289	float biggestSeamRight = 0.0f;
290	float biggestSeamDown = 0.0f;
291
292	for (RMTileImage *image in images)
293	{
294		CGRect location = [image screenLocation];
295        /*		RMLog(@"Image at %f, %f %f %f",
296         location.origin.x,
297         location.origin.y,
298         location.origin.x + location.size.width,
299         location.origin.y + location.size.height);
300         */
301		float seamRight = INFINITY;
302		float seamDown = INFINITY;
303
304        @synchronized(self){
305            for (RMTileImage *other_image in images)
306            {
307                CGRect other_location = [other_image screenLocation];
308                if (other_location.origin.x > location.origin.x)
309                    seamRight = MIN(seamRight, other_location.origin.x - (location.origin.x + location.size.width));
310                if (other_location.origin.y > location.origin.y)
311                    seamDown = MIN(seamDown, other_location.origin.y - (location.origin.y + location.size.height));
312            }
313		}
314		if (seamRight != INFINITY)
315			biggestSeamRight = MAX(biggestSeamRight, seamRight);
316		
317		if (seamDown != INFINITY)
318			biggestSeamDown = MAX(biggestSeamDown, seamDown);
319	}
320
321	RMLog(@"Biggest seam right: %f  down: %f", biggestSeamRight, biggestSeamDown);
322}
323
324- (void)cancelLoading
325{
326    @synchronized(self){
327        for (RMTileImage *image in images)
328        {
329            [image cancelLoading];
330        }
331    }
332}
333
334- (RMTileImage *)anyTileImage {
335	return [images anyObject];
336}
337
338- (short)zoom
339{
340	return zoom;
341}
342
343- (void)setZoom:(short)value
344{
345	if (zoom == value) {
346		// no need to act
347		return;
348	}
349    
350	zoom = value;
351    @synchronized(self){
352        for (RMTileImage *image in [images allObjects])
353        {
354            if (![image isLoaded]) {
355                continue;
356            }
357            [self removeTilesWorseThan:image];
358        }
359    }
360}
361
362- (BOOL)fullyLoaded
363{
364	BOOL fullyLoaded = YES;
365
366    @synchronized(self){
367        for (RMTileImage *image in images)
368        {
369            if (![image isLoaded])
370            {
371                fullyLoaded = NO;
372                break;
373            }
374        }
375    }
376	return fullyLoaded;
377}
378
379- (void)tileImageLoaded:(NSNotification *)notification
380{
381	RMTileImage *img = (RMTileImage *)[notification object];
382
383	if (!img || img != [images member:img])
384	{
385		// i don't contain img, it may be already removed or in another set
386		return;
387	}
388
389	[self removeTilesWorseThan:img];
390}
391
392- (void)removeTilesWorseThan:(RMTileImage *)newImage {
393	RMTile newTile = newImage.tile;
394
395	if (newTile.zoom > zoom) {
396		// no tiles are worse since this one is too detailed to keep long-term
397		return;
398	}
399
400    @synchronized(self){
401        for (RMTileImage *oldImage in [images allObjects])
402        {
403            RMTile oldTile = oldImage.tile;
404
405            if (oldImage == newImage)
406            {
407                continue;
408            }
409            if ([self isTile:oldTile worseThanTile:newTile])
410            {
411                [oldImage cancelLoading];
412                [self removeTile:oldTile];
413            }
414        }
415    }
416}
417
418- (BOOL)isTile:(RMTile)subject worseThanTile:(RMTile)object
419{
420	short subjZ, objZ;
421	uint32_t sx, sy, ox, oy;
422
423	objZ = object.zoom;
424	if (objZ > zoom)
425	{
426		// can't be worse than this tile, it's too detailed to keep long-term
427		return NO;
428	}
429
430	subjZ = subject.zoom;
431	if (subjZ + tileDepth >= zoom && subjZ <= zoom)
432	{
433		// this tile isn't bad, it's within zoom limits
434		return NO;
435	}
436
437	sx = subject.x;
438	sy = subject.y;
439	ox = object.x;
440	oy = object.y;
441
442	if (subjZ < objZ)
443	{
444		// old tile is larger & blurrier
445		unsigned int dz = objZ - subjZ;
446
447		ox >>= dz;
448		oy >>= dz;
449	}
450	else if (objZ < subjZ)
451	{
452		// old tile is smaller & more detailed
453		unsigned int dz = subjZ - objZ;
454
455		sx >>= dz;
456		sy >>= dz;
457	}
458	if (sx != ox || sy != oy)
459	{
460		// Tiles don't overlap
461		return NO;
462	}
463
464	if (abs(zoom - subjZ) < abs(zoom - objZ))
465	{
466		// subject is closer to desired zoom level than object, so it's not worse
467		return NO;
468	}
469
470	return YES;
471}
472
473-(void) removeTilesOutsideOf: (RMTileRect)rect
474{
475	uint32_t minX, maxX, minY, maxY, span;
476	short currentZoom = rect.origin.tile.zoom;
477	RMTile wrappedTile;
478	id<RMMercatorToTileProjection> proj = [tileSource mercatorToTileProjection];
479
480	rect = RMTileRectRound(rect);
481	minX = rect.origin.tile.x;
482	span = rect.size.width > 1.0f ? (uint32_t)rect.size.width - 1 : 0;
483	maxX = rect.origin.tile.x + span;
484	minY = rect.origin.tile.y;
485	span = rect.size.height > 1.0f ? (uint32_t)rect.size.height - 1 : 0;
486	maxY = rect.origin.tile.y + span;
487
488	wrappedTile.x = maxX;
489	wrappedTile.y = maxY;
490	wrappedTile.zoom = rect.origin.tile.zoom;
491	wrappedTile = [proj normaliseTile:wrappedTile];
492	if (!RMTileIsDummy(wrappedTile))
493	{
494		maxX = wrappedTile.x;
495	}
496
497	for(RMTileImage *img in [images allObjects])
498	{
499		RMTile tile = img.tile;
500		short tileZoom = tile.zoom;
501		uint32_t x, y, zoomedMinX, zoomedMaxX, zoomedMinY, zoomedMaxY;
502
503		x = tile.x;
504		y = tile.y;
505		zoomedMinX = minX;
506		zoomedMaxX = maxX;
507		zoomedMinY = minY;
508		zoomedMaxY = maxY;
509
510		if (tileZoom < currentZoom)
511		{
512			// Tile is too large for current zoom level
513			unsigned int dz = currentZoom - tileZoom;
514
515			zoomedMinX >>= dz;
516			zoomedMaxX >>= dz;
517			zoomedMinY >>= dz;
518			zoomedMaxY >>= dz;
519		}
520		else
521		{
522			// Tile is too small & detailed for current zoom level
523			unsigned int dz = tileZoom - currentZoom;
524            
525			x >>= dz;
526			y >>= dz;
527		}
528        
529		if (y >= zoomedMinY && y <= zoomedMaxY)
530		{
531			if (zoomedMinX <= zoomedMaxX)
532			{
533				if (x >= zoomedMinX && x <= zoomedMaxX)
534					continue;
535			}
536			else
537			{
538				if (x >= zoomedMinX || x <= zoomedMaxX)
539					continue;
540			}
541
542		}
543		// if haven't continued, tile is outside of rect
544		[self removeTile:tile];
545	}
546}
547
548@end