How are ADS-B surface position frames decoded? I am developing C# code that reads 1090ES Automatic Dependent Surveillance - Broadcast  (ADS-B). 

The ADS-B position messages broadcast by the aircraft's ADS-B transmitter have a different encoding depending on whether the aircraft is airborne or on the ground. 

When I decode Airborne Position frames as Compact Position Reporting (CPR) encoded positions, I get realistic results. However, when I decode the CPR for Surface Position frames, the positions I get are far (geographically) from what I should get.

How is the CPR in Surface position messages different from airborne position messages and how are they decoded properly?
The Compact Position Report (CPR) coding in Surface squiters is different from Airborne CPR coding. The resolution is 4 times better (~1.25 m instead of ~ 5 m), at the cost of losing the 2 most significant bits. 

Due to this, global surface position decoding gives an ambiguous solution. IIRC the solution can be at 4 points on the globe, you have to select the nearest to you. And you should validate that it is within a realistic range, like you should do with Airborne CPR decoding. After you validated the position using global decoding on a couple of odd- even squiter pairs you can use the more efficient local decoding. Of course, as with Airborne CPR decoding, you need to protect against position jumps and time gaps in your decoding algorithm.