.NET http://jasonfollas.com/blog/category/5.aspx .NET en-US Jason Follas jason@jasonfollas.com Subtext Version 2.0.0.43 Entity Framework Spatial: A Real World Example http://jasonfollas.com/blog/archive/2011/07/27/entity-framework-spatial-a-real-world-example.aspx <p><strong>Background</strong></p> <p>From the Wikipedia article, <a href="http://en.wikipedia.org/wiki/Leadership_in_Energy_and_Environmental_Design">Leadership in Energy and Environmental Design (LEED)</a> is described as “an internationally recognized green building certification system, providing third-party verification that a building or community was designed and built using strategies intended to improve performance in metrics such as energy savings, water efficiency, CO<sub>2</sub> emissions reduction, improved indoor environmental quality, and stewardship of resources and sensitivity to their impacts.”</p> <p>In my own words, LEED is a certification system that awards points for following certain environmentally-friendly practices when constructing a building. In the end, a building can be qualify for one of four different levels of certifications, based on the number of points: Certified, Silver, Gold, Platinum. There are often tax benefits associated with having a LEED certification, and many new government buildings (especially Federal) are required to be LEED certified.</p> <p>Two points in particular (out of of 100, or so) from the LEED checklist are related to geospatial data. One point is awarded if at least 20% of the building materials (by cost) used in construction were manufactured within 500 miles of the job site. A second point is awarded if 10% of the raw materials of those building materials were extracted, harvested, or recovered within 500 miles of the job site.</p> <p>As a window glass manufacturer, Tempuri Glass is often asked to provide data about its products that are being considered for use in construction. Tempuri Glass may have a certain advantage over its competitors if it can quickly show that its products would count towards these two points for any arbitrary job site.</p> <p><strong>Data</strong></p> <p>Tempuri is a simple organization, making only a single type of product (Soda Lime glass) that is then cut into different sizes per order. Therefore, regardless of how many different sized glass panes are produced by a given facility, the ingredients for that glass is the same. The formulas used will be different between facilities, though, since the raw ingredients will be sourced from different locations, and adjustments may need to be made to the ratios due to environmental factors (things like: elevation, temperature, humidity, etc).</p> <p>So, for our data model, we just need to know where each facility is, and then the formula used to make the glass at that facility (including the ingredients of that formula and the location where they were harvested from).</p> <p><a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/Windows-Live-Writer/22ea53fcc227_8DCC/a.EF_Diagram.png"> <img width="642" height="317" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/Windows-Live-Writer/22ea53fcc227_8DCC/a.EF_Diagram_thumb.png" alt="a.EF_Diagram" title="a.EF_Diagram" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" /> </a></p> <p>Within the data store, the [Geocode] columns of the Facility and FormulaComponent tables use the SQL Server <em>geography</em> type. This is useful for the large-scale/real-world distance calculations that Tempuri Glass needs to perform, since the way that you calculate distance on an sphere or ellipsoid (like the Earth) <a href="http://www.jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx">is vastly different than on a flat map</a>.</p> <p>In the Entity Framework model (using the <a href="http://blogs.msdn.com/b/adonet/archive/2011/06/30/announcing-the-microsoft-entity-framework-june-2011-ctp.aspx">June 2011 CTP</a>), the SQL Server <em>geography</em> types are mapped as the new <strong>System.Data.Spatial.DbGeography</strong> type. This makes the geospatial data a first class citizen of our data model, and not just a <a href="http://www.jasonfollas.com/blog/archive/2010/02/14/spatial-data-and-the-entity-framework.aspx">castable opaque BLOB</a>, as was the case in the past.</p> <p>Geospatial data can take on many forms, including Points, Line Strings, Polygons, and collections of these shapes. Even though it’s not apparent from the data model, our [Geocode] data will contain only Points (i.e., a single Latitude/Longitude pair). Likewise, a job site will be specified as a single Point, though there is no hard requirement for this because distance can still be calculated between a Polygon and a Point with no coding change required.</p> <p><strong>Facility Sample Data</strong> </p> <table cellspacing="0" cellpadding="0" border="1"> <tbody> <tr> <td width="66" valign="bottom"> <p><strong>FacilityID</strong></p> </td> <td width="119" valign="bottom"> <p><strong>FacilityName</strong></p> </td> <td width="96" valign="bottom"> <p><strong>City</strong></p> </td> <td width="41" valign="bottom"> <p><strong>State</strong></p> </td> <td width="357" valign="bottom"> <p><strong>Geocode</strong></p> </td> </tr> <tr> <td width="68" valign="bottom"> <p>1</p> </td> <td width="119" valign="bottom"> <p>Greenfield, IA</p> </td> <td width="97" valign="bottom"> <p>Greenfield</p> </td> <td width="43" valign="bottom"> <p>IA</p> </td> <td width="357" valign="bottom"> <p>POINT (-94.4547843933106 41.3151755156904)</p> </td> </tr> <tr> <td width="70" valign="bottom"> <p>2</p> </td> <td width="118" valign="bottom"> <p>Spring Green, WI</p> </td> <td width="98" valign="bottom"> <p>Spring Green</p> </td> <td width="45" valign="bottom"> <p>WI</p> </td> <td width="357" valign="bottom"> <p>POINT (-90.053981 43.17431)</p> </td> </tr> <tr> <td width="71" valign="bottom"> <p>3</p> </td> <td width="118" valign="bottom"> <p>Tomah, WI</p> </td> <td width="98" valign="bottom"> <p>Tomah</p> </td> <td width="47" valign="bottom"> <p>WI</p> </td> <td width="357" valign="bottom"> <p>POINT (-90.477058 43.989319)</p> </td> </tr> <tr> <td width="72" valign="bottom"> <p>4</p> </td> <td width="117" valign="bottom"> <p>Fremont, IN</p> </td> <td width="98" valign="bottom"> <p>Fremont</p> </td> <td width="48" valign="bottom"> <p>IN</p> </td> <td width="357" valign="bottom"> <p>POINT (-84.9314403533936 41.7186070559443)</p> </td> </tr> <tr> <td width="73" valign="bottom"> <p>5</p> </td> <td width="117" valign="bottom"> <p>Fargo, ND</p> </td> <td width="98" valign="bottom"> <p>Fargo</p> </td> <td width="49" valign="bottom"> <p>ND</p> </td> <td width="357" valign="bottom"> <p>POINT (-96.8667125701904 46.8985894795683)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>6</p> </td> <td width="117" valign="bottom"> <p>Waxahachie, TX</p> </td> <td width="98" valign="bottom"> <p>Waxahachie</p> </td> <td width="50" valign="bottom"> <p>TX</p> </td> <td width="357" valign="bottom"> <p>POINT (-96.8427014350891 32.4424403136322)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>7</p> </td> <td width="117" valign="bottom"> <p>Hood River, OR</p> </td> <td width="98" valign="bottom"> <p>Hood River</p> </td> <td width="51" valign="bottom"> <p>OR</p> </td> <td width="357" valign="bottom"> <p>POINT (-121.51526927948 45.630620334868)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>8</p> </td> <td width="117" valign="bottom"> <p>Vinton, VA</p> </td> <td width="98" valign="bottom"> <p>Vinton</p> </td> <td width="51" valign="bottom"> <p>VA</p> </td> <td width="357" valign="bottom"> <p>POINT (-79.863876 37.263329)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>9</p> </td> <td width="117" valign="bottom"> <p>Casa Grande, AZ</p> </td> <td width="98" valign="bottom"> <p>Casa Grande</p> </td> <td width="51" valign="bottom"> <p>AZ</p> </td> <td width="357" valign="bottom"> <p>POINT (-111.78155422210693 32.882073958767954)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>10</p> </td> <td width="117" valign="bottom"> <p>Mountain Top, PA</p> </td> <td width="98" valign="bottom"> <p>Mountain Top</p> </td> <td width="51" valign="bottom"> <p>PA</p> </td> <td width="357" valign="bottom"> <p>POINT (-75.896477 41.141327)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>11</p> </td> <td width="117" valign="bottom"> <p>Winlock, WA</p> </td> <td width="98" valign="bottom"> <p>Winlock</p> </td> <td width="51" valign="bottom"> <p>WA</p> </td> <td width="357" valign="bottom"> <p>POINT (-122.926218509674 46.5449155194259)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>12</p> </td> <td width="117" valign="bottom"> <p>Durant, OK</p> </td> <td width="98" valign="bottom"> <p>Durant</p> </td> <td width="51" valign="bottom"> <p>OK</p> </td> <td width="357" valign="bottom"> <p>POINT (-96.4133548736572 34.0001619910696)</p> </td> </tr> <tr> <td width="74" valign="bottom"> <p>13</p> </td> <td width="117" valign="bottom"> <p>Mooresville, NC</p> </td> <td width="98" valign="bottom"> <p>Mooresville</p> </td> <td width="51" valign="bottom"> <p>NC</p> </td> <td width="357" valign="bottom"> <p>POINT (-80.7865476608277 35.6316281732984)</p> </td> </tr> </tbody> </table> <p> </p> <p><strong>FormulaComponent Sample Data</strong> </p> <table cellspacing="0" cellpadding="0" border="1"> <tbody> <tr> <td width="148" valign="bottom"> <p><strong>FormulaComponentID</strong></p> </td> <td width="93" valign="bottom"> <p><strong>Name</strong></p> </td> <td width="80" valign="bottom"> <p><strong>Percentage</strong></p> </td> <td width="132" valign="bottom"> <p><strong>SourceLocation</strong></p> </td> <td width="345" valign="bottom"> <p><strong>Geocode</strong></p> </td> </tr> <tr> <td width="150" valign="bottom"> <p>14</p> </td> <td width="94" valign="bottom"> <p>Limestone</p> </td> <td width="82" valign="bottom"> <p>13</p> </td> <td width="131" valign="bottom"> <p>Genola, UT</p> </td> <td width="345" valign="bottom"> <p>POINT (-111.808204650879 40.0098667779887)</p> </td> </tr> <tr> <td width="152" valign="bottom"> <p>1</p> </td> <td width="94" valign="bottom"> <p>Silica Sand</p> </td> <td width="84" valign="bottom"> <p>75</p> </td> <td width="131" valign="bottom"> <p>Houck, AZ </p> </td> <td width="345" valign="bottom"> <p>POINT (-109.241695404053 35.2062151838369)</p> </td> </tr> <tr> <td width="153" valign="bottom"> <p>27</p> </td> <td width="94" valign="bottom"> <p>Soda Ash</p> </td> <td width="86" valign="bottom"> <p>12</p> </td> <td width="131" valign="bottom"> <p>Trona, CA</p> </td> <td width="345" valign="bottom"> <p>POINT (-117.311668395996 35.6955040738332)</p> </td> </tr> <tr> <td width="153" valign="bottom"> <p>15</p> </td> <td width="94" valign="bottom"> <p>Limestone</p> </td> <td width="87" valign="bottom"> <p>13</p> </td> <td width="130" valign="bottom"> <p>Genola, UT</p> </td> <td width="345" valign="bottom"> <p>POINT (-111.808204650879 40.0098667779887)</p> </td> </tr> <tr> <td width="154" valign="bottom"> <p>2</p> </td> <td width="93" valign="bottom"> <p>Silica Sand</p> </td> <td width="88" valign="bottom"> <p>75</p> </td> <td width="130" valign="bottom"> <p>Houck, AZ</p> </td> <td width="345" valign="bottom"> <p>POINT (-109.241695404053 35.2062151838369)</p> </td> </tr> <tr> <td width="155" valign="bottom"> <p>28</p> </td> <td width="93" valign="bottom"> <p>Soda Ash</p> </td> <td width="89" valign="bottom"> <p>12</p> </td> <td width="130" valign="bottom"> <p>Trona, CA</p> </td> <td width="345" valign="bottom"> <p>POINT (-117.311668395996 35.6955040738332)</p> </td> </tr> <tr> <td width="155" valign="bottom"> <p>16</p> </td> <td width="93" valign="bottom"> <p>Limestone</p> </td> <td width="90" valign="bottom"> <p>13</p> </td> <td width="130" valign="bottom"> <p>Chicago, IL</p> </td> <td width="345" valign="bottom"> <p>POINT (-87.6176834106445 41.5738476278005)</p> </td> </tr> <tr> <td width="156" valign="bottom"> <p>3</p> </td> <td width="93" valign="bottom"> <p>Silica Sand</p> </td> <td width="90" valign="bottom"> <p>75</p> </td> <td width="130" valign="bottom"> <p>Overton, NV</p> </td> <td width="345" valign="bottom"> <p>POINT (-114.4313621521 36.5146030619859)</p> </td> </tr> <tr> <td width="156" valign="bottom"> <p>29</p> </td> <td width="93" valign="bottom"> <p>Soda Ash</p> </td> <td width="90" valign="bottom"> <p>12</p> </td> <td width="130" valign="bottom"> <p>Green River, WY</p> </td> <td width="345" valign="bottom"> <p>POINT (-109.448783397675 41.5090754257687)</p> </td> </tr> </tbody> </table> <p> </p> <p><strong>Spatial Querying Algorithm</strong></p> <p>Input: Job Site Latitude/Longitude </p> <p>Steps: </p> <p>A. Query for closest facility to Job Site within 500 miles: </p> <ol> <li>Calculate the distance between the job site and each facility. </li> <li>Filter the list of facilities to just those where distance < 500 miles. </li> <li>Order the list of facilities by distance in ascending order. </li> <li>The first element (if any) will be the closest facility, and also signifies that the product qualifies as being manufactured within 500 miles </li> </ol> <p>B. If there is a facility within 500 miles, then sum the percentage of formula components that were sourced from within 500 miles of the Job Site: </p> <ol> <li>Calculate the distance between the job site and each of the facility’s formula components </li> <li>Filter the list of formula components to just those where distance < 500 miles </li> <li>Sum the Percentages </li> </ol> <p>Output: Boolean of whether the product qualifies; Percentage of the product’s ingredients that qualifies. </p> <p><strong>Implementation</strong></p> <p>Before we can calculate distance using an <a href="http://www.jasonfollas.com/blog/archive/2011/07/21/entity-framework-spatial-dbgeography-members.aspx">instance method of the DbGeography type</a>, we need to actually create an instance to represent the Job Site. <strong>DbGeography</strong> is immutable and does not have a constructor, so instead, a static method must be called to create a new object. There are a number of these factory methods available to create specific kinds of shapes (Point, Line String, Polygon, etc) given different kinds of input (text, byte arrays). </p> <p>For simplicity, let’s use the .Parse() method, which accepts Well-Known Text (WKT) as input, and assumes a Spatial Reference ID of 4326 (the same coordinate system that GPS and internet mapping sites use). </p> <p><em>Note: WKT uses a (Longitude, Latitude) ordering for points, which adheres to the same concept as (X, Y) ordering for Cartesian coordinates.</em></p> <pre>private static DbGeography CreatePoint(double latitude, double longitude)<br />{<br /> return DbGeography.Parse(String.Format("POINT({1} {0})", latitude, longitude));<br />}<br /> </pre> <p>The first spatial query, written as a LINQ expression, finds the closest qualifying facility. Since SRID 4326 uses meters as the unit of measure, we need to convert 500 miles into meters within the predicate:</p> <pre>private Facility GetNearestFacilityToJobsite(DbGeography jobsite)<br />{<br /> var q1 = from f in context.Facilities<br /> let distance = f.Geocode.Distance(jobsite)<br /> where distance < 500 * 1609.344<br /> orderby distance<br /> select f;<br /><br /> return q1.FirstOrDefault();<br />}<br /> </pre> <p>Assuming that a facility was returned, a second LINQ expression can be used to find the sum of Percentage from qualifying Formula Components:</p> <pre>private decimal SumQualifyingPercentages(Facility nearestFacility, DbGeography jobsite)<br />{<br /> var q2 = from fc in nearestFacility.Formula.FormulaComponents<br /> where fc.Geocode.Distance(jobsite) < 500 * 1609.344<br /> select fc;<br /><br /> return q2.Sum(c => c.Percentage.GetValueOrDefault(0));<br />}<br /> </pre> <p>Finally, putting all of the parts together (using a Tuple<> for the output):</p> <pre>private Tuple<bool, decimal> GetResults(double latitude, double longitude)<br />{<br /> DbGeography jobsite = CreatePoint(latitude, longitude);<br /> Facility nearestFacility = GetNearestFacilityToJobsite(jobsite);<br /><br /> if (nearestFacility != null)<br /> {<br /> return new Tuple<bool,decimal>(true, SumQualifyingPercentages(nearestFacility, jobsite));<br /> }<br /><br /> return new Tuple<bool, decimal>(false, 0);<br />}<br /><br />private void PerformQuery()<br />{<br /> double latitude = 47.63752;<br /> double longitude = -122.13343;<br /><br /> var results = GetResults(latitude, longitude);<br />}<br /></pre><img src="http://jasonfollas.com/blog/aggbug/92.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2011/07/27/entity-framework-spatial-a-real-world-example.aspx Wed, 27 Jul 2011 17:57:46 GMT http://jasonfollas.com/blog/archive/2011/07/27/entity-framework-spatial-a-real-world-example.aspx#feedback 5 http://jasonfollas.com/blog/comments/commentRss/92.aspx http://jasonfollas.com/blog/services/trackbacks/92.aspx Windows Azure platform AppFabric Access Control: Using the Token http://jasonfollas.com/blog/archive/2010/03/27/windows-azure-platform-appfabric-access-control-using-the-token.aspx <p>In previous articles (<a href="http://www.jasonfollas.com/blog/archive/2010/03/08/windows-azure-platform-appfabric-access-control-overview.aspx"><em>Part 1</em></a><em>, </em><a href="http://www.jasonfollas.com/blog/archive/2010/03/17/windows-azure-platform-appfabric-access-control-service-namespace.aspx"><em>Part 2</em></a><em>, </em><a href="http://www.jasonfollas.com/blog/archive/2010/03/22/windows-azure-platform-appfabric-access-control-obtaining-tokens.aspx"><em>Part 3</em></a>), ACS was configured to process token requests from different Issuers, and a Customer application obtained a SWT token from ACS using an intermediate service (that represented its particular Issuer). </p> <p>Now that the application has a token, ACS is not needed again until that token expires. This article will show how to use that token in order for the Customer application to call the Bartender web service.</p> <p>Note: OAuth WRAP defines terminology for parts of the system as follows:</p> <ul> <li>The Customer application is known as the <strong>Client</strong>. </li><li>The Bouncer (ACS) is known as the <strong>Authorization Server</strong>. </li><li>The Bartender web service is known as the <strong>Protected Resource</strong>. </li></ul> <p> </p> <h3>Passing a Token from Client to Protected Resource</h3> <p>We had to conform to OAuth WRAP specifications while acquiring a token from ACS, but there’s no actual requirement for how a Client must pass that token to the Protected Resource. An Architect or Developer could come up with any contract that they would like to use. However, a best practice would be to continue using the OAuth WRAP specification as guidance.</p> <p>WRAP defines three different ways that a Protected Resource can accept a token:</p> <ul> <li>In the HTTP header </li><li>In the Query String </li><li>In the Form contents </li></ul> <p>OAuth WRAP suggests that a Protected Resource at least support the HTTP header method, but makes no mandates. If a Protected Resource is able to implement all three, then it would be in the position to support the widest variety of Clients. However, if the Protected Resource is unable to use HTTP headers but could use a value passed in the Query String, then there would be nothing in the WRAP specification to prevent only that implementation.</p> <p>Regardless of the method used, an unauthorized request (perhaps due to a bad or missing token) will result in a HTTP status of 401 Unauthorized, and a header in the response containing: WWW-Authenticate: WRAP</p> <p>Tokens passed in the HTTP header are expected to use the “Authorization” header, with this header’s value containing the text: <font face="monospace">WRAP access_token="<em>tokenstring</em>"</font>. Note that <em>tokenstring</em> is a placeholder for the actual SWT token obtained from ACS and does not need to be URL Encoded. </p> <p>Example (using a System.Net.WebClient class):</p><pre class="csharpcode"><style type="text/css"><![CDATA[ .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }]]></style> <span class="kwrd">string</span> headerValue = <span class="kwrd">string</span>.Format(<span class="str">"WRAP access_token=\"{0}\""</span>, HttpUtility.UrlDecode(token)); var client = <span class="kwrd">new</span> WebClient(); client.Headers.Add(<span class="str">"Authorization"</span>, headerValue);</pre><br /> <p>Tokens passed in either the Query String or in Form Contents are expected to use the name/value pair of: <font face="monospace">wrap_access_token=<em>tokenstring</em></font>. Again, <em>tokenstring</em> is a placeholder for the actual SWT token obtained from ACS, and should be URL Encoded as needed in order to properly construct the HTTP Request (this may be done automatically by the framework that is used). </p> <p>Example (using a System.Net.WebClient class):</p><pre class="csharpcode"> var values = <span class="kwrd">new</span> NameValueCollection(); values.Add(<span class="str">"wrap_access_token"</span>, token); var client = <span class="kwrd">new</span> WebClient(); client.UploadValues(<span class="str">"BartenderWebservice"</span>, <span class="str">"POST"</span>, values));</pre><pre class="csharpcode"> </pre> <style type="text/css"><![CDATA[ .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }]]></style> <p /> <h3>Accepting and Validating a Token</h3> <p>SWT tokens are opaque to the Client. That is, since the Client cannot modify the token, they shouldn’t really care what’s in it or how it’s structured. The Protected Resource, however, must deconstruct, validate, and then use the claims that are contained within the tokens. If anything is invalid or malformed, then the Protected Resource should deny access by returning a 401 Unauthorized status in the response.</p> <p>The <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=39856a03-1490-4283-908f-c8bf0bfad8a5&displaylang=en">Windows Azure platform AppFabric SDK V1.0</a> contains plenty of boilerplate code in the samples demonstrating how to parse and validate a SWT token (so I won’t be repeating that code here). In essence, all of the following needs to pass validation before the Client is authorized to access the Protected Resource:</p> <ol> <li>HMACSHA256 signature is valid </li><li>Token has not yet expired </li><li>The Issuer is trusted (i.e., contains the URL of the ACS server) </li><li>The Audience is trusted (i.e., contains the URL of the Protected Resource) </li><li>Additional required claims are present </li></ol> <p>For the nightclub example, the Customer’s Birthdate will be presented as a claim (#5 in the list above). So, in addition to checking the technical aspects of the token, a business rule must also ensure that the Customer is of legal age to order a drink from the Bartender (in the United States, this means at least 21 years old).</p> <p> </p> <h3>Conclusion</h3> <p>I wrote this series of blog posts because I myself was having a hard time grasping the Hows and Whys of using Windows Azure platform AppFabric Access Control to secure a Protected Resource. What I discovered was that by using claims based identity in conjunction with a federated identity model, you can incorporate a very scalable authorization scheme into your system without requiring complex code and/or special prerequisite software. Technically, an organization could build this same functionality into a system without using Azure AppFabric. However, Windows AppFabric Access Control offers the benefit of already being internet-facing and hosted in a high availability environment, and already includes a rich implementation of OAuth WRAP that supports many different kinds of Issuers.</p> <p>Further Reading:<br /><a href="http://www.amazon.com/gp/product/193518248X?ie=UTF8&tag=aviewinsidmyh-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=193518248X"><img border="0" src="http://ecx.images-amazon.com/images/I/51ifXiU32wL._SL160_.jpg" alt="" /></a><img height="1" border="0" width="1" style="border: medium none ! important; margin: 0px ! important;" alt="" src="http://www.assoc-amazon.com/e/ir?t=aviewinsidmyh-20&l=as2&o=1&a=193518248X" /> <a href="http://www.amazon.com/gp/product/0470506385?ie=UTF8&tag=aviewinsidmyh-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0470506385"><img border="0" src="http://ecx.images-amazon.com/images/I/51cozPyJ%2B6L._SL160_.jpg" alt="" /></a><img height="1" border="0" width="1" style="border: medium none ! important; margin: 0px ! important;" alt="" src="http://www.assoc-amazon.com/e/ir?t=aviewinsidmyh-20&l=as2&o=1&a=0470506385" /> <a href="http://www.amazon.com/gp/product/1430224797?ie=UTF8&tag=aviewinsidmyh-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=1430224797"><img border="0" src="http://ecx.images-amazon.com/images/I/51gyJpLsGHL._SL160_.jpg" alt="" /></a><img height="1" border="0" width="1" style="border: medium none ! important; margin: 0px ! important;" alt="" src="http://www.assoc-amazon.com/e/ir?t=aviewinsidmyh-20&l=as2&o=1&a=1430224797" /></p><img src="http://jasonfollas.com/blog/aggbug/81.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2010/03/27/windows-azure-platform-appfabric-access-control-using-the-token.aspx Sat, 27 Mar 2010 19:46:01 GMT http://jasonfollas.com/blog/archive/2010/03/27/windows-azure-platform-appfabric-access-control-using-the-token.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/81.aspx http://jasonfollas.com/blog/services/trackbacks/81.aspx Windows Azure platform AppFabric Access Control: Introduction http://jasonfollas.com/blog/archive/2010/03/08/windows-azure-platform-appfabric-access-control-overview.aspx <p> </p> <p>The <a href="http://www.microsoft.com/windowsazure/appfabric/" target="_blank">Windows Azure platform AppFabric</a> <a href="http://msdn.microsoft.com/en-us/library/ee732536.aspx" target="_blank">Access Control</a> service was one aspect of the <a href="http://www.microsoft.com/windowsazure/" target="_blank">Windows Azure</a> platform that I found a bit challenging to understand, primarily because identity is not a domain that I regularly work with. This post, the first in a planned series of articles, will explore what the Access Control service is and why it is useful.</p> <p> </p> <h3>Three Geeks Walk Into a Bar…</h3> <p><a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/WindowsAzureAccessControlService_11534/nightclub_access_control_8.png"><img height="281" border="0" align="right" width="340" style="border-width: 0px; display: inline; margin-left: 0px; margin-right: 0px;" title="nightclub_access_control" alt="nightclub_access_control" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/WindowsAzureAccessControlService_11534/nightclub_access_control_thumb_2.png" /></a>Let’s examine a common real-world scenario: Ordering a drink from a bartender at a nightclub.</p> <p>Before the nightclub opens, the bouncers inform the bartenders about the token that customers will bear on that night to indicate that they are of legal age. This could be a stamp on the back of the hand, a colored wristband, etc.</p> <p>At the door, a customer will present their identification to the bouncer that shows, among other things, their date of birth. This ID could be a state-issued driver's license, a passport, or even a school identification card. The point is that the nightclub didn’t issue that ID, but they recognize the authority that did issue it, and will accept the claim (the birth date) that is displayed on that ID.</p> <p>If the bouncer determines that the ID is authentic and hasn’t been tampered with, then he will give the customer the token of the night (stamped hand or colored wristband), and the customer is free to enter the bar.</p> <p>Once inside, the customer only needs to show the token to the bartender in order to buy drinks. They do not need to show their ID.</p> <p>The next night, the token will change, so a customer cannot use a token obtained the night before.</p> <p> </p> <h3>Federated Access Control</h3> <p><a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/WindowsAzureAccessControlService_11534/Azure_access_control_5.png"><img height="189" border="0" align="right" width="340" style="border-width: 0px; display: inline; margin-left: 0px; margin-right: 0px;" title="Azure_access_control" alt="Azure_access_control" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/WindowsAzureAccessControlService_11534/Azure_access_control_thumb_1.png" /></a> Now let’s look at a similar scenario: Calling a web service from an application. Only, in this case, the web service should not fulfill requests from unauthorized clients. Furthermore, it’s not the web service’s responsibility to authenticate the client; it is simply expecting the client to bear some verifiable proof that it is already authorized to use the service.</p> <p>Before calling the Web Service, the Service Consumer (application) must first obtain a token that is issued by an Access Control Service (ACS). This is done by sending a number of claims to the ACS, with one of these claims being secrets belonging to an Issuer that the application is associated with. </p> <p>If the Issuer is recognized and trusted by the ACS, then a token will be created. This token (which is a collection of name/value pairs) will contain claims, an expiration date, and a signature that can be used to ensure that the token was not modified after it was created.</p> <p>Once the Service Consumer has a valid token, it can then call the Web Service and provide that token in a location where the service expects to find it, such as in a HTTP header. The Web Service validates that the token is well-formed, has not expired, and has not been modified since it was created. If anything fails validation, then the processing aborts. Otherwise, the web service returns data to the Service Consumer, possibly using claims contained in the token as input in the process.</p> <p>This scenario is considered to be Federated because the ACS doesn’t actually maintain a list of usernames and passwords. Instead, it maintains a list of trusted Issuers with the expectation is that the Issuer is responsible for authenticating its own users. </p> <p> </p> <h3>Correlation</h3> <p>In these examples, the Web Service is analogous to the nightclub’s Bartender: it has something to provide to the Service Consumer (Customer), but the Service Consumer must present an appropriate token that is generated by the Access Control Service (Bouncer).</p> <p>The web service example above is intentionally vague in the part where a token is obtained. There are a few different ways that an Issuer can be identified in a token request, and while passing the Issuer’s secret in plain text is one of those ways, it certainly shouldn’t be taken lightly. Whoever has the Issuer’s key can spoof any of the claims, and that might prove to be a challenge for the service. In the example where we need a Date of Birth claim to be presented, it would probably be a bad idea to allow the customer themselves to say “I’m from Ohio, here’s a blank driver's license that meets all of the standards of a proper ID, and, oh yeah… I’m writing on here that I am 21 years old.” </p> <p>Instead, claims should originate from the Issuer in some way that cannot be tampered with by the application (using the assumption that the application itself should not be trusted). With the plain text method, this might require having a separate service that runs within the Issuer’s domain and is aware of its users and also ACS. This service would broker the ACS token request for the application, automatically providing any claim data that might be needed (like the Date of Birth) from the Issuer’s own user database. The application would be provided with the same token, but would never have the Issuer’s secret that is required to obtain the token directly from ACS.</p> <p>(Continued in <a href="http://jasonfollas.com/blog/archive/2010/03/17/windows-azure-platform-appfabric-access-control-service-namespace.aspx">Part 2</a>)</p> <p>Further Reading:<br /><a href="http://www.amazon.com/gp/product/193518248X?ie=UTF8&tag=aviewinsidmyh-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=193518248X"><img border="0" src="http://ecx.images-amazon.com/images/I/51ifXiU32wL._SL160_.jpg" alt="" /></a><img height="1" border="0" width="1" style="border: medium none ! important; margin: 0px ! important;" alt="" src="http://www.assoc-amazon.com/e/ir?t=aviewinsidmyh-20&l=as2&o=1&a=193518248X" /> <a href="http://www.amazon.com/gp/product/0470506385?ie=UTF8&tag=aviewinsidmyh-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0470506385"><img border="0" src="http://ecx.images-amazon.com/images/I/51cozPyJ%2B6L._SL160_.jpg" alt="" /></a><img height="1" border="0" width="1" style="border: medium none ! important; margin: 0px ! important;" alt="" src="http://www.assoc-amazon.com/e/ir?t=aviewinsidmyh-20&l=as2&o=1&a=0470506385" /> <a href="http://www.amazon.com/gp/product/1430224797?ie=UTF8&tag=aviewinsidmyh-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=1430224797"><img border="0" src="http://ecx.images-amazon.com/images/I/51gyJpLsGHL._SL160_.jpg" alt="" /></a><img height="1" border="0" width="1" style="border: medium none ! important; margin: 0px ! important;" alt="" src="http://www.assoc-amazon.com/e/ir?t=aviewinsidmyh-20&l=as2&o=1&a=1430224797" /> </p><img src="http://jasonfollas.com/blog/aggbug/77.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2010/03/08/windows-azure-platform-appfabric-access-control-overview.aspx Tue, 09 Mar 2010 04:37:43 GMT http://jasonfollas.com/blog/archive/2010/03/08/windows-azure-platform-appfabric-access-control-overview.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/77.aspx http://jasonfollas.com/blog/services/trackbacks/77.aspx You Need that Using Statement for Extension Methods! http://jasonfollas.com/blog/archive/2010/02/25/you-need-that-using-statement-for-extension-methods.aspx I was working through some <a href="javascript:void(0);/*1267124492396*/">Windows Azure</a> example code today, and came across a situation where IntelliSense did not show a method that the sample code used (<span style="font-family: Courier New;">CreateCloudBlobClient()</span>, in this case):<br /> <br /> <pre type="c-sharp" name="code">var storageAccount = Microsoft.WindowsAzure.CloudStorageAccount.FromConfigurationSetting("DataConnectionString");<br />var blobStorage = storageAccount.CreateCloudBlobClient();<br /></pre> <br /> A lot of times, when I'm exploring an API, I will type out the fully qualified class name in code so that I can use IntelliSense to see what the other members exist in the same namespace. And, while I'm exploring, I usually don't bother to include a <span style="font-family: Courier New;">using </span>statement at the top of my code because it's so easy to just use the "Remove Type Qualifier" refactoring available in <a target="_blank" href="http://devexpress.com/Products/Visual_Studio_Add-in/Coding_Assistance/">CodeRush</a> to clean up the code when I'm ready to move on.<br /> <br /> Well, something that I guess I didn't remember soon enough was that <span style="font-family: Courier New;">using </span>statements are required in order to enable Extension Methods belonging to that namespace. In this case, <span style="font-family: Courier New;">CreateClousBlobClient() </span>was not a method of<span style="font-family: Courier New;"> CloudStorageAccount</span>, but rather an Extension Method (and this fact is not something that is very apparent when typing in sample code that you find in a document).<br /> <br /> IntelliSense showed that method (and more!) after adding the following line of code to the top of my file:<br /> <br /> <pre type="c-sharp" name="code">using Microsoft.WindowsAzure.StorageClient;<br /><br /><br /><br /></pre><img src="http://jasonfollas.com/blog/aggbug/75.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2010/02/25/you-need-that-using-statement-for-extension-methods.aspx Thu, 25 Feb 2010 19:11:38 GMT http://jasonfollas.com/blog/archive/2010/02/25/you-need-that-using-statement-for-extension-methods.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/75.aspx http://jasonfollas.com/blog/services/trackbacks/75.aspx Microsoft Solver Foundation http://jasonfollas.com/blog/archive/2009/04/04/microsoft-solver-foundation.aspx <p><strong>Warning: Only 3 people who read this may actually understand it.</strong></p> <p>This morning, after actually considering what the effort might be to port lp_solve from C over to .NET, I stumbled upon this:</p> <p><font face="Arial"><a href="http://code.msdn.microsoft.com/solverfoundation">http://code.msdn.microsoft.com/solverfoundation</a></font></p> <p>About 4-5 years ago, I worked on a project that used a Frankenstein'ed "lp_solve 2.0-ported-to-Java-then-ported-to-C#" solver, which was great because it was free and it was a completely managed solution (no native components). What I was doing wasn't particularly that heavy of a problem, but still required a solver to quickly and reliably minimize a cost given a bunch of user-provided constraints.</p> <p>Solver Foundation appears to satisfy both of these "requirements" (i.e., being Free and being completely managed).</p> <p>Now, if I can build a Silverlight 2.0+ app that is able to use MSF, then I essentially get infinite scaleout of my optimizer without requiring infinite server infrastructure. <evil laugh></p> <p> </p><img src="http://jasonfollas.com/blog/aggbug/68.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2009/04/04/microsoft-solver-foundation.aspx Sat, 04 Apr 2009 11:53:21 GMT http://jasonfollas.com/blog/archive/2009/04/04/microsoft-solver-foundation.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/68.aspx http://jasonfollas.com/blog/services/trackbacks/68.aspx Enterprise ASP.NET Application Performance Tip http://jasonfollas.com/blog/archive/2009/02/23/enterprise-asp.net-application-performance-tip.aspx <p><a target="_blank" href="http://www.theregion.com/">Microsoft Regional Director</a> (and friend) <a target="_blank" href="http://stevesmithblog.com/">Steven Smith</a> has a great talk about ASP.NET performance tips - I've seen the presentation probably a handful of times already, and always seem to walk away with something new to try that I didn't quite "grok" previously. </p> <p>But, here's one additional tip that I can offer that is easily overlooked, yet very important for enterprise development.</p> <p>Enterprise web applications (at least in my world) tend to use Integrated Security in order to provide Single Sign-On capability (i.e., automatically authenticating the user according to their Active Directory credentials). As such, Anonymous access to the web application is usually disabled directly in the metabase using the IIS MMC. But, with sites that use a lot of small images, there's a serious performance hit that you take when Anonymous access is disabled!</p> <p>A web browser will always try to submit a request anonymously. In the case that I described where Anonymous access is disabled, the web server will generate a 403 response (and include a list of possible ways that the client can authenticate itself). The web browser will then either prompt the user for credentials, or in the case of using Internet Explorer in the Intranet Zone with an application protected by Integrated Security, will automatically provide a response to the NTLM challenge. The point being that it takes two separate requests for each resource (not to mention a higher computational cost, since there's a challenge/response included). Multiply this by however many little images your page might have, and your load time increases significantly.</p> <p>Just today, I was asked to diagnose a load-time issue for a 3rd party web application that my client uses. The site looks nice because it uses little images all over the place - to the effect of hundreds per page! But, the price of these aesthetics was really aweful load times, especially over a VPN connection.</p> <p>For images and other resources that do not necessarily need authentication, you can get an immediate performance improvement by enabling Anonymous access to the resource directories/files themselves (leaving Anonymous access disabled for the rest of the application). </p><img src="http://jasonfollas.com/blog/aggbug/64.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2009/02/23/enterprise-asp.net-application-performance-tip.aspx Mon, 23 Feb 2009 19:15:00 GMT http://jasonfollas.com/blog/archive/2009/02/23/enterprise-asp.net-application-performance-tip.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/64.aspx http://jasonfollas.com/blog/services/trackbacks/64.aspx Using SQL Server Spatial Objects as ADO.NET Parameter Values http://jasonfollas.com/blog/archive/2008/12/11/using-ado.net-with-sql-server-spatial-objects.aspx <p>I've <a target="_blank" href="http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx">previously mentioned</a> that the SQL Server 2008 Spatial data types are freely available for use in your .NET applications, regardless of whether you have SQL Server 2008 or not. This allows you to incorporate some powerful spatial capabilities right into your application. </p> <p><em>(Look for "<font face="Arial">Microsoft SQL Server System CLR Types" on this page: <font face="Arial"><a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=228DE03F-3B5A-428A-923F-58A033D316E1&displaylang=en">http://www.microsoft.com/downloads/details.aspx?FamilyID=228DE03F-3B5A-428A-923F-58A033D316E1&displaylang=en</a> )</font></font></em></p> <p>However, in most usage scenarios, there will come a time when you have an instance of a SQL Server spatial object in your .NET application, and need to commit it to your SQL Server 2008 database. How would you do this, without losing fidelity or resorting to serialization of the object to WKT first?</p> <p>The solutions is to create a Parameter object of type System.Data.SqlDbType.Udt. Then set the UdtTypeName parameter to the SQL Server-recognized type name (i.e., for SqlGeometry, you would simply use Geometry).</p> <p>The following code demonstrates executing an UPDATE statement that sets the value of a Spatial field to a newly constructed object.</p> <pre class="c#" name="code">using (SqlConnection conn = new SqlConnection("Server=.;Integrated Security=true;Initial Catalog=scratch")) { using (SqlCommand cmd = new SqlCommand("UPDATE fe_2007_us_zcta500 SET Boundary=@boundary WHERE id=@id", conn)) { SqlParameter id = cmd.Parameters.Add("@id", System.Data.SqlDbType.Int); SqlParameter boundary = cmd.Parameters.Add("@boundary", System.Data.SqlDbType.Udt); boundary.UdtTypeName = "geometry"; SqlGeometry geom = SqlGeometry.Parse("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))"); boundary.Value = geom; id.Value = 123; conn.Open(); cmd.ExecuteNonQuery(); conn.Close(); } } </pre><img src="http://jasonfollas.com/blog/aggbug/58.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2008/12/11/using-ado.net-with-sql-server-spatial-objects.aspx Thu, 11 Dec 2008 19:04:12 GMT http://jasonfollas.com/blog/archive/2008/12/11/using-ado.net-with-sql-server-spatial-objects.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/58.aspx http://jasonfollas.com/blog/services/trackbacks/58.aspx SqlGeography: Ring Orientation of Polygon Interior Rings (Holes) http://jasonfollas.com/blog/archive/2008/11/25/sqlgeography-ring-orientation-of-polygon-interior-rings-holes.aspx <p>I have mentioned before how the Ring Orientation for the exterior ring of a Polygon is significant when instantiating a SqlGeography object. In this case, a Counter-Clockwise orientation is required so that as an observer walks along the path, the interior of the Polygon is always to their left.</p> <p><img alt="Ring Orientation for SqlGeography" hspace="5" align="right" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/r_RingOrientation.PNG" />But, what I have never really seen documented (or paid attention to, at least) is the fact that the interior rings, or holes, of a Polygon also have specific Ring Orientation requirements. </p> <p>In keeping with the "Left-handed" rule, interior rings must be defined in a Clockwise manner - the opposite orientation of the shape's exterior ring. This is because holes within a Polygon are considered to be part of the exterior of the shape, so the observer walking in a Clockwise direction is still keeping the Polygon's interior to their left.</p> <p>(I should note here that the Ring Orientation for SqlGeography is the exact opposite of ESRI's ShapeFile format, which is why Ring Orientation has been on my mind for the past few days).</p> <p> </p><img src="http://jasonfollas.com/blog/aggbug/55.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2008/11/25/sqlgeography-ring-orientation-of-polygon-interior-rings-holes.aspx Tue, 25 Nov 2008 14:14:18 GMT http://jasonfollas.com/blog/archive/2008/11/25/sqlgeography-ring-orientation-of-polygon-interior-rings-holes.aspx#feedback 3 http://jasonfollas.com/blog/comments/commentRss/55.aspx http://jasonfollas.com/blog/services/trackbacks/55.aspx Spatial: Determining Ring Orientation http://jasonfollas.com/blog/archive/2008/11/24/spatial-determining-ring-orientation.aspx <p>A Ring is a list of points such that the starting point and ending point are the same (forming a closed shape). The order the you define the points that make up a Ring - known as Ring Orientation - is significant, for various data formats (including SQL Server's Geography type) imply special meaning for rings that are defined in a clockwise manner as opposed to a counter-clockwise manner. </p> <p>Given a list of points with no additional context, it can be difficult to determine the Ring Orientation being used. </p> <p>For example, suppose that you have a generic list of points that represent the boundary of a postal code, and that you wish to use these points in order to construct a Polygon instance using the SqlGeography type. SqlGeography happens to use "Left-handed" ordering, so that as an observer walks along the set of points in the order defined, the "inside" of the polygon is always to their left. This also implies that the exterior ring of a Polygon is defined in a counter-clockwise manner.</p> <p>If you try to define a polygon with an area greater than a single hemisphere (this is a nice way to say "if you screw up and use the wrong orientation"), then the SqlGeography type will throw an exception. So, aside from using Try-Catch, what can you do?</p> <p>While researching solutions to this problem, I stumbled upon a paper entitled "<a href="http://www.engr.colostate.edu/~dga/dga/papers/point_in_polygon.pdf">A Winding Number and Point-in-Polygon Algorithm</a>" from the Colorado State University. It turns out that a simple algorithm with O(n) complexity can be used to determine if a point is within a Polygon, and a side effect also provides the Ring Orientation. The key to this algorithm is determining the trend of the ring at each crossing of an axis.</p> <p>Since I was only interested in Ring Orientation (and not point enclosure detection), I didn't need to use this particular algorithm. Instead, I took inspiration from the winding concept, and created a simpler derivative algorithm:</p> <p><img alt="Visual example of how to determine ring orientation at the extreme left point" hspace="5" align="right" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/o_orientation_example.png" /> </p> <ol> <li>Iterate the point collection and determine the extreme "left" and "right" points </li> <li>Normalize the line segments connected to these points so that they each have the same "X" dimension length </li> <li>Compare the "Y" values of the normalized segments to establish the trend through that extreme point (i.e., is the "previous" segment above or below the "next" segment) </li> <li>In the spirit of the Winding algorithm, use opposite orientations for the left and right points so that the results coincide with one another </li> <li>A negative result (negative indicates Clockwise orientation, positive result indicates Counter-Clockwise orientation, and a result of zero would be undefined </li> </ol> <p>I've actually written (and posted) several versions of this algorithm, each time discovering some edge case exception that would cause me to take down the post and rewrite the algorithm. I believe the code below works for all simple polygons on a Cartesian coordinate system (read: I have more testing to see if this will work with an ellipsoidal model, like SqlGeography). </p> <p><em>Note: The following code is generic in nature, and as such, I've defined my own Point structure instead of using a SqlGeometry or SqlGeography, etc. </em></p> <pre class="c#" name="code">struct Point { public double X { get; set; } public double Y { get; set; } } enum RingOrientation : int { Unknown = 0, Clockwise = -1, CounterClockwise = 1 }; RingOrientation Orientation(Point[] points) { // Inspired by http://www.engr.colostate.edu/~dga/dga/papers/point_in_polygon.pdf // This algorithm is to simply determine the Ring Orientation, so to do so, find the // extreme left and right points, and then check orientation if (points.Length < 4) { throw new ArgumentException("A polygon requires at least 4 points."); } if (points[0].X != points[points.Length - 1].X || points[0].Y != points[points.Length - 1].Y) { throw new ArgumentException("The array of points is not a polygon. The first and last point must be identical."); } int rightmostIndex = 0; int leftmostIndex = 0; for (int i = 1; i < points.Length; i++) { if (points[i].X < points[leftmostIndex].X) { leftmostIndex = i; } if (points[i].X > points[rightmostIndex].X) { rightmostIndex = i; } } Point p0; // Point before the extreme Point p1; // The extreme point Point p2; // Point after the extreme double m; // Holds line slope double lenP2x; // Length of the P1-P2 line segment's delta X double newP0y; // The Y value of the P1-P0 line segment adjusted for X=lenP2x RingOrientation left_orientation; RingOrientation right_orientation; // Determine the orientation at the Left Point if (leftmostIndex == 0) p0 = points[points.Length - 2]; else p0 = points[leftmostIndex - 1]; p1 = points[leftmostIndex]; if (leftmostIndex == points.Length - 1) p2 = points[1]; else p2 = points[leftmostIndex + 1]; m = (p1.Y - p0.Y) / (p1.X - p0.X); if (double.IsInfinity(m)) { // This is a vertical line segment, so just calculate the dY to // determine orientation left_orientation = (RingOrientation)Math.Sign(p0.Y - p1.Y); } else { lenP2x = p2.X - p1.X; newP0y = p1.Y + (m * lenP2x); left_orientation = (RingOrientation)Math.Sign(newP0y - p2.Y); } // Determine the orientation at the Right Point if (rightmostIndex == 0) p0 = points[points.Length - 2]; else p0 = points[rightmostIndex - 1]; p1 = points[rightmostIndex]; if (rightmostIndex == points.Length - 1) p2 = points[1]; else p2 = points[rightmostIndex + 1]; m = (p1.Y - p0.Y) / (p1.X - p0.X); if (double.IsInfinity(m)) { // This is a vertical line segment, so just calculate the dY to // determine orientation right_orientation = (RingOrientation)Math.Sign(p1.Y - p0.Y); } else { lenP2x = p2.X - p1.X; newP0y = p1.Y + (m * lenP2x); right_orientation = (RingOrientation)Math.Sign(p2.Y - newP0y); } if (left_orientation == RingOrientation.Unknown) { return right_orientation; } else { return left_orientation; } } void Test() { // Simple triangle - left extreme point is vertically "in between" line segments Point[] points = new Point[] { new Point(5,-1), new Point(0,0), new Point(5,1), new Point(5,-1) }; System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise); Array.Reverse(points); System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise); // Case where both line segments are above the left extreme point points = new Point[] { new Point(2,1), new Point(0,0), new Point(1,1), new Point(2,1) }; System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise); Array.Reverse(points); System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise); // Case where both line segments are below the left extreme point points = new Point[] { new Point(2,-1), new Point(0,0), new Point(1,-1), new Point(2,-1) }; System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise); Array.Reverse(points); System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise); // Case where line segment is vertical (slope cannot be determined) points = new Point[] { new Point(0,0), new Point(0,1), new Point(1,1), new Point(1,0), new Point(0,0) }; System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise); Array.Reverse(points); System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise); // Case where angle thru left extreme point is a right angle points = new Point[] { new Point(0,0), new Point(1,1), new Point(1,-1), new Point(0,0) }; System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise); Array.Reverse(points); System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise); // Real-world case from a SHP file points = new Point[] { new Point(-156.92467299999998,20.738695999999997), new Point(-156.924636,20.738822), new Point(-156.924608,20.73894), new Point(-156.92458,20.739082), new Point(-156.92460599999998,20.739234), new Point(-156.924551,20.739326), new Point(-156.924507,20.739241999999997), new Point(-156.924482,20.739082), new Point(-156.924466,20.738854999999997), new Point(-156.924387,20.738602999999998), new Point(-156.924308,20.738325), new Point(-156.924239,20.738063999999998), new Point(-156.92424,20.737887999999998), new Point(-156.924285,20.737811999999998), new Point(-156.924475,20.73762), new Point(-156.92458299999998,20.737603999999997), new Point(-156.924754,20.737579), new Point(-156.924851,20.737731), new Point(-156.924956,20.738101), new Point(-156.924909,20.738343999999998), new Point(-156.924818,20.738487), new Point(-156.92467299999998,20.738695999999997) }; System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise); Array.Reverse(points); System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise); }</pre><img src="http://jasonfollas.com/blog/aggbug/54.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2008/11/24/spatial-determining-ring-orientation.aspx Mon, 24 Nov 2008 18:00:46 GMT http://jasonfollas.com/blog/archive/2008/11/24/spatial-determining-ring-orientation.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/54.aspx http://jasonfollas.com/blog/services/trackbacks/54.aspx Upcoming Events http://jasonfollas.com/blog/archive/2008/09/25/upcoming-events.aspx <p>There are a few events taking place in October that I would like to promote:</p> <p><strong><a target="_blank" href="http://www.dodn.org"><font size="4">Day of .NET in Ann Arbor</font></a><font size="4"><br /> Saturday, October 18, 2008</font></strong></p> <p><img hspace="5" align="left" alt="" src="http://www.dodn.org/AnnArbor/Fall2008/images/AnnArbor2008HBadge.png" />The fourth Day of .NET in Ann Arbor event will take place on the campus of Washenaw Community College in Ann Arbor (or is it actually Ypsilanti? I can never tell). The original conference was a collaboration between <a target="_blank" href="http://www.migang.org">GANG</a>, <a target="_blank" href="http://www.aadnd.org">AADND</a>, and <a target="_blank" href="http://www.nwnug.com">NWNUG</a>. This year, we have officially added <a target="_blank" href="http://portal.artemis-solutions.com/glugnet/">GLUG.net</a> and <a target="_blank" href="http://www.grdotnet.org/">West Michigan</a> to the list of groups who are assisting in the organization and promotion. So, for those of you following along at home, this event is the product of <u>FIVE</u> regional user groups, and like its predecessors, should be a great day of learning and networking.</p> <p>The session list has been posted, and there should be something on the schedule for everyone. Registration is free, and all that we ask is that if you do sign up, then <u>please show up</u>. There's nothing worse than wasting sponsorship dollars by ordering too much food or too many T-Shirts (though, this year, we're going to try to get Pizza back on the menu, so food waste should hopefully be minimized).</p> <p><font face="Arial"><a target="_blank" href="http://www.dayofdotnet.org/AnnArbor/Fall2008/Sessions.aspx">http://www.dayofdotnet.org/AnnArbor/Fall2008/Sessions.aspx</a></font></p> <p> </p> <p><font size="4"><a target="_blank" href="http://www.nwnug.com/PermaLink,guid,20233af5-bc9f-4bf3-a1b4-5fa94233328b.aspx">Wally McClure appearing at Northwest Ohio .NET User Group</a><br /> Monday, October 20, 2008</font></p> <p><img hspace="5" align="left" alt="" src="http://upload.wikimedia.org/wikipedia/en/thumb/6/6c/Troymcclure.png/120px-Troymcclure.png" />"Hello. I'm Wally McClure. You might remember me from my many books published by Wrox Press with my picture on them. Or, the <a target="_blank" href="http://aspnetpodcast.com/CS11/">ASP.NET Podcast</a>, where on the website, you'll find pictures of my books with my pictures on them. Or, the very popular <a target="_blank" href="http://geekswithblogs.net/images/geekswithblogs_net/rowser/WindowsLiveWriter/IhavemoreWallyinmylife_148F8/PIC_0185_thumb.jpg">T-Shirt with my picture on the back</a>...."</p> <p>I met <a href="http://morewally.com">Wally</a> this summer at TechEd Developer in Orlando, where one of the first experiences was being crammed into the back seat of a car between him and <a target="_blank" href="http://www.keithelder.net">Keith Elder</a>. He seemed to take a liking to me, and let's just say that my phone now receives more random text messages than ever before. :-) </p> <p>In all seriousness, Wally was supposed to have appeared at NWNUG in June, just after TechEd, but had to back out due to work commitments (<a target="_blank" href="http://stevesmithblog.com/">Steve Smith</a> ended up filling in for him). We're thrilled that he was still interested in coming to Toledo, and we were able to arrange an October 20th date (note that this is a Monday, not the regular Tuesday meeting night).</p> <p>Watch the NWNUG website for further details about the meeting topic.</p><img src="http://jasonfollas.com/blog/aggbug/53.aspx" width="1" height="1" /> Jason Follas http://jasonfollas.com/blog/archive/2008/09/25/upcoming-events.aspx Thu, 25 Sep 2008 16:50:25 GMT http://jasonfollas.com/blog/archive/2008/09/25/upcoming-events.aspx#feedback http://jasonfollas.com/blog/comments/commentRss/53.aspx http://jasonfollas.com/blog/services/trackbacks/53.aspx