google maps 的 KML 文件可以用于静态的地图标注,在某些应用中,我们手上往往有成百上千个地址,我们需要把这些地址和描述批量标注到 google maps 上去,如果手工来做,太耗时间,在这里我写了一个程序批量来生成这个 KML 文件。
首先看一下 KML 文件的格式:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.google.com/earth/kml/2">
<Document>
<name>kml_sample2.kml</name>
<Style id="red">
<IconStyle>
<Icon>
<href>http://www.google.com/intl/en_us/mapfiles/ms/icons/red-dot.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="green">
<IconStyle>
<Icon>
<href>http://www.google.com/intl/en_us/mapfiles/ms/icons/green-dot.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="blue">
<IconStyle>
<Icon>
<href>http://www.google.com/intl/en_us/mapfiles/ms/icons/blue-dot.png</href>
</Icon>
</IconStyle>
</Style>
<Placemark>
<name>Google Inc.</name>
<description><![CDATA[
Google Inc.<br />
1600 Amphitheatre Parkway<br />
Mountain View, CA 94043<br />
Phone: +1 650-253-0000<br />
Fax: +1 650-253-0001<br />
<p>Home page: <a href="http://www.google.com">www.google.com</a></p>
]]>
</description>
<styleUrl>#red</styleUrl>
<Point>
<coordinates>-122.0841430, 37.4219720, 0</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Yahoo! Inc.</name>
<description><![CDATA[
Yahoo! Inc.<br />
701 First Avenue<br />
Sunnyvale, CA 94089<br />
Tel: (408) 349-3300<br />
Fax: (408) 349-3301<br />
<p>Home page: <a href="http://yahoo.com">http://yahoo.com</a></p>
]]>
</description>
<styleUrl>#green</styleUrl>
<Point>
<coordinates>-122.0250403,37.4163228</coordinates>
</Point>
</Placemark>
</Document>
</kml>
这个是一个典型的用于google maps 的 KML 文件,(注意不同应用的KML 格式会有所不同,比如 google earth 的 kml 格式就复杂得多)
从这个kml 文件格式来看,其实它就是一个 xml 文件,我们只要自动生成这个文件中各个元素的信息就可以得到这个xml 文件。这里其实最大的问题是如何自动通过地址获取经纬度坐标。值得庆幸的是 google 提供了这方面的 api 函数。google api 获取地理坐标的官方例子见:geocodingapi
我的实现稍微复杂一些,因为我需要在函数中为不同的位置自动分配颜色
1: /// <summary>
2: /// Generate placemark by address description
3: /// </summary>
4: /// <param name="addrDescription">address and description</param>
5: /// <returns>if no matched, return false</returns>
6: public bool Generate(AddressDescription addrDescription)
7: { 8: _LastErrorOrWarning = null;
9:
10: Thread.Sleep(DelayInMs);
11:
12: List<GeographicCoordinate> coordinates = Geocoding.Geocode(addrDescription.Address);
13:
14: if (coordinates.Count == 0)
15: { 16: _LastErrorOrWarning = string.Format("Address:{0}, Description:{1} does not find the coordinates, please make sure the address is correctly.", 17: addrDescription.Address, addrDescription.Description);
18:
19: return false;
20: }
21:
22: if (coordinates.Count > 1)
23: { 24: _LastErrorOrWarning = string.Format("Address:{0}, Description:{1} has more than one coordinates.", 25: addrDescription.Address, addrDescription.Description);
26: }
27:
28: string colorId = Colors[_ColorIndex];
29:
30: _ColorIndex++;
31:
32: if (_ColorIndex >= Colors.Count)
33: { 34: _ColorIndex = 0;
35: }
36:
37: _Kml.Document.Add(new Placemark(addrDescription.Address, addrDescription.Description, colorId,
38: coordinates[0].Latitude, coordinates[0].Longitude));
39:
40: return true;
41: }
第32行有个bug,应该是 >= ,我原来写成 > 了,博客中我改过来了,源码我就不改了。
如上代码,第12行就是通过GeocodingApi 获取指定地址的物理坐标,由于有时候获取不到坐标,有时候由于地址不确切,有多个坐标,所以我加了一个错误和警告的属性,用于调用者得到相关的信息。
_Kml 这个对象是一个 Kml 类的实例,这个类用于生成 KML 文件结构,并可以保存到KML文件中。这个类在后面介绍。
下面的 _Color 部分是自动的顺序分配标注点的颜色,我为了省事,在代码中写死了4种颜色,你也可以修改代码增加颜色或其他图标。
标注颜色这里其实还有一个问题,就是如果让相邻的节点显示不同颜色,这个算法比较复杂了,我没有实现,各位如果有兴趣可以思考一下这个怎么做。
好了,最大的问题解决了,剩下就是写 xml 文件了,这个很简单,我就不深入讲了,直接把代码贴出来。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace GenerateKML
{
public class Placemark
{
public class KMLPoint
{
public KMLPoint()
{
}
public KMLPoint(double latitude, double longitude)
{
SetCoordinates(latitude, longitude);
}
private string _coordinates;
public void SetCoordinates(double latitude, double longitude)
{
_coordinates = longitude.ToString() + "," + latitude.ToString();
}
public string coordinates
{
get
{
return _coordinates;
}
set
{
_coordinates = value;
}
}
}
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("styleUrl")]
public string StyleUrl { get; set; }
public KMLPoint Point { get; set; }
public Placemark()
{
}
public Placemark(string name, string description, string styleUrl,
double latitude, double longitude)
{
Name = name;
Description = description;
StyleUrl = styleUrl;
Point = new KMLPoint(latitude, longitude);
}
}
public class kml
{
[XmlIgnore]
string Name { get; set; }
List<Placemark> _Placemarks = new List<Placemark>();
[XmlArray()]
public List<Placemark> Document
{
get
{
return _Placemarks;
}
set
{
_Placemarks = value;
}
}
public kml()
{
}
public kml(string name)
{
Name = name;
}
private XmlNode GetColorStyle(XmlDocument xmlDoc, string color)
{
XmlNode style = xmlDoc.CreateNode(XmlNodeType.Element, "Style", "");
XmlAttribute attr = style.OwnerDocument.CreateAttribute("id");
attr.Value = color;
style.Attributes.Append(attr);
XmlNode iconStyle = xmlDoc.CreateNode(XmlNodeType.Element, "IconStyle", "");
XmlNode icon = xmlDoc.CreateNode(XmlNodeType.Element, "Icon", "");
XmlNode href = xmlDoc.CreateNode(XmlNodeType.Element, "href", "");
href.InnerText = string.Format("http://www.google.com/intl/en_us/mapfiles/ms/icons/{0}-dot.png",
color);
style.AppendChild(iconStyle);
iconStyle.AppendChild(icon);
icon.AppendChild(href);
return style;
}
public void SaveToFile(string xml)
{
using (FileStream fs = new FileStream(xml, FileMode.Create, FileAccess.ReadWrite))
{
using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
{
XmlSerializer serializer = new XmlSerializer(this.GetType());
serializer.Serialize(sw, this);
}
}
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xml);
xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null);
XmlNode documentNode = xmlDoc.SelectSingleNode(@"/kml/Document");
XmlNode nameNode = xmlDoc.CreateNode(XmlNodeType.Element, "name", "");
nameNode.InnerText = this.Name;
XmlNode placeMarkNode = documentNode.FirstChild;
documentNode.InsertBefore(nameNode, placeMarkNode);
documentNode.InsertBefore(GetColorStyle(xmlDoc, "red"), placeMarkNode);
documentNode.InsertBefore(GetColorStyle(xmlDoc, "green"), placeMarkNode);
documentNode.InsertBefore(GetColorStyle(xmlDoc, "blue"), placeMarkNode);
documentNode.InsertBefore(GetColorStyle(xmlDoc, "yellow"), placeMarkNode);
XmlNode kmlNode = xmlDoc.SelectSingleNode(@"/kml");
XmlAttribute attr = kmlNode.OwnerDocument.CreateAttribute("xmlns");
attr.Value = "http://earth.google.com/kml/2.0";
kmlNode.Attributes.Append(attr);
xmlDoc.Save(xml);
}
}
}
下面看一下调用的方法,使用者如果不想仔细研究细节,那就关注这个就可以了,调用方法非常简单
1: static void Main(string[] args)
3: Generator kmlGenerator = new Generator("Test");
5: kmlGenerator.Generate(new AddressDescription("1600 Amphitheatre Parkway, Mountain View, CA 94043",
8: if (!string.IsNullOrEmpty(kmlGenerator.LastErrorOrWarning))
10: Console.WriteLine(kmlGenerator.LastErrorOrWarning);
13: kmlGenerator.Generate(new AddressDescription("1 Microsoft Way, Redmond, WA 98052",
16: if (!string.IsNullOrEmpty(kmlGenerator.LastErrorOrWarning))
18: Console.WriteLine(kmlGenerator.LastErrorOrWarning);
21: kmlGenerator.Generate(new AddressDescription("1601 S. California Ave., Palo Alto, CA 95304",
24: if (!string.IsNullOrEmpty(kmlGenerator.LastErrorOrWarning))
26: Console.WriteLine(kmlGenerator.LastErrorOrWarning);
29: kmlGenerator.Generate(new AddressDescription("701 First Ave, Sunnyvale, CA 94089",
32: if (!string.IsNullOrEmpty(kmlGenerator.LastErrorOrWarning))
34: Console.WriteLine(kmlGenerator.LastErrorOrWarning);
37: kmlGenerator.Save("test.kml");
第三行,实例化 KML 生成器,并指定一个名字,这个名字对于 kml 文档中的 name 字段。
第五行,在kml 文件中标注 google 总部的地址
第八行,判断是否有最新的错误,每次执行第五行的Generate 方法,会将最新错误清空,所以这里永远是得到最近一次调用 Generate 方法的错误或警告。
最后,我们可以把这个 kml 文件导入到我们自己创建的 google map 中。这个在 google maps 里面有相应的导入功能,这里就不介绍了。
<add key="GeocodingApi.Key" value="google api key" />
<add key="GeocodingApi.Url" value="http://maps.google.com/maps/geo?" />
GeocodingApi.key 这里要填写你自己的 google api key,你可以在 google 网站上获取,地址如下:
http://code.google.com/apis/maps/signup.html