地图经纬度C#和Javascript的压缩以及解压

此方法没有只适用于数字类型。不适应于字符串类型。(转摘:http://soulsolutions.com.au/Default.aspx?tabid=96,其中有些变量没有赋值,可以看转摘网址后面的评论中有说明。)

Encoding Latitude and Longitude pairs for performance

In Virtual Earth we use lat/lon pairs everywhere, for pushpins, polylines and polygons but also for map bounds like the current view. This article aims to look at a better way to store and transmit these values. The encoding algorithm used is from Google maps. You will find an explanation of how and why we use it for Virtual Earth.

What am I talking about?

Take this polyline for example (scroll down very fast):

        (-10.83330598364249,142.20703125)
        (-17.434510551522894,140.7568359375)
        (-14.859850400601036,135.3955078125)
        (-12.254127737657366,136.845703125)
        (-11.307707707765436,130.2978515625)
        (-14.817370620155252,129.5068359375)
        (-13.710035342476668,126.298828125)
        (-18.81271785640776,121.640625)
        (-22.67484735118851,113.642578125)
        (-26.667095801104803,113.5546875)
        (-31.98944183792288,115.6201171875)
        (-34.95799531086791,115.9716796875)
        (-33.394759218577974,123.22265625)
        (-31.802892586706747,129.19921875)
        (-32.39851580247401,133.59375)
        (-35.17380831799957,135.52734375)
        (-33.02708758002873,137.8564453125)
        (-35.8534396195918,136.9775390625)
        (-36.17335693522159,139.74609375)
        (-38.75408327579141,143.349609375)
        (-40.58058466412763,144.66796875)
        (-43.54854811091287,146.07421875)
        (-43.54854811091287,147.0849609375)
        (-40.97989806962013,148.0517578125)
        (-40.68063802521456,144.6240234375)
        (-38.616870463929736,143.3056640625)
        (-37.92686760148134,149.3701171875)
        (-34.19817309627723,151.083984375)
        (-28.92163128242129,153.369140625)
        (-25.641526373065755,153.28125)
        (-18.60460138845525,146.2939453125)
        (-15.24178985596171,145.0634765625)
        (-10.660607953624762,142.20703125)

1,127 characters
This can be encoded into this:

d{baA}x}bZphhg@vfzGszuNhcv_@y|{NwfzGczwD`{}f@lnlTznyC{gwE`qqRvrc^vxl
[hiqVxbyo@pvjWhdPton_@}krKlxbQgtcAgipHsegk@c|uHohnc@risB{hyYp`}O}sxJ_hbL
{{eMt_gPdtjDnn}@ov{Op`wN}x~TrvcJwn`GvtbQatqG?eldEautNmy {Dkmy@hn|SoarKvn
`GqweCym_d@igwUuvmIkqe_@gi}Lus_ShdPwk}i@tusi@sxoSlioFkw}Zv{lP

272 characters
That’s less the 25% of the original!

 

How accurate do we need to be?

I’ve seen lat/lon values with 15 decimal places to mark the centre of a country.
0.00001° ≈ 1 m
For me, and therefore this article that is as accurate as I’m going to be for displaying and working with Virtual Earth. In my database I will store values with many more digits, I use a float(8). Let’s not forget the lessons from year 2000 bug here. We don’t want to lose accuracy for the future but it’s not required for today’s mapping. The benefit is we have far less data to deal with. You will see why that’s important in a moment.

What characters can we use and why?

We are going to encode values into plain text strings to transmit on URL query strings, store as string variables in js and .net and pass in XML. Therefore we want to use plain ASCII characters. The Google algorithm we use simply uses characters above 63 to skip <>?& etc. An interesting discovery though is for plain text emails in outlook you can’t use {}[]\ in links. If you plan to do this you easily do a replacement using numbers 0-9 for the offending characters.

Differences

If we have many lat/lon values on our map we can save space by only storing the differences in lat/lon between them. The closer the set of points, which is typical, the smaller the information we need to encode.

The Algorithm (courtesy of Google)

  1. Take the initial signed value:
    -179.9832104
  2. Take the decimal value and multiply it by 1e5, flooring the result:
    -17998321
  3. Convert the decimal value to binary. Note that a negative value must be inverted and provide padded values toward the byte boundary:
    00000001 00010010 10100001 11110001
    11111110 11101101 10100001 00001110
    11111110 11101101 01011110 00001111
  4. Shift the binary value:
    11111110 11101101 01011110 00001111 0
  5. If the original decimal value is negative, invert this encoding:
    00000001 00010010 10100001 11110000 1
  6. Break the binary value out into 5-bit chunks (starting from the right hand side):
    00001 00010 01010 10000 11111 00001
  7. Place the 5-bit chunks into reverse order:
    00001 11111 10000 01010 00010 00001
  8. OR each value with 0x20 if another bit chunk follows:
    100001 111111 110000 101010 100010 000001
  9. Convert each value to decimal:
    33 63 48 42 34 1
  10. Add 63 to each value:
    96 126 111 105 97 64
  11. Convert each value to its ASCII equivalent:
    `~oia@

Encoding

JavaScript

// Encode a signed number in the encode format.
function encodeSignedNumber(num) {
  var sgn_num = num << 1;

  if (num < 0) {
    sgn_num = ~(sgn_num);
  }

  return(encodeNumber(sgn_num));
}

// Encode an unsigned number in the encode format.
function encodeNumber(num) {
  var encodeString = "";

  while (num >= 0x20) {
    encodeString += (String.fromCharCode((0x20 | (num & 0x1f)) + 63));
    num >>= 5;
  }

  encodeString += (String.fromCharCode(num + 63));
  return encodeString;
}

// Create the encoded bounds.
function createEncodings(points) {
  var i = 0;
  var plat = 0;
  var plng = 0;
  var encoded_points = "";

  for(i = 0; i < points.length; ++i) {
    var point = points[i];
    var lat = point.Latitude;
    var lng = point.Longitude;

    var late5 = Math.floor(lat * 1e5);
    var lnge5 = Math.floor(lng * 1e5);

    dlat = late5 - plat;
    dlng = lnge5 - plng;

    plat = late5;
    plng = lnge5;

    encoded_points += encodeSignedNumber(dlat) + encodeSignedNumber(dlng);
  } 
  return encoded_points;
}

C#

/// <summary>
/// encoded a list of latlon objects into a string
/// </summary>
/// <param name="points">the list of latlon objects to encode</param>
/// <returns>the encoded string</returns>
public static string EncodeLatLong(List<LatLong> points)
{
    int plat = 0;
    int plng = 0;
    int len = points.Count;

    StringBuilder encoded_points = new StringBuilder();

    for (int i = 0; i < len; ++i)
    {
        //Round to 5 decimal places and drop the decimal
        int late5 = (int)(points[i].Lat * 1e5);
        int lnge5 = (int)(points[i].Lon * 1e5);

        //encode the differences between the points
        encoded_points.Append(encodeSignedNumber(late5 - plat));
        encoded_points.Append(encodeSignedNumber(lnge5 - plng));

        //store the current point
        plat = late5;
        plng = lnge5;
    }
    return encoded_points.ToString();
}

/// <summary>
/// Encode a signed number in the encode format.
/// </summary>
/// <param name="num">the signed number</param>
/// <returns>the encoded string</returns>
private static string encodeSignedNumber(int num)
{
    int sgn_num = num << 1; //shift the binary value
    if (num < 0) //if negative invert
    {
        sgn_num = ~(sgn_num);
    }
    return (encodeNumber(sgn_num));
}

/// <summary>
/// Encode an unsigned number in the encode format.
/// </summary>
/// <param name="num">the unsigned number</param>
/// <returns>the encoded string</returns>
private static string encodeNumber(int num)
{
    StringBuilder encodeString = new StringBuilder();
    while (num >= 0x20)
    {
        //while another chunk follows
        encodeString.Append((char)((0x20 | (num & 0x1f)) + minASCII)); 
        //OR value with 0x20, convert to decimal and add 63
        num >>= binaryChunkSize; //shift to next chunk
    }
    encodeString.Append((char)(num + minASCII));
    return encodeString.ToString();
}

  

Decoding

JavaScript

// Decode an encoded string into a list of VE lat/lng.
function decodeLine(encoded) {
    var len = encoded.length;
    var index = 0;
    var array = [];
    var lat = 0;
    var lng = 0;
    try
    {
        while (index < len) {
            var b;
            var shift = 0;
            var result = 0;
            do {
                  b = encoded.charCodeAt(index++) - 63;
                  result |= (b & 0x1f) << shift;
                  shift += 5;
            } while (b >= 0x20);
            var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
            lat += dlat;

            shift = 0;
            result = 0;
            do {
                  b = encoded.charCodeAt(index++) - 63;
                  result |= (b & 0x1f) << shift;
                  shift += 5;
            } while (b >= 0x20);
            var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
            lng += dlng;

            array.push(new VELatLong((lat * 1e-5), (lng * 1e-5)));
        }
    } catch(ex)
    {
        //error in encoding.
    }
    return array;
}

C#

/// <summary>
/// decodes a string into a list of latlon objects
/// </summary>
/// <param name="encoded">encoded string</param>
/// <returns>list of latlon</returns>
public static List<LatLong> DecodeLatLong(string encoded)
{
    List<LatLong> locs = new List<LatLong>();

    int index = 0;
    int lat = 0;
    int lng = 0;

    int len = encoded.Length;
    while (index < len)
    {
        lat += decodePoint(encoded, index, out index);
        lng += decodePoint(encoded, index, out index);

        locs.Add(new LatLong((lat * 1e-5), (lng * 1e-5)));
    }

    return locs;
}

/// <summary>
/// decodes the cuurent chunk into a single integer value
/// </summary>
/// <param name="encoded">the complete encodered string</param>
/// <param name="startindex">the current position in that string</param>
/// <param name="finishindex">output - the position we end up in that string</param>
/// <returns>the decoded integer</returns>
private static int decodePoint(string encoded, int startindex, out int finishindex)
{
    int b;
    int shift = 0;
    int result = 0;
    do
    {
        //get binary encoding
        b = Convert.ToInt32(encoded[startindex++]) - minASCII;
        //binary shift
        result |= (b & 0x1f) << shift;
        //move to next chunk
        shift += binaryChunkSize;
    } while (b >= 0x20); //see if another binary value
    //if negivite flip
    int dlat = (((result & 1) > 0) ? ~(result >> 1) : (result >> 1));
    //set output index
    finishindex = startindex;
    return dlat;
}

Utilising the above function you can encode your lat/lon values server or client side and see a 400% decrease in transmission size. If you need to store a few values these encoding are much easier to deal with then an array of lat/lon.
Additionally if you are able to use some form of compression, like that found in IIS6 you can reduce the data size again.

Final Thoughts

Using the encoding as a base it is possible to not only encode lat/lon values but also other simple information.
I had an application that was found to be transmitting 640KB of data to the VE map. I got it down to less than 25KB by removing everything that wasn’t necessary and encoding what was left. With IIS compression this would go down to less than 10KB. That’s the different between a slow Virtual Earth application and a high performance one.
Stay tuned for my next Article: “Loading your pushpin popup content on demand”.

 

posted @ 2012-07-05 11:06  悟行  阅读(1274)  评论(1编辑  收藏  举报