Chapter 6. MapPoint Web Service Find APIs

The Find Service is one of the four core components of the MapPoint Web Service, allowing you to find places, addresses, points of interest around a given place or address, locations based on entity types, and so on. In this chapter, we’ll take an in-depth look at the MapPoint Web Service Find APIs .

Understanding Find APIs

If you remember from our discussion in Chapter 5, the Find Service endpoint is located at the FindService.asmx, and the APIs available for Find Service are exposed via the FindServiceSoap class in the Web Service proxy (Reference.cs or Reference.vb). The FindServiceSoap class contains a number of find-related methods used for finding places, addresses, nearby points, and so on. Table 6-1 shows the methods exposed on the FindServiceSoap class.

Table 6-1. Methods available on the FindServiceSoap class

Method

Description

Find

Finds geographic locations in a specified data source based on a place name.

Returns an array of results as a FindResults object. Maximum returned values is 500. Default number of returned results is 25.

FindAddress

Finds geographic locations in a specified data source based on an address.

Returns an array of possible results as a FindResults object. Maximum returned values is 100. Default number of returned results is 25.

FindByID

Finds points of interest based on entity ID. The found results are returned as a FindResults object.

FindByProperty

Finds points of interest based on predefined or custom properties. This method is independent of the location information such as distance and latitude/longitude. The found results are returned as a FindResults object.

FindNearby

Finds points of interest within a specified distance of a given latitude and longitude coordinate; returns an array of found points of interest ordered by proximity to a selected point as a FindResults object. Maximum returned values is 500. Default number of returned results is 25.

FindNearRoute

Finds points of interest within a specified distance from a route. The found results are returned as a FindResults object.

FindPolygon

Finds polygons in a specified data source, based on a FindPolygonSpecification object. The found results are returned as a FindResults object.

GetLocationInfo

Finds geographic entities and addresses for a specified latitude and longitude coordinate (also known as reverse geocoding). Returns an array of locations (Location objects). Maximum returned values is 250.

ParseAddress

Parses a specified address string and returns an Address object.

In this chapter, we will go over these methods in detail; first, let’s look at some basic concepts.

The Anatomy of Find Methods

The FindServiceSoap class contains the methods related to find functionality, such as Find (place), FindAddress, FindNearby, and so on. Choose the appropriate find method based on your application’s needs. All find methods share a common signature pattern as shown in the following example, where XXX can be an address or a nearby entity):

        FindResults FindXXX( FindXXXSpecifiction )

A find method, no matter what type of find it is, always returns the results in the form of the FindResults instance, which tells you how many matches are found for your find query along with the actual location matches. The actual location matches are represented as an array of FindResult class instances, each of which contains an instance of matched location as a Location object that provides information such as latitude/longitude, address, entity type, and the confidence score of the location match. It is important to note that Location instances in MapPoint Web Service expose the actual latitude and longitude information of the physical location and a corresponding best possible map view associated with it.

Unlike the consistent output returned by all find methods, the input argument is unique to each find method type, so the specification corresponding to each find type is represented by specific types. For example, if you are using the FindAddress method, use the FindAddressSpecification object as an input argument; if you are using the FindNearby method, use the FindNearbySpecification object as an input argument. Each input specification object contains three core parts:

Data source name

This field is found across all specification objects for all find methods and indicates which data source to use for the find.

Find-type specific arguments

This field is unique to each find method and includes arguments such as input address for an address search, place name for a place search, and so forth.

Optional FindOptions object

This field is found across all the specification objects for all find methods and can be used to control the output behavior.

For example, the FindAddressSpecification object, which is passed to the FindAddress method to find an address, contains the following three fields:

DataSourceName

A string value that represents the data source to be used for the address search (such as MapPoint.NA and MapPoint.EU).

InputAddress

An instance of the Address class that represents the input address to be used in the search.

Options

An instance of the FindOptions class that can be used to control the behavior of the results returned by any find method. This argument is optional.

Any specification object used in the find method is consistently modeled in this format.

By now, I’m sure you are wondering how the FindOptions object can alter the behavior of the results returned by the find method. Let’s look briefly at this object so that you understand its purpose. The FindOptions object contains the following fields:

Range

This field can be used to control the number of returned matches and the starting index of the returned results. For example, if you are searching for a place and your search yields 200 matches, you can request to return only the top 10 results. Along the same lines, you can also request a range of results starting with any valid index (such as from 21 to 30), which is very useful if you are displaying the search results from multiple pages (the typical pagination feature for displaying data). This field is of type FindRange.

ResultMask

By default, all find methods return latitude/longitude, entity type information, address, and best map view information for each matched location of the input query. You can use this field to mask the unwanted information returned from a find method. For example, if you are searching for a place and looking only for latitude/longitude information, you can prevent the find method from returning any other information using this mask. The ResultMask field is of type FindResultMask and is an enumeration.

SearchContext

This field indicates the entity ID within which you want to perform your search. In this case, the entity ID represents a geographic area to which you want to limit your search. Assigning an entity to provide a search context returns more relevant matches faster. This field is of type Integer.

ThresholdScore

Each result returned by any find method contains a score indicating the level of confidence in the match. Using this field, you can filter out the low-confidence results below the specified ThresholdScore. This field is of type Double, and the valid values are between zero and one (low confidence to high confidence).

Using the FindOptions object, you can tailor the results returned by the find method according to your application’s needs. There is one more interesting tidbit you may want to know about the FindOptions object: when you use it in your find method calls, you actually positively impact the performance (and possibly the economics) of your application. I will discuss the details at the end of this chapter in the section on optimizing your find calls performance.

Now that you know the anatomy of the Find Service, let’s look briefly at entities and the entity relationships model in MapPoint Web Service before we get into coding aspects of the Find Service.

Understanding Entities and Entity Relationships

Entities in MapPoint Web Service represent real things (e.g., countries, cities, landmarks, schools) that exist in the real world. All entities are first-class citizens in the world of MapPoint Web Service; they have type definitions, carry their own identities, enjoy entity-level find capabilities, maintain relationships, and in some cases even persist their identity across different versions of MapPoint Web Service.

Before I get into too many details, here is an example to help you understand this concept. Consider the United States: in MapPoint Web Service, the country is an entity with entity ID 244; the states contained within it are represented as children entities to the United States entity. Counties, cities, and towns within these states are represented as the children entities to the state entities; this parent-child relationship continues down to things like bridges, roads, parks, and schools. Figure 6-1 summarizes this discussion by showing the entity relationships for the town of Redmond, WA.

How do all the entities and entity relationships defined in MapPoint Web Service help you? The short answer is that you can issue smarter and more focused find queries to find things that suit your needs. Many applications require you to drill down into the subgeographic areas starting with a larger coverage. To continue with our United States example, you may want to develop an application where your customers start with the United States, go to the 50 states in the United States, pick a state and get all counties, pick a county and get all cities, and pick a city and get all interested entities within it. Entities are used to issue queries with limited geographic context (such as all states within the United States). You can also add your own custom entity types (or points of interest) that allow you to find places using the entities and entity relationships.

Entity parent-child relationships
Figure 6-1. Entity parent-child relationships

Programmatically, all entities are represented by the Entity class, and the definition of an entity type is represented by the EntityType class. The EntityType objects define the schema of an entity type, including the name, parent, and valid properties of an entity type.

Each physical entity represented using the Entity class has an ID, a DisplayName, which is a long descriptive name, and a common short Name. An Entity object also indicates the type of entity using the TypeName field (which is a string); this type name maps to a corresponding EntityType object. Finally, an Entity object exposes a collection of properties specific to that entity type as an array of EntityPropertyValue objects. The EntityPropertyValue object is a simple name value pair that gives you the name and value of the property. If you know an entity’s properties, you can issue queries to find entities by their properties—we will look at this concept later in this chapter.

If you find the concepts of EntityType and Entity confusing, think of it this way: an EntityType defines the schema, and an Entity represents the instance of a “thing” with that schema (similar to the difference between a class and an object). For example, the entity type PopulatedPlace defines the schema and parent-child relationships for geographic entities such as cities, towns, and villages; the entity type PopulatedPlace is represented by the EntityType class programmatically. The city of Paris is an entity of type PopulatedPlace, so the actual city is represented by an Entity object programmatically with a TypeName of PopulatedPlace.

Data Sources and Entity Types

Because entities in MapPoint Web Service are real things, they are contained in corresponding data sources for any given geographic extent. For example, if you consider the North American map data source MapPoint.NA, entities within the United States, Canada, Mexico, and Puerto Rico are contained in this data source. The only way to get to these entities is by knowing which entity types are supported by a particular data source. To find out which entity types are supported by a given data source, use the CommonServiceSoap method to get a list of the entity types supported by different data sources.

Getting all supported entity types within a data source

You can get a list of entity types supported by a specific data source using the CommonServiceSoap.GetEntityTypes method, which takes the data source name as an input argument and returns an array of EntityType objects. The following code snippet shows how to get all entity types defined in the MapPoint.NA data source:

    //Define an instance of CommonServiceSoap class
    CommonServiceSoap commonService = new CommonServiceSoap( );
    //Assign credentials
    . . .
    //Define a local entitytypes array
    EntityType[] entityTypes;
    //Datasource in question
    string datasource = "MapPoint.NA";
    //Get all entity types using the GetEntityTypes method 

    entityTypes = commonService.GetEntityTypes(datasource);
    //Now loop through each entity display the name, parent
    foreach(EntityType et in entityTypes)
    {
        Console.WriteLine(et.Name);
        Console.WriteLine(et.Definition);
        Console.WriteLine(et.ParentName);
    }

Along the same lines, you can also get all entity type definitions for all data sources in MapPoint Web Service environment:

    //Create an instance of common service soap
    CommonServiceSoap commonService = new CommonServiceSoap( );
    //Assign credentials
    . . .
    xDataSource[] dataSources;
    dataSources = commonService.GetDataSourceInfo(null);
    foreach (DataSource ds in dataSources)
{
    String datasource = ds.Name;
    //Now get entity type info for this data source
    //Define a local entitytypes array
    EntityType[] entityTypes;
    //Datasource in question
    //Get all entity types using the GetEntityTypes method 

    entityTypes = commonService.GetEntityTypes(datasource);
    //Now loop through each entity display the name, parent
    foreach(EntityType et in entityTypes)
     {
       Console.WriteLine(et.Name);
       Console.WriteLine(et.Definition);
       Console.WriteLine(et.ParentName);
      }
    }

I use the CommonServiceSoap.GetDataSourceInfo method to get a list of data sources and then the CommonServiceSoap.GetEntityTypes method to get a list of entity types defined in each data source.

Once you know the names of the entity types, you can use the Find APIs, which we will see in detail later in the chapter, to customize the find queries to find only particular entity types.

Data Sources and Countries/Regions

Just as each data source in MapPoint Web Service supports different entity types, there is country/region level mapping to each MapPoint data source. The geographic extent (or the geographic coverage) of each data source is predefined by MapPoint Web Service. For example, the data source MapPoint.NA has a geographic extent of the United States, Canada, Mexico, and Puerto Rico. In the same way, other data sources have different supported country listings. You don’t need to remember which data source needs to be used for each country name—you can use the CommonServiceSoap class for this purpose as well.

Countries/Regions and Their Entity IDs

Before we get into the details of how to query data source geographic extents and how to map a country to a supported data source programmatically, you need to understand how the countries/regions are managed in MapPoint Web Service Data sources. Each country/region is given an entity ID, which is a unique integer number. Each country/region also holds an ISO2- and ISO3-compatible code associated with that country. All of this information is represented as the CountryRegionInfo object, which exposes several properties, such as EntityID, FriendlyName, Iso2, Iso3, and OfficialName. If you take the United States, for example, the country region information is organized as follows:

    Entity ID         244
    FriendlyName      United States
    Iso2              US
    Iso3              USA
    OfficialName      United States of America

An instance of the CountryRegionInfo class that represents a valid country/region also includes the centroid latitude/longitude of the country. The entity ID for the United States is 244, and it does not change across different versions of the MapPoint Web Service, so you can safely hardcode this ID into your applications for any United States-specific find queries.

To get the country/region information using MapPoint Web Service, use the CommonServiceSoap.GetCountryRegionInfo method:

    //Create Common Service SOAP Class instance
    CommonServiceSoap commonsoap = new CommonServiceSoap( );
    //Assign credentials
    . . .

    //Get country region info
    CountryRegionInfo[] countryregioninfos =
        commonsoap.GetCountryRegionInfo(null);

    //Do some processing
    foreach(CountryRegionInfo crinfo in countryregioninfos)
    {
      . . .
    }

The GetCountryRegionInfo method takes an array of entity IDs as integers; however, in the previous code example, I’m passing null in order to get country/region information for all the countries listed in MapPoint Web Service. Similarly, if you want only country/region information for the United States, your call would look like this with 244 entity ID as an input argument:

    //Get country region info
    CountryRegionInfo[] countryregioninfos =
            commonsoap.GetCountryRegionInfo(new int[] {244});

You get only one CountryRegionInfo object (that corresponds to the United States) back from this method.

Note that since the list of county/region information does not change that frequently, it is good idea to store it in an in-memory data structure (such as a Hashtable with the entity ID as the key) in your applications to avoid round trips to the MapPoint Web Service.

Querying for Geographic Extent for a Data Source

Now that you know how the country/region information is organized in MapPoint Web Service, let’s look at how to get a list of countries supported by different data sources.

The geographic extent of the countries supported by a particular data source is defined using the EntityExtent field of the DataSource object. The DataSource.EntityExtent field is an array of integers that represent the corresponding country entity IDs. The following code snippet shows how to get the geographic extent of the data source MapPoint.NA:

    //Create Common Service SOAP Class instance
    CommonServiceSoap commonsoap = new CommonServiceSoap( );
    //Assign credentials
    . . .

    //Get Data Source Info for MapPoint.NA
    DataSource[] datasources =
          commonsoap.GetDataSourceInfo(new string[] {"MapPoint.NA"});
    //Get entity extent
    int[] extents = datasources[0].EntityExtent;

The geographic extent is expressed using the country/region entity IDs discussed in the previous section. To get the name of the country that each entity ID corresponds to, call the CommonServiceSoap.GetCountryRegionInfo method.

Programmatically Mapping a Country/Region to a Data Source

So far, I have shown how to get supported countries given a MapPoint Web Service data source. A common application scenario would be the other way around: choosing an appropriate data source for a given country. To programmatically map a country/region to a data source, you need to identify the task for which you need the data source, deciding whether you want a data source to find a place, find an address, calculate a route, or render a map. You can use the DataSourceCapability enumeration discussed in Chapter 5. Once you have the DataSourceCapability figured out, you can get an appropriate MapPoint Web Service data source name using the following code:

    private string[] GetDataSources(string countryRegionName,
                                      DataSourceCapability capability)
    {
       if(countryRegionName == null || countryRegionName.Length <= 0)
          throw new Exception(
            "Invalid country/region name; country/region
              name cannot be null or empty.");

       ArrayList datasourceList = new ArrayList( );

       //Now loop through the list of data sources and
       //see what data source fits the purpose
       foreach(DataSource datasource in datasources)
       {
         if(((int)datasource.Capability & (int)capability) != 0)
          {
             //OK, this data source has the capability, but does it support the
             //entity extent (the country that we need a data source for?)
             int[] entityextent = datasource.EntityExtent;
            //Now loop through each entity extent and compare the input name
             foreach(int entityid in entityextent)
              {
                //Now look up using the entity id to see if the input
                //country/region name matches any entity extent
                //in this case I'm country region information
                //cached in a Hastable
                if(this.countryRegionsTable[entityid] as string
                         == countryRegionName.Trim( ))
                 {
                    //Found a match for both requirements
                    //Return the name
                     datasourceList.Add(datasource.Name);
                     break;
                 }
               }
          }
        }

        if(datasourceList.Count <= 0)
           throw new Exception(
              "No data source found to match your needs for this country.");

        //Return a sorted list
        datasourceList.Sort( );

        return datasourceList.ToArray(typeof(String)) as string[];
     }

The previous function returns an array of suitable MapPoint Web Service data source names. The countryRegionsTable is a Hashtable containing cached country/region information indexed on the entity IDs. The algorithm used in this function is pretty simple: first, find a data source with matching capability, and then get the geographic extent for that data source and see whether there is a match with the input country/region name. You can also find the code for this function, along with other functionalities discussed in this section, in the Chapter 6 solutions on the companion material.

With this introduction to find methods , entity types, and data source relationships, let’s look at how to perform various spatial queries using Find Service.

Working with Find Methods

MapPoint Web Service Find Service is programmatically exposed as part of the FindServiceSoap class, which has many find methods including Find, FindAddress, FindNearby, and FindById. Choose an appropriate find method based on your application’s requirements. In this section, let’s look in detail at each find method offered by the MapPoint Web Service Find Service.

Finding Places

To find geographic entities and places by their names, use the FindServiceSoap.Find method. This method takes the FindSpecification as an input argument and returns the FindResults object as a return value. The FindSpecification wraps several values, including the input place name as a string, the data source to be used for searching the place, and an array of entity type names to find. Table 6-2 shows the fields of the FindSpecification class.

Table 6-2. Fields of the FindSpecification class

Field

Description

DataSourceName

A string representing the name of the data source in which to search for a place. For example, MapPoint.NA is the data source used for finding places in North America.

EntityTypeNames

An array of strings representing the names of the entity types to find.

InputPlace

The place name to find.

Options

The search options (FindOptions object), which includes the range of results, the threshold score of results returned, the search context, and a flag to identify which objects are desired in the returned results.

The data source used for the Find method must have the CanFindPlaces capability. The FindResults return value indicates the number of matches for the input place query using the FindResults.NumberFound field; when no results match your query, the NumberFound field is set to zero. All matches are exposed via the FindResults.Results field as a collection of FindResult objects; each FindResult object returned as a match contains a Location object that wraps the location information and a score indicating the level of confidence in the match. A valid Location object provides one or all of the following: address information, entity information, latitude/longitude information, and best map view information.

Next, let’s look at the Find method details: the following code shows how the Find API can be used to find all places named Redmond:

    //Create find service soap
    FindServiceSoap findsoap = new FindServiceSoap( );
    //Assign credentials
    . . .

    //Create FindSpecification
    FindSpecification findspec = new FindSpecification( );
    //Assign data source
    findspec.DataSourceName = "MapPoint.NA";

    //Assign input place to search
    findspec.InputPlace = "Redmond";

    //Now call find
    FindResults findresults = findsoap.Find(findspec);
    //Assign found count
    foreach(FindResult findresult in findresults.Results)
{
       //Display results
       . . .
    }

With options set at their defaults, this query returns the following seven places named Redmond:

    Redmond, Washington, United States
    Redmond, Oregon, United States
    Redmond, Western Australia, Australia
    Redmond, Larimer, Colorado, United States
    Redmond, Butler, Pennsylvania, United States
    Redmond, Sevier, Utah, United States
    Redmond, Mason, West Virginia, United States

Finding more default matches

These results include places from both the United States and Australia. By default, the find threshold score is set to 0.85, which means that any find match with a confidence score of less than 0.85 is not returned. So, to get more results for this query, you can simply decrease the threshold score using the FindOptions.ThresholdScore field:

    //Create find options
    findspec.Options = new FindOptions( );

    //Set threshold score to zero
    findspec.Options.ThresholdScore = 0;

With the threshold score set to zero, the same query for Redmond yields 32 results of which only the following first 25 are returned:

    Redmond, Washington, United States
    Redmond, Oregon, United States
    Redmond, Western Australia, Australia
    Redmond, Larimer, Colorado, United States
    Redmond, Butler, Pennsylvania, United States
    Redmond, Sevier, Utah, United States
    Redmond, Mason, West Virginia, United States
    Redmond Fall City Road Park (park), Washington, United States
    Redmond Park (park), Cedar Rapids, Iowa, United States
    Redmond Park (city park), Yonkers, New York, United States
    Redmond Branch Library (library), Redmond, Oregon, United States
    Redmond Chamber of Commerce (tourist information office), Redmond, Oregon, 
United States
    Redmond Chamber of Commerce (tourist information office), Redmond, Washington, 
United States
    Redmond City Hall (city hall), Redmond, Oregon, United States
    Redmond City Hall (city hall), Redmond, Washington, United States
    Redmond Community Cemetery (cemetery), Redmond, Washington, United States
    Redmond Corner, Oneida, New York, United States
    Redmond Cut (pass), California, United States
    Redmond District Court (courthouse), Redmond, Washington, United States
    Redmond Elementary School (school), Redmond, Washington, United States
    Redmond High School (school), Redmond, Washington, United States
    Redmond Junior High School (school), Redmond, Washington, United States
    Redmond Memorial Cemetery (cemetery), Redmond, Oregon, United States
    Redmond Municipal Court (courthouse), Redmond, Oregon, United States
    Redmond Municipal Court (courthouse), Redmond, Washington, United States

Returning more find results

By default, MapPoint Web Service always returns only 25 results at once, but you can get a maximum of 500 results using the FindOptions.Range field:

    //Create find options
    findspec.Options = new FindOptions( );
    //Set result count
    findspec.Options.Range = new FindRange( );
    //Set to the maximum count
    findspec.Options.Range.Count = 500;

After adding this code to the Find code, you get all 32 results returned by the Find method.

Selectively finding entity types

Notice that this list includes all kinds of entities, such as city halls, parks, libraries, and schools in the result list. Imagine for now that you need only a list of cities named after Redmond—you need to tell MapPoint Web Service that you are only looking for city entity matches to your query. You can do this using the FindSpecification.EntityTypeNames field. The EntityTypeNames field is an array of strings that represents the entity type names that the Find method needs to look for. Since you are only interested in cities named Redmond, pass the entity type name PopulatedPlace:

    //Assign entities to search
    fndspec.EntityTypeNames = new string[] {"PopulatedPlace"};

A call to the Find method with this addition returns the following nine cities named Redmond:

    Redmond, Washington, United States
    Redmond, Oregon, United States
    Redmond, Western Australia, Australia
    Redmond, Larimer, Colorado, United States
    Redmond, Butler, Pennsylvania, United States
    Redmond, Sevier, Utah, United States
    Redmond, Mason, West Virginia, United States
    Redmond Corner, Oneida, New York, United States
    Redmondville, Iron, Missouri, United States

Even though your threshold score is 0 and there are 500 returned results requested, by assigning specific entity type, the find is narrowed down to 9 results from the original 32 results.

Limiting search to a geographic area

Notice that the above list contains cities from both the United States and Australia. If you are looking only for cities in the United States and need to instruct MapPoint Web Service to limit the search within a geographic boundary, using the FindOptions.SearchContext field you can limit the search to a particular geographic area. The SearchContext field is an integer value that represents the entity ID of a specific geographic area. Since you are specifically looking for cities named Redmond in the United States, the context ID should be set to the United States country entity ID 244. The following code shows the addition of search context to the find request:

    //Assign country context for United States
    findspec.Options.SearchContext = 244;

With this addition, the search now only returns the following 8 results:

    Redmond, Washington, United States
    Redmond, Oregon, United States
    Redmond, Larimer, Colorado, United States
    Redmond, Butler, Pennsylvania, United States
    Redmond, Sevier, Utah, United States
    Redmond, Mason, West Virginia, United States
    Redmond Corner, Oneida, New York, United States
    Redmondville, Iron, Missouri, United States

The list now includes only cities in the United States.

Finding geographic entities with no input place name

The Find method is very powerful because it allows you to find geographic entities without actually specifying a place name. Example queries include: “Find all states in the United States” and “Find all airports in Australia.” You can perform these queries based on entity type names, geographic contexts, or by assigning the input place name a null value. The following code shows how to get all state names in the United States:

    //Create find service soap
    FindServiceSoap findsoap = new FindServiceSoap( );
    //Assign credentials
    . . .

    //Create FindSpecification
    FindSpecification findspec = new FindSpecification( );
    //Assign data source
    findspec.DataSourceName = "MapPoint.NA";

    //Assign null to input place
    findspec.InputPlace = null;

    //Create find options
    findspec.Options = new FindOptions( );
    //Set result count
    findspec.Options.Range = new FindRange( );
    //Set to the maximum count
    findspec.Options.Range.Count = 500;

    //Set threshold score to zero
    findspec.Options.ThresholdScore = 0;

    //Assign state entity type to search
    fndspec.EntityTypeNames = new string[] {"AdminDivision1"};

    //Now call find
    FindResults findresults = findsoap.Find(findspec);
    //Assign found count
    foreach(FindResult findresult in findresults.Results)
    {
       //Display results
       . . .
    }

This search results in 51 entities (50 states and Washington D.C.).

To explore more on data sources, entity types, and entity based finds, I have included an application, MapPoint Web Service Data Source Browser, on the companion material as part of the Chapter06 sample solution. Figure 6-2 shows a screenshot of the application with results for the query “Find all airports in Australia.”

Finally, it is important remember that, due to performance reasons, for any find query, the maximum number of results returned (FindResult objects) is 500; you cannot issue a find query such as “Find all cities in the world,” but if you do have such a requirement, I recommend breaking down the query to get a manageable result set that is less than or equal to 500 each time. An example of such implementation would be to provide a browse functionality where your customers can select a country first, a state second, a county third, and then find all cities within that county without hitting any maximum result count issues, since you are confining your query to a smaller, limited geographic area.

Finding Addresses

While the FindServiceSoap.Find method works well for finding places and geographic entities in general, it doesn’t offer any help to find addresses; use the FindServiceSoap.FindAddress method for that purpose.

Like any FindServiceSoap.Find method, the FindServiceSoap.FindAddress method takes a specification object of type FindAddressSpecification and inputs a data source name field of type String and an Options field of type FindOptions as input fields to expose an InputAddress field of type Address. Table 6-3 shows the fields exposed on the FindAddressSpecification object.

MapPoint Web Service data source browser
Figure 6-2. MapPoint Web Service data source browser
Table 6-3. Fields exposed in the FindAddressSpecification class

Field

Description

DataSourceName

A string representing the name of the data source in which to search for the address. Example: MapPoint.NA.

InputAddress

The input address to be found. This field is of type Address class.

Options

The search options (FindOptions object), which include the range of results, the threshold score of results returned, and a flag to identify which objects are desired in the returned results.

To use a data source to find addresses, it must have the CanFindAddress capability. An address in MapPoint Web Service is always represented as a valid instance of the Address object; the Address class provides fields such as AddressLine, PrimaryCity, SecondaryCity, Subdivision, PostalCode, CountryRegion to represent a valid address for all countries/regions. When used as input, the address line information of an address is optional. When an address is returned as an output, the address object also provides a formatted address string via the FormattedAddress field.

Say you want to find the following address:

    1 Microsoft Way
    Redmond, WA 98052
    US

First, you need to create an Address object:

    //Create an address object
    //And assign address values
    Address address =  new Address( );
    address.AddressLine = "1 Microsoft Way";
    address.PrimaryCity = "Redmond";
    address.Subdivision = "WA";
    address.PostalCode = "98052";
    address.CountryRegion = "US";

Once the Address object is ready, you can find the address information (such as latitude/longitude, best map view information, etc.) using the FindServiceSoap.FindAddress method:

    //Create a find address specification object
    FindAddressSpecification findAddressSpec = new FindAddressSpecification();
    //Assign input address
    findAddressSpec.InputAddress = address;
    findAddressSpec.DataSourceName = "MapPoint.NA";

    //Call the find address method
    FindResults foundAddressResults =
                  findService.FindAddress(findAddressSpec);

    //Process found results
    if (foundAddressResults.NumberFound > 0)
    {
       if(foundAddressResults.Results[0].FoundLocation.LatLong != null)
         {
           //Process latitude/longitude information
           . . .
         }
    }

The previous code snippet assumes that you have the FindService object available with proper credentials assigned.

The FindAddressSpecification also exposes the Options field so that you can control the behavior of the locations returned by this method using the FindOptions class; the FindOptions behavior that we have looked at in the Find method still holds true for this method except that you can only get a maximum of 100 results instead of 500.

Finding Points of Interest Around a Location

To find points of interest around a given location, use the FindServiceSoap.FindNearby method. The FindNearby method works only with data sources that have the CanFindNearby capability. As a general rule of thumb, only the point of interest data sources supplied by data vendors such as NavTeq and Acxiom have this capability; data sources such as MapPoint.NA and MapPoint.EU do not support the FindNearby method.

Like any other find service method, the FindNearby method also takes a specification of type FindNearbySpecification class. The FindNearbySpecification object takes information such as the data source name, input location around which you want to find points of interest (as a latitude/longitude), distance to be covered around the original location to find points of interest, and entity types you want to find. Table 6-4 gives an idea of the fields presented in the FindNearbySpecification object.

Table 6-4. Fields in a FindNearbySpecification object

Field

Description

DataSourceName

Data source name as a string.

Distance

The distance from the LatLong property.

Filter

The filter (FindFilter object) to apply to the results. In other words, it is the specific entity type, properties, and values that the returned results must match.

LatLong

The latitude and longitude coordinate (LatLong object) of the point around which the search is made.

Options

The search options (FindOptions object), which include the range of results and a flag to identify which objects are desired in the returned results.

To find all ATMs around the address 1 Microsoft Way, Redmond, WA, get the latitude and longitude information using the FindServiceSoap.FindAddress method, and call FindServiceSoap.FindNearby with one of the point of interest data sources (in this case, I chose to use NavTech.NA) and the entity type name for ATM, SIC3578:

    //Create find service soap instance
    FindServiceSoap findService = new FindServiceSoap();
    //Assign credentials
    . . .

    //Define findnearby specification
    FindNearbySpecification findNearbySpec  = new FindNearbySpecification();
    //Assign a data source
    findNearbySpec.DataSourceName = "NavTech.NA";
    //Since you are looking for ATMs, assign ATMs entity type
    findNearbySpec.Filter = new FindFilter();
    //Assign entity type for ATMs
    findNearbySpec.Filter.EntityTypeName = "SIC3578";

    //Set the distance in miles
    findNearbySpec.Distance = 1;
    //Assign the location around which you want to find ATMs
    findNearbySpec.LatLong = new LatLong();
    findNearbySpec.LatLong.Latitude = 47.6;
    findNearbySpec.LatLong.Longitude = -122.33;

    //Call findnearby method
    FindResults foundResults;
    foundResults = findService.FindNearby(findNearbySpec);
    //Process the results
    foreach(FindResult fr in foundResults.Results)
    {
       . . .
    }

The previous code finds ATMs around the specified address within one mile using the NavTech.NA data source; of course you could have also used other points of interest data sources from Acxiom.

Next, say that you are working for a banking company and building an ATM locator application; obviously you would display your company’s ATMs around any specified address. It is possible to display your ATMs with the FindNearby method, but since none of the MapPoint data sources or vendor data sources (NavTech, Acxiom, and so on) know about your bank’s ATMs specifically, you need to provide a data source for MapPoint Web Service to use with the FindNearby method. That’s when the customer data sources come into the picture.

Tip

If you do not want to upload your data to MapPoint servers due to security reasons, you can implement your own FindNearby functionality using SQL Server—see Appendix C for more details.

Customer data sources—displaying your data

When you sign up for MapPoint Web Service, you are assigned space on MapPoint servers to upload your business data (such as points of interest and icons) to use with the FindNearby method. Using this space, you can create a maximum of 25 data sources on the MapPoint Web Service servers. For example, if your company has banks and ATMs, you would create two data sources with one assigned to each entity type. So, with multiple data sources, you can use different data source files for different types of data. Having said that, there are certain requirements for creating your own data sources:

  • The combined size of all your data sources cannot exceed 2 gigabytes.

  • Each data file that you upload (that contains a number of location records) cannot exceed 100 megabytes.

  • Each data source and the entities it contains must have an entity type, which is a user-defined alphanumeric string, and an entity id, which is an integer.

  • The total number of searchable non-Boolean cells (or entity properties) per data source cannot exceed 8.75 million.

Every entity (or location record) has three required properties (EntityID, latitude, and longitude) and six additional properties created by the MapPoint Geocoder sevice, including MatchCode, MatchedAddress, MatchedMethod, EditedLocationUTC, EditedPropertyUTC, and InputModified. All of these properties are treated as searchable non-Boolean cells, so the number of entities contained in a single data source cannot exceed 972,222 (8.75 million cells divided by 9 non-Boolean, searchable properties per row). The number of Boolean cells per entity in a data source cannot exceed 200.

There are additional requirements applicable for entity property types in MapPoint Web Service, such as number of characters per field and type of characters per field.

Tip

For more information on the customer data formatting requirements, see the “Requirements for Custom Location Data” section from the MapPoint Web Service Customer Services site help documentation.

There are two options for uploading and downloading your entities to and from the customer services site: you can either use the Customer Services site web UI, or programmatically upload and download using the Customer Data Service Web Service , which is discussed in detail in Appendix A. Once you upload your custom data, you can use the FindServiceSoap.FindNearby method to use it against your own data.

Finding Points of Interest Along a Route

To find points of interest along a given route, use the FindServiceSoap.FindNearRoute method. The FindNearRoute method works only with data sources that have the CanFindNearby capability. As with the FindNearby method, only the point of interest data sources supplied by data vendors such as NavTeq and Acxiom have this capability; data sources such as MapPoint.NA and MapPoint.EU do not support the FindNearRoute method.

Tip

You can read more about calculating routes using RouteServiceSoap in Chapter 7.

Like any other find service method, the FindNearRoute method also takes a specification of type FindNearRouteSpecification class. The FindNearRouteSpecification object takes information, such as the data source name, input route (as a Route object) around which you want to find points of interest, and distance to be covered along the route, and uses it to find points of interest and entity types you want to find. Table 6-5 gives an idea of the fields presented in the FindNearRouteSpecification object.

Table 6-5. Fields in a FindNearRouteSpecification object

Field

Description

DataSourceName

The data source name as a string

Distance

The distance from the Route property

Filter

The filter (FindFilter object) to apply to the results; that is, the specific entity type, properties, and values that the returned results must match

Route

The route from which the points of interest are searched

Options

The search options (FindOptions object), which include the range of results and a flag to identify which objects are desired in the returned results

Say you want to find all the coffee shops along the route that you are planning for a road trip. First, calculate your route using the RouteServiceSoap class; next, call the FindServiceSoap.FindNearRoute with one of the point of interest data sources (I chose to use MapPoint.FourthCoffeeSample) and the entity type name for coffee shops, FourthCoffeeShops, as follows:

    FindServiceSoap findService =
                      new FindServiceSoap();
    findService.Credentials =
                  new System.Net.NetworkCredential(myMapPointUserId,
                                                   mySecurePassword);

    RouteServiceSoap routeService = new RouteServiceSoap();
    routeService.Credentials =
                 new System.Net.NetworkCredential(myMapPointUserId,
                                                 mySecurePassword);

    //Route between two locations
    LatLong[] latLongs = new LatLong[2];
    latLongs[0] = new LatLong();
    latLongs[1] = new LatLong();
    latLongs[0].Latitude = 52.5;
    latLongs[0].Longitude = 13.1;
    latLongs[1].Latitude = 52.51;
    latLongs[1].Longitude = 13.11;

    //Calculate route
    Route myRoute =
    routeService.CalculateSimpleRoute(latLongs,
                                 "MapPoint.EU",
                                SegmentPreference.Quickest);

    //Create near route specificiation
    FindNearRouteSpecification findnearroutespec =
                              new FindNearRouteSpecification();

    findnearroutespec.DataSourceName = "MapPoint.FourthCoffeeSample";
    findnearroutespec.Filter = new FindFilter();
    findnearroutespec.Filter.EntityTypeName = "FourthCoffeeShops";
    findnearroutespec.Distance = 20;
    findnearroutespec.Route = myRoute;

    FindResults foundResults;
    foundResults = findService.FindNearRoute(findnearroutespec);

    //Process the results to display on a map
    ...

This code finds the coffee shops around the specified route within 20 miles from the beginning of the route using the MapPoint.FourthCoffeeSample data source; you could use different point of interest data sources from Acxiom or NavTech or even your own data source.

Finally, note that the distance must always be greater than 0.1 miles (0.160934 kilometers) and less than 25 miles (40.2336 kilometers).

Finding Custom Entity Types

MapPoint Web Service has a certain set of methods that find entities using their identities and properties, but these methods can only be used with the custom data uploaded to the MapPoint servers. These methods are particularly useful for queries that depend on nonspatial attributes. For example, if you upload all your ATMs to MapPoint servers and you want to display all ATMs in the city of Chicago, or only the ATM that has the unique identity of 13324, these methods can be either simple non-spatial queries or spatial queries. In this section, let’s look at these find methods that can be used with your custom data.

Find entity by identity

You can use the FindServiceSoap.FindByID method to find entities using their entity IDs. Like any other find method, this method takes a specification object of type FindByIDSpecification and returns a FindResults object. The FindByIDSpecification object takes up to 500 IDs as input parameters. Table 6-6 shows the fields exposed on the FindByIDSpecification object.

Table 6-6. Fields exposed in the FindByIDSpecification object

Field

Description

DataSourceName

Data source name as a string

EntityIDs

Array of unique entity IDs; only points of interest with matching entity IDs are returned, while the rest are ignored

Filter

The filter (FindFilter object) to apply to the results, which includes the specific entity type, properties, and values that the returned results must match

Options

The search options (FindOptions object), which may include the range of results and a flag to identify which objects are desired in the returned results

The following code shows how to use the FindByID method:

    //Create a Find Service proxy
    FindServiceSoap findService = new FindServiceSoap();
    //Assign credentials
    . . .

    //Define find by id specification
    FindByIDSpecification findbyidspec = new FindByIDSpecification();

    //Assign a data source name
    findbyidspec.DataSourceName = "MapPoint.FourthCoffeeSample";

    //Apply a filter for entity name
    findbyidspec.Filter = new FindFilter();
    findbyidspec.Filter.EntityTypeName = "FourthCoffeeShops";

    //Now assign the entity IDs to find
    int[] arrayID = {-21835, -21836};
    findbyidspec.EntityIDs = arrayID;

    //Call FindById method
    FindResults foundResults;
    foundResults = findService.FindByID(findbyidspec);

The found entities are returned in the same order that the entity IDs are passed in, but you can override this sorting behavior using the FindFilter.SortProperties. Assuming that you want to sort the ATMs in the previous FindByID method by their associated bank name (assuming that there is a property called ParentBankName), the method call looks as follows:

    //Create a Find Service proxy
    FindServiceSoap findService = new FindServiceSoap();
    //Assign credentials
    . . .

    //Define find by id specification
    FindByIDSpecification findbyidspec = new FindByIDSpecification();

    //Assign a data source name
    findbyidspec.DataSourceName = "MapPoint.FourthCoffeeSample";

    //Apply a filter for entity name
    findbyidspec.Filter = new FindFilter();
    findbyidspec.Filter.EntityTypeName = "FourthCoffeeShops";

    //Specify what properties to be used to sort the found results
    SortProperty[] sortproperties = new SortProperty[1];
    sortproperties[0] = new SortProperty();
    //Assign the property name to be sorted on
    sortproperties[0].PropertyName = "ParentBankName";
    //Specify the sort direction: Ascending or Descending
    sortproperties[0].Direction = SortDirection.Descending;

    //Assign sort specification to the find filter
    findbyidspec.Filter.SortProperties = sortproperties;

    //Now assign the entity ids to find
    int[] arrayID = {-21835, -21836};
    findbyidspec.EntityIDs = arrayID;

    //Call FindById method
    FindResults foundResults;
    foundResults = findService.FindByID(findbyidspec);

As you can see, the SortProperties method is an array, so you can sort the resulting entities by more than one attribute if needed.

It is important to remember that the points of interest entity identities are not persisted from one version of the MapPoint Web Service to another, so if you hardcode the point of interest entity IDs into your application, when you upgrade to a newer MapPoint Web Service, your application may break. To make it easy to distinguish between positive and negative IDs, all negative entity IDs (such as entity ID -21835 for a coffee shop) are not persisted across versions, while the positive IDs are (such as entity ID 244 for the United States).

Finding entity by properties

Many times, you want to query for entities based on their properties—for example, finding all ATMs in the city of Chicago, or all coffee shops that accept credit cards. In this case, the query is based solely on the entity properties, and you should use the FindServiceSoap.FindByProperty method for this purpose. The FindByProperty method takes a specification object of type FindByPropertySpecification, which takes the queries to find entities using their properties. Table 6-7 shows the fields exposed on the FindByPropertySpecification object.

Table 6-7. Fields in the FindByPropertySpecification object

Field

Description

DataSourceName

Name of the data source as a string

Filter

The filter (FindFilter object) to apply to the results; that is, the specific entity type, properties, and values that the returned results must match

Options

The search options (FindOptions object), which may include the range of results and a flag to identify which objects are desired in the returned results

The following code shows how to use these expressions to find entities using the FindByProperty method:

    //Create a find service soap proxy class
    FindServiceSoap findService = new FindServiceSoap();
    //Assign credentials
    . . .

    //Create find by property specification
    FindByPropertySpecification findbypropspec = new FindByPropertySpecification();

    //Define find by property specification
    findbypropspec.DataSourceName = "MapPoint.FourthCoffeeSample";
    //Assign a filter
    findbypropspec.Filter = new FindFilter();
    //Specify the entity type that you are looking for
    findbypropspec.Filter.EntityTypeName = "FourthCoffeeShops";

    //Now define and assign the expression
    findbypropspec.Filter.Expression = new FilterExpression();
    findbypropspec.Filter.Expression.Text = "PrimaryCity = {0} AND IsWiFiHotSpot";
    findbypropspec.Filter.Expression.Parameters = new object[] {"Chicago"};

    FindResults foundResults;
    foundResults = findService.FindByProperty(findbypropspec);

The resulting expression from this code is "PrimaryCity = 'Chicago' AND IsWiFiHotSpot", which means to return only coffee shops in the city of Chicago that have WiFi Hotspots available. Even though the filter expressions look and behave like SQL expressions, there are limitations that you need to be aware of:

  • The expression text should never contain the values that are being compared, but the text must provide the placeholders for all non-Boolean value types. Placeholders are represented by “{nn}” where n is an integer between 0 and 9.

  • The comparison operators LIKE and NOT LIKE support only the “Starts with” condition.

  • Maximum length of the expression text is limited to 2,000 characters.

  • No more than one level of nesting (parenthesis) is allowed.

  • A maximum of 10 non-Boolean comparisons and a maximum of 10 sub-clauses are allowed.

  • A maximum of 50 total comparisons per expression is allowed.

Next, let’s look at an example expression: you want to find all coffee shops in the city of Chicago that have a seating capacity greater than 20 or that are open 24 hours a day whose names start with the letter C. The expression to pass for the FindByProperty method would be: (City={0} AND SeatingCapacity>{1}) OR (StoreType={2} AND Name LIKE {3}) with the arguments Chicago, 20, Open 24 Hours and C.

Now that you know how to use the find service APIs, let’s look at some of the common service methods that are relevant to the finding places, addresses, and entities.

Finding Polygons

With the find methods, you have seen how to find places, addresses, and points around a place or address, but all you have been finding so far are points (latitude and longitude coordinates). You may have a requirement to find polygons in situations with queries such as: “find all polygons that contain a point (latitude/longitude)” or “find all polygons that have spatial relationship with a rectangle.” In order to accomplish such tasks, use the FindServiceSoap.FindPolygon method.

Tip

To learn more about polygons, refer to Appendix B.

Like any find method in Find Service, the FindPolygon method takes the FindPolygonSpecification object as an argument and returns a valid FindResults object. The FindPolygonSpecification object provides a way for you to specify arguments such as data source name and spatial filter. Table 6-8 shows the fields exposed by the FindPolygonSpecification class.

Table 6-8. Fields of the FindPolygonSpecification class

Field

Description

DataSourceName

Name of the data source as a string

Filter

The filter (FindFilter object) to apply to the results, including the specific entity type, properties, and values that the returned results must match

Options

The search options (FindOptions object), which may include the range of results and a flag to identify which objects are desired in the returned results

SpatialFilter

The spatial filter (SpatialFilter object) to apply to the results

One interesting field from Table 6-8 is the SpatialFilter field; this field is of type SpatialFilter class, and it defines the spatial relationship between polygons, points, and rectangles. The SpatialFilter class is an abstract class, and there are two classes that derive this abstract class to define two specific spatial relationships:

LatLongSpatialFilter

Defines a spatial filter that returns only polygons that include the point specified by the LatLong object. This is used in specifying a spatial filter to find polygons that contain a certain point. This class has only one field that takes the target point as a LatLong object. The following code shows how to specify a LatLongSpatialFilter to find polygons that contain a given set of latitude and longitude coordinates:

    //Create a new instance of LatLongSpatialFilter
    LatLongSpatialFilter filter = new LatLongSpatialFilter();
    //Assign the given latitude and longitude values
    Filter.LatLong = new LatLong();
    Filter.LatLong.Latitude = 47.44;
    Filter.LatLong.Longitude = -122.55;
LatLongRectangleSpatialFilter

Defines a spatial filter that returns polygons related to the LatLongRectangle specified via the BoundingRectangle field. The relation between the polygons and the rectangle is determined by the PolygonRectangleRelation field. This field is of type SpatialRelation enumeration and has two values that are shown in Table 6-10. The LatLongRectangleSpatialFilter class is used in defining a spatial filter to find polygons that fall within or touch a rectangle. The following code shows how to define this spatial filter to find all polygons that fall within a rectangle:

    //Define a new instance of LatLongRectanglSpatialFilter
    LatLongRectangleSpatialFilter rectangleFilter =
                        new LatLongRectangleSpatialFilter();

    //Define a bounding rectangle with north east and south west
    //corners
    LatLongRectangle boundingRectangle = new LatLongRectangle();
    boundingRectangle.Northeast = new LatLong();
    boundingRectangle.Northeast.Latitude = 47.44;
    boundingRectangle.Northeast.Latitude = -122.56;

    boundingRectangle.Southwest = new LatLong();
    boundingRectangle.Southwest.Latitude = 41.44;
    boundingRectangle.Southwest.Latitude = -119.56;

    //Now assign bounding rectangle to the filter
    rectangleFilter.BoundingRectangle = boundingRectangle;
    //Define the spatial relationship to be
    //"find polygons inside the rectangle"
    rectangleFilter.PolygonRectangleRelation =
                                SpatialRelation.WithinArea;

Now that you know how to define spatial filters , let’s look at the FindPolygon method in action, using the relations shown in Table 6-9.

Table 6-9. SpatialRelation enumeration

Item

Description

WithinArea

Returns all polygons contained entirely within the specified rectangle

TouchesArea

Returns all polygons that come into contact with the specified rectangle

Use the FindPolygon method to find Polygons that either contain a specified point or are spatially related to a rectangle. The y method takes the FindPolygonSpecification object as an argument, as shown in the following code:

    //Create an instance of FindServiceSoap and assign
    //Credentials
    FindServiceSoap findService
                         = new FindserviceSoap();
    //Assign your credentials
    . . .

    //Create an instance of FindPlygonSpecification
    FindPolygonSpecification findPolySpec
                         = new FindPolygonSpecification();

    //Create a new instance of LatLongSpatialFilter
    LatLongSpatialFilter filter = new LatLongSpatialFilter();
    //Assign the given latitude and longitude values
    Filter.LatLong = new LatLong();
    Filter.LatLong.Latitude = 47.44;
    Filter.LatLong.Longitude = -122.55;

    //Assign the spatial 
 filter to the find polygon specification
    findPolySpec.SpatialFilter=filter;

    //Assign your polygon data source
    findPolySpec.DataSourceName="your polygon data source";

    //Define what kind of entities you are looking for
    FindFilter findfilter = new FindFilter();
    findfilter.EntityTypeName = "your entity name";
    findPolySpec.Filter = findfilter;

    //Call Find Polygon
    FindResults findResults = findService.FindPolygon(findPolySpec);
    //Now get the polygon entities
    foreach(FindResult findResult in findResults.Results)
    {
        //Get polygons that matched the query
        Console.WriteLine(String.Format(
                           "Polygon Entity Matched with ID: {0}",
                            findResult.FoundLocation.Entity.ID)
                         );
    }

Now that you have the entity IDs of polygons that match your spatial filter criteria, you can use that information either to render the polygons (covered more in Chapter 8) or to perform any other processing to suit your business needs.

Getting Entities from Latitude/Longitude

We have looked at find service methods that take place names and addresses and return the corresponding latitude/longitude and other entity information. To find entity (or address) information for any given latitude/longitude, use the FindServiceSoap.GetLocationInfo; it gives you entity information for any given latitude and longitude. The GetLocationInfo method takes the GetInfoOptions object as an argument, along with a latitude/longitude and a data source name as a string. The GetLocationInfo object gives you control to decide which entity types you want using the GetInfoOptions.EntityTypesToReturn field; you also have the option to obtain addresses for a given latitude/longitude (if available) using the GetInfoOptions.IncludeAddresses flag. The following code shows how to use the GetLocationInfo method:

    //Create a find service soap proxy
    FindServiceSoap findService = new FindServiceSoap();
    //Take an example lat long information that you want to
    //find using GetLocationInfo
    LatLong latlong = new LatLong();
    latlong.Latitude = 47.682;
    latlong.Longitude = -122.132;

        //Define get info options object
    GetInfoOptions options = new GetInfoOptions();
    //I'm looking only for cities
    options.IncludeAllEntityTypes = false;
    options.EntityTypesToReturn = new string[] {"PopulatedPlace"};

    //Define a field to hold returned locations
    Location[] returnedLocations;
    //Call GetLocationInfo with "MapPoint.NA" data source
    returnedLocations = findService.GetLocationInfo(latlong, "MapPoint.NA", options);
    //Get entity information
    for(int i = 0; i < returnedLocations.Length; i++)
    {
    Console.WriteLine(returnedLocations[i].Entity.DisplayName);
    }

When I’m querying for corresponding entities for the latitude/longitude, I limit my query to a city (the entity name PopulatedPlace) using the EntityTypesToReturn field; it is important to remember that you must set the IncludeAllEntityTypes to false when you request specific entities.

This method is not a direct inverse to the FindServiceSoap.FindAddress method, so if you call the FindServiceSoap.FindAddress method to obtain latitude/longitude for an address and pass that latitude/longitude to the FindServiceSoap.GetLocationInfo method, the resulting address won’t match your original address because of the internal representation of the address data; in MapPoint data sources, addresses are stored in address range blocks along the streets, and the interpolation algorithms are used to calculate the address for a given latitude/longitude and vice versa. So, the addresses returned by the FindServiceSoap.GetLocationInfo are approximations of the original address; in fact, this method returns an array of four possible addresses for any given latitude/longitude in increasing order of the distance from the given latitude/longitude.

Parsing Addresses

We have seen various capabilities of Find Service to find places, addresses, nearby entities, and points of interest, but what happens if you have an address in an unstructured or unformatted form? What if you want to create an application where your users can type their address in a textbox without worrying about the formatting? How do you parse the address field to understand various parts of the address? You can use the FindServiceSoap.ParseAddress method for these purposes. The ParseAddress method takes two arguments, input address as a string and an optional country/region name, and returns an Address object for valid addresses. For example, if you have the address 1 Microsoft Way, Redmond, WA in string format, you can use the ParseAddress method to parse it into an Address object:

    //Create a web service proxy
    FindServiceSoap findService  = new FindServiceSoap();
    //Assign credentials
    . . .

    //Parse a string into a valid address object
    Address address =
         findService.ParseAddress("1 Microsoft Way, Redmond", "United States");

One of the greatest advantages of this method is that you can implement one user interface that can perform both find place and find address depending on what users input without having to design two different UIs for two different purposes.

Asynchronous Programming with Find Service

When developing applications using Web Service, keep in mind that you are making a network round-trip with every method call, which has serious implications on your application’s performance in terms of responsiveness. For example, since calls over the network take a long time to return, you don’t want to block the UI thread for your Windows application. This is where the asynchronous programming patterns come to the rescue. Using the .NET framework, it is easy to call Web Service methods asynchronously. So, let’s see how you would implement a FindServiceSoap.Find method call asynchronously.

Asynchronous Programming for Windows Applications

When you generate the MapPoint Web Service proxy class using Visual Studio .NET, it also generates the necessary methods for asynchronous programming. For example, if you look for the FindServiceSoap.Find method, you also find the FindServiceSoap.BeginFind and FindServiceSoap.EndFind methods in the proxy class. The Begin and End method pairs enable the asynchronous programming patterns for your web service client applications. Using these methods is really easy; in a synchronous scenario, your Find call looks like the following code:

    //Call the Find Method
    FindResults findresults = findsoap.Find(findspec);
    //Now display find results
    DisplayFindResults(findresults);

If this code is running on the UI thread, it does not get to the DisplayFindResults method until the Find method call completes and returns the findresults value; during this period, users of your application may find it unresponsive. To avoid this situation, create a worker thread and call the Find method using it so that your UI thread is free during this long network round-trip. In fact, that’s exactly what the BeginFind and EndFind methods do behind the scenes. To implement the previous code using asynchronous methods, you would do something similar to the following:

First, define a callback method for your asynchronous method calls:

    private void FindServiceCallback(IAsyncResult ar)
    {
       FindServiceSoap findSoap
          = ar.AsyncState as FindServiceSoap;
       if(findSoap == null)
         return;
       FindResults findresults = findSoap.EndFind(ar);
       DisplayFindResults(findresults);
    }

Next, modify your find call to become an asynchronous BeginFind call:

    //Async call to find
    AsyncCallback callback = new AsyncCallback(FindServiceCallback);
    findsoap.BeginFind(findspec, callback, findsoap);

The BeginFind invokes the Find method on a different (worker) thread and passes a pointer to the FindSeviceCallback method as a callback method; when the Find method returns a FindResults instance, the callback delegate is invoked so that the FindServiceCallback method gets executed on the UI thread again. In the FindServiceCallback method, you need to obtain the FindResults returned by the Find method by calling the EndFind method and displaying them. Keep in mind that the HTTP session is kept alive during this asynchronous operation behind the scenes—this pattern is asynchronous at your application thread level but not at the HTTP communication level.

Asynchronous Programming for Web Applications

Multithreaded programming works well for Windows applications if you are calling web services, but wouldn’t it be nice to adopt this asynchronous programming model for web applications as well? Wouldn’t it be convenient to develop more responsive applications without doing a complete page refresh? You can do these things with a combination of JavaScript and Msxml2.XMLHTTP ActiveX control. The use of JavaScript with asynchronous XML messaging is called Asynchronous JavaScript and XML, or simply AJAX. While AJAX terminology is fairly new to web application development, the use of JavaScript and XMLHTTP is not. In this section, I will go over some scenarios where AJAX can be used in your web applications to improve the overall user experience.

AJAX-Enabling Your Web Applications

In theory, AJAX is no different from any other web application that uses HTTP request and response—however, adding asynchronous calls from your web page to the web server that uses JavaScript dramatically improves the user’s experience.

To understand how to leverage AJAX in your MapPoint Web Service web applications, you need to understand how AJAX works, which is explained in the following section.

For AJAX to work, you need three core components:

  • A web page (htm, aspx, etc.) that hosts JavaScript containing asynchronous calls to the web server

  • XMLHTTP ActiveX control enabled from the client web browser

  • A server-side component that can process HTTP GET requests using the query string parameters

All three of these components together make an AJAX implementation. Usually, the server-side component is an HTTP Handler, since it renders only the script instead of conventional HTML. An HTTP Handler is similar to an ISAPI extension, and you need to implement the IHTTPHandler interface to develop an ASP.NET HTTP Handler. These concepts are shown pictorially in Figure 6-3.

Since designing a web page and writing JavaScript are straightforward tasks, let me delve into the server-side components that are required for AJAX.

For example, you want to develop a web application that implements “Find a place” functionality; in this application, when users search for a place, generally MapPoint Web Service comes back with a list of possible matches to be displayed for disambiguation. Conventionally, you would implement this process using the following series of actions:

AJAX architecture for MapPoint Web Service applications
Figure 6-3. AJAX architecture for MapPoint Web Service applications
  1. Have the user type in a place and click a button to find it.

  2. Post the request to the server page that invokes the MapPoint Find Service calls to find the place.

  3. If there is more than one match to the input string, display them in a list box where the user can select the place that he is looking for.

  4. Post that selection back to the server page that invokes MapPoint Render Service to get a map.

The same application can be implemented with AJAX to improve the overall user experience:

  1. As the user types each character into the input place textbox, make an asynchronous call to the server to fetch matching places and display them in a dynamic drop-down list.

  2. Have the user select the place she is looking for.

  3. Post that selection back to the server page to display a map.

An implementation of this application results in the user interface shown in Figure 6-4.

The experience of using the application is far richer with AJAX when compared to a traditional MapPoint find web application. Next, let’s see how to implement this application.

Place lookup using MapPoint Web Service and AJAX
Figure 6-4. Place lookup using MapPoint Web Service and AJAX

Implementing MapPoint Lookup AJAX Application

The implementation of this application consists of developing the following:

  • The web page that hosts JavaScript that makes async calls to the server

  • An HTTP Handler that processes async requests and returns proper JavaScript

  • JavaScript that makes async calls.

Let’s look at each step in detail.

Developing the web page. In this step, simply create an ASPX page that has the input place textbox with the following event wired up:

    <input id="place" onkeyup="DoLookup()" type="text" size="56">

The input textbox "place" has the onkeyup event wired up to the DoLookup() method, which will be called each time the user types in a character.

Also define a div element to hold the results; this element acts as a dynamic list box to show results. If no results are found, the div is hidden as an invisible element on the page, and if search results are found, the div is visible for the user to select the desired find result:

    <div id="placepanel" UNSELECTABLE='on' style="VISIBILITY: hidden;"></div>

The next step is to implement the HTTP Handler that processes find requests on HTTP GET.

Developing the HTTP Handler. To develop an HTTP Handler, you need to implement the System.Web.IHttpHandler interface, which requires overriding the ProcessRequest method that handles the incoming request.

In this method, you check an incoming request’s query string to see whether there is any place name; if a place name exists, make a FindServiceSoap.Find method call to return matching find results. The results need to be formatted so that the calling JavaScript can understand and display the contents. So, I decided to do the result formatting on the server side in the HTTP handler and simply return the find results that are display-ready. The following code shows the ProcessRequest method implementation:

    public void ProcessRequest(HttpContext context)
    {
        //See incoming place query string parameter
        string input = context.Request["place"] as string;

        if(input == null || input == string.Empty)
        {
            context.Response.Write("var invalidInputFound = 0;");
            return;
        }

        //If there is a valid input call MapPoint Web Service
        try
        {
            //See if this has a cache item:
            string response = context.Cache[input] as string;
            if(response != null)
            {
                context.Response.Write("ShowLocation(" + response + ");");
                return;
            }
            else
            {
               String[] findings = null;
               //Call MapPoint Web Service and get the place names into
               //the findings string array
                . . . .

               //Now write back appropriate JavaScript
                if(findings == null || findings.Length == 0)
                {
                    context.Response.Write("var novalidOutputFound = 0;");
                    return;
                }

                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                int count = findings.Length;
                sb.Append(""");
                for(int i =0; i< count; i++)
                {
                    //Open div and add id
                    sb.Append("<div id='findresult_" + i.ToString() + "' ");
                    //Add unselectable = on
                    sb.Append(" UNSELECTABLE='on' ");
                    //Add findresult content
                    sb.Append("<img align=absmiddle src=arrow.gif border=0> " +
                                findings[i].Trim().Replace("'", "'"));
                    //close div
                    sb.Append("</div>");
                }
                sb.Append(""");

                //Write back the necessary JavaScript and
                //add this entry to cache so that we don't have to hit MapPoint
                //Web Service
                if(sb.Length > 5)
                {
                    //Write JavaScript
                    context.Response.Write("ShowLocation(" + sb.ToString() + ");");
                    //Add to cache
                    context.Cache.Add(input, sb.ToString(), null,
                                     System.DateTime.MaxValue,
                                     TimeSpan.FromHours(6),
                                     System.Web.Caching.CacheItemPriority.Default,
                                     null);
                }
                else
                {
                    context.Response.Write("var zeroOutputFound = 0;");
                }
            }

            return;

        }
        catch(Exception ex)
        {
            context.Response.Write("var errorMesage = '" + ex.Message + "';");
        }
    }

This HTTP handler writes appropriate JavaScript back to the client as a response to any incoming request. For example, an incoming request such as the following:

    http://yourHTTPHandlerUrl?place=New%20York

results in the following JavaScript being sent back as response:

    ShowLocation("<div id='findresult_0'  UNSELECTABLE='on' ><img align=absmiddle
    src=arrow.gif border=0> New York, New York, United States</div><div id='findresult_1'
    UNSELECTABLE='on'><img align=absmiddle src=arrow.gif border=0> New York (state),
    United States</div><div id='findresult_2'  UNSELECTABLE='on'><img align=absmiddle
    src=arrow.gif border=0> New York (county), New York, United States</div><div
    id='findresult_3'  UNSELECTABLE='on'><img align=absmiddle src=arrow.gif border=0> New
    York, Santa Rosa, Florida, United States</div><div id='findresult_4'
    UNSELECTABLE='on'><img align=absmiddle src=arrow.gif border=0> New York, Wayne, Iowa, United States</div>");

You can also simply return an array of strings in JavaScript and let the client code do the formatting for the display, but I chose to implement it on the server side since it simplifies the JavaScript implementation.

The next step is to develop the JavaScript to glue these pieces together.

Developing the JavaScript. You might have noticed a couple of JavaScript functions that I have been using so far:

DoLookup

Captures the user’s key inputs and sends an asynchronous call to the HTTP Handler.

ShowLocation

Displays the find match results to the user in a dynamic drop-down list.

Let’s see how to implement these two functions.

The DoLookup function uses XMLHTTP functionality to send the request that we developed in the previous step asynchronously to the HTTP handler; a simplified version of DoLookup is shown in the following code:

    //Define a global variable to hold http request
    //object
    var xmlhttp=null;

    Function DoLookup()
    {
        //Create a valid url with user typed place value
        var url = "your http handler url"? + place.value;

        //Create an instance of xmlHttp
        xmlhttp=new ActiveXObject("Msxml2.XMLHTTP");
        if(xmlhttp)
        {
            //Now open a request and assign callback
            xmlhttp.open("GET",url,true);
            xmlhttp.onreadystatechange=FindPlaceRequestCallBack;
            //Send the request
            xmlhttp.send(null);
        }
    }

A callback function, FindPlaceRequestCallBack, is assigned to handle the response and any other state changes in the input request; the implementation of the FindPlaceRequestCallBack is as follows:

    function FindPlaceRequestCallBack()
    {
      if(xmlhttp)
      {
        if(xmlhttp.readyState==4&&xmlhttp.responseText)
        {
           //Get the response content
           var placecontent = xmlhttp.responseText;
           //Just execute the response content
           eval(placecontent);
           xmlhttp = null;
        }
      }
    }

The callback function receives the response text from the HTTP handler and executes the resulting JavaScript using the eval method. The resulting JavaScript is nothing but the ShowLocation function call with Find result matches for the input place. Next, we need to implement the ShowLocation function:

    function ShowLocation(findResults)
    {
        if(!findResults)
        {
            return;
        }

        //Display results
        if(document.getElementById(displayplanelname))
        {
          document.getElementById(displayplanelname).style.visibility="visible";
          document.getElementById(displayplanelname).innerHTML = findResults;
          document.getElementById("place").focus();
        }
    }

In this function, the results are assigned to the div element to be displayed to the user. Your AJAX application for place lookup is now ready. With minimal effort, you can AJAX-enable your MapPoint Web Service web application to provide a great user experience.

Obviously, you can extend these functions more to optimize your client-server communication by limiting the requests to strings more than three characters in length. Also, you can implement more features such as scroll enabling with arrow keys, and mouse-select enabling in JavaScript to add more depth to the application. The sample application included in the companion material has all these features implemented in JavaScript.

Finally, because you are charged a fee for each MapPoint Web Service Find call, you need to evaluate this design appropriately; a more cost-effective variation to this implementation might be to do a lookup asynchronously when the user clicks Enter, instead of performing a lookup for each character typed.

Optimizing Find Call Performance

One of the advantages of working with web services is that you can invoke methods over the wire using XML, but due to the remote nature of this method and XML’s inherent behavior to bloat the packet size, it may pose performance and end user experience issues. For example, say you are building a mapping application for a handheld device that depends on GPRS for connectivity; usually the users of these connected handheld devices pay the network service provider for the data plans (the number of bytes downloaded over the air using the GPRS connections).

To find a place, your MapPoint Web Service application sends a request SOAP XML message and receives the response SOAP XML message from the Web Service. Because the XML bloats the size of the request and response packet, this could cost your application users a lot of money. Not only could having large request and response messages slow down your application for network transfer, but it also may result in poor end user experience. There are multiple ways to optimize your MapPoint Web Service applications for SOAP XML size and response speed; let’s look at them in detail.

Optimizing the SOAP Response Size

Always write your applications to receive only the information you need and filter out all unnecessary noise. There are three elements you can limit to do this:

Result-set size

If you are looking for a place and are certain of its name, request only one find result using the FindRange object:

    //Create a FindServiceSoap object.
    FindServiceSoap findservicesoap = new FindServiceSoap();
    //Assign credentials go here.
    . . .
    //Create a FindSpecification object.
    FindSpecification findspecification = new FindSpecification();
    //Assign a valid data source name.
    findspecification.DataSourceName = "MapPoint.NA";
    //Specify a place to find.
    findspecification.InputPlace = "Redmond, WA";
    //Create a FindOptions object.
    findspecification.Options = new FindOptions();
    //Create a Range object.
    findspecification.Options.Range = new FindRange();
    //Assign the Range StartIndex and the result count to be returned.
    findspecification.Options.Range.StartIndex = 0;
    findspecification.Options.Range.Count = 1;
    //Invoke the Find method.
    FindResults findresults =
              findservicesoap.Find(findspecification);
Result information

Usually, all find methods return FindResult objects with location, entity, and best map view information, leaving it up to you to pick and choose what information you need and don’t need. If you are looking only for latitude/longitude information for one particular place, get only that information by filtering all other information using the FindResultMask enumeration:

    //Create a FindServiceSoap object.
    FindServiceSoap findservicesoap = new FindServiceSoap();
    //Assign credentials go here.
    . . .
    //Create a FindSpecification object.
    FindSpecification findspecification = new FindSpecification();
    //Assign a valid data source name.
    findspecification.DataSourceName = "MapPoint.NA";
    //Specify a place to find.
    findspecification.InputPlace = "Redmond, WA";
    //Set ResultMask to retrieve only map view information.
    findspecification.Options.ResultMask = FindResultMask.BestMapViewFlag;
    //Invoke the Find method.
    FindResults findresults =
              findservicesoap.Find(findspecification);
Limit entity information

Even though this is only applicable to point of interest methods, such as FindNearby, FindById, FindByProperty, and FindNearRoute, it is always a good practice to request entity attributes using the FindFilter object. This object has a property, PropertyNames of type string array, that allows you to define which attributes you want to see on returned entities. If an entity (such as a coffee shop) has 200 properties and you plan to use only 2 properties (say, Name and PhoneNumber), you can specify that in your FindNearby request using the FindFilter.PropertyNames so that your response SOAP XML contains only 2 properties instead of all 200. The only caveat to this approach is that your definition for property names must also include the names used in filter expression in the FindFilterExpression.Expression object. The following code snippet shows the usage of the FindFilter.PropertyNames property:

    //Declare a find nearby specification object
    //and assign all required information
    FindNearbySpecification findNearbySpec  = new FindNearbySpecification();
    findNearbySpec.DataSourceName = "MapPoint.FourthCoffeeSample";
    findNearbySpec.Distance = 1;
    findNearbySpec.LatLong = new LatLong();
    findNearbySpec.LatLong.Latitude = 47.6;
    findNearbySpec.LatLong.Longitude = -122.33;
    findNearbySpec.Filter = new FindFilter();
    findNearbySpec.Filter.EntityTypeName = "FourthCoffeeShops";
    //Minimize the properties on returned entities by
    //specifying the property names field
    //Define the properties you plan to use
    //in your application
    string[] returnProperties = new string[2];
    returnProperties[0] = "Name";
    returnProperties[1] = "Phone";
    //Assign it to find nearby specification
    findNearbySpec.Filter.PropertyNames = returnProperties;

    FindResults foundResults;
    foundResults = findService.FindNearby(findNearbySpec);

Applying Proper Metadata for Faster Searches

Whenever possible, try to apply proper metadata for your find queries, including applying proper entity type information and adding search contexts. For example, if you are searching for Redmond, WA in the United States, you can improve your application’s performance by adding the entity type information of PopulatedPlace (using the entity type name for cities means that you are looking only for a city) and a context of 244 (the entity ID of the United States narrows your search to the United States) during your find call:

    //Create a find specifications object
    FindSepcification findspecification = new FindSepcification();

    //Assign the EntityTypeNames value
    findspecification.EntityTypeNames = new string[] {"PopulatedPlace"};

    //Assign search context
    findspecification.Options = new FindOptions();
    //Add context for United States
    findspecification.Options.SearchContext = 244;

Use Asynchronous Programming Patterns

Since any web service call involves a network round-trip, using asynchronous programming improves the user’s experience dramatically. With MapPoint Web Service Find Service, you can use asynchronous programming paradigms provided by Microsoft .NET Framework. If you are building a web or Windows application using MapPoint Web Service, you can use the MapPoint Web Service asynchronous methods to perform tasks such as Find or FindAddress; however, in order to use asynchronous methods, you use the Begin and End pair methods instead of the actual method itself. For example, to call the Find method in asynchronous patterns, use the BeginFind and EndFind methods, which internally use the SoapHttpClientProtocol object’s BeginInvoke and EndInvoke methods.

When you are building an enterprise-level application using MapPoint Web Service, obviously performance is not the only thing you need to keep in mind; you also need to think about supporting multiple languages and cultures, so globalizing your applications to support local languages is an essential part of your application. In the next section, let’s see how to leverage some of the MapPoint Web Service features to build global applications

Globalizing Find

MapPoint Web Service currently supports 10 different languages, meaning that when you use the Find Service with a desired (and supported) language, the Find results are returned in that language. It is important to note that in the case of the Find service, only city names and other entity information are localized to display in that specific language.

Table 6-10 shows the list of languages currently supported in MapPoint Web Service:

Table 6-10. Languages supported in MapPoint Web Service

Language

Language Code

Lcid

Dutch

nl

19

English

en

9

English-United States

en-us

1033

French

fr

12

German

de

7

Italian

it

16

Portuguese

pt

22

Spanish

es

10

Swedish

sv

29

The data sources MapPoint.Moon and MapPoint.World support all of these languages as well as Japanese (language code ja and LCID 1041).

To send your desired language information to the MapPoint Web Service during your Find Service calls, MapPoint Web Service provides SOAP Headers. With Find Service, these settings use the FindServiceSoap.UserInfoFindHeaderValue field. The UserInfoFindHeaderValue is of type UserInfoFindHeader, and it and provides fields to set values for the location search context (the UserInfoFindHeader.Context field), user preferred culture (the UserInfoFindHeader.Culture field), and default distance unit as either miles or kilometers (the UserInfoFindHeader.DefaultDistanceUnit field). To use Find Service with a search context for Canada (whose country entity ID is 39) with a preferred language of French (name fr), you would have to provide the user information header to the FindServiceSoap:

    //Create a find service soap object
    FindServiceSoap findService = new FindServiceSoap();
    //Create a user header
    UserInfoFindHeader userInfoFindHeader  = new UserInfoFindHeader();
    //Set the country context to Canada
    userInfoFindHeader.Context = new CountryRegionContext();
    userInfoFindHeader.Context.EntityID = 39;

    //Set the language preference
    userInfoRenderHeader.Culture = new CultureInfo();
    userInfoRenderHeader.Culture.Name = "fr";

    //Then assign the header to the find service proxy
    findService.UserInfoFindHeaderValue = userInfoFindHeader;

The entity ID and language name are assigned to the find header before making any find calls. To set the desired culture information, you could also use the Lcid ID for French instead of using the language name. One thing to remember while using the user information header to obtain localized information from Find Service is that the addresses returned by the FindServiceSoap.FindAddress method are never localized.

Where Are We?

Find Service is one of the core components of the MapPoint Web Service, providing many features such as finding places, addresses, and points of interest around a given location. Find Service also provides a way to convert any latitude/longitude information to an entity that contains the geographic information about that given point; it also provides necessary tools to parse addresses to see whether a given string is a place or an address so that you can call the appropriate method to find information.

Since Find Service methods are web service methods, it is important to think about performance optimization and asynchronous programming patters where applicable. Finally, Find Service also provides a way to get information in a specific localized language (among the 10 supported languages).

In the next chapter, we’ll look at MapPoint Web Service Route and Render Service components.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.117.214