Tip: How to Attach an ArcSDE Personal Database File After Moving It

by James Richards April 13, 2011

I recently needed to move an ArcSDE Personal database file from my local hard drive to another location. The directory where the MDF and LDF files are located is different on the target computer. This caused problems when attempting to attach the database in ArcCatalog. I received the following error message:

Problem selecting this Geodatabase file
File activation failure. The physical file name
"C:\GIS Data\MyDatabase.LDF" may be incorrect.

It seems that ArcCatalog does not figure out that log file has moved along side the MDF file. To solve the problem, I attached the database using SQL Server Management Studio, which automatically fixes the path to the LDF file.

But this created a second problem. Now that the database is attached to the local SQLEXPRESS instance with Management Studio, attempting to attach it in ArcCatalog causes this error:

Problem selecting this Geodatabase file
CREATE FILE encountered operating system error 32(The process cannot
access the file because it is already being used by another process.) while
attempting to open or create the physical file 'D:\GIS
Data\MyDatabase.mdf'.

This was easily solved by detaching the database in SQL Server Management Studio and then attaching it in ArcCatalog.

Summary

To move an ArcSDE Personal database to a new location with a different directory path, first attach the MDF file to SQLEXPRESS using SQL Server Management Studio. This will fix the broken path to the log file. Then detatch the database and attach it in ArcCatalog.

Hope this helps!

Tags: , ,

ArcGIS Server | ArcSDE | How To

ESRI ArcGIS iPhone API – Class Breaks Renderer Sample

by James Richards June 04, 2010

Introduction

Here is a quick sample demonstrating the use of a Class Breaks Renderer in the ESRI ArcGIS iPhone API. The sample queries an ArcGIS Server Map Service for cities in California and renders them as graphics based on the population. Here’s a screenshot of the app running in the simulator.

ClassBreaks

Click here to download the source code from this article.

[more]

Discussion

If you’ve been programming with any of the ESRI Client APIs, then you’ll recognize the Class Breaks Renderer concept. For those who aren’t familiar, here’s a snippet from the Working with Symbols and Renderers section of the ArcGIS iPhone SDK Concepts documentation:

A class breaks renderer symbolizes each Graphic based on the value of some numeric attribute. Graphics with similar values for the attribute get the same Symbol. The "breaks" define the values at which the symbology changes.

Code Listings

ClassBreaksViewController.h

#import <UIKit/UIKit.h>
#import "AGSiPhone.h"

#define kTiledMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"
#define kDynamicMapServiceURL @"http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/0"

@interface ClassBreaksViewController : UIViewController<AGSMapViewDelegate, AGSQueryTaskDelegate> {
    AGSMapView *mapView;
    AGSGraphicsLayer *cityGraphicsLayer;
    AGSQueryTask *cityQueryTask;
    
}

@property (nonatomic, retain) IBOutlet AGSMapView *mapView;
@property (nonatomic, retain) AGSGraphicsLayer *cityGraphicsLayer;
@property (nonatomic, retain) AGSQueryTask *cityQueryTask;

@end

ClassBreaksViewController.m

#import "ClassBreaksViewController.h"

@implementation ClassBreaksViewController

@synthesize mapView;
@synthesize cityGraphicsLayer;
@synthesize cityQueryTask;

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Set map view delegate
    self.mapView.mapViewDelegate = self;
    
    // Create tile base map layer
    AGSTiledMapServiceLayer *tiledLayer = [[AGSTiledMapServiceLayer alloc] initWithURL:[NSURL URLWithString:kTiledMapServiceURL]];
    [self.mapView addMapLayer:tiledLayer withName:@"BaseLayer"];
    [tiledLayer release];
    
    // Create grpahics layer
    self.cityGraphicsLayer = [AGSGraphicsLayer graphicsLayer];
    
    // Create symbols for the three class breaks
    AGSSimpleMarkerSymbol *lowSymbol = [AGSSimpleMarkerSymbol simpleMarkerSymbol];
    lowSymbol.color = [UIColor colorWithRed:151.0/255.0 green:216.0/255.0 blue:255.0/255.0 alpha:0.8];
    lowSymbol.outline.width = 0;
    lowSymbol.size = 15;
    
    AGSSimpleMarkerSymbol *mediumSymbol = [AGSSimpleMarkerSymbol simpleMarkerSymbol];
    mediumSymbol.color = [UIColor colorWithRed:255.0/255.0 green:165.0/255.0 blue:83.0/255.0 alpha:0.8];
    mediumSymbol.outline.width = 0;
    mediumSymbol.size = 20;
    
    AGSSimpleMarkerSymbol *highSymbol = [AGSSimpleMarkerSymbol simpleMarkerSymbol];
    highSymbol.color = [UIColor colorWithRed:222.0/255.0 green:0.0 blue:0.0 alpha:0.8];
    highSymbol.outline.width = 0;
    highSymbol.size = 25;
    
    // Create a class breaks renderer with a default simple marker symbol and an attribute field
    AGSClassBreaksRenderer *cityRenderer = [AGSClassBreaksRenderer
                                            classBreaksRendererWithDefaultSymbol:lowSymbol 
                                                               forAttributeField:@"POP1990"];
    
    // Create three AGSClassBreak objects, one each for low, medium and high populations
    AGSClassBreak* lowClassBreak = [AGSClassBreak
                                    classBreakInfoWithSymbol:lowSymbol forMinValue:DBL_MIN
                                    maxValue:50000.0];
    
    AGSClassBreak* mediumClassBreak = [AGSClassBreak
                                       classBreakInfoWithSymbol:mediumSymbol forMinValue:50000.0
                                       maxValue:250000];
    
    AGSClassBreak* highClassBreak = [AGSClassBreak
                                     classBreakInfoWithSymbol:highSymbol forMinValue:250000.0
                                     maxValue:DBL_MAX];
    
    // Create an NSMutableArray, fill it with the class break objects,
    // and set it to the renderer’s classBreaks property
    cityRenderer.classBreaks = [NSMutableArray arrayWithObjects:
                                lowClassBreak, mediumClassBreak, highClassBreak, nil];
    
    // Add the renderer to the graphics layer
    self.cityGraphicsLayer.renderer = cityRenderer;
    
    // Add the graphics layer to the map view
    [self.mapView addMapLayer:self.cityGraphicsLayer withName:@"CityGraphicsLayer"];
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return YES;
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.cityGraphicsLayer = nil;
    self.mapView = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [cityGraphicsLayer release];
    [mapView release];
    [super dealloc];
}

#pragma mark AGSMapViewDelegate

// Called when the map view is loaded (after the view is loaded) 
- (void)mapViewDidLoad:(AGSMapView *)mapView {
        
    // Set up query task for cities and perform query returning all attributes
    self.cityQueryTask = [AGSQueryTask queryTaskWithURL:[NSURL URLWithString:kDynamicMapServiceURL]];
    self.cityQueryTask.delegate = self;
    
    AGSQuery *cityQuery = [AGSQuery query];
    cityQuery.where = @"STATE_NAME = 'California'";
    cityQuery.outFields = [NSArray arrayWithObject:@"*"];
        
    [self.cityQueryTask executeWithQuery:cityQuery];
    
    // Create extent to be used as default
    AGSEnvelope *envelope = [AGSEnvelope envelopeWithXmin:-118.6
                                                     ymin:33.6
                                                     xmax:-118.1
                                                     ymax:34.2
                                         spatialReference:self.mapView.spatialReference];
    
    // Call method to set extent, pass in envelope
    [self.mapView performSelector:@selector(zoomToEnvelope:animated:) 
                       withObject:envelope
                       afterDelay:0.5];    
}    

#pragma mark AGSQueryTaskDelegate

// When query is executed ....
- (void)queryTask:(AGSQueryTask *)queryTask didExecuteWithFeatureSetResult:(AGSFeatureSet *)featureSet {
    // Iterate the returned features (graphics) and add them to the graphics layer
    for (AGSGraphic *graphic in featureSet.features) {
        [self.cityGraphicsLayer addGraphic:graphic];
    }
    [self.cityGraphicsLayer dataChanged];
    
    // Clean up query task memory
    self.cityQueryTask = nil;
}

// If there's an error with the query task give info to user
- (void)queryTask:(AGSQueryTask *)queryTask didFailWithError:(NSError *)error {
    // Clean up query task memory
    self.cityQueryTask = nil;

    // Display error message
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
                                                    message:[error localizedDescription]
                                                   delegate:nil
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil];
    [alert show];
    [alert release];
}

@end

Summary

In this post you learned how to use a Class Breaks Renderer in the ESRI ArcGIS iPhone API to display cities of varying population with different symbols.

Click here to download the source code from this article.

I hope you are finding these posts on the ESRI iPhone API to be helpful. If you have any suggestions, ideas or feedback, please leave them in the comments below.

Additional Resources

Tags: , , ,

ArcGIS Server | ESRI | iPhone | Mobile

ESRI ArcGIS iPhone API – Integration with Core Location

by James Richards May 14, 2010

Introduction

Last week I started to familiarize myself with the ESRI ArcGIS iPhone API public beta and I blogged about my First Impressions. This week I had a chance to play around with it some more and I decided to investigate working with location. I enhanced my sample application from last week to include a Location button, which when pressed zooms the map to the current location and displays a custom push pin graphic.

Screen1     Screen2

Click here to download the source code from this article.

There are two ways you can work with location while writing an app with ESRI’s iPhone API:

ESRI’s AGSGPS class provides a convenient wrapper around Core Location if you don’t want (or need) to delve into the details of the framework.

[more]

ESRI AGSGPS Class vs. Apple Core Location Framework

Here’s how the ESRI iPhone API documentation describes the AGSGPS class:

“This object controls how the map responds to GPS input. To make the map start responding to GPS input, call the start method. The map will automatically zoom to the first location specified by the GPS input. You can control the zoom level by specifying the zoomLevel property. If the autoPan property is enabled, the map will recenter everytime a new GPS location is received. To make the map stop responding to GPS input, call the stop method. By default, the map uses a round, blue symbol to display the current location. You can replace this symbol with an image icon of your choice. This image must be included in the application bundle, it must be named GpsDisplay.png and it must be 35x35 pixels in size.”

The class definitely simplifies working with Core Location, but the trade off is that you are somewhat limited in what you can do. For example, Core Location includes the CLLocationManagerDelegate protocol which is “used to receive location and heading updates” so you can respond to them in your code. I’d like to see something similar in the ESRI iPhone SDK to give us more flexibility to respond to location updates. With the ESRI iPhone SDK, you just turn on the GPS and let the map do it’s thing.

For my sample app, I decided to work with the Core Location framework for finer grained control over the user interaction.

Implementation Details

The first step I took was to rearrange the UI in Interface Builder to accommodate the new button. I decided to add a proper toolbar along the bottom and center the new button and existing segment control. This required adding Flexible Bar Button Items on each side of the toolbar, as shown:

Finding the proper icon for the Location button was a bit tricky. Interface Builder doesn’t give you a way to specify this particular system icon for your button. I had to follow Diallo’s advice from this Stack Overflow question to get the icon from the UI Kit and save it to disk. I temporarily added this code to the viewDidLoad event, and ran the application once to write the icon out to my document directory.

UIImage* img = [UIImage kitImageNamed:@"UIButtonBarLocate.png"];
// Get the location of the Documents directory NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) ; NSString *imagePath = [paths objectAtIndex:0] ; NSString *filename = @"Locate.png" ; NSString *filepath = [NSString stringWithFormat:@"%@/%@", imagePath, filename] ; // Save the image NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(img)]; [imageData writeToFile:filepath atomically:YES];

If your not sure where the file gets saved, you can add an NSLog call to output imagePath to the Console. After the icon was saved, I removed the temporary code and copied the icon into the project directory, under the Resources group.

Once I had the view setup properly in interface builder, I modified the view controller’s header file to:

  • Import Core Location
  • Conform to the CLLocationManagerDelegate protocol
  • Declare members and properties for the location manager and locate button
  • Declare an IBAction method to run when the user clicks the locate button

I also cleaned up the previous week’s code a bit, to remove unnecessary AGSTiledMapServiceLayer members and properties.

Here is the new version of MyFirstMapAppViewController.h:

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "AGSiPhone.h"

#define kTiledStreetMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer"
#define kTiledImageryMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"
#define kTiledReliefMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_ShadedRelief_World_2D/MapServer"

@interface MyFirstMapAppViewController : UIViewController<AGSMapViewDelegate, CLLocationManagerDelegate> {
    AGSMapView *mapView;
    CLLocationManager *locationManager;
    
    UIView *streetView;    
    UIView *imageryView;    
    UIView *reliefView;
    
    UIBarButtonItem *locateButton;
}

@property (nonatomic, retain) IBOutlet AGSMapView *mapView;
@property (nonatomic, retain) CLLocationManager *locationManager;

@property (nonatomic, retain) UIView *streetView;
@property (nonatomic, retain) UIView *imageryView;
@property (nonatomic, retain) UIView *reliefView;

@property (nonatomic, retain) IBOutlet UIBarButtonItem *locateButton;

- (IBAction)toggleLayer:(id)sender;
- (IBAction)showLocation:(id)sender;

@end

For the implementation file, Core Location is used in the following manner:

  • The location manager is created and started in viewWillAppear
  • The locate button is disabled in viewWillAppear to ensure that the button can’t be used until Core Location services have become available
  • A new graphics layer is created and added to the map in viewDidLoad
  • The location button is enabled in the location manager’s didUpdateToLocation event once core location is started and ready to use
  • When the user touches the Locate button, the showLocation method gets the location, creates and zooms to an envelope around that location, and displays a push pin graphic in the graphics layer
  • The location manager is shut down in viewWillDisappear

This code follows the same basic pattern shown in one of the examples in Head First iPhone Development.

Here is the MyFirstMapAppViewController.m file:

#import "MyFirstMapAppViewController.h"

@implementation MyFirstMapAppViewController

@synthesize mapView;
@synthesize streetView;
@synthesize imageryView;
@synthesize reliefView;
@synthesize locationManager;
@synthesize locateButton;

- (void)viewWillAppear:(BOOL)animated {
    // Setup location manager
    NSLog(@"Starting core location");
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
    self.locationManager.delegate = self;
    [self.locationManager startUpdatingLocation];
    self.locateButton.enabled = NO;
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.mapView.mapViewDelegate = self;
    
    AGSTiledMapServiceLayer *streetLayer = [[AGSTiledMapServiceLayer alloc] 
                                            initWithURL:[NSURL URLWithString:kTiledStreetMapServiceURL]];
    self.streetView = [self.mapView addMapLayer:streetLayer withName:@"Street"];
    [streetLayer release];
    
    AGSTiledMapServiceLayer *imageryLayer = [[AGSTiledMapServiceLayer alloc] 
                                             initWithURL:[NSURL URLWithString:kTiledImageryMapServiceURL]];
    self.imageryView = [self.mapView addMapLayer:imageryLayer withName:@"Imagery"];
    [imageryLayer release];
    
    AGSTiledMapServiceLayer *reliefLayer = [[AGSTiledMapServiceLayer alloc] 
                                            initWithURL:[NSURL URLWithString:kTiledReliefMapServiceURL]];
    self.reliefView = [self.mapView addMapLayer:reliefLayer withName:@"Relief"];
    [reliefLayer release];

    self.streetView.hidden = NO;
    self.imageryView.hidden = YES;
    self.reliefView.hidden = YES;

    // Create a graphics layer to display the push pin
    AGSGraphicsLayer *graphicsLayer = [AGSGraphicsLayer graphicsLayer];
    [self.mapView addMapLayer:graphicsLayer withName:@"GraphicsLayer"];
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return YES;
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void) viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    NSLog(@"Shutting down core location");
    [self.locationManager stopUpdatingLocation];
    self.locationManager = nil;
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
    self.mapView = nil;
    self.streetView = nil;
    self.imageryView = nil;
    self.reliefView = nil;
    
    [locateButton release];
    [super dealloc];
}

- (IBAction)toggleLayer:(id)sender {
    
    self.streetView.hidden = (((UISegmentedControl *)sender).selectedSegmentIndex != 0);
    self.imageryView.hidden = (((UISegmentedControl *)sender).selectedSegmentIndex != 1);
    self.reliefView.hidden = (((UISegmentedControl *)sender).selectedSegmentIndex != 2);    
}

- (IBAction)showLocation:(id)sender {
    NSLog(@"Show Location");
    
    CLLocation *location = self.locationManager.location;
    double lat = location.coordinate.latitude;
    double lon = location.coordinate.longitude;
    NSLog(@"%.3f, %.3f", lat, lon);
    
    double size = 0.05;
    AGSEnvelope *envelope = [AGSEnvelope envelopeWithXmin:lon - size 
                                                     ymin:lat - size
                                                     xmax:lon + size 
                                                     ymax:lat + size
                                         spatialReference:self.mapView.spatialReference];
    [self.mapView zoomToEnvelope:envelope animated:YES];
    
    // Get reference to the graphics layer
    id<AGSLayerView> graphicsLayerView = [self.mapView.mapLayerViews objectForKey:@"GraphicsLayer"];
    AGSGraphicsLayer *graphicsLayer = (AGSGraphicsLayer*)graphicsLayerView.agsLayer;
    
    // Clear graphics
    [graphicsLayer removeAllGraphics];
    
    // Create a marker symbol using the Location.png graphic
    AGSPictureMarkerSymbol *markerSymbol = [AGSPictureMarkerSymbol pictureMarkerSymbolWithImageNamed:@"Location.png"];
    
    // Create a new graphic using the location and marker symbol
    AGSGraphic* graphic = [AGSGraphic graphicWithGeometry:[envelope center]
                                                   symbol:markerSymbol
                                               attributes:nil
                                             infoTemplate:nil];
    
    // Add the graphic to the graphics layer
    [graphicsLayer addGraphic:graphic];
}

- (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    NSLog(@"Core location has a new position");
    self.locateButton.enabled = YES;
}

- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    NSLog(@"Core location failed to get position");
    self.locateButton.enabled = NO;
}

#pragma mark AGSMapViewDelegate

//called when the map view is loaded (after the view is loaded) 
- (void)mapViewDidLoad:(AGSMapView *)mapView {
    
    //create extent to be used as default
    AGSEnvelope *envelope = [AGSEnvelope envelopeWithXmin:-124.83145667
                                                     ymin:30.49849464
                                                     xmax:-113.91375495
                                                     ymax:44.69150688
                                         spatialReference:mapView.spatialReference];
    
    //call method to set extent, pass in envelope
    [self.mapView performSelector:@selector(zoomToEnvelope:animated:) 
                       withObject:envelope
                       afterDelay:0.5];
}    

@end

Summary

In this post we looked at some options for utilizing Core Location with the ESRI ArcGIS iPhone API. ESRI’s AGSGPS class provides an easy to use convenience wrapper around Core Location, but it is not as flexible as working directly with Apple’s Core Location framework. An example was presented that demonstrates how to work with the Core Location framework and ESRI’s iPhone API. The example showed how to get the current location and zoom to it while showing a custom graphic on the map.

Click here to download the source code from this article.

I’m having a lot of fun learning about iPhone programming and ESRI’s iPhone API. I addition to testing in the simulator, I signed up for the Apple Developer Program so I can now run these apps on my iPod Touch. (The Mac Mini in the background is my dev box!)

iPodTouch

I hope you are enjoying these posts and finding the information helpful. If you have any suggestions, ideas or feedback, please leave them in the comments below.

Additional Resources

Tags: , , , ,

ArcGIS Server | ESRI | iPhone | Mobile

ESRI ArcGIS iPhone API – First Impressions

by James Richards May 04, 2010

The ESRI ArcGIS iPhone API was released to public beta today. Jeff Shaner blogged and tweeted about it this morning.

I downloaded the SDK this afternoon and took it for a spin. My first impressions are very favorable. Although the documentation is still a bit sparse in a few places, that’s to be expected for a first beta.

The SDK is another client API for consuming ArcGIS Server REST endpoints, and it works with versions 9.3.1 and 10.0. If you have worked with any of the other client APIs, you will already be familiar with the basic paradigm of interaction with the various REST services offered by ArcGIS Server.

The SDK Concepts Documentation provides a basic overview of the technology and includes a number of brief walkthroughs as well as short code samples illustrating how to perform common programming tasks with the API.

The installation also includes six sample applications which are installed into your ~/Library/SDKs/Samples folder. Studying these sample apps is a good way to jumpstart your familiarity with the API.

But enough talk already, let’s see some code! [more]

After following the My First iPhone Application tutorial,  I decided to jump in and modify the application to improve it in a some specific ways:

  • Add a button bar to toggle three base map types: Streets, Aerial Imagery, and Shaded Relief Map
  • Zoom to a predefined initial extent on application startup
  • Support any device rotation

I won’t spend a lot of time describing everything here. I’ll just show a few screen snapshots with the source code and make everything available for download.

Here are some screen snapshots of the app running in the simulator:

StreetView     ImageryView     ReliefView

Rotated

Here is the MyFirstMapAppViewController.h header file:

#import <UIKit/UIKit.h>
#import "AGSiPhone.h"

#define kTiledStreetMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer"
#define kTiledImageryMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer"
#define kTiledReliefMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_ShadedRelief_World_2D/MapServer"

@interface MyFirstMapAppViewController : UIViewController<AGSMapViewDelegate> {
    AGSMapView *_mapView;
    
    UIView *_streetView;
    AGSTiledMapServiceLayer *_streetLayer;
    
    UIView *_imageryView;
    AGSTiledMapServiceLayer *_imageryLayer;

    UIView *_reliefView;
    AGSTiledMapServiceLayer *_reliefLayer;
    
}

@property (nonatomic, retain) IBOutlet AGSMapView *mapView;

@property (nonatomic, retain) UIView *streetView;
@property (nonatomic, retain) AGSTiledMapServiceLayer *streetLayer;

@property (nonatomic, retain) UIView *imageryView;
@property (nonatomic, retain) AGSTiledMapServiceLayer *imageryLayer;

@property (nonatomic, retain) UIView *reliefView;
@property (nonatomic, retain) AGSTiledMapServiceLayer *reliefLayer;

- (IBAction)toggleLayer:(id)sender;

@end

(Sorry, this is my first post about iPhone programming and I haven’t figured out how to highlight the syntax for Objective-C yet!)

And here is the MyFirstMapAppViewController.m implementation file:

#import "MyFirstMapAppViewController.h"

@implementation MyFirstMapAppViewController

@synthesize mapView = _mapView;
@synthesize streetView = _streetView;
@synthesize streetLayer = _streetLayer;
@synthesize imageryView = _imageryView;
@synthesize imageryLayer = _imageryLayer;
@synthesize reliefView = _reliefView;
@synthesize reliefLayer = _reliefLayer;

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.mapView.mapViewDelegate = self;
    
    self.streetLayer = [AGSTiledMapServiceLayer tiledMapServiceLayerWithURL:[NSURL URLWithString:kTiledStreetMapServiceURL]];
    self.streetView = [self.mapView addMapLayer:self.streetLayer withName:@"Street"];
    self.streetView.hidden = NO;

    self.imageryLayer = [AGSTiledMapServiceLayer tiledMapServiceLayerWithURL:[NSURL URLWithString:kTiledImageryMapServiceURL]];
    self.imageryView = [self.mapView addMapLayer:self.imageryLayer withName:@"Imagery"];
    self.imageryView.hidden = YES;

    self.reliefLayer = [AGSTiledMapServiceLayer tiledMapServiceLayerWithURL:[NSURL URLWithString:kTiledReliefMapServiceURL]];
    self.reliefView = [self.mapView addMapLayer:self.reliefLayer withName:@"Relief"];
    self.reliefView.hidden = YES;
        
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return YES;
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
    self.mapView = nil;
    self.streetView = nil;
    self.streetLayer = nil;
    self.imageryView = nil;
    self.imageryLayer = nil;
    self.reliefView = nil;
    self.reliefLayer = nil;
    
    [super dealloc];
}

- (IBAction)toggleLayer:(id)sender {
    
    self.streetView.hidden = (((UISegmentedControl *)sender).selectedSegmentIndex != 0);
    self.imageryView.hidden = (((UISegmentedControl *)sender).selectedSegmentIndex != 1);
    self.reliefView.hidden = (((UISegmentedControl *)sender).selectedSegmentIndex != 2);    
}

#pragma mark AGSMapViewDelegate

//called when the map view is loaded (after the view is loaded) 
- (void)mapViewDidLoad:(AGSMapView *)mapView {
    
    //create extent to be used as default
    AGSEnvelope *envelope = [AGSEnvelope envelopeWithXmin:-124.83145667
                                                     ymin:30.49849464
                                                     xmax:-113.91375495
                                                     ymax:44.69150688
                                         spatialReference:mapView.spatialReference];
    
    //call method to set extent, pass in envelope
    [self.mapView performSelector:@selector(zoomToEnvelope:animated:) 
                       withObject:envelope
                       afterDelay:0.5];
}    

@end

There are a couple of points to note about how things are wired up in Interface Builder.

The UISegmentedControl’s Value Changed event is hooked up to the File’s Owner’s toggleLayer function.

IB1

And the UISegmentedControl’s Autosizing is set to bottom center. By configuring the control this way in Interface Builder, we do not need to write any code to handle placement of the UISegmentedControl when the view rotates.

IB2

Summary

Well, that about covers everything for my first experience with the ESRI iPhone SDK. All in all, I’d say it looks like another great client API for use with ArcGIS Server. For those of us who have been wanting to create native iPhone apps on the ESRI stack, the wait is over!

I hope you enjoyed this brief tour of ESRI’s latest offering.

Click here to download the source code from this article.

Additional Resources

Tags: , , ,

ArcGIS Server | ESRI | Mobile | iPhone

How To: Display an ArcGIS Server Cached Tile Layer as a Custom Map Type with the Google Maps API for Flash

by James Richards July 06, 2009

Overview

This is the third post in a series where I discuss techniques for interacting with the ArcGIS Server REST API from within a Flex 3 application built with the Google Maps API for Flash. The first and second posts presented and refined an example that demonstrated how to stream features from ArcGIS Server and overlay them on top of Google Maps data. This post demonstrates how to display an ArcGIS Server cached tile layer as a custom map type with the Google Maps API for Flash.

Sample Application Concepts

The sample application works as follows:

  • The Google Maps API for Flash is embedded in a Flex 3 application.
  • A cached tile layer representing Land Base features for Portland, Oregon is served via an ArcGIS Server Map Service, accessible via ArcGIS Server’s REST API.
  • The cached tile layer appears as a custom map type in the Google Maps API for Flash User Interface.
  • The cached tile layer is displayed at zoom levels 0 - 19.

Here are a couple of screen shots of the sample application. The first shows the application with the custom “Land Base” map type selected. The second shows the application with the normal map type selected.

image

image

Live example is here, and source code is here. [more]

Discussion

MXML File

Starting with a basic Flex Application, we have a single map with a specified “map ready” event:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:gm="com.google.maps.*"
    backgroundGradientColors="[0xFFFFFF, 0xAAAAAA]"
    height="100%" width="100%" viewSourceURL="srcview/index.html">
    <gm:Map id="map"
        width="100%"
        height="100%"
        key="(Your API Key)"
        mapevent_mapready="map_mapReady(event)" />
    <mx:Script>
        // Omitted for clarity
    </mx:Script>
</mx:Application>

In the map_mapReady function we create an instance of the custom tile layer, specifying the URL endpoint of the cached tiles, the minimum and maximum zoom levels, and the copyright text. Then we create a new map type using the custom tile layer instance. Next we add the usual map controls, and finally we initialize the map using the new map type.

private function map_mapReady(event:Event):void {
    // Create custom tile layer
    var baseUrl:String = http://sampleserver1.arcgisonline.com/ +
        "ArcGIS/rest/services/Portland/ESRI_LandBase_WebMercator/MapServer";
    var copyright:String = "Taxlots, Zoning © Oregon METRO RLIS; " +
        "Buildings © City of Portland Planning"; 
    var tileLayer:TileLayer = new TileLayer(baseUrl, 0, 19, copyright);

    // Create custom map type and add to map
    var mapType:IMapType = new MapType([tileLayer], map.MERCATOR_PROJECTION, "Land Base");
    map.addMapType(mapType);

    // Add map controls
    var mtc:MapTypeControl = new MapTypeControl();
    map.addControl(mtc);
    var pc:PositionControl = new PositionControl();
    map.addControl(pc);
    var zc:ZoomControl = new ZoomControl();
    map.addControl(zc);
    var sc:ScaleControl = new ScaleControl();
    map.addControl(sc);
    var omc:OverviewMapControl = new OverviewMapControl();
    map.addControl(omc);

    // Initialize map. Use custom tile layer for the initial map type           
    map.setCenter(new LatLng(45.520211, -122.65553), 17, mapType);
}

The custom tile layer is implemented with the reusable TileLayer class and its companion Tile utility class. The basic design for these classes comes from Pamela Fox’s custom tile layer sample in the Google Maps API for Flash Demo Gallery.

The TileLayer constructor takes four arguments: the URL endpoint of the cached tiles, the minimum zoom level, the maximum zoom level, and the copyright text. Our example uses an ESRI sample server that has a Land Base cached tile service for Portland, Oregon. 

We determine the appropriate values for the constructor’s arguments by exploring the MapServer REST API using the interactive SDK . The MapServer is hosted at: http://sampleserver1.arcgisonline.com/arcgis/rest/services

image

By drilling down into the Portland Folder and the associated MapServices, we learn about the functionality and data layers that are available for consumption. If we drill down to the Portland/ESRI_LandBase_WebMercator MapServer, we can gather the information we need to fill in the above mentioned TileLayer constructor.

image

First, we need to check to make sure that the MapServer is cached. Look at the Single Fused Map Cache property and make sure it is set to true. Next, verify that the MapServer uses the Google Maps (and Virtual Earth) tiling scheme by making sure the Spatial Reference is set to 102113 (Web Mercator), the Width and Height are 256 pixels, and the DPI is 96.

If you are designing your own map cache to overlay on top of Google Maps, be sure to read the following information in the ESRI online documentation (depending on your platform):

To determine the base URL constructor argument, look at the URL in the browser’s address bar. The URL should end with “MapServer”.

image

To determine the minimum and maximum zoom levels, scroll down and look at the Tile Info - Levels of Detail section. The first Level ID in the list is the minimum zoom level (in this case 0) and the last Level ID is the maximum zoom level (in this case 19).

image

Finally, the copyright parameter can be anything you deem appropriate. In this case, I abbreviated the information found in the Copyright Text property of the MapServer.

TileLayer Class Implementation

Moving on to the TileLayer class implementation, here is the code:

// Custom tile layer for displaying ArcGIS Server cached tiles
// in a Google Maps API for Flash application.
public class TileLayer extends TileLayerBase
{
    private var baseUrl:String;
    private var minResolution:Number;
    private var maxResolution:Number;

    // Constructor
    public function TileLayer(baseUrl:String, minResolution:Number,
                          maxResolution:Number, copyright:String) {
        // Save the options
        this.baseUrl = baseUrl;
        this.minResolution = minResolution;
        this.maxResolution = maxResolution;

        // Add copyright information.
        var copyrightCollection:CopyrightCollection = new CopyrightCollection();
        copyrightCollection.addCopyright(new Copyright("TileLayer",
            new LatLngBounds(new LatLng(-180, 90), new LatLng(180, -90)),
            0, copyright));
        super(copyrightCollection);
    }

    // Return the coarsest zoom level. Provides information necessary
    // to adjust the boundaries of the zoom level control when this 
    // layer is included in a custom map type.
    public override function getMinResolution():Number {
        return minResolution;
    }

    // Return the finest zoom level. Provides information necessary to
    // adjust the boundaries of the zoom level control when this layer
    // is included in a custom map type.
    public override function getMaxResolution():Number {
        return maxResolution;
    }

    // Creates and loads a tile (x, y) at the given zoom level.
    public override function loadTile(tilePos:Point, zoom:Number):DisplayObject {
        return new Tile(baseUrl, tilePos, zoom);
    }       
}

As you can see, the class is pretty simple. It extends the Google Maps API’s TileLayerBase abstract base class, which does most of the work. According to the Google Maps API for Flash Documentation, sub-classes must override the loadTile() method; however, overriding the other methods is optional. The loadTile() method must return a DisplayObject that represents the tile.

Tile Class Implementation

In the loadTile() method implementation, we defer to the Tile utility class, which is defined as follows:

// Custom tile for displaying ArcGIS Server cached tiles
// in a Google Maps API for Flash application.
public class Tile extends Sprite
{
    // Constructor
    public function Tile(baseUrl:String, tilePos: Point, zoom:Number)
    {
        // Construct the url to the ArcGIS Server tile:
        // http://<path to map service>/tile/<zoom level>/<y>/<x>
        var tileUrl:String = baseUrl + "/tile/" + zoom.toString() + "/" +
            tilePos.y.toString() + "/" + tilePos.x.toString();
        trace(tileUrl);

        // Load the tile and add it to the sprite as a child
        var loader:Loader = new Loader();
        loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        loader.load(new URLRequest(tileUrl));
        addChild(loader);
    }

    // Error handler when a tile cannot be loaded by the loader.
    public function ioErrorHandler(event:IOErrorEvent):void {
        // If the tile was not found, just load an empty sprite
        // so nothing draws.
        addChild(new Sprite());
    }
}

The Tile class extends Sprite, which in turn inherits from DisplayObject. Therefore, we can return an instance of the Tile class from the loadTile() method in the TileLayer class. The Tile class’s constructor builds a URL to the tile as served by the ArcGIS Server REST API. The URL pattern for accessing tiles through the REST API is: http://<path to map service>/tile/<zoom level>/<y>/<x>

Once the URL string is created, a flash Loader is used in concert with a URLRequest object to load the tile image and add it as a child to the Sprite. If the tile is not found and an error is thrown, we simply add an empty Sprite instead so nothing draws. Alternatively, we could load some kind of "tile not found" graphic, as shown by Pamela Fox’s custom tile layer sample in the Google Maps API for Flash Demo Gallery.

Wrap Up

This post has demonstrated how to display an ArcGIS Server cached tile layer as a custom map type with the Google Maps API for Flash. The TileLayer and Tile classes are reusable. To add an ArcGIS Server cached tile layer to your project, simply copy the classes as-is and modify the code shown in the map_mapReady event to use your cached MapServer.

Live example is here, and source code is here.

Resources

Downloads
Documentation and Sample Servers
Blogs, Forums and Code Galleries

Tags: , , ,

ArcGIS Server | Flex 3 | Google Maps | How To | Planet GS | REST

ArcGIS Server WebADF: Adjusting the Zoom Scale for Find Address Task Results

by James Richards July 02, 2009

Overview

When working with the Find Address Task in the ArcGIS Server WebADF, the default zoom scale that is displayed when the user zooms to a found address might not be what you want. This article discusses how to change it using the ZoomToPointFactor property. [more]

Background

Let’s assume that you have a WebADF application with some Map Layers and a Find Address Task that is configured to use a Geocoding Service. This could be an application that you created from scratch in Visual Studio, or one that you created with ArcGIS Server Manager.

After entering an address:

image

The results are shown in the Task Results panel. From there you can right click on an address and choose Zoom To:

image

This will zoom the map to an area centered on the address point. The problem is that scale to which the map is zoomed might not be what you want. Often it’s zoomed out too far.

Solution

To adjust the zoom scale to your liking, add the ZoomToPointFactor property to the esri:TaskResults element on the ASP.NET Page hosting the application.

If you created the application with ArcGIS Server Manager using the default options, this would be found in the Default.aspx file in the C:\inetpub\wwwroot\[YourApplicationName] directory.

Open this file and search for the esri:TaskResults tag, then add the ZoomToPointFactor property and give it a numeric value. In the example below, I’ve set the property to “1000”.

image ArcGIS Server will divide the width and height of the map by this value when zooming to a point. For this example, the zoom area will be 1/1000th of the full extent. The larger the ZoomToPointFactor, the further in that the map will zoom. You will need to experiment to find the ratio that works best, as the full extent can vary greatly depending on the data in your application.

Also note that if you have any cached map services in your application, ArcGIS Server will snap to the cache scale that is closest to the calculated map extent. Consequently, you might not see the map extent change if you don’t modify the ZoomToPointFactor property by a large enough value to jump to another scale.

Resources

Tags: , , ,

.NET | ArcGIS Server | How To | Planet GS | WebADF

Google Maps API for Flash and ArcGIS Server REST API Integration – Part 2

by James Richards June 15, 2009

Overview

This is the second post in a series where I discuss techniques for interacting with the ArcGIS Server REST API from within a Flex 3 application built with the Google Maps API for Flash. If you haven’t read the first post yet, I encourage you to do so now.

The first post presented a simple example that demonstrated how to stream features from ArcGIS Server and overlay them on top of Google Maps data. In this second post, we will reorganize the code for better reusability and add some geocoding functionality.

image

I’ll keep this post pretty brief and only cover the major points. Feel free to check out the live example and dive into the source code! [more]

Reorganizing the Code

In the previous post all code was presented in-line in a single mxml file. While this helped illustrate the concepts simply and succinctly, the code was not really reusable other than by the old copy/paste method. In this updated example, all of the feature streaming and drawing code has been placed in its own PolygonOverlay class.

This new class has a number of advantages over the old one. Many of the hard coded values, such as minimum and maximum zoom levels, the tool tip field and symbolization options are now encapsulated in the class definition.

The PolygonOverlay class’s constructor takes a number of these options, making it more flexible. For example:

polygonOverlay = new PolygonOverlay(map, // Google Map
    parcelsUrl,         // Root url of layer in ArcGIS Server REST API
    17,                    // Minimum Zoom Level
    21,                   // Maximum Zoom Level
    "RNO",              // Tool tip field
    transparentFill,  // Default fill style
    greyStroke);     // Default line style

In the previous post, all graphics on the map were erased each time the polygons needed to be redrawn. This is a problem if there are other graphics on the map. Now that we have added geocoding functionality to the sample application, we will draw the geocode results as a marker on the map along with an info window. We don't want these graphics to be erased when the user pans or zooms and the parcel polygons are redrawn.

The new PolygonOverlay class handles this by keeping track of all the Google Polygon objects that it draws on the map. When it needs to redraw the polygons, only the previous polygons are removed from the map, while the other graphics are left on the map. Check out the PolygonOverlay.as class for more details.

Adding Geocode Functionality

The new sample application also adds geocoding using the ClientGeocoder class. The findAddress function sets up the client geocoder and dispatches the request:

private function findAddress():void {
    trace("findAddress() - entered");
    var address:String = addressText.text;
    trace("findAddress() - address: " + address);

    // Constrain geocoder to a viewport that is in/around Portland, Oregon
    var sw:LatLng = new LatLng(-122.76728, 45.45137);
    var ne:LatLng = new LatLng(-122.53211, 45.59233);
    var viewport:LatLngBounds = new LatLngBounds(sw, ne);
    var options:ClientGeocoderOptions = new ClientGeocoderOptions();
    options.viewport = viewport;

    // Setup client geocoder and dispatch request
    var geocoder:ClientGeocoder = new ClientGeocoder();
    geocoder.setOptions(options);
    geocoder.addEventListener(GeocodingEvent.GEOCODING_SUCCESS, geocoder_GeocodingSuccess);
    geocoder.addEventListener(GeocodingEvent.GEOCODING_FAILURE, geocoder_GeocodingFailure);
    geocoder.geocode(address);
}

One interesting thing that’s happening here is that we’re constraining the geocode request to a viewport that is in/around the Portland, Oregon area. Google’s geocoding service will attempt to find the best match in that area.

I don’t know much about Portland (I’ve never been there) but I do know that it’s home to a number of microbreweries as well as the historic Burnside Skate Park. Try entering these addresses and place names:

  • 206 SW Morrison St (Location of the Rock Bottom Brewery)
  • Burnside Skate Park

One of the interesting things about using the Geocoding Service with a viewport is that many place names get resolved to actual locations without having to go through a confirmation step. This won’t work everywhere for every place name, but it helps narrow things down a bit so Google’s Geocoding Service can work its magic.

Wrap Up

That’s all the detail I’ll cover in the post, but be sure to check out the source code for more goodies like how to add the address marker and pop up an info window.

Stay tuned for future posts, where we will continue to add functionality and improve upon the code.

Live example is here, and source code is here.

Resources

Downloads
Documentation and Sample Servers
Blogs and Forums
Other Items of Interest

Tags: , , , ,

ArcGIS Server | Flex 3 | Google Maps | How To | Planet GS

Google Maps API for Flash and ArcGIS Server REST API Integration – Part 1

by James Richards May 12, 2009

Overview

This is the first post in a series where I will discuss techniques for interacting with the ArcGIS Server REST API from within a Flex 3 application built with the Google Maps API for Flash.

With this post, we start with a simple example that demonstrates how to stream features from ArcGIS Server and overlay them on top of Google Maps data. In this case, we will be working with parcel data.

This first example serves to demonstrate the basic concepts as succinctly as possible. In future posts, I will gradually refactor and improve upon the code to create reusable components. [more]

Background

You may be asking yourself “ESRI has a Flex API, why aren’t we using that?” Yes, ESRI does have an excellent Flex API - but it lacks the ability to show Google Maps data or use the Google Geocode API.

This is probably more of a Google licensing issue than anything else. But as of today, if you want to display your own ArcGIS Server content on top of Google Maps data in a Flex/Flash project, you can’t use the ESRI Flex API to do it without violating the Google Maps Terms of Service.

If Google were to open up its TOS to allow direct access to the map tiles (as many have called for) that would solve part of the problem. But you would still have the issue of being restricted in what you can do with the results of a call to the Geocode API. My understanding is that you are only allowed to display the result(s) on a Google Map, not in any other mapping system.

In my experience, the Google Maps data and Geocode functions are superior to the ArcGIS Online equivalents that are accessible from the ESRI Flex API. If you want to take advantage of Google’s map content and Geocode functionality, it’s worth the effort to integrate the ArcGIS Server REST API with the Google Maps API for Flash .

Don’t take this to mean I don’t like ESRI’s Flex API – it has many strengths which I’ll cover in future posts. In the meantime, if you’re going that route, check out Thunderhead Explorer - an excellent blog by ESRI’s Mansour Raad featuring “Tips and Tricks with ArcGIS Flex Mapping API”.

Sample Application Concepts

Our simple sample application works as follows:

  • The Google Maps API for Flash is embedded in a Flex 3 application.
  • The parcels are served via an ArcGIS Server Map Service, accessible via ArcGIS Server’s REST API.
  • The parcels are displayed at zoom levels 17 - 19.
  • The parcels are fetched via an HTTPService each time the map extent changes.
  • The parcels are returned in JSON format.
  • The JSON response is decoded using the open source JSON decoder in as3corelib.
  • Each parcel’s geometry is converted to a Polygon Overlay and added to the map.
  • Parcels are drawn with dark grey lines when the normal map is selected and white lines when the Satellite or Hybrid map is selected.
  • When the user hovers the mouse over a parcel, it’s parcel number is displayed in a tooltip.

Here are a couple of screenshots:

image

image

Live example is here, and source code is here.

Discussion

Starting with a basic Flex Application, we have a single map with a specified “map ready” event:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:gm="com.google.maps.*"
    backgroundGradientColors="[0xFFFFFF, 0xAAAAAA]"
    height="100%" width="100%" >
    <gm:Map id="map"
        width="100%"
        height="100%"
        key="(Your API Key Here)"
        mapevent_mapready="map_mapReady(event)" />

    <mx:Script>
        // Omitted for clarity
    </mx:Script>
</mx:Application>

Next, we declare a few member variables at the top of the script block:

// Parcels URL and parameters
private var parcelsUrl:String = "http://sampleserver1.arcgisonline.com/" +
    "ArcGIS/rest/services/Portland/ESRI_LandBase_WebMercator/MapServer/1/query";
private var wgs84wkid:String = "4236";
private var toolTipField:String = "RNO";

// Parcels map service
private var parcelsService:HTTPService = new HTTPService();

// Parcels symbols
private var transparentFill:FillStyle;
private var greyStroke:StrokeStyle;
private var whiteStroke:StrokeStyle;

With the parcelsUrl variable, we are specifying an endpoint to an ArcGIS Server REST API query function on a Layer in a MapService. See the ESRI ArcGIS Server REST API Reference for more information on the specific URL format for calling this operation.

For this example, we have chosen an ESRI sample server that has a parcels layer available for Portland, Oregon. We can explore this MapService and other sample services using the interactive SDK located at the following URL: http://sampleserver1.arcgisonline.com/arcgis/rest/services

image 

By drilling down into the Portland Folder and MapServices, we can learn about the functionality and data layers that are available for consumption. If we drill down to the layer in question, we can see a few interesting things about it, including the field names and aliases.

We also see that the layer has a spatial reference well known id (wkid) of 102113 – also known as Web Mercator. This is great if you need to overlay the layer as map tiles on Google Maps, because Google’s map tiles are also in Web Mercator. However, streamed geometries with coordinates in the Web Mercator spatial reference will not overlay properly on top of Google Maps. They must be in WGS 84 (Lat/Lon).

Therefore, we have a wgs84wkid variable which holds the well known id for WGS 84. As you will see, the ArcGIS Server REST API allows us to specify input and output spatial references that are different from the spatial reference of the MapService – and it will perform the coordinate conversions on fly.

The map_mapReady function is filled in next:

private function map_mapReady(event:Event):void {
    // Setup parcel symbols
    transparentFill = new FillStyle({
        alpha: 0.0 });
    greyStroke = new StrokeStyle({
        alpha: 1.0,
        thickness: 1.5,
        color: 0x333333 });
    whiteStroke = new StrokeStyle({
        alpha: 1.0,
        thickness: 1.5,
        color: 0xffffff });
   
    // Add map controls
    var mtc:MapTypeControl = new MapTypeControl();
    map.addControl(mtc);
    var pc:PositionControl = new PositionControl();
    map.addControl(pc);
    var zc:ZoomControl = new ZoomControl();
    map.addControl(zc);
    var sc:ScaleControl = new ScaleControl();
    map.addControl(sc);
    var omc:OverviewMapControl = new OverviewMapControl();
    map.addControl(omc);

    // Add event listeners
    map.addEventListener(MapMoveEvent.MOVE_END, map_moveEnd);

    // Initialize map           
    map.setCenter(new LatLng(45.520211, -122.65553), 17,
        MapType.NORMAL_MAP_TYPE);
}

In the map_mapReady function, we setup a transparent fill style and stroke styles for the dark grey and white lines. We also initialize the map controls and add an event listener for the map’s “move end” event. This event is where we will call the ArcGIS Server REST API to get the parcels for the current map extent. Finally, we center the map and zoom in on an area of Portland so that we’ll have something to look at when the application starts.

The map’s “move end” event is where we send an http request to ArcGIS Server to get the parcels:

private function map_moveEnd(event:MapMoveEvent):void {
    // Get zoom level
    var zoom:Number = map.getZoom();
    trace("map_moveEnd - Zoom level: " + zoom.toString());

    // Only draw parcels if zoomed in far enough
    if (zoom < 17) {
        // This just clears the overlays if we're not zoomed in far enough.
        // This would be a problem if there are other overlays besides parcels.
        // We'll deal with that in a future post.
        map.clearOverlays();
        return;
    }

    // Get current extent
    var bnds:LatLngBounds = map.getLatLngBounds();
   
    // An ESRI envelope is configured as xmin, ymin, xmax, ymax
    var envelope:String = bnds.getWest().toString() + "," +
                          bnds.getSouth().toString() + "," +
                          bnds.getEast().toString() + "," +
                          bnds.getNorth().toString();
    trace("map_moveEnd - Envelope: " + envelope);
   
    // Cancel http service in case a request was sent
    // that hasn't come back yet
    parcelsService.cancel();

    // Setup http service and add listeners
    parcelsService.url = parcelsUrl;
    parcelsService.addEventListener(ResultEvent.RESULT, onParcelResult);
    parcelsService.addEventListener(FaultEvent.FAULT, onHttpFault);

    // Setup parameters to pass to the service
    var params:Object = new Object();
    params.geometry = envelope;
    params.geometryType = "esriGeometryEnvelope";
    params.inSR = wgs84wkid;
    params.spatialRel = "esriSpatialRelIntersects";
    params.returnGeometry = "true";
    params.outSR = wgs84wkid;
    params.outFields = toolTipField;
    params.f = "json";

    // Send the request
    parcelsService.send(params);
}

First, we get the current zoom level. If the level is less than 17, we clear all overlays and exit. This is a shortcut to keep the example simple. If we were drawing any other overlays besides parcels, this would obviously be a problem. We’ll look at a better way to handle clearing the parcels in a future post.

Next, we get the LatLngBounds of the map and convert it into a string representation of an ESRI envelope that can be passed to the ArcGIS Server REST API.

Before making a new request through the HTTPService, we cancel it in case a prior request was sent that hasn’t come back yet.

Next we assign the URL endpoint to the HTTP Service and set up the event listeners.

In Flex 3 (like in Javascript), you can create an anonymous object and add properties to it “on the fly”. Here we create a new Object named params and add all of the parameters we want to pass as arguments in the URL query string. See the ESRI ArcGIS Server REST API Reference for more information on what parameters are available when calling the query operation on a layer.

Once the params Object is constructed, it is passed to the send method on the parcelsService (HTTPService). The HTTPService converts each property into a query string argument before sending the request to the REST API for the MapService.

The “on parcel result” event will be fired by the HTTPService when a response is received.

private function onParcelResult(event:ResultEvent):void {
    //trace(event.result as String);

    // Use ascorelib JSON serializer to decode returned object
    var obj:Object = JSON.decode(event.result as String);
    if (obj.error != null) {
        Alert.show('An error occured on the server. The response message returned was:\n\n' +
            event.result, 'Server Error');
        return;
    }

    // Simply clear all the polygons from the map.
    // This would be a problem if there are other overlays.
    // We'll deal with that in a future post.
    map.clearOverlays();

    // Get the features array from the returned JSON
    var features:Array = obj.features as Array;

    // This code breaks multipolygons into a separate polygon for each
    // ring. Not ideal, but necessary at this zoom level because the only
    // way to create multi ringed polygons in GMaps Flash API (as of v 1.9c)
    // is with the Polyline Encoding Algorithm - and this algorithm rounds
    // to 5 decimal places - thus creating choppy looking polygons.
    // We'll discuss this further in a future post.
    for each (var feature:Object in features) {
        for each (var ring:Array in feature.geometry.rings) {
            var points:Array = new Array();
            for each (var point:Array in ring) {
                points.push(new LatLng(point[1], point[0]));
            }

            var polyOpts:PolygonOptions = new PolygonOptions();
            polyOpts.fillStyle = transparentFill;
            polyOpts.strokeStyle =
                (map.getCurrentMapType() == MapType.NORMAL_MAP_TYPE ?
                greyStroke : whiteStroke);
            polyOpts.tooltip = feature.attributes[toolTipField];

            var poly:Polygon = new Polygon(points, polyOpts);
            map.addOverlay(poly);
        }
    }               
}

The ResultEvent’s result property contains the JSON string returned from the server. We use the ascorelib JSON serializer to decode the returned string into an Object. From there, we can access the properties of the result object defined for the ArcGIS Server response.

The JSON Response Syntax is defined as follows:

{
"displayFieldName" : "<displayFieldName>",
"fieldAliases" : {
    "<fieldName1>" : "<fieldAlias1>",
    "<fieldName2>" : "<fieldAlias2>"
},
"geometryType" : "<geometryType>",
"spatialReference" : {<spatialReference>},
"features" : [
    {
    "attributes" : {
        "<fieldName1>" : <fieldValue11>,
        "<fieldName2>" : <fieldValue21>
    },
    "geometry" : {<geometry1>}
    },
    {
    "attributes" : {
        "<fieldName1>" : <fieldValue12>,
        "<fieldName2>" : <fieldValue22>
    },
    "geometry" : {<geometry2>}
    }
]
}

In this case, since we are requesting polygons, the JSON Response Syntax for the geometries is defined as follows:

{
"rings" : [
    [ [<x11>, <y11>], [<x12>, <y12>], ..., [<x11>, <y11>] ],
    [ [<x21>, <y21>], [<x22>, <y22>], ..., [<x21>, <y21>] ]
],
"spatialReference" : {<spatialReference>}
}

Once we get the features array from the Object we iterate each feature, get its attributes and geometry, construct a Google Maps Polygon Overlay, and add it to the map.

When constructing the Polygon, a PolygonOptions object is created to define its look and behavior. We use this object to define the symbology, choosing dark grey lines if the map type is Normal, and white lines if the map type is one of the others such as Satellite or Hybrid. The PolygonOptions object is also used to define the tool tip that will be displayed when the user hovers the mouse over the Polygon.

Note that if the ESRI polygon has multiple rings, we simply create a separate Google Polygon for each ring. This is necessary because the Google Polygon constructor can only take one array of points and does not handle multiple rings.

There is a way to create multi-ringed Google Polygons using the Polygon.fromEncoded() static method, but this turns out to be problematic due to the rounding errors introduced by the encoding algorithm.

Default 500 Feature Limit

ArcGIS Server MapServices have a default limit of 500 features that can be returned from a layer query request. If you Pan/Zoom around the live example application, you might notice missing parcels if you are in a dense area and the limit is reached. The limit can be changed by modifying the MaxRecordCount in the MapService’s configuration file on the server. But since we’re using an ESRI sample server, I can’t very well make that change.

Wrap Up

This simple application has served to demonstrate the basics of communicating with the ArcGIS Server REST API and overlaying the results on a Google Map built with the Google Maps API for Flash.

Stay tuned for future posts, where we will refactor and improve upon the code to create reusable components.

Live example is here, and source code is here.

Resources

Downloads

Documentation and Sample Servers

Blogs and Forums

Tags: , , , ,

ArcGIS Server | Flex 3 | Google Maps | How To | Planet GS

Reduce ArcGIS Server Boilerplate Code By Implementing IDisposable

by James Richards April 19, 2009

When programming with ArcObjects and ArcGIS Server, code often follows a common pattern of connecting to ArcGIS Server, getting hold of an IServerContext, doing some work, and releasing the context. This leads to a lot of unnecessarily repeated setup and teardown code, usually taking a form similar to the following example:

IServerContext context = null;

try

{

    IGISServerConnection conn = new GISServerConnectionClass()

        as IGISServerConnection;

 

    // Connect as ASPNET or NETWORK SERVICE.

    // Get optional host setting from web.config,

    // or use localhost if not found.

    string host = ConfigurationManager.AppSettings["AGSHost"];

    if (String.IsNullOrEmpty(host))

    {

        host = "localhost";

    }

 

    // Make connection

    conn.Connect(host);

    IServerObjectManager som = conn.ServerObjectManager;

    context = som.CreateServerContext("MyServer", "MapServer");

   

    // Do some stuff with the server context

}

finally

{

    if (context != null)

    {

        context.ReleaseContext();

    }

}

In this example, there is a lot of boilerplate happening just to get to the server context and do something interesting. Seeing this kind of code repeated throughout an ArcGIS Server project made me a little queasy as it violates the DRY Principle, so that got me thinking about how to eliminate it. [more]

I decided to create a wrapper class that handles making the connection and implements IServerContext and IDisposable. Inspiration for this idea comes from the .NET SqlConnection class, which also implements IDisposable and can therefore be instantiated inside of a using block to automatically close the connection and release any resources when the using block goes out of scope. (See this David Hayden post for a basic discussion of why this is a good thing.) Proper implementation of IDisposable is known as the Dispose Pattern and is documented both here and in Cwalina and Adams excellent Framework Design Guidelines.

With this new class, we can reduce the boilerplate code block to the following:

using (ArcGisServerContext context = new ArcGisServerContext("MyServer",

    ArcGisServiceType.MapServer))

{

    // Do some stuff with the server context

}

Notice also the use of an Enum to indicate the server type, thus eliminating one of the error prone hard coded strings as well. (In a real application, I would also retrieve the value “MyServer” from a config or resource file.) The Enum is defined like so:

public enum ArcGisServiceType

{

    MapServer,

    GeocodeServer,

    GlobeServer,

    GPServer,

    GeoDataServer,

    GeometryServer,

    ImageServer,

}

and the ArcGISServerContext class is implemented as follows:

using System;

using System.Configuration;

using ESRI.ArcGIS.Server;

public class ArcGisServerContext : IServerContext, IDisposable

{

    #region Fields

    private IServerContext serverContext;

    #endregion

 

    #region Constructors()

    // Here you can add overloaded constructors to take additional

    // arguments such as login credentials - Or you could alter the

    // constructor to use an ArcGIS Server Identity from web.config.

 

    // This example just connects as the current user, which will be

    // ASPNET or NETWORK SERVICE if running under IIS on Wind XP or 2003.

    public ArcGisServerContext(string serviceName,

        ArcGisServiceType serviceType)

    {

        IGISServerConnection conn = new GISServerConnectionClass()

            as IGISServerConnection;

 

        // Get optional host setting from web.config,

        // or use localhost if not found.

        string host = ConfigurationManager.AppSettings["AGSHost"];

        if (String.IsNullOrEmpty(host))

        {

            host = "localhost";

        }

 

        // Make connection and cache IServerContext

        conn.Connect(host);

        IServerObjectManager som = conn.ServerObjectManager;

        serverContext = som.CreateServerContext(serviceName,

            serviceType.ToString());

    }

    #endregion

 

    #region IServerContext Implementation

    // These methods just pass through to the cached server context

 

    public object CreateObject(string clsid)

    {

        return serverContext.CreateObject(clsid);

    }

 

    public object GetObject(string name)

    {

        return serverContext.GetObject(name);

    }

 

    public object LoadObject(string str)

    {

        return serverContext.LoadObject(str);

    }

 

    public void ReleaseContext()

    {

        serverContext.ReleaseContext();

    }

 

    public void Remove(string name)

    {

        serverContext.Remove(name);

    }

 

    public void RemoveAll()

    {

        serverContext.RemoveAll();

    }

 

    public string SaveObject(object obj)

    {

        return serverContext.SaveObject(obj);

    }

 

    public IServerObject ServerObject

    {

        get { return serverContext.ServerObject; }

    }

 

    public void SetObject(string name, object obj)

    {

        serverContext.SetObject(name, obj);

    }

    #endregion

 

    #region IDisposable Implementation

    // Implementation of the Dispose Pattern

 

    public void Dispose()

    {

        Dispose(true);

        GC.SuppressFinalize(this);

    }

 

    protected virtual void Dispose(bool disposing)

    {

        // Release the server context

        if (disposing)

        {

            if (serverContext != null)

            {

                serverContext.ReleaseContext();

            }

        }

    }

    #endregion

}

 

It’s really pretty simple, and it sure is nice not to have to copy / paste or keep re-typing the same boilerplate code over and over.

Hope this helps!

Tags: , , ,

.NET | ArcGIS Server | Planet GS

Powered by BlogEngine.NET 1.6.0.0
Theme by Mads Kristensen | Modified by Mooglegiant
Creative Commons License This work is licensed under a Creative Commons Attribution 3.0 United States License.

Welcome

James Richards

Hi, I'm James Richards the CTO and co-founder of Artisan Global LLC. We make location-aware mobile apps with maps. I'm the author of QuakeFeed and I helped launch Zaarly at LASW Feb 2011. I also enjoy surfing, snowboarding, golfing, yoga, and music. I love my family: Linda, Sequoya and our cats Remy and Twiggy. Thanks for stopping by, I hope you find something helpful here.

Subscribe by RSS   Follow me on Twitter   Connect on Facebook   View my profile on LinkedIn


Amazon Associates

Some of my posts may contain Amazon Associates links. I only include these links when it makes sense within the context of the article. If you are in the market for one of these items, please consider clicking on an affiliate link to make your purchase. You still get the same great deal from Amazon and it will help me pay for hosting and bandwidth. Thanks!