65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Ruby on Rails 和 PostGIS 创建一个感知位置的网站

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2012年1月9日

CPOL

4分钟阅读

viewsIcon

27803

如何使用 Ruby on Rails、PostgreSQL 和 PostGIS 创建一个地理位置感知网站

PostGISPostgreSQL 的一个地理空间扩展,它提供了一系列函数来处理地理空间数据和查询,例如,查找某个位置附近的兴趣点,或在数据库中存储导航路线。您可以在此处找到 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。此数据类型具有 xy 属性。我们将直接在模型上公开这些属性。在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.rbcreate 方法中,将此替换为

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>

它将显示您刚输入的 latitudelongitude

获取我们的当前位置

我们希望能够从当前位置创建签到。现代浏览器通过 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 查看它。尝试在世界各地创建一些签到,看看地图如何扩展。

结论

这就是一个地理位置感知应用程序,它可以根据我们的当前位置存储签到,显示附近的签到,并在地图上显示签到。

查看完整的源代码查看最终应用程序的实际运行效果

我很想听听您的想法!

写评论在 Twitter 上关注我订阅我的 RSS 源

© . All rights reserved.