SlideShare a Scribd company logo
+


OPEN GEOSPATIAL
 RUBY LIBRARIES
  Christopher Helm - GeoIQ
   http://geocommons.com
The Plan
• Keep   it simple

• Introduce     you to geospatial

• Dive   into a few example libs

• Demo     some code!
THIS TALK...




...is for (~Ruby) geospatial newcomers
STAYING SIMPLE
  GeoSpatial is a deep subject

we’re barely skimming the surface
WHAT IS GEOSPATIAL?
LOCATION
DATA
ANALYSIS
VISUALIZATION
WHY DOES THIS MATTER?



     location is everywhere :)
GEOGRAPHY LESSON 1:
         Vector Data Types




Points       Lines           Polygons
GEOGRAPHY LESSON 1:
           OGC’s Simple Features


              POINT(30, 40)

     LINESTRING(30 10, 10 30, 40 40)

POLYGON(30 10, 10 20, 20 40, 40 40, 30 10)

                 MULTI*
GEOGRAPHY LESSON 2:
     Spatial Relationships

         Buffers
      Intersections
          Clips
        Dissolves
         Unions
       Aggregation
          etc...
GEOGRAPHY LESSON 2:
     Spatial Relationships




     Buffers
GEOGRAPHY LESSON 2:
     Spatial Relationships




          Dissolves
GEOGRAPHY LESSON 2:
         Spatial Relationships




 Clips                    Intersections
GEOGRAPHY LESSON 2:
     Spatial Relationships




    Spatial Aggregation
GEOGRAPHY LESSON 3:
    Spatial Reference Systems




                   http://www.cartogrammar.com/blog/accidental-map-projections/
GEOGRAPHY LESSON 3:
     Spatial Reference Systems




  Plate Carrée, Geographic, or un-projected
GEOGRAPHY LESSON 3:
    Spatial Reference Systems




      Spherical Mercator (Web Mercator)
SOME GEOSPATIAL LIBS


•2   categories of libraries

 • 1. Ruby   based GeoSpatial Data Libraries

 • 2. Ruby   bindings location-aware APIs & services
GEOS
             http://trac.osgeo.org/geos/




Geometry Engine - Open Source (GEOS)

 A C++ port of the Java Topology Suite

  Simple Features and spatial operators
GEOS
require 'geos'

@reader = Geos::WktReader.new()

# Create points
p1 = @reader.read(‘POINT(1, 2)’)
p2 = @reader.read(‘POINT(3, 4)’)
p3 = @reader.read(‘POINT(5, 2)’)




                             p2

       y
                 p1                p3



                             x
GEOS
# Create a more complex shapes
line1 = @reader.read(‘LINESTRING(1 2, 3 4, 5 2)’)
polygon = @reader.read(‘POLYGON(1 2, 3 4, 5 2, 1 2)’)

line2 = @reader.read('LINESTRING(1 4, 4 1)')




             line1                                Polygon




                             line2
GEOS
# Perform geometric operations and calculations
line3 = polygon.intersection(line2)
dist = line3.distance(p3)




         line2
                                                  dist
                                        line3            p3


         line3
GEOS
# new point
p4 = @reader.read(‘POINT(2.5, 3.5)’)

# create a buffer on p4
buffer = p4.buffer(2)

# now test against the buffer
buffer.contains?(p1) # false
buffer.contains?(p2) # true
buffer.touches?(p3.buffer(1)) # true



                             p2

                                  p4

                 p1                    p3
                              buffer
GEORUBY
                 http://rubyforge.org/projects/georuby/


A Library for working with geometric data types in Ruby

       follows the OGC’s Simple Features Spec

            Think: Point, Lines, and Polygons

            Also data formatters / parsers
                      (KML to Well Known Text)
SPATIAL ADAPTER
      https://github.com/fragility/spatial_adapterxt




      Adapter for ActiveRecord

Handles spatial datatypes coming from:

    PostGreSQL (via PostGIS)
              &
    MySQL (via its spatial ext.)
RGEO
          http://virtuoso.rubyforge.org/rgeo/




     Geospatial datatypes in Ruby

  Standard Spatial analysis operations:
            (buffers, intersections, distances, etc)




       Data format conversions

Flexible - gem and active-record adapter
RUBY GEOCODER
                  http://www.rubygeocoder.com/



        Full featured geocoder for Ruby

                Gem and Rails Plugin

Converts addresses and IPs in lat/lon coordinates

     configurable to work with any service


         Also checkout geokit: http://geokit.rubyforge.org/
RUBY GEOCODER
                       http://www.rubygeocoder.com/


require ‘geocoder’

# look up coordinates of some location
Geocoder.coordinates("25 Main St, Cooperstown, NY")
=> [42.700149, -74.922767]

# distance between Eiffel Tower and Empire State Building
Geocoder::Calculations.distance_between([47.858205,2.294359],
[40.748433,-73.985655])
=> 3619.77359999382

# find the geographic coordinates for an IP address
Geocoder.coordinates("71.166.236.36")
=> [38.6485, -77.3108]
DATABASES

      PostgreSQL / Postgis
              MySQL
            MongoDB
Couch...DB....base (via GeoCouch)
POSTGRESQL

   PostGIS - spatial extension

Uses GEOS for spatial operations
ACTIVERECORD POSTGIS
               https://github.com/dazuma/activerecord-postgis-adapter


create_table :my_spatial_table do |t|
  t.column :shape, :geometry # or t.geometry :shape
  t.line_string :path, :srid => 3785
  t.point :latlon, :geographic => true
end

change_table :my_spatial_table do |t|
  t.index :latlon, :spatial => true
end

# querying
rec = MySpatialTable.where(:latlon => 'POINT(-122 47)').first
MONGODB


require 'mongo'

db = Mongo::Connection.new("localhost", 27017).db("mydb")
auth = db.authenticate(my_user_name, my_password)
coll = db.collection("testCollection")
coll.find("i" => 71).each { |row| puts row.inspect }
MONGODB

# Spatial indexes / queries

coll.create_index([["loc", Mongo::GEO2D]])

coll.find({"loc" => {"$near" => [50, 50]}}, {:limit => 20}).each
do |p|
  puts p.inspect
end
MONGODB


box = [[40.73083, -73.99756], [40.741404, -73.988135]]
coll.find({"loc" : {"$within" : {"$box" : box}}})

center = [50, 50]
radius = 10
coll.find({"loc" : {"$within" : {"$center" : [center, radius]}}})
SOCIAL MEDIA APIS
TWEETSTREAM
            https://github.com/intridea/tweetstream



Simple wrapper to the Twitter Streaming API

 Can search via tags or locations (via bboxes)

Good way to generate geospatial data/content
TWEETSTREAM
require 'rubygems'
require 'tweetstream'

# Use 'track' to track a list of single-word keywords
TweetStream::Client.new('username','password').track('term1',
'term2') do |status|
  puts "#{status.text}"
end

# track tweets within a bbox
TweetStream::Client.new('username','password').locations(‘-122.75,
36.8,-121.75,37.8’) do |status|
  puts "#{status.text}"
end
INSTAGRAM
         https://github.com/Instagram/instagram-ruby-gem




Wrapper to the Instragram’s photo sharing API

    Allows for spatial searching via lat/lon

        very similar to the Flick’r API
CARTAGR.AM
    bloom.io
INSTAGRAM
require "instagram"


# All methods require authentication
Instagram.configure do |config|
  config.client_id = YOUR_CLIENT_KEY
  config.access_token = YOUR_ACCESS_TOKEN
end

# Get a list of media at a given location
puts Instagram.location_recent_media(514276)


# Get a list of media close to a given latitude and longitude
puts Instagram.media_search("37.7808851,-122.3948632")
YELP!
                     http://github.com/shaper/yelp



      Location based lookup for business reviews

Highly local listing accessed in variety of geospatial ways

 another way to create some geospatial data/content
YELP!
YELP!
# create a new client
 client = Yelp::Client.new

# do an address-based search for cream puffs nearby
request = Yelp::Review::Request::Location.new(
             :address => '650 Mission St',
             :city => 'San Francisco',
             :state => 'CA',
             :radius => 2,
             :term => 'cream puffs',
             :yws_id => 'YOUR_YWSID_HERE')

response = client.search(request)
YELP!
# a location-based search for ice cream or donut shops in SF
request = Yelp::Review::Request::Location.new(
            :city => 'San Francisco',
            :state => 'CA',
            :category => [ 'donuts', 'icecream' ],
            :yws_id => 'YOUR_YWSID_HERE')
response = client.search(request)

# a neighborhood name lookup for a geo-location point
request = Yelp::Neighborhood::Request::GeoPoint.new(
            :latitude => 37.782093,
            :longitude => -122.483230,
            :yws_id => 'YOUR_YWSID_HERE')
response = client.search(request)
DATA & ANALYSIS APIS



             Fusion Tables
SIMPLEGEO
             https://github.com/simplegeo/simplegeo-ruby




   API for adding geospatial capabilities to apps

Access POIs, Locational Context, and personal data

    Emphasis on speed, simplicity, and flexibility
SIMPLEGEO
                          Context API

# grab the context of a lat/lon
SimpleGeo::Client.get_context(37.772445,-122.405913)

# context
SimpleGeo::Client.get_context_by_address("41 Decatur St, San
Francisco, CA 94103")

SimpleGeo::Client.get_context_ip("184.84.228.110")
SIMPLEGEO
                           Places API

SimpleGeo::Client.get_places(37.772445, -122.405913)

options = {'q'=>'Starbucks', 'category'=>'Coffee & Tea',
'radius'=>5}
SimpleGeo::Client.get_places(37.772445, -122.405913, options)

address = "41 Decatur St, San Francisco, CA"
options = {'radius' => 1}
SimpleGeo::Client.get_places_by_address(address, options)

ip = '173.164.219.53'
SimpleGeo::Client.get_places_by_ip(ip)
FUSION TABLES
         https://github.com/tokumine/fusion_tables




Direct access to Google’s Fusion Tables API

Full SQL interface or OO query interface

Easy integration to other Google services
                     (maps, earth, viz.)


  Easy storage/retrieve of geospatial data
FUSION TABLES

require 'fusion_tables'


# Connect to service
@ft = GData::Client::FusionTables.new
@ft.clientlogin(username, password)


# 1. SQL interface
@ft.execute "SHOW TABLES"
@ft.execute "INSERT INTO #{my_table_id} (name, geo) VALUES
('tokyo', '35.6894 139.6917');"
@ft.execute "SELECT count() FROM #{my_table_id};"
GEOCOMMONS
               http://geocommons.com




Open Data Storage, Analysis, and Visualization

        Strong emphasis on usability

    Build complex geospatial workflows

    Share data visualizations on the web
GEOCOMMONS
 CSV
       Easy Spatial Visualization / Analytics
 SHP
JSON
WMS
                                                Maps & Viz




                     Analytics
GEOCOMMONS GEM
  API
  GeoCommons
     Mgmt            Analysis

        Visualization



            geoiq-
             ruby
GEOCOMMONS GEM
                             finding data...




# create a client
@geoiq = Geoiq.client("http://geocommons.com", "user", "password")

# search for tags
@geoiq.search("tag:economics")

# Search for data in a location
@geoiq.search("points",{:bbox => ‘-87.2,33.4,-75.9,38.4', :limit=>
10})
GEOCOMMONS GEM
                             getting data...



# get dataset and features
dataset = geoiq.dataset.find(1348)
features = dataset.features

# get features with a custom filter
filtered_features = @dataset.features({"filter[PERIMETER][][max]"
=> 0.3})
bbox_filtered_features = @dataset.features({:bbox =>
'-87.2,33.4,-75.9,38.4'})
GEOCOMMONS
                            uploading data...

# Upload CSV
csv_file = 'data/sculptures.csv'
@dataset = @geoiq.dataset.csv_upload(csv_file, {:title =>"garden
sculptures"})


# Upload SHP
@dataset = @geoiq.dataset.shp_upload("data/simple.shp", "data/
simple.dbf", "data/simple.shx")


# Upload by URL (type = csv,kml,rss,wms,tile)
@dataset = @geoiq.dataset.url_upload("http://geothings.net/geoiq/
simple_testing.csv", {:type => "csv"})
GEOCOMMONS
                          create new datasets...



# Create a new overlay
data = {‘title’ => ‘My Overlay’, ‘attributes’ => {‘attr1’ => ...}}
overlay = @geoiq.dataset.create(data)


# add features to the overlay
features = {features => [{‘type’ => ‘Point’, ‘coordinates’ =>
[0,0]},{...}]}
@geoiq.dataset.update(id, features)
GEOCOMMONS
                           create new visualizations...

# Create a new map
map = @geoiq.map(:title => "my empty map")
@geoiq_map = map.create

# get a map and it's layers
@map = @geoiq.map.find(239)
layer_titles = @map.layers.map{ |l| l['title']}

# add a dataset to a map as a new layer
@map.add_layer(1466)

#deleting map or dataset
@map.delete
@dataset.delete
PUTTING IT TOGETHER
SOME EXAMPLES...

...THEN QUESTIONS
Code Samples from this talk:

https://github.com/chelm/OSCON-Ruby-GeoSpatial-Intro

More Related Content

What's hot

Durian: a PHP 5.5 microframework with generator-style middleware
Durian: a PHP 5.5 microframework with generator-style middlewareDurian: a PHP 5.5 microframework with generator-style middleware
Durian: a PHP 5.5 microframework with generator-style middleware
Kuan Yen Heng
 
Git for beginners
Git for beginnersGit for beginners
Git for beginners
Vinh Nguyen
 
SQLite Techniques
SQLite TechniquesSQLite Techniques
SQLite Techniques
Ben Scheirman
 
ES6: Features + Rails
ES6: Features + RailsES6: Features + Rails
ES6: Features + Rails
Santosh Wadghule
 
Camping
CampingCamping
Tres Gemas De Ruby
Tres Gemas De RubyTres Gemas De Ruby
Tres Gemas De Ruby
Leonardo Soto
 

What's hot (6)

Durian: a PHP 5.5 microframework with generator-style middleware
Durian: a PHP 5.5 microframework with generator-style middlewareDurian: a PHP 5.5 microframework with generator-style middleware
Durian: a PHP 5.5 microframework with generator-style middleware
 
Git for beginners
Git for beginnersGit for beginners
Git for beginners
 
SQLite Techniques
SQLite TechniquesSQLite Techniques
SQLite Techniques
 
ES6: Features + Rails
ES6: Features + RailsES6: Features + Rails
ES6: Features + Rails
 
Camping
CampingCamping
Camping
 
Tres Gemas De Ruby
Tres Gemas De RubyTres Gemas De Ruby
Tres Gemas De Ruby
 

Viewers also liked

Romero 2010 Euro Cat Cscl Workshop Barcelona
Romero 2010 Euro Cat Cscl Workshop BarcelonaRomero 2010 Euro Cat Cscl Workshop Barcelona
Romero 2010 Euro Cat Cscl Workshop Barcelona
EuroCAT CSCL
 
Cc&Ab&Ae Euro Cat Cscl Def
Cc&Ab&Ae Euro Cat Cscl DefCc&Ab&Ae Euro Cat Cscl Def
Cc&Ab&Ae Euro Cat Cscl Def
EuroCAT CSCL
 
Developing applications using Embedded Rich Client Platform (eRCP)
Developing applications using Embedded Rich Client Platform (eRCP)Developing applications using Embedded Rich Client Platform (eRCP)
Developing applications using Embedded Rich Client Platform (eRCP)
Gorkem Ercan
 
Development With eRCP
Development With eRCPDevelopment With eRCP
Development With eRCP
Gorkem Ercan
 
The More Capable Series 40 Java Platform
The More Capable Series 40 Java PlatformThe More Capable Series 40 Java Platform
The More Capable Series 40 Java Platform
Gorkem Ercan
 
Nokia Uygulama Geliştirme Platfornları
Nokia Uygulama Geliştirme PlatfornlarıNokia Uygulama Geliştirme Platfornları
Nokia Uygulama Geliştirme Platfornları
Gorkem Ercan
 
Cloud GIS - GIS in the Rockies 2011
Cloud GIS - GIS in the Rockies 2011Cloud GIS - GIS in the Rockies 2011
Cloud GIS - GIS in the Rockies 2011
chelm
 
The Six Highest Performing B2B Blog Post Formats
The Six Highest Performing B2B Blog Post FormatsThe Six Highest Performing B2B Blog Post Formats
The Six Highest Performing B2B Blog Post Formats
Barry Feldman
 
The Outcome Economy
The Outcome EconomyThe Outcome Economy
The Outcome Economy
Helge Tennø
 

Viewers also liked (9)

Romero 2010 Euro Cat Cscl Workshop Barcelona
Romero 2010 Euro Cat Cscl Workshop BarcelonaRomero 2010 Euro Cat Cscl Workshop Barcelona
Romero 2010 Euro Cat Cscl Workshop Barcelona
 
Cc&Ab&Ae Euro Cat Cscl Def
Cc&Ab&Ae Euro Cat Cscl DefCc&Ab&Ae Euro Cat Cscl Def
Cc&Ab&Ae Euro Cat Cscl Def
 
Developing applications using Embedded Rich Client Platform (eRCP)
Developing applications using Embedded Rich Client Platform (eRCP)Developing applications using Embedded Rich Client Platform (eRCP)
Developing applications using Embedded Rich Client Platform (eRCP)
 
Development With eRCP
Development With eRCPDevelopment With eRCP
Development With eRCP
 
The More Capable Series 40 Java Platform
The More Capable Series 40 Java PlatformThe More Capable Series 40 Java Platform
The More Capable Series 40 Java Platform
 
Nokia Uygulama Geliştirme Platfornları
Nokia Uygulama Geliştirme PlatfornlarıNokia Uygulama Geliştirme Platfornları
Nokia Uygulama Geliştirme Platfornları
 
Cloud GIS - GIS in the Rockies 2011
Cloud GIS - GIS in the Rockies 2011Cloud GIS - GIS in the Rockies 2011
Cloud GIS - GIS in the Rockies 2011
 
The Six Highest Performing B2B Blog Post Formats
The Six Highest Performing B2B Blog Post FormatsThe Six Highest Performing B2B Blog Post Formats
The Six Highest Performing B2B Blog Post Formats
 
The Outcome Economy
The Outcome EconomyThe Outcome Economy
The Outcome Economy
 

Similar to OSCON july 2011

Geolocation on Rails
Geolocation on RailsGeolocation on Rails
Geolocation on Rails
nebirhos
 
Application devevelopment with open source libraries
Application devevelopment with open source librariesApplication devevelopment with open source libraries
Application devevelopment with open source libraries
Allan Laframboise
 
Getting Started with Geospatial Data in MongoDB
Getting Started with Geospatial Data in MongoDBGetting Started with Geospatial Data in MongoDB
Getting Started with Geospatial Data in MongoDB
MongoDB
 
Demoiselle Spatial Latinoware 2011
Demoiselle Spatial Latinoware 2011Demoiselle Spatial Latinoware 2011
Demoiselle Spatial Latinoware 2011
Rafael Soto
 
Geospatial Enhancements in MongoDB 2.4
Geospatial Enhancements in MongoDB 2.4Geospatial Enhancements in MongoDB 2.4
Geospatial Enhancements in MongoDB 2.4
MongoDB
 
2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator
Alberto Paro
 
2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator
Alberto Paro
 
Geolocation
GeolocationGeolocation
Geolocation
Timothy Boronczyk
 
A Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to GoA Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to Go
Matt Stine
 
Server side geo_tools_in_drupal_pnw_2012
Server side geo_tools_in_drupal_pnw_2012Server side geo_tools_in_drupal_pnw_2012
Server side geo_tools_in_drupal_pnw_2012
Mack Hardy
 
Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time Geostreams
Raffi Krikorian
 
Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time Geostreams
guest35660bc
 
PyDX Presentation about Python, GeoData and Maps
PyDX Presentation about Python, GeoData and MapsPyDX Presentation about Python, GeoData and Maps
PyDX Presentation about Python, GeoData and Maps
Hannes Hapke
 
huhu
huhuhuhu
LocationTech Projects
LocationTech ProjectsLocationTech Projects
LocationTech Projects
Jody Garnett
 
Scripting GeoServer
Scripting GeoServerScripting GeoServer
Scripting GeoServer
Jared Erickson
 
Geo search introduction
Geo search introductionGeo search introduction
Geo search introduction
kenshin03
 
Cross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineCross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App Engine
Andy McKay
 
Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...
Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...
Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...
Neo4j
 
GeoMesa on Apache Spark SQL with Anthony Fox
GeoMesa on Apache Spark SQL with Anthony FoxGeoMesa on Apache Spark SQL with Anthony Fox
GeoMesa on Apache Spark SQL with Anthony Fox
Databricks
 

Similar to OSCON july 2011 (20)

Geolocation on Rails
Geolocation on RailsGeolocation on Rails
Geolocation on Rails
 
Application devevelopment with open source libraries
Application devevelopment with open source librariesApplication devevelopment with open source libraries
Application devevelopment with open source libraries
 
Getting Started with Geospatial Data in MongoDB
Getting Started with Geospatial Data in MongoDBGetting Started with Geospatial Data in MongoDB
Getting Started with Geospatial Data in MongoDB
 
Demoiselle Spatial Latinoware 2011
Demoiselle Spatial Latinoware 2011Demoiselle Spatial Latinoware 2011
Demoiselle Spatial Latinoware 2011
 
Geospatial Enhancements in MongoDB 2.4
Geospatial Enhancements in MongoDB 2.4Geospatial Enhancements in MongoDB 2.4
Geospatial Enhancements in MongoDB 2.4
 
2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator
 
2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator2017 02-07 - elastic & spark. building a search geo locator
2017 02-07 - elastic & spark. building a search geo locator
 
Geolocation
GeolocationGeolocation
Geolocation
 
A Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to GoA Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to Go
 
Server side geo_tools_in_drupal_pnw_2012
Server side geo_tools_in_drupal_pnw_2012Server side geo_tools_in_drupal_pnw_2012
Server side geo_tools_in_drupal_pnw_2012
 
Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time Geostreams
 
Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time Geostreams
 
PyDX Presentation about Python, GeoData and Maps
PyDX Presentation about Python, GeoData and MapsPyDX Presentation about Python, GeoData and Maps
PyDX Presentation about Python, GeoData and Maps
 
huhu
huhuhuhu
huhu
 
LocationTech Projects
LocationTech ProjectsLocationTech Projects
LocationTech Projects
 
Scripting GeoServer
Scripting GeoServerScripting GeoServer
Scripting GeoServer
 
Geo search introduction
Geo search introductionGeo search introduction
Geo search introduction
 
Cross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App EngineCross Domain Web
Mashups with JQuery and Google App Engine
Cross Domain Web
Mashups with JQuery and Google App Engine
 
Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...
Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...
Training Series - Build A Routing Web Application With OpenStreetMap, Neo4j, ...
 
GeoMesa on Apache Spark SQL with Anthony Fox
GeoMesa on Apache Spark SQL with Anthony FoxGeoMesa on Apache Spark SQL with Anthony Fox
GeoMesa on Apache Spark SQL with Anthony Fox
 

Recently uploaded

Full-RAG: A modern architecture for hyper-personalization
Full-RAG: A modern architecture for hyper-personalizationFull-RAG: A modern architecture for hyper-personalization
Full-RAG: A modern architecture for hyper-personalization
Zilliz
 
Observability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdf
Observability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdfObservability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdf
Observability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdf
Paige Cruz
 
Video Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the FutureVideo Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the Future
Alpen-Adria-Universität
 
Infrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI modelsInfrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI models
Zilliz
 
Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1
DianaGray10
 
“I’m still / I’m still / Chaining from the Block”
“I’m still / I’m still / Chaining from the Block”“I’m still / I’m still / Chaining from the Block”
“I’m still / I’m still / Chaining from the Block”
Claudio Di Ciccio
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
Daiki Mogmet Ito
 
Removing Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software FuzzingRemoving Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software Fuzzing
Aftab Hussain
 
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
Neo4j
 
Pushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 daysPushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 days
Adtran
 
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
名前 です男
 
Climate Impact of Software Testing at Nordic Testing Days
Climate Impact of Software Testing at Nordic Testing DaysClimate Impact of Software Testing at Nordic Testing Days
Climate Impact of Software Testing at Nordic Testing Days
Kari Kakkonen
 
How to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptxHow to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptx
danishmna97
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
panagenda
 
Mind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AIMind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AI
Kumud Singh
 
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Speck&Tech
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
Zilliz
 
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with SlackLet's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
shyamraj55
 
TrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy SurveyTrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy Survey
TrustArc
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
IndexBug
 

Recently uploaded (20)

Full-RAG: A modern architecture for hyper-personalization
Full-RAG: A modern architecture for hyper-personalizationFull-RAG: A modern architecture for hyper-personalization
Full-RAG: A modern architecture for hyper-personalization
 
Observability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdf
Observability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdfObservability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdf
Observability Concepts EVERY Developer Should Know -- DeveloperWeek Europe.pdf
 
Video Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the FutureVideo Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the Future
 
Infrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI modelsInfrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI models
 
Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1
 
“I’m still / I’m still / Chaining from the Block”
“I’m still / I’m still / Chaining from the Block”“I’m still / I’m still / Chaining from the Block”
“I’m still / I’m still / Chaining from the Block”
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
 
Removing Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software FuzzingRemoving Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software Fuzzing
 
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
 
Pushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 daysPushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 days
 
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
 
Climate Impact of Software Testing at Nordic Testing Days
Climate Impact of Software Testing at Nordic Testing DaysClimate Impact of Software Testing at Nordic Testing Days
Climate Impact of Software Testing at Nordic Testing Days
 
How to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptxHow to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptx
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
 
Mind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AIMind map of terminologies used in context of Generative AI
Mind map of terminologies used in context of Generative AI
 
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
 
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with SlackLet's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
 
TrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy SurveyTrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy Survey
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
 

OSCON july 2011

  • 1. + OPEN GEOSPATIAL RUBY LIBRARIES Christopher Helm - GeoIQ http://geocommons.com
  • 2. The Plan • Keep it simple • Introduce you to geospatial • Dive into a few example libs • Demo some code!
  • 3. THIS TALK... ...is for (~Ruby) geospatial newcomers
  • 4. STAYING SIMPLE GeoSpatial is a deep subject we’re barely skimming the surface
  • 10. WHY DOES THIS MATTER? location is everywhere :)
  • 11. GEOGRAPHY LESSON 1: Vector Data Types Points Lines Polygons
  • 12. GEOGRAPHY LESSON 1: OGC’s Simple Features POINT(30, 40) LINESTRING(30 10, 10 30, 40 40) POLYGON(30 10, 10 20, 20 40, 40 40, 30 10) MULTI*
  • 13. GEOGRAPHY LESSON 2: Spatial Relationships Buffers Intersections Clips Dissolves Unions Aggregation etc...
  • 14. GEOGRAPHY LESSON 2: Spatial Relationships Buffers
  • 15. GEOGRAPHY LESSON 2: Spatial Relationships Dissolves
  • 16. GEOGRAPHY LESSON 2: Spatial Relationships Clips Intersections
  • 17. GEOGRAPHY LESSON 2: Spatial Relationships Spatial Aggregation
  • 18. GEOGRAPHY LESSON 3: Spatial Reference Systems http://www.cartogrammar.com/blog/accidental-map-projections/
  • 19. GEOGRAPHY LESSON 3: Spatial Reference Systems Plate Carrée, Geographic, or un-projected
  • 20. GEOGRAPHY LESSON 3: Spatial Reference Systems Spherical Mercator (Web Mercator)
  • 21. SOME GEOSPATIAL LIBS •2 categories of libraries • 1. Ruby based GeoSpatial Data Libraries • 2. Ruby bindings location-aware APIs & services
  • 22. GEOS http://trac.osgeo.org/geos/ Geometry Engine - Open Source (GEOS) A C++ port of the Java Topology Suite Simple Features and spatial operators
  • 23. GEOS require 'geos' @reader = Geos::WktReader.new() # Create points p1 = @reader.read(‘POINT(1, 2)’) p2 = @reader.read(‘POINT(3, 4)’) p3 = @reader.read(‘POINT(5, 2)’) p2 y p1 p3 x
  • 24. GEOS # Create a more complex shapes line1 = @reader.read(‘LINESTRING(1 2, 3 4, 5 2)’) polygon = @reader.read(‘POLYGON(1 2, 3 4, 5 2, 1 2)’) line2 = @reader.read('LINESTRING(1 4, 4 1)') line1 Polygon line2
  • 25. GEOS # Perform geometric operations and calculations line3 = polygon.intersection(line2) dist = line3.distance(p3) line2 dist line3 p3 line3
  • 26. GEOS # new point p4 = @reader.read(‘POINT(2.5, 3.5)’) # create a buffer on p4 buffer = p4.buffer(2) # now test against the buffer buffer.contains?(p1) # false buffer.contains?(p2) # true buffer.touches?(p3.buffer(1)) # true p2 p4 p1 p3 buffer
  • 27. GEORUBY http://rubyforge.org/projects/georuby/ A Library for working with geometric data types in Ruby follows the OGC’s Simple Features Spec Think: Point, Lines, and Polygons Also data formatters / parsers (KML to Well Known Text)
  • 28. SPATIAL ADAPTER https://github.com/fragility/spatial_adapterxt Adapter for ActiveRecord Handles spatial datatypes coming from: PostGreSQL (via PostGIS) & MySQL (via its spatial ext.)
  • 29. RGEO http://virtuoso.rubyforge.org/rgeo/ Geospatial datatypes in Ruby Standard Spatial analysis operations: (buffers, intersections, distances, etc) Data format conversions Flexible - gem and active-record adapter
  • 30. RUBY GEOCODER http://www.rubygeocoder.com/ Full featured geocoder for Ruby Gem and Rails Plugin Converts addresses and IPs in lat/lon coordinates configurable to work with any service Also checkout geokit: http://geokit.rubyforge.org/
  • 31. RUBY GEOCODER http://www.rubygeocoder.com/ require ‘geocoder’ # look up coordinates of some location Geocoder.coordinates("25 Main St, Cooperstown, NY") => [42.700149, -74.922767] # distance between Eiffel Tower and Empire State Building Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655]) => 3619.77359999382 # find the geographic coordinates for an IP address Geocoder.coordinates("71.166.236.36") => [38.6485, -77.3108]
  • 32. DATABASES PostgreSQL / Postgis MySQL MongoDB Couch...DB....base (via GeoCouch)
  • 33. POSTGRESQL PostGIS - spatial extension Uses GEOS for spatial operations
  • 34. ACTIVERECORD POSTGIS https://github.com/dazuma/activerecord-postgis-adapter create_table :my_spatial_table do |t| t.column :shape, :geometry # or t.geometry :shape t.line_string :path, :srid => 3785 t.point :latlon, :geographic => true end change_table :my_spatial_table do |t| t.index :latlon, :spatial => true end # querying rec = MySpatialTable.where(:latlon => 'POINT(-122 47)').first
  • 35. MONGODB require 'mongo' db = Mongo::Connection.new("localhost", 27017).db("mydb") auth = db.authenticate(my_user_name, my_password) coll = db.collection("testCollection") coll.find("i" => 71).each { |row| puts row.inspect }
  • 36. MONGODB # Spatial indexes / queries coll.create_index([["loc", Mongo::GEO2D]]) coll.find({"loc" => {"$near" => [50, 50]}}, {:limit => 20}).each do |p| puts p.inspect end
  • 37. MONGODB box = [[40.73083, -73.99756], [40.741404, -73.988135]] coll.find({"loc" : {"$within" : {"$box" : box}}}) center = [50, 50] radius = 10 coll.find({"loc" : {"$within" : {"$center" : [center, radius]}}})
  • 39. TWEETSTREAM https://github.com/intridea/tweetstream Simple wrapper to the Twitter Streaming API Can search via tags or locations (via bboxes) Good way to generate geospatial data/content
  • 40. TWEETSTREAM require 'rubygems' require 'tweetstream' # Use 'track' to track a list of single-word keywords TweetStream::Client.new('username','password').track('term1', 'term2') do |status| puts "#{status.text}" end # track tweets within a bbox TweetStream::Client.new('username','password').locations(‘-122.75, 36.8,-121.75,37.8’) do |status| puts "#{status.text}" end
  • 41. INSTAGRAM https://github.com/Instagram/instagram-ruby-gem Wrapper to the Instragram’s photo sharing API Allows for spatial searching via lat/lon very similar to the Flick’r API
  • 42. CARTAGR.AM bloom.io
  • 43. INSTAGRAM require "instagram" # All methods require authentication Instagram.configure do |config| config.client_id = YOUR_CLIENT_KEY config.access_token = YOUR_ACCESS_TOKEN end # Get a list of media at a given location puts Instagram.location_recent_media(514276) # Get a list of media close to a given latitude and longitude puts Instagram.media_search("37.7808851,-122.3948632")
  • 44. YELP! http://github.com/shaper/yelp Location based lookup for business reviews Highly local listing accessed in variety of geospatial ways another way to create some geospatial data/content
  • 45. YELP!
  • 46. YELP! # create a new client client = Yelp::Client.new # do an address-based search for cream puffs nearby request = Yelp::Review::Request::Location.new( :address => '650 Mission St', :city => 'San Francisco', :state => 'CA', :radius => 2, :term => 'cream puffs', :yws_id => 'YOUR_YWSID_HERE') response = client.search(request)
  • 47. YELP! # a location-based search for ice cream or donut shops in SF request = Yelp::Review::Request::Location.new( :city => 'San Francisco', :state => 'CA', :category => [ 'donuts', 'icecream' ], :yws_id => 'YOUR_YWSID_HERE') response = client.search(request) # a neighborhood name lookup for a geo-location point request = Yelp::Neighborhood::Request::GeoPoint.new( :latitude => 37.782093, :longitude => -122.483230, :yws_id => 'YOUR_YWSID_HERE') response = client.search(request)
  • 48. DATA & ANALYSIS APIS Fusion Tables
  • 49. SIMPLEGEO https://github.com/simplegeo/simplegeo-ruby API for adding geospatial capabilities to apps Access POIs, Locational Context, and personal data Emphasis on speed, simplicity, and flexibility
  • 50. SIMPLEGEO Context API # grab the context of a lat/lon SimpleGeo::Client.get_context(37.772445,-122.405913) # context SimpleGeo::Client.get_context_by_address("41 Decatur St, San Francisco, CA 94103") SimpleGeo::Client.get_context_ip("184.84.228.110")
  • 51. SIMPLEGEO Places API SimpleGeo::Client.get_places(37.772445, -122.405913) options = {'q'=>'Starbucks', 'category'=>'Coffee & Tea', 'radius'=>5} SimpleGeo::Client.get_places(37.772445, -122.405913, options) address = "41 Decatur St, San Francisco, CA" options = {'radius' => 1} SimpleGeo::Client.get_places_by_address(address, options) ip = '173.164.219.53' SimpleGeo::Client.get_places_by_ip(ip)
  • 52. FUSION TABLES https://github.com/tokumine/fusion_tables Direct access to Google’s Fusion Tables API Full SQL interface or OO query interface Easy integration to other Google services (maps, earth, viz.) Easy storage/retrieve of geospatial data
  • 53. FUSION TABLES require 'fusion_tables' # Connect to service @ft = GData::Client::FusionTables.new @ft.clientlogin(username, password) # 1. SQL interface @ft.execute "SHOW TABLES" @ft.execute "INSERT INTO #{my_table_id} (name, geo) VALUES ('tokyo', '35.6894 139.6917');" @ft.execute "SELECT count() FROM #{my_table_id};"
  • 54. GEOCOMMONS http://geocommons.com Open Data Storage, Analysis, and Visualization Strong emphasis on usability Build complex geospatial workflows Share data visualizations on the web
  • 55. GEOCOMMONS CSV Easy Spatial Visualization / Analytics SHP JSON WMS Maps & Viz Analytics
  • 56. GEOCOMMONS GEM API GeoCommons Mgmt Analysis Visualization geoiq- ruby
  • 57. GEOCOMMONS GEM finding data... # create a client @geoiq = Geoiq.client("http://geocommons.com", "user", "password") # search for tags @geoiq.search("tag:economics") # Search for data in a location @geoiq.search("points",{:bbox => ‘-87.2,33.4,-75.9,38.4', :limit=> 10})
  • 58. GEOCOMMONS GEM getting data... # get dataset and features dataset = geoiq.dataset.find(1348) features = dataset.features # get features with a custom filter filtered_features = @dataset.features({"filter[PERIMETER][][max]" => 0.3}) bbox_filtered_features = @dataset.features({:bbox => '-87.2,33.4,-75.9,38.4'})
  • 59. GEOCOMMONS uploading data... # Upload CSV csv_file = 'data/sculptures.csv' @dataset = @geoiq.dataset.csv_upload(csv_file, {:title =>"garden sculptures"}) # Upload SHP @dataset = @geoiq.dataset.shp_upload("data/simple.shp", "data/ simple.dbf", "data/simple.shx") # Upload by URL (type = csv,kml,rss,wms,tile) @dataset = @geoiq.dataset.url_upload("http://geothings.net/geoiq/ simple_testing.csv", {:type => "csv"})
  • 60. GEOCOMMONS create new datasets... # Create a new overlay data = {‘title’ => ‘My Overlay’, ‘attributes’ => {‘attr1’ => ...}} overlay = @geoiq.dataset.create(data) # add features to the overlay features = {features => [{‘type’ => ‘Point’, ‘coordinates’ => [0,0]},{...}]} @geoiq.dataset.update(id, features)
  • 61. GEOCOMMONS create new visualizations... # Create a new map map = @geoiq.map(:title => "my empty map") @geoiq_map = map.create # get a map and it's layers @map = @geoiq.map.find(239) layer_titles = @map.layers.map{ |l| l['title']} # add a dataset to a map as a new layer @map.add_layer(1466) #deleting map or dataset @map.delete @dataset.delete
  • 64. Code Samples from this talk: https://github.com/chelm/OSCON-Ruby-GeoSpatial-Intro

Editor's Notes

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n
  45. \n
  46. \n
  47. \n
  48. \n
  49. \n
  50. \n
  51. \n
  52. \n
  53. \n
  54. \n
  55. \n
  56. \n
  57. \n
  58. \n
  59. \n
  60. \n
  61. \n
  62. \n
  63. \n
  64. \n