MSDN Magazine - December 2007 - (Page 51) ically generated an XML schema that described the contents of the XML files. This enabled me to then use the new Visual Basic XML IntelliSense® feature, which shows XML member access expressions based on any schema files included in a project (see Figure 6). of Vertex tags that describe the boundaries of the hole. I’ve included them in the data files primarily for completeness; they are not used by my application. This does mean that there may be a few very small areas of the map that show slightly incorrect values when data is visualized on it. For the Figure 7 An Incredibly Simple Map Projection purposes of my application, this was not an Map Data Format issue, and so I simply ignored it. The schema describing the XML map files defines a simple forIf you have an application where accuracy in this regard is immat consisting of a root File tag that in turn contains a sequence portant, or are mapping regions where polygon islands are prevaof Region tags. Each Region tag contains a series of FipsCode lent, you easily can solve the problem by controlling the order in and Polygon tags, with each Polygon tag containing a sequence which the map is rendered. By first drawing the polygon containof Vertex and Island tags. ing an island and then drawing the island on top of it, it is possible A Region tag is used to describe a single region. It defines two to accurately render polygon islands. In many cases this may also attributes, both of which are required: Type and Name. The Type require extra processing to determine areas of overlap and conattribute provides a description of the type of the region. For the struct the appropriate ordering. purposes of my application, I always use the values State or County. In general, however, any value can be used for the Type attribute. Importing the Data The Name attribute describes the region’s name. Using LINQ, importing map data into the application is relaEach FipsCode tag defined in a region describes one of its FIPS tively easy. The code that does this is shown in Figure 8. It defines a code entries. The last FipsCode tag within a Region defines the re- procedure, LoadFile, that takes two parameters: file and list. The gion’s numeric ID. The other FipsCode tags recursively describe file parameter is a string that contains the full path of the XML the numeric IDs of the region’s parents. file to load from disk. The list parameter is a reference to a colFor the purposes of my application, all regions will have at most lection of MapRegion instances. The procedure then opens the two associated FipsCode values. In particular, regions describing XML file, processes it, and inserts all of the regions it defines into states (or state equivalent areas) will always have one entry that de- the provided collection. scribes the FIPS code associated with that state. Regions describing counties (or county equivalent areas) will always have two entries, Figure 8 Loading MapRegion Instances from an XML File with the first entry denoting the FIPS code of the state that contains Private Sub LoadFile(ByVal file As String, ByVal list As ICollection(Of _ the county and the second denoting the FIPS code of the county MapRegion)) itself. The numeric ID of any particular region is always guaranteed Dim doc = XDocument.Load(file) Dim q = _ to be unique within the scope of its immediate parent. From v In doc. . . . _ Each of a region’s Polygon tags describes a single polygon conLet _ Polygon = v.Parent, _ tained within it. The polygon is composed of a series of Vertex tags, Region = v.Parent.Parent _ with each tag describing a single vertex. The Vertex tag defines five Group By _ Polygon, _ attributes, all of which are required: Ordinal, Longitude, Latitude, Region _ X, and Y. The Ordinal attribute defines an ordering for a polygon’s Into _ Group, _ vertices. Its primary purpose is to make it possible to correctly order MinX = MinDBL(v.@X), _ vertices should they be processed by queries that do not preserve MinY = MinDBL(v.@Y), _ MaxX = MaxDBL(v.@X), _ ordering. It is currently not used by my application. The Longitude MaxY = MaxDBL(v.@Y) _ and Latitude attributes describe the longitudinal and latitudinal Select _ TheMapPolygon = (From tmp In Group Select _ coordinates of the vertex. Similarly, the X and Y attributes define tmp.v).ToMapPolygon(), _ the coordinates of the vertex when projected into rectangular space Region, _ BoundingBox = New Rect(New Point(MinX, _ and are what the application uses for drawing maps. MinY), New Point(MaxX, MaxY)) _ The X and Y attributes were computed using the simple projecGroup By _ Region _ tion shown in Figure 7. This projection is not a particularly accurate Into _ one and may not be suitable for some scenarios. In those cases, it Group, _ BoundingBox = Enclose(BoundingBox) _ is possible to calculate new values directly using Longitude and Select _ Latitude rather than using the X and Y values stored in the file. Region = New MapRegion(Region.@Name, (From item In Group _ Select item.TheMapPolygon), (From f In _ For my application, which does not require a high degree of geoRegion Select CInt(f.Value)), BoundingBox) graphical precision, this was sufficient. list.AddRange(q) An Island tag within a polygon defines a hole enclosed within End Sub it that is not part of its containing region. It contains a collection Map LINQ december2007 51
For optimal viewing of this digital publication, please enable JavaScript and then refresh the page. If you would like to try to load the digital publication without using Flash Player detection, please click here.