2.5K Views
November 30, 23
スライド概要
FOSS4G Asia 2023 Seoul
Download materials: https://github.com/smellman/foss4g-asia-2023-rgeo
Schedule: https://talks.osgeo.org/foss4g-asia-2023/talk/9WU8PR/
Georepublic / OSGeo.JP / Japan Unix Society / OpenStreetMap Foundation Japan
RGeo: Handling Geospatial Data for Ruby and Ruby on Rails Taro Matsuzawa (@smellman) Georepublic Japan FOSS4G Asia 2023 Seoul 1
This presentation available at online. https://smellman.github.io/foss4g-asia-2023-rgeo/ FOSS4G Asia 2023 Seoul 2
My works Georepublic Japan GIS Engineer Sub-President, Japan UNIX Society Director, OSGeo.JP Director, OpenStreetMap Foundation Japan Lead of United Nation OpenGIS/7 core FOSS4G Asia 2023 Seoul 3
My hobbies Breakcore music Playing video games JRPGs, 2D shooting games, etc. Reading novels Fantasy FOSS4G Asia 2023 Seoul 4
My skills Ruby / Ruby on Rails Python PostgreSQL / PostGIS JavaScript / TypeScript React Native MapLibre GL JS AWS CDK with TypeScript UNIX / Linux FOSS4G Asia 2023 Seoul 5
My community works Maintainer of tile.openstreetmap.jp A worldwide tile server for OpenStreetMap Japan community. Maintainer of charites A set of tools for building Mapbox/MapLibre style. Maintainer of redmine-gtt A plugin for Redmine to add spatial features. Contributor of types-ol-ext, redux-persist and more. FOSS4G Asia 2023 Seoul 6
Main topics 1. Ruby language is very powerful and simple, and friendly with Geo Data using RGeo. 2. RGeo is firendly with Ruby on Rails and PostGIS. FOSS4G Asia 2023 Seoul 7
Today's talk 1. Core concepts and data models of RGeo 2. Basics of manipulating and querying geospatial data 3. Integration with Ruby on Rails and real-world application examples using RGeo FOSS4G Asia 2023 Seoul 8
1. Core concepts and data models of RGeo FOSS4G Asia 2023 Seoul 9
What is RGeo? RGeo is a geospatial data library for Ruby. RGeo provides a set of data classes for representing geospatial data. RGeo provides a set of spatial analysis operations and predicates. FOSS4G Asia 2023 Seoul 10
RGeo's implementation RGeo is a pure Ruby library if GEOS is not available. RGeo is a Ruby wrapper of GEOS if GEOS is available. GEOS is a C++ library for manipulating and querying geospatial data. GEOS is a part of OSGeo Foundation. GEOS is used by many geospatial software, such as PostGIS, QGIS, etc. FOSS4G Asia 2023 Seoul 11
RGeo's features RGeo suppports many geospatial data formats. WKT, WKB, GeoJSON, Shapefile, etc. RGeo supports Proj4. RGeo supports many spatial analysis operations and predicates. Buffer, Convex Hull, Intersection, Union, etc. FOSS4G Asia 2023 Seoul 12
RGeo requirements MRI Ruby 2.6.0 or later. Partial support for JRuby 9.0 or later. The FFI implementation of GEOS is available (ffigeos gem required) but CAPI is not. Highly recommended to use MRI Ruby and GEOS CAPI. FOSS4G Asia 2023 Seoul 13
CAPI benchmark CAPI is faster than FFI and pure ruby. ❯ bundle exec ruby benchmark.rb Warming up -------------------------------------with CAPI GEOS 188.859k i/100ms with FFI GEOS 84.720k i/100ms simple ruby 155.000 i/100ms Calculating ------------------------------------with CAPI GEOS 1.967M (± 1.3%) i/s with FFI GEOS 860.127k (± 1.1%) i/s simple ruby 1.579k (± 0.9%) i/s Comparison: with CAPI GEOS: with FFI GEOS: simple ruby: FOSS4G Asia 2023 Seoul 10.010M in 4.321M in 7.905k in 5.089275s 5.023924s 5.008210s 1967118.2 i/s 860127.1 i/s - 2.29x slower 1578.5 i/s - 1246.17x slower 14
How to install (Debian/Ubuntu) $ apt install libgeos-dev libproj-dev proj-data Then $ gem install rgeo or insert the following line into your Gemfile. gem 'rgeo' FOSS4G Asia 2023 Seoul 15
RGeo extensions rgeo-geojson GeoJSON format support rgeo-shapefile Shapefile format support rgeo-proj4 Proj4 support FOSS4G Asia 2023 Seoul 16
Ruby on Rails support RGeo provides ActiveRecord extensions for Ruby on Rails with PostGIS. https://github.com/rgeo/activerecord-postgis-adapter Mysql / Spatialite ActiveRecord adapter is archived or not maintained. FOSS4G Asia 2023 Seoul 17
RGeo's data models RGeo supports OGC Simple Features Specification. RGeo provides a set of data classes for representing geospatial data. Coordinates, Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection, etc. FOSS4G Asia 2023 Seoul 18
Coordinates basis Coordinates is a set of X, Y(, Z and M) values. require factory point = point.x point.y 'rgeo' = RGeo::Cartesian.factory factory.point(1, 2) # => 1.0 # => 2.0 FOSS4G Asia 2023 Seoul 19
factories RGeo implements a lot of factories. Cartesian, Geographic, Geographic Projected, Spherical, etc. Cartesian factory is the default factory. FOSS4G Asia 2023 Seoul 20
Cartesian factory require factory point = point.x point.y 'rgeo' = RGeo::Cartesian.factory factory.point(1, 2) # => 1.0 # => 2.0 implementation will be GEOS::CAPIFactory if GEOS is available. FOSS4G Asia 2023 Seoul 21
Ruby Cartesian factory require factory point = point.x point.y 'rgeo' = RGeo::Cartesian.simple_factory factory.point(1, 2) # => 1.0 # => 2.0 Create a 2D Cartesian factory using a Ruby implementation. FOSS4G Asia 2023 Seoul 22
Spherical Factory require factory point = point.x point.y 'rgeo' = RGeo::Geographic.spherical_factory factory.point(1, 2) # => 1.0 # => 2.0 Create a factory that uses a spherical model of Earth when creating and analyzing geometries. FOSS4G Asia 2023 Seoul 23
3D factory require factory point = point.x point.y point.z 'rgeo' = RGeo::Geos.factory(has_z_coordinate: true) factory.point(1, 2, 3) # => 1.0 # => 2.0 # => 3.0 Create a 3D factory using GEOS. FOSS4G Asia 2023 Seoul 24
3D Factory (With M-Coordinate) require 'rgeo' factory = RGeo::Geos.factory(has_z_coordinate: true, has_m_coordinate: true) Create a 3D factory with M-Coordinate using GEOS. FOSS4G Asia 2023 Seoul 25
Specify an SRID require 'rgeo' factory = RGeo::Geos.factory(srid: 4326) point = factory.point(139.766865, 35.680760) # Tokyo station Create a factory with SRID 4326 using GEOS. FOSS4G Asia 2023 Seoul 26
Point require factory point = point.x point.y 'rgeo' = RGeo::Geos.factory(srid: 4326) factory.point(139.766865, 35.680760) # Tokyo station # => 139.766865 # => 35.68076 Create a point from coordinates. FOSS4G Asia 2023 Seoul 27
working with WKT require 'rgeo' factory = RGeo::Geos.factory(srid: 4326) wkt = 'POINT(139.766865 35.680760)' point = factory.parse_wkt(wkt) Create a point from WKT. FOSS4G Asia 2023 Seoul 28
LineString require 'rgeo' factory = RGeo::Geos.factory line_string = factory.line_string([ factory.point(1, 2), factory.point(3, 4), factory.point(5, 6), ]) Create a LineString from points. FOSS4G Asia 2023 Seoul 29
Others LinerRing Polygon GeometryCollection MultiPoint MultiLineString MultiPolygon All features supports WKT. see: https://github.com/rgeo/rgeo/blob/main/doc/Examples.md FOSS4G Asia 2023 Seoul 30
Basics of manipulating and querying geospatial data FOSS4G Asia 2023 Seoul 31
Spatial analysis operations unary predicates. ccw? empty? simple? binary predicates. contains? crosses? disjoint? crosses? intersects? overlaps? FOSS4G Asia 2023 Seoul 32
contains? require require require require "open-uri" "json" "rgeo" "rgeo-geojson" my_lat, my_lng = [45, 5] my_position = RGeo::Cartesian. factory. point(my_lng, my_lat) geojson = URI. open("https://git.io/rhone-alpes.geojson"). read rhone_alpes = RGeo::GeoJSON.decode(geojson).geometry if rhone_alpes.contains?(my_position) " puts "Let's ski end FOSS4G Asia 2023 Seoul 33
intersects? require require require require "open-uri" "json" "rgeo" "rgeo-geojson" # FeatureCollection line1 = 'https://raw.githubusercontent.com/smellman/foss4g-asia-2023-rgeo/main/data/line1.geojson' line2 = 'https://raw.githubusercontent.com/smellman/foss4g-asia-2023-rgeo/main/data/line2.geojson' geojson1 = URI.open(line1).read geojson2 = URI.open(line2).read geometry1 = RGeo::GeoJSON.decode(geojson1)[0].geometry geometry2 = RGeo::GeoJSON.decode(geojson2)[0].geometry p geometry1.intersects?(geometry2) # => true FOSS4G Asia 2023 Seoul 34
Analysis operations distance buffer envelope convex_hull intersection union unary_union difference sym_difference FOSS4G Asia 2023 Seoul 35
distance require require require require "open-uri" "json" "rgeo" "rgeo-geojson" lng, lat = [139.764786, 35.677724] tokyo_station = RGeo::Cartesian. factory. point(lng, lat) url = "https://raw.githubusercontent.com/smellman/foss4g-asia-2023-rgeo/main/data/hotels.geojson" geojson = URI.open(url).read hotels = RGeo::GeoJSON.decode(geojson) nearest_hotel = hotels.min_by do |hotel| hotel.geometry.distance(tokyo_station) end puts nearest_hotel.properties["name:en"] # => "Tokyo Station Hotel" farthest_hotel = hotels.max_by do |hotel| hotel.geometry.distance(tokyo_station) end puts farthest_hotel.properties["name"] # => "Appt Ikebukuro" FOSS4G Asia 2023 Seoul 36
Integration with Ruby on Rails and real-world application examples using RGeo FOSS4G Asia 2023 Seoul 37
Ruby on Rails support RGeo provides ActiveRecord extensions for Ruby on Rails with PostGIS. activerecord-postgis-adapter ActiveRecord is powerful and simple to use. And RGeo is friendly with ActiveRecord. FOSS4G Asia 2023 Seoul 38
Overview FOSS4G Asia 2023 Seoul 39
1. Create a new Rails application rails new myapp --api -d postgresql FOSS4G Asia 2023 Seoul 40
2. Add activerecord-postgis-adapter to Gemfile gem 'rgeo' gem 'rgeo-geojson' gem 'activerecord-postgis-adapter' FOSS4G Asia 2023 Seoul 41
3. Setup database --- a/myapp/config/database.yml +++ b/myapp/config/database.yml @@ -15,7 +15,7 @@ # gem "pg" # default: &default - adapter: postgresql + adapter: postgis encoding: unicode # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling FOSS4G Asia 2023 Seoul 42
4. Create database rails db:create FOSS4G Asia 2023 Seoul 43
5. Create a migration file to enable PostGIS extension rails g migration AddPostgisExtensionToDatabase class AddPostgisExtensionToDatabase < ActiveRecord::Migration[7.0] def change enable_extension 'postgis' end end FOSS4G Asia 2023 Seoul 44
6. Create a model/migration/controller via scaffold rails g scaffold toilet FOSS4G Asia 2023 Seoul 45
7. Edit migration file --- a/myapp/db/migrate/20231120235529_create_toilets.rb +++ b/myapp/db/migrate/20231120235529_create_toilets.rb @@ -1,6 +1,8 @@ class CreateToilets < ActiveRecord::Migration[7.0] def change create_table :toilets do |t| + t.string :name + t.st_point :location, geographic: true t.timestamps end FOSS4G Asia 2023 Seoul 46
8. Run migration rails db:migrate FOSS4G Asia 2023 Seoul 47
9. Prepare seeds Download data from Overpass Turbo. node [amenity=toilets] ({{bbox}}); out; Export as GeoJSON. FOSS4G Asia 2023 Seoul 48
10. Put the GeoJSON file into db/seed_data directory. - Put the GeoJSON file into `db/seed_data` directory. ```sh mkdir db/seed_data mv ~/Downloads/export.geojson db/seed_data/toilets.geojson FOSS4G Asia 2023 Seoul 49
11. Edit db/seeds.rb def seed_toilets Rails.logger.info 'Seed toilets' toilets_geojson = File.read('db/seed_data/toilets.geojson') toilets = RGeo::GeoJSON.decode(toilets_geojson) toilets.each do |toilet| name = toilet.properties['name'] ? toilet.properties['name'] : 'no name' Toilet.create( name: name, location: toilet.geometry ) end end seed_toilets FOSS4G Asia 2023 Seoul 50
12. Run db:seed rails db:seed Check the database. ❯ rails r "p Toilet.count" 662 FOSS4G Asia 2023 Seoul 51
13. Edit app/models/toilet.rb
class Toilet < ApplicationRecord
def as_geojson
{
type: "Feature",
geometry: RGeo::GeoJSON.encode(self.location),
properties: self.attributes.except("location")
}
end
def as_json(options = {})
as_geojson
end
end
FOSS4G Asia 2023 Seoul
52
14. Edit app/controllers/toilets_controller.rb def index @toilets = Toilet.all geojson = { type: "FeatureCollection", features: @toilets.map(&:as_json) } render json: geojson end FOSS4G Asia 2023 Seoul 53
Check output
curl "http://127.0.0.1:3000/toilets.json" | jq .|head -n 20
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
139.7978412,
35.6785662
]
},
"properties": {
"id": 1,
"name": "no name",
"created_at": "2023-11-21T00:18:37.776Z",
"updated_at": "2023-11-21T00:18:37.776Z"
}
},
{
FOSS4G Asia 2023 Seoul
54
as_json
called in json: render by default.
❯ curl "http://127.0.0.1:3000/toilets/1.json" | jq .
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
139.7978412,
35.6785662
]
},
"properties": {
"id": 1,
"name": "no name",
"created_at": "2023-11-21T00:18:37.776Z",
"updated_at": "2023-11-21T00:18:37.776Z"
}
}
FOSS4G Asia 2023 Seoul
55
15. Supports GeoJSON output Create config/initalizers/mime_types.rb Mime::Type.register 'application/vnd.geo+json', :geojson FOSS4G Asia 2023 Seoul 56
16. Fix config/routes.rb Default format to geojson . Rails.application.routes.draw do resources :toilets, defaults: { format: 'geojson' } end FOSS4G Asia 2023 Seoul 57
Check output
curl "http://127.0.0.1:3000/toilets/1.geojson" | jq .
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
139.7978412,
35.6785662
]
},
"properties": {
"id": 1,
"name": "no name",
"created_at": "2023-11-21T00:18:37.776Z",
"updated_at": "2023-11-21T00:18:37.776Z"
}
}
FOSS4G Asia 2023 Seoul
58
FOSS4G Asia 2023 Seoul 59
17. Define scope for spatial query in
app/models/toilet.rb
scope :distance_sphere, lambda { |longitude, latitude, meter|
where("ST_DWithin(toilets.location, ST_GeomFromText('POINT(:longitude :latitude)', 4326), :meter)",
{ longitude: longitude, latitude: latitude, meter: meter })
}
FOSS4G Asia 2023 Seoul
60
18. Use scope in app/controllers/toilets_controller.rb def index if params[:longitude] && params[:latitude] && params[:radius] @toilets = Toilet.distance_sphere( params[:longitude].to_f, params[:latitude].to_f, params[:radius].to_i ) else @toilets = Toilet.all end geojson = { type: "FeatureCollection", features: @toilets.map(&:as_json) } render json: geojson end FOSS4G Asia 2023 Seoul 61
Check output # without params ❯ curl "http://localhost:3000/toilets.json" | jq '.features | length' 662 # with params ❯ curl "http://localhost:3000/toilets.json?latitude=35.677724&longitude=139.76478f6&radius=1000" | jq '.features | length' 31 FOSS4G Asia 2023 Seoul 62
TODO for this application: Add cors support. Add a map to the frontend. Add routing function using pgRouting. FOSS4G Asia 2023 Seoul 63
Thank you! FOSS4G Asia 2023 Seoul 64