|
|||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Download Coordinate_CS.zip - 3 KB - Coordinate.cs class source code
IntroductionI have seen many articles here at CodeProject about coordinates and GPS signal handling, like "Writing your own GPS Applications" and "Distance between locations using latitude and longitude" among others. Most of them do some tasks around a latitude/longitude pair, but them don't handle coordinate storage with care. This article provides a solution to manage coordinates at both memory and persistent storage (XML or database), complying with BackgroundPrevious readings:
In-memory storageA geodesic coordinate has 2 main components:
With these contraints we can conclude that coordinates could be stored in a pair of 32-bit After many years working with coordinates, I concluded the best unit to store coordinates is not degrees but "seconds of degree". This way, all coordinate elements (degrees, minutes and seconds) will remain on the integral portion of values, with exception of decimal of seconds, avoiding the annoying loose of precision. The first approach to implement a coordinate class would be then: class Coordinate
{
float latitude;
float longitude;
}
Here is an example on how to store a coordinate as explained above:
XML Serialization
<Coordinate>
<Latitude>-18781.3</Latitude>
<Longitude>-290269.5</Longitude>
</Coordinate>
This is barely 90 - 100 bytes per coordinate, including spacing characters. Just figure out a 100,000 node geographic file. This will require near 10 Mb of file storage! Here is where ISO comes to the rescue. If you have read the ISO 6709 explanation, you will know a proper representation for our sample data would be, among others:
So, if we override the default serializer for our Coordinate class to use the second version, we can expect a result like: <Coordinate>-051301.3-0803749.5/</Coordinate>
At a first glance you will notice a storage saving of more than 50%. Also notice that, besides the fact our class stores data in-memory conveniently in "seconds of degree", XML storage is expressed in degrees, minutes and seconds, so you can interpret the xml file by yourself, without convertion formulas. Now you have the best of both worlds. Coordinate class implementationFirst part of the Coordinate class implementation has the following fields and properties declarations: [Serializable()]
public class Coordinate : ICloneable, IXmlSerializable, IFormattable
{
#region Private fields
private float latitude; // Expressed in seconds of degree, positive values for north
private float longitude; // Expressed in seconds of degree, positive values for east
#endregion
#region Constructors
public Coordinate()
{
Latitude = Longitude = 0.0f;
}
public Coordinate(float lat, float lon)
{
Latitude = lat;
Longitude = lon;
}
#endregion
#region Properties
public float Latitude
{
set
{
latitude = value * 3600.0f;
}
get
{
return latitude / 3600.0f; // return degrees
}
}
public float Longitude
{
set
{
longitude = value * 3600.0f;
}
get
{
return longitude / 3600.0f; // return degrees
}
}
#endregion
You can see how latitude and longitude fields have been hidden from external usage and serialization. There are a couple of properties instead, Following with code, you will find the public methods, to either set and get values in a friendly way: #region Public methods
// Multi-argument setters
public void SetD(float latDeg, float lonDeg) {...}
public void SetDM(float latDeg, float latMin, bool north, float lonDeg, float lonMin,
bool east) {...}
public void SetDMS(float latDeg, float latMin, float latSec, bool north, float lonDeg,
float lonMin, float lonSec, bool east) {...}
// Multi-argument getters
public void GetD(out float latDeg, out float lonDeg) {...}
public void GetDM(out float latDeg, out float latMin, out bool north, out float lonDeg,
out float lonMin, out bool east) {...}
public void GetDMS(out float latDeg, out float latMin, out float latSec, out bool north,
out float lonDeg, out float lonMin, out float lonSec, out bool east) {...}
// Distance in meters
public float Distance(Coordinate other) {...}
// Parsing method
public void ParseIsoString(string isoStr) {...}
#endregion
First version of setter, Finally you will find a method to calculate distance agains other coordinate. It is implemented using classical Next methods in code implement some fundamental overrides: #region Overrides
public override string ToString() {...}
public override bool Equals(object obj) {...}
public override int GetHashCode() {...}
#endregion
The default implementation of #region IFormattable Members
// Not really IFormattable member
public string ToString(string format) {...}
// ToString version with formatting
public string ToString(string format, IFormatProvider formatProvider) {...}
#endregion
Due to
Any other formatting string will produce an exception. Calling any version with empty or null formatting string will output the default version. But still we have not seen the best feature of IFormattable interface; we can embed the formatting string inside a more complex formatting case, by example: string s = string.Format("Our sample coordinate is: {0:DM}\r\n.", someCoord); Finally there is the #region IXmlSerializable Members
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() {...}
void IXmlSerializable.ReadXml(XmlReader reader) {...}
void IXmlSerializable.WriteXml(XmlWriter writer) {...}
#endregion
You don't have to invoke these functions directly, them will be invoked by any serialization function, as you can find in the test source code bellow. Coordinate collectionsOnce using the Coordinate class, you will notice another source of space wasting. Consider the following example: <CoordinateList>
<Coordinate>+010000.0+0010000.0/</Coordinate>
<Coordinate>-022400.0+0013000.0/</Coordinate>
<Coordinate>-034200.0+0021200.0/</Coordinate>
<Coordinate>-020000.0-0003000.0/</Coordinate>
</CoordinateList>
A real-world GIS application will need hundreds of <Coordinate> entries for each polygon. So the ISO standard comes again to the rescue by storing the data in a more compact fashion: <CoordinateList>+010000.0+0010000.0/-022400.0+0013000.0/-034200.0+0021200.0/-020000.0-0003000.0/</CoordinateList>
Again we have a storage saving of more than 50%. Implementing [Serializable()]
public class CoordinateList : List<Coordinate>, IXmlSerializable
{
#region Constructors
public CoordinateList() {...}
#endregion
#region Overrides
public override string ToString() {...}
#endregion
#region Public methods
public void ParseIsoString(string isoStr) {...}
#endregion
#region System.Xml.Schema.IXmlSerializable Members
XmlSchema IXmlSerializable.GetSchema() {...}
void IXmlSerializable.ReadXml(XmlReader reader) {...}
void IXmlSerializable.WriteXml(XmlWriter writer) {...}
#endregion
}
Database storage/retrievingA single coodinate or a coordinate collection can be stored in any text field type into a database. For storage, you have to prepare the query string using the proper // Single point example
string query =
string.Format("INSERT INTO PointMarks SET ID={0}, LOCATION='{1:ISO}'",
someID, coord);
// Coordinate list example
string query =
string.Format("INSERT INTO Boundaries SET ID={0}, POLYGON='{1}'",
someID, coord_list);
Notice when inserting a single coordinate, the To retrieve a single coordinate or coordinate collection, just create a new object and invoke the // Single point example
string iso = datareader["LOCATION"].ToString();
Coordinate coord = new Coordinate();
coord.ParseIsoString(iso);
// Coordinate list example
string iso = datareader["POLYGON"].ToString();
CoordinateList coord_list = new CoordinateList();
coord_list.ParseIsoString(iso);
The sample codeSupplied sample code at static void Main()
{
// Create a new coordinate with some values
Coordinate c = new Coordinate();
c.SetDMS(00, 10, 20.8f, true, 30, 40, 50.9f, false);
// Create a new coordinate list with
CoordinateList cl = new CoordinateList();
cl.Add(new Coordinate( 1.0f, 1.0f));
cl.Add(new Coordinate(-2.4f, 1.5f));
cl.Add(new Coordinate(-3.7f, 2.2f));
cl.Add(new Coordinate(-2.0f, -.5f));
// Show formatting capabilities
MessageBox.Show(
string.Format("String Formatting:\r\nD: {0:D}\r\nDM: {0:DM}\r\nDMS: {0:DMS}\r\nISO: {0:ISO}", c),
"Formatting example");
// Serialize coordinates to a string object
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
string part1, part2;
// Single coordinate
System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(Coordinate));
xs.Serialize(sw, c);
sw.Flush();
part1 = sb.ToString();
// Coordinate list
sb.Length = 0;
System.Xml.Serialization.XmlSerializer xsl = new System.Xml.Serialization.XmlSerializer(typeof(CoordinateList));
xsl.Serialize(sw, cl);
sw.Flush();
part2 = sb.ToString();
sw.Close();
// Show serialized data
MessageBox.Show(string.Format("Single coordinate:\r\n{0}\r\n\r\nCoordinate list:\r\n{1}", part1, part2),
"Serialization example");
// Deserialize single coordinate from a string object
System.IO.StringReader sr = new System.IO.StringReader(part1);
Coordinate c1 = (Coordinate)xs.Deserialize(sr);
sr.Close();
// Deserialize coordinate list from a string object
sr = new System.IO.StringReader(part2);
CoordinateList cl1 = (CoordinateList)xsl.Deserialize(sr);
sr.Close();
// Show properties of deserialized data
string message = string.Format(
"Single coordinate:\r\n{0}\r\nLatitude: {1}°\r\nLongitude: {2}°\r\n\r\nCoordinate list:\r\n",
c1, c1.Latitude, c1.Longitude);
foreach (Coordinate coord in cl1)
message += coord.ToString() + "\r\n";
MessageBox.Show(message, "Deserialization example");
}
Addendum: Why to use float?I have receive many comments about why to use
About
About precision, you have to be aware of the internals of the A longitude with a maximum value of 180 degrees, at the Equator has 60 nautical miles per minute, when stored in a 180 * 60 * 1,852 meters / 223 values = 20,001,600 / 8,388,608 = about 2.4 meters But longitude magnitude will vary according to latitude, with the equation decreasing up to zero at the poles. Then precision will increase when latitude is greater, according to the following chart:
The case of latitude is different. It doesn't vary in any zone of the earth, with a constant magnitude of 60 nautical miles per minute of degree, and a maximum value of 90 in the poles. The most pesimistic calculation for latitude (near to the poles) will be: 90 * 60 * 1,852 meters / 223 values = 10,000,800 / 8,388,608 = about 1.2 meters The following chart depicts the precision of latitude stored in a
So we can conclude that in practical terms, we will have an average precision value fewer than 1 meter. This is far more precise than a GPS output, and suitable for most GIS applications up to street level. Class enhacementsThere are lot of things you can do to enhance this fundamental class, I will include some of the following in the next version:
History
| ||||||||||||||||||||||||||||||||