使用 Ruby on Rails 和 PostGIS 创建一个感知位置的网站
如何使用 Ruby on Rails、PostgreSQL 和 PostGIS 创建一个地理位置感知网站
PostGIS 是 PostgreSQL 的一个地理空间扩展,它提供了一系列函数来处理地理空间数据和查询,例如,查找某个位置附近的兴趣点,或在数据库中存储导航路线。您可以在此处找到 PostGIS 文档。
在此示例中,我将展示如何使用 Ruby on Rails、PostgreSQL 和 PostGIS 创建一个地理位置感知网站。完成后,该应用程序将能够将您的当前位置——或一个“签到”——存储在数据库中,在地图上显示您所有的签到,并显示附近签到。
这个应用程序是用 Rails 3.1 编写的,但也可以用其他版本编写。在撰写本文时,最新版本的 spatial_adapter gem 在 Rails 3.1 中存在一个问题,但我们将为此创建一个变通方法,直到它被修复。
创建启用 PostGIS 的数据库
我们将首先创建支持地理空间功能的数据库。首先,请查看我关于在 Mac OS X 上安装 PostgreSQL 和 PostGIS 的帖子。
创建您的数据库
$ createdb -h localhost my_checkins_development
在您的数据库中安装 PostGIS
$ cd /opt/local/share/postgresql90/contrib/postgis-1.5/
$ psql -d my_checkins_development -f postgis.sql -h localhost
$ psql -d my_checkins_development -f spatial_ref_sys.sql -h localhost
您的数据库现在已准备好进行地理空间查询。
创建支持地理空间功能的 Rails 应用程序
创建您的应用程序
$ rails new my_checkins
spatial_adapter gem 是一个插件,在与 PostgreSQL 和 PostGIS 一起使用时,为 Rails 添加了地理空间功能。它使用 GeoRuby 作为数据类型。将此 gem 和 pg (Postgres) gem 添加到您的Gemfile 中
gem 'spatial_adapter'
gem 'pg'
运行 bundle install
$ bundle install
设置您的config/database.yml
development:
adapter: postgresql
database: my_checkins_development
host: localhost
这样,您的应用程序就具备了地理空间功能。:)
创建处理签到的代码
让我们创建一些脚手架代码来处理我们的签到
$ rails g scaffold checkin title:string location:point
请注意point 数据类型——这是一个地理空间类型。
在运行迁移之前,编辑db/migrate/create_checkins.rb,将此替换为
t.point :location
用这个:
t.point :location, :geographic => true
这告诉您的迁移添加一个地理列,该列设置为处理地理坐标(也称为纬度和经度)。
运行您的迁移
$ rake db:migrate
我们现在可以存储我们的签到了。
Checkin
模型现在包含一个 location
字段,其数据类型为 GeoRuby::SimpleFeatures::Point
。此数据类型具有 x
和 y
属性。我们将直接在模型上公开这些属性。在app/models/checkin.rb 中
class Checkin < ActiveRecord::Base
def latitude
(self.location ||= Point.new).y
end
def latitude=(value)
(self.location ||= Point.new).y = value
end
def longitude
(self.location ||= Point.new).x
end
def longitude=(value)
(self.location ||= Point.new).x = value
end
end
现在公开了纬度和经度。
在app/views/checkins/_form.html.erb 中,将此替换为
<div class="field">
<%= f.label :location %><br />
<%= f.text_field :location %>
</div>
用这个
<div class="field">
<%= f.label :latitude %><br />
<%= f.text_field :latitude %>
</div>
<div class="field">
<%= f.label :longitude %><br />
<%= f.text_field :longitude %>
</div>
如果不是因为 spatial_adapter
在 Rails 3.1 下存在一个小bug,我们现在就可以从我们的 Rails 应用程序保存位置。然而,这个 bug 的作用是,当 location
字段被设置时,它无法创建记录。它可以更新记录,所以我们将确保它首先创建一个 location 字段为 nil 的签到,然后用正确的位置更新它。像这样,在app/controllers/checkins_controller.rb 的 create
方法中,将此替换为
def create
...
if @checkin.save
...
用这个:
def create
...
if @checkin.valid?
location = @checkin.location
@checkin.location = nil
@checkin.save!
@checkin.location = location
@checkin.save!
...
这样就可以正常工作了。
尝试启动您的服务器
$ rails s
然后在您的浏览器中访问https://:3000/checkins/new。
接下来,在app/views/checkins/show.html.erb 中,将此替换为
<p>
<b>Location:</b>
<%= @checkin.location %>
</p>
用这个:
<p>
<b>Location:</b>
<%= @checkin.latitude %>, <%= @checkin.longitude %>
</p>
它将显示您刚输入的 latitude
和 longitude
。
获取我们的当前位置
我们希望能够从当前位置创建签到。现代浏览器通过 JavaScript API 公开此功能。创建app/assets/javascripts/checkins.js 并添加此内容
function findMe() {
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
document.getElementById('checkin_latitude').value = position.coords.latitude;
document.getElementById('checkin_longitude').value = position.coords.longitude;
}, function() {
alert('We couldn\'t find your position.');
});
} else {
alert('Your browser doesn\'t support geolocation.');
}
}
以及在app/views/checkins/_form.html.erb 顶部的一个按钮
<input type="button" value="Find me!" onclick="findMe();" />
在您的浏览器中尝试一下。如果出现 JavaScript 错误,提示 findMe
方法未定义,请尝试重新启动服务器以加载新的 JavaScript。现在,您应该可以通过单击Find me! 按钮来获取当前位置。
查找附近的签到
让我们创建一个查找附近签到的方法。PostGIS 有一个名为 ST_DWithin
的函数,如果两个位置相距一定距离,则返回 true
。在app/models/checkin.rb 中,将以下内容添加到类的顶部
class Checkin < ActiveRecord::Base
scope :nearby_to,
lambda { |location, max_distance|
where("ST_DWithin(location, ?, ?) AND id != ?", checkin.location, max_distance, checkin.id)
}
...
在app/controllers/checkins_controller.rb 中,添加以下内容
def show
@checkin = Checkin.find(params[:id])
@nearby_checkins = Checkin.nearby_to(@checkin, 1000)
...
在app/views/checkins/show.html.erb 中,在底部的链接之前添加以下内容
<h2>Nearby check-ins</h2>
<ul>
<% @nearby_checkins.each do |checkin| %>
<li><%= link_to checkin.title, checkin %></li>
<% end %>
</ul>
它现在会显示所有附近的签到。尝试根据您当前的位置添加更多签到,看看它的实际效果。
创建包含所有签到的地图
将我们所有的签到显示在地图上不是很好吗?我们将使用 Google Maps API 来实现这一点。
在app/views/checkins/index.html.erb 中,清除表格和列表,并添加以下内容
<script type="text/javascript"
src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
这将加载 Google Maps JavaScript API 功能。
创建一个用于地图的 div
<div id="map" style="width: 600px; height: 500px;"></div>
并在底部添加以下 script
<script type="text/javascript">
// Create the map
var map = new google.maps.Map(document.getElementById("map"), {
mapTypeId: google.maps.MapTypeId.ROADMAP
});
// Initialize the bounds container
var bounds = new google.maps.LatLngBounds();
<% @checkins.each do |checkin| %>
// Create the LatLng
var latLng = new google.maps.LatLng(<%= checkin.latitude %>, <%= checkin.longitude %>);
// Create the marker
var marker = new google.maps.Marker({
position: latLng,
map: map,
title: '<%= escape_javascript(checkin.title) %>'
});
// Add click event
google.maps.event.addListener(marker, 'click', function() {
document.location = '<%= checkin_path(checkin) %>';
});
// Extend the bounds
bounds.extend(latLng);
<% end %>
// Fit to bounds
map.fitBounds(bounds);
</script>
这就是我们的地图。: ) 在 https://:3000/checkins 查看它。尝试在世界各地创建一些签到,看看地图如何扩展。
结论
这就是一个地理位置感知应用程序,它可以根据我们的当前位置存储签到,显示附近的签到,并在地图上显示签到。
我很想听听您的想法!