Event Finder - Google Places 活动应用程序
Event Finder - 一款 WinRT 应用程序,利用 Google Places API 和其他数据源来显示本地活动列表
引言
Event Finder 是一款 C# WinRT 应用,它利用 Google Places API、WinRT 定位功能、微格式和 RSS 来发现本地活动场馆并显示即将举行的活动。下面包含了一些示例代码片段,完整的半成品源代码可在 github 上找到。
我们在世界中的位置
Google Places API 是一个实验性 API,用于根据位置数据、地点类型、距离等搜索地点。例如(利用一些包装代码并将结果直接放入 JSON 解析器中,并使用一些预先准备好的对象类型),我们可以搜索指定半径内已知点的电影院和艺术画廊。
public async Task<String> GoogleRequest(String url)
{
WebRequest req = WebRequest.Create(url);
WebResponse res = await req.GetResponseAsync();
Stream inputStream = res.GetResponseStream();
StreamReader reader = new StreamReader(inputStream);
return reader.ReadToEnd();
}
public async Task<GooglePlaceSearchResult> FetchVenuesAsync()
{
var pos = await GetPositionAsync();
var venues = JsonConvert.DeserializeObject<GooglePlaceSearchResult>(
await GoogleRequest("https://maps.googleapis.com/maps/api/place/search/json?" +
"key=APIKEY&sensor=true" +
"&types=movie_theater|art_gallery&radius=6000&" +
"&location=" +
pos.Coordinate.Latitude.ToString() +"," +
pos.Coordinate.Longitude.ToString()
)
);
}
为了找到那个已知点,我们可以使用 WinRT 的 Geolocator
。
private async Task<Geoposition> GetPositionAsync()
{
Geolocator geo = new Geolocator();
geo.DesiredAccuracy = PositionAccuracy.High;
Geoposition pos = await geo.GetGeopositionAsync();
return pos;
}
环顾四周
从 Places
API 返回的数据非常丰富,通常包括场馆的完整邮政地址、坐标、电话号码、网站 URL 以及用户评论。该 API 的一个特点是它还可以包含地点的活动信息;例如,当地电影院的电影放映列表。这些活动可以由活动组织者通过 API 发布,也可以从其他来源检索。不幸的是,在开发此应用时,我未能找到任何通过 API 提供活动数据的地点,因此该功能要么尚未实现,要么我查看的场馆都没有发布它们的活动。
Places
API 提供的信息似乎与使用 schema.org 数据格式的 microdata 提供的信息相匹配。在没有直接从 Places
API 获取活动数据的情况下,或许我们可以改用 API 返回的 URL,看看场馆的网站是否发布了微数据。我们期望找到类似这样的标记页面。
<div itemscope itemtype="http://schema.org/Event">
<header><h1 itemprop="name">Josephine Foster</h1></header>
<div style="float: right">
<img itemprop="image" src="imgsmall.jpg" alt="" width="399" height="264">
</div>
<h3>
<time itemprop="startDate"
datetime="2012-10-31T20:00:00+00:00">8:00 p.m.</time>
to
<time itemprop="endDate"
datetime="2012-10-31T23:00:00">
11:00 p.m. Wednesday 31 October 2012</time>
</h3>
<p itemprop="description">The wonderful singer-songwriter Josephine Foster
(USA) tours her newalbum 'Blood Rushing' (Fire Records) with full band.</p>
<a itemprop="url" href="http://www.starandshadow.org.uk/on/gig/504">
http://www.starandshadow.org.uk/on/gig/504
</a>
</div>
然而,同样令人担忧的是,似乎还没有多少场馆发布此类数据。一个这样的场馆是纽卡斯尔的 Star And Shadow Cinema(我有点偏袒,因为我写了他们的网站)。如上所示,活动的开始时间、标题以及其他各种字段都以标准化格式呈现。下面是一个关于如何从类似页面提取数据的示例。
private async void GetByMicroFormat(SyndicationItem feeditem)
{
HtmlDocument doc = await EventDataSource._docGetter.LoadFromWebAsync(_uri.ToString());
this._subtitle = GetMicroFormatByProp(doc, "startDate", "time");
this._image = new BitmapImage(new Uri(GetMicroFormatByProp(doc, "image", "meta", "content")));
this._description = GetMicroFormatByProp(doc, "description", "td");
this._content = GetMicroFormatByProp(doc, "about", "div");
}
private string GetMicroFormatByProp(HtmlDocument doc, string itemprop,
string elementtype, string itemattr)
{
var result =
from el in doc.DocumentNode.Descendants(elementtype)
.Where(x => x.Attributes.Contains("itemprop")
&& x.Attributes["itemprop"].Value.Contains(itemprop))
select el.Attributes[itemattr].Value;
var res = result.FirstOrDefault();
return res;
}
private string GetMicroFormatByProp(HtmlDocument doc, string itemprop, string elementtype)
{
var result =
from el in doc.DocumentNode.Descendants(elementtype)
.Where(x => x.Attributes.Contains("itemprop")
&& x.Attributes["itemprop"].Value.Contains(itemprop))
select el.InnerText;
var res = result.FirstOrDefault();
return res;
}
下一个可能性是传统的 RSS。从网站 URL 获取 RSS feed(链接标头等)有几种相对标准的方法,该应用未来开发的主要内容将是编写各种可能性来从尽可能多的网站提取数据。到目前为止,该应用非常信任地检索每个场馆的主网页,并抓取第一个看起来像事件列表的链接元素。
private async Task<Uri> FindRss(Uri websiteUrl)
{
HtmlDocument doc = await _docGetter.LoadFromWebAsync(websiteUrl.ToString());
var hrefs =
from link in doc.DocumentNode.Descendants("link")
.Where(x => x.Attributes["rel"].Value == "alternate")
.Where(x => x.Attributes["type"].Value == "application/rss+xml")
select link.Attributes["href"].Value;
string href = hrefs.FirstOrDefault();
return new Uri(href, UriKind.RelativeOrAbsolute);
}
SyndicationFeed feeditems = await client.RetrieveFeedAsync(feedurl);
List<EventDataEevent> items = new List<EventDataEevent>();
foreach (var item in feeditems.Items.ToList())
{
items.Add(new EventDataEevent(item));
}
展示我们的发现
Visual Studio 2012 附带了几个项目模板,可以轻松创建新的 Windows 应用商店应用。此应用最初是从 Split App (XAML) C# 示例开始的。从那个起点开始,只需要几个步骤就可以构建一个数据源,利用上述功能,并以熟悉且易于使用的方式显示数据。
上面的代码片段被添加为 EventDataSource
类(从原始的 SampleDataSource
重命名)的方法,并用于填充一个名为 AllVenues
的 ObservableCollection<EventDataVenue>
。
public sealed class EventDataSource
{
public static EventDataSource _eventDataSource = new EventDataSource();
...
public async Task FetchVenuesAsync()
{
var pos = await GetPositionAsync();
var venues = JsonConvert.DeserializeObject<GooglePlaceSearchResult>(
await GoogleRequest("https://maps.googleapis.com/" +
"maps/api/place/search/json?" +
"key=APIKEY&sensor=true" +
"&types=movie_theater|amusement_park|art_gallery" +
"|night_club|stadium&radius=6000&" +
"&location=" +
pos.Coordinate.Latitude.ToString() + "," +
pos.Coordinate.Longitude.ToString()
)
);
foreach (GooglePlaceSearchResult.Result venueDetails in venues.results)
{
var venueFullDetails = await FetchVenueDetailsAsync(venueDetails);
var edv = new EventDataVenue(venueDetails, venueFullDetails.result);
if (edv.EventFeed == null)
{
edv.Items.Add(new EventDataEevent(
venueDetails.name + "-noevent",
"No Events Found", edv));
}
else
{
SyndicationFeed feeditems =
await _client.RetrieveFeedAsync(edv.EventFeed);
foreach (var item in feeditems.Items.ToList())
{
edv.Items.Add(new EventDataEevent(item, edv));
}
}
this.AllVenues.Add(edv);
}
}
private ObservableCollection<EventDataVenue> _allVenues =
new ObservableCollection<EventDataVenue>();
public ObservableCollection<EventDataVenue> AllVenues
{
get { return this._allVenues; }
}
public static IEnumerable<EventDataVenue> GetVenues(string uniqueId)
{
return _eventDataSource.AllVenues;
}
...
public EventDataSource()
{
FetchVenuesAsync();
}
}
然后,在 ItemsPage.xaml.cs 代码中,AllVenues
被调用以提供页面的项目。
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
var venueDatavenues = EventDataSource.GetVenues((String)navigationParameter);
this.DefaultViewModel["Items"] = venueDatavenues;
}
此时,我们已经足够显示应用的第一屏,其中包含场馆列表。值得注意的是,这只是显示了从 Google Places
API 收到的信息。图片是直接从 Google 使用 API 响应中提供的 URL 检索的。
在 SplitPage.xaml.cs 代码中(同样由 Visual Studio 在创建项目时预填充),使用的不是 GetVenues
方法,而是 GetVenue
和 GetItem
方法。但同样,安排您的数据以便能够显示在页面上是一项相当简单的任务。
XAML 设计视图简化了调整模板以适应您数据的任务。经过一些调整,并从场馆网站检索数据后,我们现在拥有了所有需要的信息。
关注点
借助 WinRT 和 Visual Studio 2012,可以轻松地构建一个具有体面外观的可用应用程序。地理定位和始终在线的移动数据连接现在已成为日常生活中熟悉的部分,而微数据模式等技术以及像 Google 这样的公司在其之上构建的数据服务将为轻松访问和普遍可用性提供更多信息。
试一试
如果您想尝试一下,请随时从 github 获取代码并进行构建。如果您模拟位置 54.974227,-1.594066,您将看到纽卡斯尔的场馆列表。如果您选择 The Star And Shadow Cinema,您将获得一个即将举行的活动列表,其中包含从其网站检索到的实时数据和图片。其他一些场馆也将显示一种或另一种数据,但并非总是活动数据。