Introduction
In this article I show a simple app that finds an ATM, Hotel, School, Collage, Hospital etc. at the current location or a specified location. To search a location we use a search bar and a list of searched items shows on a table then can be seen on a map; for that we use mapview.
To better understand it use the following procedure.
Step 1
Open XCode by double-clicking on it.
Step 2
Create a New XCode Project by clicking on it.
Step 3
Now select Empty View Application and click on Next.
Step 4
Now provide your Product Name and Company Identifier.
Step 5
Select the location where you want to save your project and click on Create.
Step 6
Now here we write the code.
AppDelegate.h
#import <UIKit/UIKit.h>
@class ViewController;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) ViewController *viewController;
@property(strong,nonatomic)UINavigationController *nav;
@end
AppDelegate.m
#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
@synthesize nav;
- (void)dealloc
{
[_window release];
[_viewController release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
sleep(5);
self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease];
self.nav = [[[UINavigationController alloc]initWithRootViewController:_viewController]autorelease];
self.window.rootViewController = self.nav;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
exit(0);
}
@end
viewController.h
#import <UIKit/UIKit.h>
#import "firstViewController.h"
@interface ViewController : UIViewController
@property(nonatomic,strong)IBOutlet UITableView *tblView;
@property(nonatomic,strong)NSMutableArray *arr;
@property(nonatomic,strong)IBOutlet UILabel *lbl;
@property(nonatomic,assign)Boolean flag;
@property(nonatomic,strong)NSString * lblStr;
-(IBAction)Click;
-(IBAction)Go;
@end
viewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
@synthesize tblView,arr,lblStr,lbl,flag;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
tblView.sectionHeaderHeight = 1;
return @" ";
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arr count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell==nil)
{
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]autorelease];
}
cell.textLabel.text=[arr objectAtIndex:indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
lblStr =[arr objectAtIndex:indexPath.row];
lbl.text = [NSString stringWithFormat:@" %@",lblStr]; //adding space before string.
flag = YES;
tblView.hidden = YES;
}
-(IBAction)Click
{
tblView.hidden = NO;
}
-(IBAction)Go
{
if (flag == YES)
{
firstViewController * first = [[firstViewController alloc]init];
first.optStr = [lblStr stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
first.optStr_new = [[first.optStr lowercaseString]stringByReplacingOccurrencesOfString:@" "withString:@"_"];
tblView.hidden = YES;
[self.navigationController pushViewController: first animated:YES];
[first release];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"Choose Category";
arr = [[NSMutableArray alloc]init];
[arr addObject:@"Accounting"];
[arr addObject:@"Airport"];
[arr addObject:@"Amusement park"];
[arr addObject:@"Aquarium"];
[arr addObject:@"ATM"];
[arr addObject:@"Bakery"];
[arr addObject:@"Art Gallery"];
[arr addObject:@"Bank"];
[arr addObject:@"Bar"];
[arr addObject:@"Beauty Salon"];
[arr addObject:@"Bicycle Store"];
[arr addObject:@"Zoo"];
[arr addObject:@"College"];
[arr addObject:@"Hotel"];
[arr sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
tblView.hidden = YES;
flag = NO;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
firstviewController.h
#import <UIKit/UIKit.h>
#import "MapKit/MapKit.h"
#import "mapViewController.h"
#import "CoreLocation/CoreLocation.h"
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define kGOOGLE_API_KEY @"AIzaSyDMHz7IOl-o6Dl89RHaO7d7Xzk3U9B_Ns8"
@interface firstViewController : UIViewController <UISearchBarDelegate,UISearchDisplayDelegate,MKMapViewDelegate, CLLocationManagerDelegate>
{
CLLocationManager *locationManager;
CLLocationCoordinate2D currentCentre;
int currentDist;
BOOL firstLaunch;
}
@property(nonatomic,strong)IBOutlet UIButton *btnLocate;
@property (retain, nonatomic) IBOutlet UITableView *table;
@property(nonatomic,strong)NSString *optStr,*optStr_new,*latStr,*longStr,*srchBarString,*nameStr,*addressStr;
@property(nonatomic,strong)IBOutlet UISearchBar *srchBar;
@property(nonatomic,strong)NSMutableArray *arrAddress,*arrName,*arrLati,*arrLongi;
@property(nonatomic,assign)BOOL isAddress;
@property(nonatomic,strong)IBOutlet MKMapView *viewMap;
@property(nonatomic,strong)IBOutlet UIButton *btnCurLoc,*btnAddrs;
-(IBAction)locateMap:(id)sender;
-(IBAction)btnCurrentLocation:(id)sender;
-(IBAction)btnAddress:(id)sender;
-(IBAction)Exit:(id)sender;
@end
firstviewController.m
#import "firstViewController.h"
#import "customCell.h"
@interface firstViewController ()
@end
@implementation firstViewController
@synthesize optStr,optStr_new,srchBar,table,arrAddress,arrName,btnLocate,arrLati,arrLongi,latStr,longStr,srchBarString,isAddress,nameStr,viewMap,addressStr,btnAddrs,btnCurLoc;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
btnAddrs.enabled = NO;
btnCurLoc.enabled = NO;
btnLocate.enabled = NO;
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar{
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
//btnLocate.enabled = NO;}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
//[self.searchDisplayController finalize];
btnAddrs.enabled = YES;
btnCurLoc.enabled = YES;
// }
NSLog(@"clicked");
}
- (void) searchBarSearchButtonClicked:(UISearchBar*) theSearchBar
{
int len = [ [srchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length];
if (len > 2)
{
[table reloadData];
[ srchBar resignFirstResponder ];
[self searchBarCancelButtonClicked:srchBar];
NSLog(@"clicked");
[self queryGooglePlaces:optStr_new];
[table reloadData];
[self.searchDisplayController setActive:NO animated:YES];
}else
{
[ srchBar resignFirstResponder ];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Improper Search"
message:@"Search term needs to be at least 3 characters in length."
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[self.searchDisplayController setActive:NO animated:YES];
[alert release];
}
}
-(void)viewDidLoad
{
[super viewDidLoad];
srchBar.delegate = self;
self.title = optStr;
btnLocate.enabled = NO;
isAddress = YES;
viewMap.hidden = YES;
//Make this controller the delegate for the map view.
self.viewMap.delegate = self;
// Ensure that you can view your own location in the map view.
[self.viewMap setShowsUserLocation:YES];
//Instantiate a location object.
locationManager = [[CLLocationManager alloc] init];
//Make this controller the delegate for the location manager.
[locationManager setDelegate:self];
//Set some parameters for the location object.
[locationManager setDistanceFilter:kCLDistanceFilterNone];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
firstLaunch=YES;
[srchBar setUserInteractionEnabled:NO];
}
-(IBAction)btnCurrentLocation:(id)sender
{
btnLocate.enabled = NO;
isAddress = NO;
[self queryGooglePlaces:optStr_new];
NSLog(@"isaddress = curr loc");
[srchBar setUserInteractionEnabled:NO];
}
-(IBAction)btnAddress:(id)sender
{
isAddress = YES;
NSLog(@"isaddress = addressss");
[srchBar setUserInteractionEnabled:YES];
}
-(void) queryGooglePlaces: (NSString *) googleType
{
srchBarString = [srchBar.text stringByReplacingOccurrencesOfString:@" "withString:@"+"];
// Build the url string to send to Google. NOTE: The kGOOGLE_API_KEY is a constant that should contain your own API key that you obtain from Google. See this link for more info:
// https://developers.google.com/maps/documentation/places/#Authentication
NSString *url = [[[NSString alloc]init]autorelease];
if(isAddress==YES)
{
url = [NSString stringWithFormat:@"https://maps.googleapis.com/maps/api/place/textsearch/json?query=%@+in+%@&sensor=true&key=%@",googleType,srchBarString,kGOOGLE_API_KEY];
//NSLog(@"URL = %@",url);
}
if (isAddress==NO)
{
// url = [NSString stringWithFormat:@"https://maps.googleapis.com/maps/api/place/search/json?location=%f,%f&radius=%@&types=%@&sensor=true&key=%@", currentCentre.latitude, currentCentre.longitude, [NSString stringWithFormat:@"%i", currentDist], googleType, kGOOGLE_API_KEY];
url = [NSString stringWithFormat:@"https://maps.googleapis.com/maps/api/place/search/json?location=%f,%f&radius=%@&types=%@&sensor=true&key=%@", currentCentre.latitude, currentCentre.longitude, [NSString stringWithFormat:@"%i", currentDist], googleType, kGOOGLE_API_KEY];
//NSLog(@"URL = %@",url);
}
//Formulate the string as a URL object.
NSURL *googleRequestURL=[NSURL URLWithString:url];
// Retrieve the results of the URL.
dispatch_async(kBgQueue, ^
{
NSData* data = [NSData dataWithContentsOfURL: googleRequestURL];
[self performSelectorOnMainThread:@selector(fetchedData:) withObject:data waitUntilDone:YES];
});
}
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
//Get the east and west points on the map so you can calculate the distance (zoom level) of the current map view.
MKMapRect mRect = self.viewMap.visibleMapRect;
MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
//Set your current distance instance variable.
currentDist = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
//Set your current center point on the map instance variable.
currentCentre = self.viewMap.centerCoordinate;
}
-(void)fetchedData:(NSData *)responseData
{
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions
error:&error];
//The results from Google will be an array obtained from the NSDictionary object with the key "results".
NSArray* places = [json objectForKey:@"results"];
NSLog(@"places = %@",places);
arrAddress = [[NSMutableArray alloc]init];
arrName = [[NSMutableArray alloc]init];
arrLati =[[NSMutableArray alloc]init];
arrLongi =[[NSMutableArray alloc]init];
for (int i=0; i<[places count]; i++)
{
//Retrieve the NSDictionary object in each index of the array.
NSDictionary* place = [places objectAtIndex:i];
NSDictionary *address = [place objectForKey:@"formatted_address"];
if (address)
{
[arrAddress addObject:[place objectForKey:@"formatted_address"]];
}
else
{
[arrAddress addObject:[place objectForKey:@"vicinity"]];
}
// 3 - There is a specific NSDictionary object that gives us the location info.
NSDictionary *geo = [place objectForKey:@"geometry"];
// Get the lat and long for the location.
NSDictionary *loc = [geo objectForKey:@"location"];
[arrLati addObject:[loc objectForKey:@"lat"]];
[arrLongi addObject:[loc objectForKey:@"lng"]];
// 4 - Get your name and address info for adding to a pin.
[arrName addObject:[place objectForKey:@"name"]];
}
NSLog(@"Name Array = %@",arrName);
NSLog(@"Address Array = %@",arrAddress);
NSLog(@"Latitude Array = %@",arrLati);
NSLog(@"Logitude Array = %@",arrLongi);
if ([arrName count]==0)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Search Result"
message:@"No Search Result."
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
[table reloadData];
NSLog(@"works");
NSLog(@"works");
}
////// TABLE VIEW BLOCK STARTS --
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
table.sectionHeaderHeight = 25;
return @"Your Search Result";
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([arrName count]==0)
{
return 10;
}
else
{
return [arrName count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"customCell";
customCell *cell = (customCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"customCell" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (customCell *) currentObject;
break;
}}}
if ([arrName count]==0)
{
[table reloadData];
}
else
{
cell.lblName.text = [arrName objectAtIndex:indexPath.row];
cell.lblAddress.text = [arrAddress objectAtIndex:indexPath.row];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
latStr = [arrLati objectAtIndex:indexPath.row];
longStr = [arrLongi objectAtIndex:indexPath.row];
nameStr = [arrName objectAtIndex:indexPath.row];
addressStr = [arrAddress objectAtIndex:indexPath.row];btnLocate.enabled = YES;
}
-(IBAction)locateMap:(id)sender
{
mapViewController *map = [[mapViewController alloc]init];map.latitude=[latStr doubleValue];
map.longitude=[longStr doubleValue];
map.strName = nameStr;
map.strAddress = addressStr;
[self.navigationController pushViewController: map animated:YES];
[map release]; // here should autorelease the object instead of release;
}
-(IBAction)Exit:(id)sender
{
exit(0);
}
- (void)dealloc
{
[table release];
[super dealloc];
}
- (void)viewDidUnload
{//[self setTblView:nil];
[super viewDidUnload];
}
@end
customCell.h
#import <UIKit/UIKit.h>
@interface customCell : UITableViewCell
{
IBOutlet UILabel *lblName, *lblAddress;
}
@property(nonatomic,strong)IBOutlet UILabel *lblName, *lblAddress;
@end
customCell.m
#import "customCell.h"
@implementation customCell
@synthesize lblName,lblAddress;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
}
@end
MapviewController.h
#import <UIKit/UIKit.h>
#import "MapKit/MapKit.h"
#import "CoreLocation/CoreLocation.h"
#import "MapPoint.h"
@interface mapViewController : UIViewController <MKMapViewDelegate, CLLocationManagerDelegate>
{
CLLocationManager *locationManager;
CLLocationCoordinate2D currentCentre;
int currentDist;
BOOL firstLaunch;
}
@property(nonatomic,assign)double latitude,longitude;
@property (strong, nonatomic) IBOutlet MKMapView *mapView;
@property(strong,nonatomic)NSString *strName,*strAddress;
-(IBAction)Exit:(id)sender;
@end
MapviewController.m
#import "mapViewController.h"
@interface mapViewController ()
@end
@implementation mapViewController
@synthesize latitude,longitude,strName,mapView,strAddress;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//[mapView reloadInputViews];
mapView.mapType=MKMapTypeHybrid;
//mapView.mapType=MKMapTypeSatellite;
//mapView.showsUserLocation = YES;
self.mapView.delegate = self;
[self displayMap];
[self plotPositions];
// Do any additional setup after loading the view from its nib.
}
-(void)displayMap
{
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta=0.2;
span.longitudeDelta=0.2;
CLLocationCoordinate2D location;
location.latitude = latitude ;
location.longitude = longitude;
region.span=span;
region.center=location;
[mapView setRegion:region animated:TRUE];
}
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
//Get the east and west points on the map so you can calculate the distance (zoom level) of the current map view.
MKMapRect mRect = self.mapView.visibleMapRect;
MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
//Set your current distance instance variable.
currentDist = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
//Set your current center point on the map instance variable.
currentCentre = self.mapView.centerCoordinate;
}
-(void)plotPositions
{
// 1 - Remove any existing custom annotations but not the user location blue dot.
for (id<MKAnnotation> annotation in mapView.annotations)
{
if ([annotation isKindOfClass:[MapPoint class]])
{
[mapView removeAnnotation:annotation];
}
}
// Create a special variable to hold this coordinate info.
CLLocationCoordinate2D placeCoord;
// Set the lat and long.
placeCoord.latitude=latitude;
placeCoord.longitude=longitude;
// 5 - Create a new annotation.
MapPoint *placeObject = [[MapPoint alloc] initWithName:strName address:strAddress coordinate:placeCoord];
[mapView addAnnotation:placeObject];
[placeObject release];
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
// Define your reuse identifier.
static NSString *identifier = @"MapPoint";
if ([annotation isKindOfClass:[MapPoint class]]) {
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]autorelease];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.animatesDrop = YES;
return annotationView;
}
return nil;
}
#pragma mark - MKMapViewDelegate methods.
-(void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views {
//Zoom back to the user location after adding a new set of annotations.
//Get the center point of the visible map.
CLLocationCoordinate2D centre = [mv centerCoordinate];
MKCoordinateRegion region;
//If this is the first launch of the app, then set the center point of the map to the user's location.
if (firstLaunch) {
region = MKCoordinateRegionMakeWithDistance(locationManager.location.coordinate,1000,1000);
firstLaunch=NO;
}else {
//Set the center point to the visible region of the map and change the radius to match the search radius passed to the Google query string.
region = MKCoordinateRegionMakeWithDistance(centre,currentDist,currentDist);
}
//Set the visible region of the map.
[mv setRegion:region animated:YES];
}
-(IBAction)Exit:(id)sender
{
exit(0);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
MapPoint.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface MapPoint : NSObject <MKAnnotation>
{
NSString *_name;
NSString *_address;
CLLocationCoordinate2D _coordinate;
}
@property (copy) NSString *name;
@property (copy) NSString *address;
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate;
@end
MapPoint.m
#import "MapPoint.h"
@implementation MapPoint
@synthesize name = _name;
@synthesize address = _address;
@synthesize coordinate = _coordinate;
-(id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate {
if ((self = [super init])) {
_name = [name copy];
_address = [address copy];
_coordinate = coordinate;
}
return self;
}
-(NSString *)title {
if ([_name isKindOfClass:[NSNull class]])
return @"Unknown charge";
else
return _name;
}
-(NSString *)subtitle {
return _address;
}
@end
Step 7
Finally we click on the Run button to show the output.
Step 8
Splash in iPhone:
Output1 in iPhone:
Here we tap on the blue downside arrow button.
Output2 in iPhone:
Then tap on the blue downside arrow button and we see a list of items.
Output3 in iPhone:
Here we choose the ATM category and click on the Go button to move to the second view.
Output4 in iPhone:
In this view a popup (Alert View) is automatically generated.
Output5 in iPhone:
Now here we tap on the "Current Location" Button.
Output6 in iPhone:
Now it shows the current location "ATM" address.
Output7 in iPhone:
If we want to check another location address then we enter the address of the location name on the search bar.
Output8 in iPhone:
Now it shows a list of ATMs in Noida.
Output9 in iPhone:
Now to see it on the map select a cell from the list and tap on the "Locate on Map" button.
Output10 in iPhone:
Now using a Notation Pin it shows the location on the map.
Output11 in iPhone:
In the same manner we can search for an airport or show it on the map.