Archive

Archive for August, 2010

iPhone Bug Note #1: Route-Me and Three20 in the same Project. _aasin dup symbol when building on iOS4

August 14th, 2010 rupert Comments off

It seems route-me and three20 doesn’t mix well on iOS4. When building my project (Philippines), I get an error on _aasin duplicate symbol, which was also discussed on route-me issue 138 at code.google.com

Three20 needs linker flag shown below:

-all_load

The only way to go about this was linking proj4 directly from the project and removing it from route-me, thus no need to remove the “all_load” linker flag. Note: I tried removing the “all_load” linker flag from my project and it builds fine. However, the app crashes.

1. Drag the proj4.xcodeproj from RouteMe to your main project.

2. Afterwards delete the proj4.xcodeproj from RouteMe.
RouteMe.png

3. Your project should now reference both MapView and Proj4.
three20.png

Categories: iphone Tags:

iPhone Dev Note #21: Route-Me Offline Mapping from Database

August 12th, 2010 rupert 22 comments

Part I: Download the osm (openstreetmap) tiles
1. Download the tiles from osm using downloadosmtiles.pl
- Download Geo-OSM-Tiles-0.02.tar.gz from CPAN
- See README file. Compile and build

   perl Makefile.PL
   make
   make test
   make install

- Copy downloadosmtiles.pl to /usr/bin
- Usage:

downloadosmtiles.pl --lat=min_lat:max_lat --lon=min_long:max_long --zoom=min_zoom:max_zoom

How do you set the min_lat, max_lat and min_long, max_long?
- Go to www.openstreetmap.org
- Click on the “Edit” tab
- You will see the extent or bounds of the map under the section “Area to Export”
- Alternatively, you can click on “Manually select a different area” to specifically choose an area.

osm.png

downloadosmtiles.pl --lat=6.9443:7.2261 --lon=125.5082:125.7104 --zoom=6:12

The tiles will be downloaded to the current directory.
dir.png

Part II:Put the tiles in the sqlite database
1. Read Frank’s email to route-me group regarding map2sqlite

2. Download map2sqlite-1.0.tar.bz2.

3. Build the map2sqlite XCodeProj. Afterwards, find map2sqlite and drop it in /usr/bin.

cp map2sqlite /usr/bin

4. Run map2sqlite to import the tiles in sqlite.

map2sqlite -db ph-1.0.0.db -mapdir ph-osm-map/
 
2010-08-12 17:24:40.749 map2sqlite[14113:903] map2sqlite 1.0
2010-08-12 17:24:40.756 map2sqlite[14113:903] Creating ph-1.0.0.db
2010-08-12 17:24:40.761 map2sqlite[14113:903] Importing map tiles at ph-osm-map/
2010-08-12 17:25:03.169 map2sqlite[14113:903] 
2010-08-12 17:25:03.170 map2sqlite[14113:903] Map statistics
2010-08-12 17:25:03.170 map2sqlite[14113:903] --------------
2010-08-12 17:25:03.171 map2sqlite[14113:903] map db:            ph-1.0.0.db
2010-08-12 17:25:03.171 map2sqlite[14113:903] file size:         13758464 bytes
2010-08-12 17:25:03.172 map2sqlite[14113:903] tile directory:    ph-osm-map/
2010-08-12 17:25:03.172 map2sqlite[14113:903] number of tiles:   9091
2010-08-12 17:25:03.173 map2sqlite[14113:903] zoom levels:       6 - 11
2010-08-12 17:25:03.218 map2sqlite[14113:903] zoom level  6:        12 tiles, (    28,    52)x(    31,    54), {x=112.500000,y=21.943047}x{x=129.375000,y=0.000000}
2010-08-12 17:25:03.219 map2sqlite[14113:903] zoom level  7:        35 tiles, (    56,   105)x(    62,   109), {x=115.312500,y=21.943047}x{x=129.375000,y=2.811371}
2010-08-12 17:25:03.222 map2sqlite[14113:903] zoom level  8:       117 tiles, (   112,   210)x(   124,   218), {x=115.312500,y=21.943047}x{x=127.968750,y=4.214943}
2010-08-12 17:25:03.223 map2sqlite[14113:903] zoom level  9:       450 tiles, (   225,   420)x(   249,   437), {x=115.312500,y=21.289375}x{x=127.968750,y=4.214943}
2010-08-12 17:25:03.225 map2sqlite[14113:903] zoom level 10:      1715 tiles, (   450,   841)x(   498,   875), {x=115.664062,y=21.289375}x{x=127.968750,y=4.565474}
2010-08-12 17:25:03.231 map2sqlite[14113:903] zoom level 11:      6762 tiles, (   900,  1683)x(   997,  1751), {x=115.839844,y=21.289375}x{x=127.968750,y=4.565474}

5. ph-osm-map is 43.9 MB but was compressed to ph-1.0.0.db (13.8 MB)

Part III: Downlaod the route-me code from trunk and run some examples.
- Follow this previous tutorial

Part IV: Patch the trunk to incorporate the RMDBMapSource from Frank Schroder

1. What we need to add to the trunk. Download RMDBMapSource.zip

+ RMDBMapSource.h
+ RMDBMapSource.m
+ RMDBTileImage.h
+ RMDBTileImage.m

Copy the files above to the “Map” directory.

route-me-1.png

2. Edit RMTileImage.h and RMTileImage.m base on the patch below.

Index: MapView/Map/RMTileImage.h
===================================================================
--- MapView/Map/RMTileImage.h	(revision 605)
+++ MapView/Map/RMTileImage.h	(working copy)
@@ -37,8 +37,10 @@
 #import "RMNotifications.h"
 #import "RMTile.h"
 #import "RMTileProxy.h"
+#import "FMDatabase.h"
 
 @class RMTileImage;
+@class NSData;
 
 @interface RMTileImage : NSObject {
 	// I know this is a bit nasty.
@@ -64,6 +66,7 @@
 + (RMTileImage*)imageForTile: (RMTile) tile withURL: (NSString*)url;
 + (RMTileImage*)imageForTile: (RMTile) tile fromFile: (NSString*)filename;
 + (RMTileImage*)imageForTile: (RMTile) tile withData: (NSData*)data;
++ (RMTileImage*)imageForTile: (RMTile) tile fromDB: (FMDatabase*)db;
 
 - (void)moveBy: (CGSize) delta;
 - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center;
Index: MapView/Map/RMTileImage.m
===================================================================
--- MapView/Map/RMTileImage.m	(revision 605)
+++ MapView/Map/RMTileImage.m	(working copy)
@@ -29,6 +29,7 @@
 #import "RMWebTileImage.h"
 #import "RMTileLoader.h"
 #import "RMFileTileImage.h"
+#import "RMDBTileImage.h"
 #import "RMTileCache.h"
 #import "RMPixel.h"
 #import <QuartzCore/QuartzCore.h>
@@ -108,6 +109,11 @@
 	return [image autorelease];
 }
 
++ (RMTileImage*)imageForTile:(RMTile) _tile fromDB: (FMDatabase*)db
+{
+	return [[[RMDBTileImage alloc] initWithTile: _tile fromDB:db] autorelease];
+}
+
 -(void) cancelLoading
 {
 	[[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageLoadingCancelledNotification

We just need to add these lines on RMTileImage.h:

+#import "FMDatabase.h"
...
++ (RMTileImage*)imageForTile: (RMTile) tile fromDB: (FMDatabase*)db;

do the same for RMTileImage.m:

+#import "RMDBTileImage.h"
...
++ (RMTileImage*)imageForTile:(RMTile) _tile fromDB: (FMDatabase*)db
+{
+	return [[[RMDBTileImage alloc] initWithTile: _tile fromDB:db] autorelease];
+}

UPDATE (OCT 15, 2010): I am attaching my current RMTileImage.h and RMTileImage.m so you guys could double-check the changes I made. RMTileImage.zip

4. Still with me? Comment NSAssert on 609 on RMMapContents.m
rmcontents.png

Part V: RouteMeSampleMapDBOffline code
1. Download RouteMeSampleMapDBOffline.zip

2. Drop the project in the samples directory.
sample_proj.png

3. Build. You should be able to build this since the header path is relative to the route-me trunk.
build-config.png

4. Run from the simulator
finish.png

Categories: iphone, sqlite3 Tags: , ,

iPhone Note #14: Drawing a Point, Line, Polygon on top of MKMapview

August 9th, 2010 rupert 7 comments

UPDATE: Aug 9, 2010
DrawMap2.zip
Note: This does not contain the new MapKit functions for overlaying lines and polygons. This zip was created to compile against 4.0.0 but still have the same codebase.
============================

This is an update to iPhone DevNote #13. This post has solved my zooming/panning problem with a CustomView on top of my MKMapView courtesy of http://spitzkoff.com/craig/?p=108 (Craig’s blog).

The trick here is instead of doing the drawing on the drawRect method of the CustomView, we will use Craig’s methodology to use the drawRect method of a custom MKAnnotationView. Note, that he also used an internal view and made clipsToBounds = NO, this way we can draw the whole geometry on top of MKMapView not just a portion of it. The end result is the shape (polygon in this example) is below the added pins.

polygon_on_top_mapview.png

@interface LinePolygonAnnotationInternalView : UIView
{
	// line view which added this as a subview. 
	LinePolygonAnnotationView* _mainView;
}
@property (nonatomic, retain) LinePolygonAnnotationView* mainView;
@end
 
@implementation LinePolygonAnnotationInternalView
 
@synthesize mainView = _mainView;
 
-(id) init
{
	self = [super init];
	self.backgroundColor = [UIColor clearColor];
	self.clipsToBounds = NO;
 
	return self;
}
 
 
-(void) drawRect:(CGRect) rect
{
	GeometryAnnotation* myAnnotation = (GeometryAnnotation*)self.mainView.annotation;
 
	// only draw our lines if we're not int he moddie of a transition and we 
	// acutally have some points to draw. 
	if(!self.hidden && nil != myAnnotation.points && myAnnotation.points.count > 0)
	{
		CGContextRef context = UIGraphicsGetCurrentContext(); 
 
		// Drawing lines with a white stroke color
		CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
 
		// Draw them with a 2.0 stroke width so they are a bit more visible.
		CGContextSetLineWidth(context, 2.0);		
 
		if(myAnnotation.geometryType == kGeometryTypePolygon){
			CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
		}
 
		// Draw them with a 2.0 stroke width so they are a bit more visible.
		CGContextSetLineWidth(context, 2.0);
 
		for(int idx = 0; idx < myAnnotation.points.count; idx++)
		{
			CLLocation* location = [myAnnotation.points objectAtIndex:idx];
			CGPoint point = [self.mainView.mapView convertCoordinate:location.coordinate toPointToView:self];
 
			NSLog(@"Point: %lf, %lf", point.x, point.y);
 
			if(idx == 0)
			{
				// move to the first point
				CGContextMoveToPoint(context, point.x, point.y);
			}
			else
			{
				CGContextAddLineToPoint(context, point.x, point.y);
			}
		}
 
		if(myAnnotation.geometryType == kGeometryTypeLine){
			CGContextStrokePath(context);
		}
		else if(myAnnotation.geometryType == kGeometryTypePolygon){
			CGContextClosePath(context);
 
			CGContextDrawPath(context, kCGPathFillStroke);
		}
 
	}
 
 
}
 
-(void) dealloc
{
	self.mainView = nil;
 
	[super dealloc];
}
@end
 
@implementation LinePolygonAnnotationView
 
@synthesize mapView = _mapView;
 
- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
 
		self.backgroundColor = [UIColor clearColor];
 
		// do not clip the bounds. We need the LinePolygonAnnotationInternalView to be able to render the whole line/polygon, regardless of where the
		// actual annotation view is displayed. 
		self.clipsToBounds = NO;
 
		// create the internal line view that does the rendering of the line. 
		_internalView = [[LinePolygonAnnotationInternalView alloc] init];
		_internalView.mainView = self;
 
		[self addSubview:_internalView];
    }
    return self;
}
 
-(void) setMapView:(MKMapView*) mapView
{
	[_mapView release];
	_mapView = [mapView retain];
 
	[self regionChanged];
}
 
-(void) regionChanged
{
	NSLog(@"Region Changed");
 
	// move the internal line view. 
	CGPoint origin = CGPointMake(0, 0);
	origin = [_mapView convertPoint:origin toView:self];
 
	_internalView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height);
	[_internalView setNeedsDisplay];
 
}
 
- (void)dealloc 
{
	[_mapView release];
	[_internalView release];
 
    [super dealloc];
}
 
 
@end

I extended the class above to be able to draw both lines and polygons by checking a property (geometryType) of the GeometryAnnotation. If the geometryType is a line, then just stroke the path. However, if the geometryType is a polygon, then close the path and fill it.

if(myAnnotation.geometryType == kGeometryTypeLine){
	CGContextStrokePath(context);
}
else if(myAnnotation.geometryType == kGeometryTypePolygon){
	CGContextClosePath(context);
 
	CGContextDrawPath(context, kCGPathFillStroke);
}

And here is the GeometryAnnotation class. Most of the code is from Craig, i just added the geometryType property:

//  Created by Craig on 8/18/09.
//  Copyright Craig Spitzkoff 2009. All rights reserved.
//
 
#import "GeometryAnnotation.h"
 
@implementation GeometryAnnotation
@synthesize coordinate = _center;
@synthesize points = _points; 
@synthesize annotationID;
@synthesize geometryType;
 
-(id) initWithPoints:(NSArray*) points withGeometry:(GeometryType)geomType
{
	self = [super init];
 
	geometryType = geomType;
 
	_points = [[NSMutableArray alloc] initWithArray:points];
 
	// create a unique ID for this line so it can be added to dictionaries by this key. 
	self.annotationID = [NSString stringWithFormat:@"%p", self];
 
 
	// determine a logical center point for this line based on the middle of the lat/lon extents.
	double maxLat = -91;
	double minLat =  91;
	double maxLon = -181;
	double minLon =  181;
 
	for(CLLocation* currentLocation in _points)
	{
		CLLocationCoordinate2D coordinate = currentLocation.coordinate;
 
		if(coordinate.latitude > maxLat)
			maxLat = coordinate.latitude;
		if(coordinate.latitude < minLat)
			minLat = coordinate.latitude;
		if(coordinate.longitude > maxLon)
			maxLon = coordinate.longitude;
		if(coordinate.longitude < minLon)
			minLon = coordinate.longitude; 
	}
 
	_span.latitudeDelta = (maxLat + 90) - (minLat + 90);
	_span.longitudeDelta = (maxLon + 180) - (minLon + 180);
 
	// the center point is the average of the max and mins
	_center.latitude = minLat + _span.latitudeDelta / 2;
	_center.longitude = minLon + _span.longitudeDelta / 2;
 
	NSLog(@"Found center of new Annotation at %lf, %ld", _center.latitude, _center.longitude);
 
	return self;
}
 
-(MKCoordinateRegion) region
{
	MKCoordinateRegion region;
	region.center = _center;
	region.span = _span;
 
	return region;
}
 
-(void) dealloc
{
	[_points release];
 
	[super dealloc];
}
 
@end

Now that we have a way to draw a line/polygon as a custom MKAnnotationView, we need a custom TouchView (GeometryTouchView) which could accept the touch events.

For example, if the user wants to draw a line geometry, the GeometryTouchView would accept touch events from the user and add a point as a PointAnnotation in the Map. Succeeding points would be added to an array. For every point added, the MKAnnotationView drawRects method connects the points to produce a line. The MKAnnotationView is now added to the map.

Once the geometry is added as an annotation, the custom TouchView is hidden. This way we have access (panning/zooming) to the mapview. If we make a pan or a zoom, the region changes, thus we need to redraw the shape of the annotation again.

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
	if(currentAnnotationView != nil){
		NSLog(@"regionWillChangeAnimated");
 
		currentAnnotationView.hidden = YES;
	}
}
 
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
	if(currentAnnotationView != nil){
		NSLog(@"regionDidChangeAnimated");
 
		currentAnnotationView.hidden = NO;
		[currentAnnotationView regionChanged];
	}
}
-(void) regionChanged
{
	NSLog(@"Region Changed");
 
	// move the internal line view. 
	CGPoint origin = CGPointMake(0, 0);
	origin = [_mapView convertPoint:origin toView:self];
 
	_internalView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height);
	[_internalView setNeedsDisplay];
 
}

The resulting image is now:
polygon_small_on_top_of_mapview.png

(Download the DrawMap.zip code.) – old. This is for iOS < 4

Categories: iphone Tags: