class DefraRuby::Area::BaseAreaService
Attributes
Public Class Methods
# File lib/defra_ruby/area/services/base_area_service.rb, line 13 def initialize(easting, northing) @easting = easting @northing = northing end
# File lib/defra_ruby/area/services/base_area_service.rb, line 9 def self.run(easting, northing) new(easting, northing).run end
Public Instance Methods
# File lib/defra_ruby/area/services/base_area_service.rb, line 18 def run Response.new(response_exe) end
Private Instance Methods
Name of the Environment Agency dataset we are querying.
Not actually a part of the WFS interface or spec. This is simply how we route through to the right 'dataset' (how the EA terms it).
# File lib/defra_ruby/area/services/base_area_service.rb, line 63 def dataset implemented_in_subclass end
Domain where the WFS is hosted
# File lib/defra_ruby/area/services/base_area_service.rb, line 55 def domain "https://environment.data.gov.uk" end
# File lib/defra_ruby/area/services/base_area_service.rb, line 41 def extract_areas(xml_response) areas = [] xml_response.xpath("//wfs:FeatureCollection/gml:featureMember").each do |parent| areas << Area.new(parent.first_element_child) end areas end
WFS's use filters in GetFeature requests to return data that only matches a certain criteria.
docs.geoserver.org/latest/en/user/filter/function.html
There are various formats you can use for doing those, though it will be dependent on what the WFS supports. In our case we use XML-based Filter Encoding language because
-
this was how the team that manage the WFS have always provided their examples
-
we know this format is supported by the WFS's we are interacting with
The others are CQL and ECQL. docs.geoserver.org/latest/en/user/tutorials/cql/cql_tutorial.html
Our filter is looking for the result where our coordinates intersect with the feature property SHAPE
, with SHAPE
being a MultiPolygonPropertyType
.
xml.fmi.fi/namespace/meteorology/conceptual-model/meteorological-objects/2009/04/28/docindex647.html
# File lib/defra_ruby/area/services/base_area_service.rb, line 178 def filter # The filter is done in this way purely for readability. It could just # as easily been a one line string statement, but we think this is # better. filter = <<-XML ( <Filter> <Intersects> <PropertyName>SHAPE</PropertyName> <gml:Point> <gml:coordinates>#{easting},#{northing}</gml:coordinates> </gml:Point> </Intersects> </Filter> ) XML filter.strip.squeeze(" ").gsub(/\s+/, "") end
# File lib/defra_ruby/area/services/base_area_service.rb, line 197 def implemented_in_subclass raise NotImplementedError, "This #{self.class} cannot respond to:" end
Specify which attribute of the feature we want to return. You can check the attributes of a feature by making DescribeFeatureType
request.
In our case the administrative boundary features contain a number of properties, but we are only interested in
-
area_id
-
area_name
-
code
-
long_name
-
short_name
# File lib/defra_ruby/area/services/base_area_service.rb, line 136 def property_name "area_id,area_name,code,long_name,short_name" end
Used to tell the WFS what kind of request you are making. In the case of `GetFeature` this means
Return a selection of features from a data source including geometry and attribute values
docs.geoserver.org/latest/en/user/services/wfs/reference.html#getfeature
# File lib/defra_ruby/area/services/base_area_service.rb, line 110 def request "GetFeature" end
# File lib/defra_ruby/area/services/base_area_service.rb, line 26 def response_exe lambda do response = RestClient::Request.execute( method: :get, url: url, timeout: DefraRuby::Area.configuration.timeout ) areas = extract_areas(Nokogiri::XML(response)) raise NoMatchError unless areas.any? { areas: areas } end end
There are generally 3 kinds of GIS services; WFS, WMS, and WCS.
-
WFS - allows features to be queried, updated, created, or deleted by the client
-
WMS - returns map images based on the request made
-
WCS - transfer “coverages”, ie. objects covering a geographical area
We are querying a 'feature' hence we request to use the WFS service.
N.B. A feature is an Object that is an abstraction of a real world phenomenon. This object has a set of properties associated with each having a name, a type, and a value.
# File lib/defra_ruby/area/services/base_area_service.rb, line 90 def service "WFS" end
SRS stands for Spatial Reference System. It can also be known as a Coordinate Reference System (CRS). It is a coordinate-based local, regional or global system used to locate geographical entities. A spatial reference system defines a specific map projection, as well as transformations between different spatial reference systems.
The SRSName paramater tells the WFS which SRS definition to use. The value is an EPSG (European Petroleum Survey Group) code. You can find a list of them at spatialreference.org/ref/epsg/
EPSG:27700 refers to OSGB 1936 - British National Grid (spatialreference.org/ref/epsg/27700/)
For more info on SRS read en.wikipedia.org/wiki/Spatial_reference_system
# File lib/defra_ruby/area/services/base_area_service.rb, line 154 def srs_name "EPSG:27700" end
Name of the feature we wish to query.
A WFS may host multiple features, so you need to specify which one you are querying in the url.
# File lib/defra_ruby/area/services/base_area_service.rb, line 118 def type_name implemented_in_subclass end
# File lib/defra_ruby/area/services/base_area_service.rb, line 50 def url "#{domain}/spatialdata/#{dataset}/wfs?#{url_params}" end
# File lib/defra_ruby/area/services/base_area_service.rb, line 67 def url_params { "SERVICE" => service, "VERSION" => version, "REQUEST" => request, "typeName" => type_name, "propertyName" => property_name, "SRSName" => srs_name, "Filter" => filter }.map { |k, v| "#{k}=#{v}" }.join("&") end
Currently there are various versions of the WFS standard, for example 1.0, 1.1 and 2.0.
The WFS's we are working with only support version 1.0. A WFS may suppport multiple versions hence you need to state the version in the request.
# File lib/defra_ruby/area/services/base_area_service.rb, line 100 def version "1.0.0" end