opencellid-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitgnore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ coverage
3
+ pkg
data/.simplecov ADDED
@@ -0,0 +1,3 @@
1
+ SimpleCov.start do
2
+ add_filter 'spec'
3
+ end
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in aebus.gemspec
4
+ gemspec
data/License.txt ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2012 Marco Sandrini
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ OpenCellID Client Library
2
+ =========================
3
+
4
+ `opencellid-client` is a ruby gem that aims at simplifying the usage of the APIs provided by opencellid.org to transform cell IDs into coordinates.
5
+
6
+ Installing
7
+ ----------
8
+
9
+ Simply install the gem to your system
10
+
11
+ `gem install opencellid-client`
12
+
13
+ or if you are using Bundler add it to your gemspec file and run `bundle install`.
14
+
15
+ Usage
16
+ -----
17
+
18
+ First of all `require 'opencellid'` in your application.
19
+
20
+ Read functionality (i.e. methods that only query the database generally do not require an API key), write methods instead
21
+ do require an API key, so if you do not have one, head to the [OpenCellID website](http://www.opencellid.org/) and get one
22
+ for yourself.
23
+
24
+ Initialize the main Opencellid object with the API key (if any)
25
+
26
+ `opencellid = Opencellid::Opencellid.new`
27
+
28
+ or
29
+
30
+ `opencellid = Opencellid::Opencellid.new my_key`
31
+
32
+ and then invoke methods on the object just created. The return values of all methods is of type `Response`. invoking method
33
+ `ok?` verifies that the response is a successful one. If the response is successful, method-dependent results can be
34
+ got from the response, otherwise an error object can be extracted from the response and the use its `code` and `info`
35
+ methods to obtain the error code and human readable description.
36
+
37
+ Querying Cells
38
+ ==============
39
+
40
+ Cells can be queried using a combination of parameters: the cell id, the mnc (operator) and mcc (country) code or the local area
41
+ code lac. For a more detailed description of these parameters please refere to the OpenCellID site.
42
+
43
+ Parameters are first set into a `Cell` object and then the object is passed to the method. E.g. to query for
44
+ the position of the cell with id 123000, mnc 5 and mcc 244
45
+
46
+ `target_cell = Cell.new(123000,244,5,nil
47
+ response = opencellid.get_cell(target_cell)
48
+ result_cell = response.cells[0]`
49
+
50
+ similarly to query for the measures related to the same cell
51
+
52
+ `target_cell = Cell.new(123000,244,5,nil
53
+ response = opencellid.get_cell_measures(target_cell)
54
+ result_cell = response.cells[0]
55
+ measures = result.cell.measures`
56
+
57
+ Finally to query for cells in a bounding box, a BBox object must provided. In addition to that, the set of results can
58
+ be limited by number, by mcc or by mnc code by specifying respectively the `:limit`,`:mcc` and `:mnc` keys of the options
59
+ hash.
60
+
61
+ To get the first 10 cells belonging to operator whose mnc code is 5 within the bounding box the code is the following
62
+
63
+ `bbox = BBox.new(30.0,40.0,60.0,70.0)
64
+ response = opencellid.get_cells_in_area(bbox, :limit => 10, :mnc => 5)
65
+ cells = response.cells`
66
+
67
+ Adding and Deleting Measures
68
+ ============================
69
+
70
+ Adding, deleting and listing "own" measures (i.e. measures inserted by the same user) require that an API key is
71
+ made available to the library at initialization time.
72
+
73
+ Measure can be added by providing a cell object identifying the cell to which the measure refers to, and a measure containing
74
+ the actual measure details.
75
+
76
+ `target_cell = Cell.new(123000,244,5,9000)
77
+ measure = Measure.new(30.0,60.0,Time.now)
78
+ measure.signal = 15
79
+ response = opencellid.add_measure(target_cell,measure)`
80
+
81
+ The response object will contain the cell id and the measure id. Using the measure id it is later on possible to delete
82
+ the measure, if so desired
83
+
84
+ `response = opencellid.delete_measure(measure_id)
85
+
86
+ Measure Ids can also be obtained by listing all the measures belonging to the user whose API key has been provided to the
87
+ library
88
+
89
+ `response = opencellid.list_measures
90
+ measures = response.measures`
91
+
92
+
93
+ Bulk addition of measures
94
+ =========================
95
+
96
+ Bulk addition of measure by uploading a CSV formatted file to the OpenCellID server is not yet supported by this library.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
data/lib/opencellid.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'opencellid/opencellid'
2
+ require 'opencellid/cell'
3
+ require 'opencellid/version'
4
+ require 'opencellid/measure'
5
+ require 'opencellid/error'
6
+ require 'opencellid/response'
7
+ require 'opencellid/bbox'
@@ -0,0 +1,34 @@
1
+ module Opencellid
2
+
3
+ # A class to simplify the setting the coordinates of a bounding box while calling methods on the
4
+ # OpenCellId API
5
+ class BBox
6
+
7
+ attr_accessor :latmin, :lonmin, :latmax, :lonmax
8
+
9
+ # @param latmin [Float] latmin the latitude of the SW corner of the box
10
+ # @param lonmin [Float] lonmin the longitude of the SW corner of the box
11
+ # @param latmax [Float] latmax the latitude of the NE corner of the box
12
+ # @param lonmax [Float] lonmax the longitude of the NE corner of the box
13
+ def initialize(latmin, lonmin, latmax, lonmax)
14
+ raise ArgumentError, "latmin must not be nil" unless latmin
15
+ raise ArgumentError, "lonmin must not be nil" unless lonmin
16
+ raise ArgumentError, "latmax must not be nil" unless latmax
17
+ raise ArgumentError, "lonmax must not be nil" unless lonmax
18
+ @latmin = latmin
19
+ @lonmin = lonmin
20
+ @latmax = latmax
21
+ @lonmax = lonmax
22
+ end
23
+
24
+ # Transforms the coordinates of this bounding box in a format suitable for the OpenCellId API
25
+ # @return [String] the coordinates of the bbox is a format compatible with the OpenCellId API
26
+ def to_s
27
+ "#{latmin},#{lonmin},#{latmax},#{lonmax}"
28
+ end
29
+
30
+ end
31
+
32
+
33
+
34
+ end
@@ -0,0 +1,62 @@
1
+ require 'rexml/document'
2
+ require_relative 'utils'
3
+
4
+ module Opencellid
5
+
6
+ # Models a Cell object, both as an output from the server and as an input to queries. When using an object of this type
7
+ # to specify query parameters, the class attributes that should be used as filters should be set to the desired values,
8
+ # while the other attributes should be set to nil
9
+ class Cell
10
+
11
+ attr_accessor :lat, :lon, :id, :mnc, :mcc, :lac, :range, :measures, :no_of_samples
12
+
13
+ # @param id [Integer] the id of the cell
14
+ # @param mnc [Integer] the mnc code of the cell
15
+ # @param mcc [Integer] the mcc code of the cell
16
+ # @param lac [Integer] the lac code of the cell
17
+ def initialize(id, mnc, mcc, lac)
18
+ @id = id
19
+ @mnc = mnc
20
+ @mcc = mcc
21
+ @lac = lac
22
+ @measures = []
23
+ end
24
+
25
+
26
+ # @param element [REXML::Element] the XML element containing the representation of the Cell element
27
+ # @return [Cell] the Cell object obtained by parsing the XML
28
+ def self.from_element(element)
29
+ return nil unless element
30
+ raise ArgumentError, "element must be of type XEXML::Element" unless element.is_a? REXML::Element
31
+ raise ArgumentError, "element must be a <cell>" unless element.name == "cell"
32
+ attrs = element.attributes
33
+
34
+ result = Cell.new(::Opencellid.to_i_or_nil(attrs['cellId']), ::Opencellid.to_i_or_nil(attrs['mnc']),
35
+ ::Opencellid.to_i_or_nil(attrs['mcc']),::Opencellid.to_i_or_nil(attrs['lac']))
36
+ result.lat = ::Opencellid.to_f_or_nil(attrs['lat'])
37
+ result.lon = ::Opencellid.to_f_or_nil(attrs['lon'])
38
+ result.range = ::Opencellid.to_i_or_nil(attrs['range'])
39
+ result.no_of_samples= ::Opencellid.to_i_or_nil(attrs['nbSamples'])
40
+ element.elements.each('measure') { |e| result.add_measure Measure.from_element e}
41
+ result
42
+ end
43
+
44
+ # @return [bool] whether this cell contains measure objects
45
+ def has_measures?
46
+ @measures.count > 0
47
+ end
48
+
49
+ # @param measure [Measure] a measure object to be added to this cell. Note that adding the object to the cell
50
+ # does NOT result in an add_measure call being invoked on the Opencellid object
51
+ def add_measure(measure)
52
+ @measures << measure
53
+ end
54
+
55
+ # @return [Hash] a hash object containing the non nil paramters which can be used in while querying for cells
56
+ def to_query_hash
57
+ {cellid: id, mnc: mnc, mcc: mcc, lac: lac}.delete_if {|k,v| v.nil?}
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,30 @@
1
+ require 'rexml/document'
2
+ require_relative 'utils'
3
+
4
+ module Opencellid
5
+
6
+ # Models an error object received from the server
7
+ class Error
8
+
9
+ attr_accessor :info, :code
10
+
11
+ # @param code [Integer] the code identifying this error
12
+ # @param info [String] a human readable explanation of the error
13
+ def initialize(code, info)
14
+ @info = info
15
+ @code = code
16
+ end
17
+
18
+ # @param element [REXML::Element] an XML element containing the error
19
+ # @return [Error] the error object created by parsing the XML element
20
+ def self.from_element(element)
21
+ return nil unless element
22
+ raise ArgumentError, "element must be of type XEXML::Element" unless element.is_a? REXML::Element
23
+ raise ArgumentError, "element must be an <err>" unless element.name == "err"
24
+ attrs = element.attributes
25
+ return Error.new(::Opencellid.to_i_or_nil(attrs['code']),attrs['info'])
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'utils'
2
+
3
+ module Opencellid
4
+
5
+ # A class which models the measurement of a cell position
6
+ class Measure
7
+
8
+ # The format used by the OpenCellId API to pass date/time information
9
+ DATE_FORMAT = "%a %b %d %H:%M:%S %z %Y"
10
+
11
+ attr_accessor :lat, :lon, :taken_by, :taken_on, :id, :signal
12
+
13
+ # @param lat [Float] the latitude of the position measured
14
+ # @param lon [Float] the longitude of the position measured
15
+ # @param taken_on [DateTime] the date/time information at which the measurement was taken
16
+ def initialize(lat, lon, taken_on = nil)
17
+ @lat = lat
18
+ @lon = lon
19
+ @taken_on = taken_on
20
+ end
21
+
22
+
23
+ # @param element [REXML::Element] the XML element containing the representation of the measurement
24
+ # @return [Measure] the Measure object obtained by parsing the XML
25
+ def self.from_element(element)
26
+ return nil unless element
27
+ raise ArgumentError, "element must be of type XEXML::Element" unless element.is_a? REXML::Element
28
+ raise ArgumentError, "element must be a <measure>" unless element.name == "measure"
29
+ attrs = element.attributes
30
+ date = attrs['takenOn']
31
+ date ||= attrs['measured_at']
32
+ measure = Measure.new(::Opencellid::to_f_or_nil(attrs['lat']),::Opencellid::to_f_or_nil(attrs['lon']),
33
+ ::Opencellid::to_datetime_or_nil(date,DATE_FORMAT))
34
+ measure.id = ::Opencellid.to_i_or_nil(attrs['id'])
35
+ measure.taken_by= attrs['takenBy']
36
+ measure.signal = ::Opencellid.to_i_or_nil(attrs['signal'])
37
+ measure
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,108 @@
1
+ require 'rexml/document'
2
+ require 'net/http'
3
+ require_relative 'cell'
4
+ require_relative 'response'
5
+ require_relative 'measure'
6
+ require_relative 'bbox'
7
+ require_relative 'error'
8
+
9
+ module Opencellid
10
+
11
+ # The main entry for this library. Each method maps to the corresponding method of the
12
+ # OpenCellId API defined at {http:://www.opencellid.org/api}.
13
+ class Opencellid
14
+
15
+ DEFAULT_URI = "http://www.opencellid.org"
16
+ GET_IN_AREA_ALLOWED_PARAMS = [:limit, :mcc, :mnc]
17
+ attr_reader :key
18
+
19
+ # @param key [String] the API key used for "write" operations. Defaults to nil
20
+ def initialize(key=nil)
21
+ @key = key
22
+ end
23
+
24
+ # Retrieves the cell information based on the parameters specified inside the `cell` object
25
+ # @param[Cell] cell the object containing the parameters to be used in searching the database
26
+ # the result received from the server
27
+ def get_cell(cell)
28
+ query_cell_info "/cell/get", cell
29
+ end
30
+
31
+ # Retrieves the cell information and the measures used to calculate its position based on the parameters
32
+ # specified in the cell object
33
+ # @param[Cell] cell the object containing the parameters used to search the cell database
34
+ # @return[Response] the result received from the server
35
+ def get_cell_measures(cell)
36
+ query_cell_info "/cell/getMeasures", cell
37
+ end
38
+
39
+ # Retrieves all the cells located inside the bounding box and whose parameters match the ones specified in the options
40
+ # @param bbox [BBox] the bounding box limiting the cell search
41
+ # @param options [Hash] a hash containing further filtering criteria. Valid criteria are `:limit` (limiting the amount
42
+ # of results), `:mnc` specifying the mnc value of the desired cells and `:mcc` specifying the mcc value of the desired cells
43
+ # @return [Response] the result received from the server
44
+ def get_cells_in_area(bbox, options = {})
45
+ raise ArgumentError, "options must be a Hash" unless options.is_a? Hash
46
+ raise ArgumentError, "bbox must be of type BBox" unless bbox.is_a? BBox
47
+ params = {bbox: bbox.to_s, fmt: 'xml'}
48
+ params.merge!(options.reject { |key| !GET_IN_AREA_ALLOWED_PARAMS.include? key})
49
+ execute_request_and_parse_response "/cell/getInArea", params
50
+ end
51
+
52
+ # Adds a measure (specified by the measure object) to a given cell (specified by the cell object). In case of
53
+ # success the response object will also contain the cell_id and the measure_id of the newly created measure.
54
+ # Although the library does not check this, a valid APIkey must have been specified while initializing the
55
+ # this object
56
+ # @param cell [Object] the cell to which this measure must be added to
57
+ # @param measure [Object] the measure to be added
58
+ # @return [Response] the result obtained from the server
59
+ def add_measure(cell, measure)
60
+ raise ArgumentError, "cell must be of type Cell" unless cell.is_a? Cell
61
+ raise ArgumentError, "measure must be of type Measure" unless measure.is_a? Measure
62
+ params = cell.to_query_hash
63
+ params[:lat] = measure.lat
64
+ params[:lon] = measure.lon
65
+ params[:signal] = measure.signal if measure.signal
66
+ params[:measured_at] = measure.taken_on if measure.taken_on
67
+ execute_request_and_parse_response "/measure/add", params
68
+ end
69
+
70
+ # List the measures added with a given API key.
71
+ # @return [Response] the result received from the server
72
+ def list_measures
73
+ execute_request_and_parse_response "/measure/list"
74
+ end
75
+
76
+ # Deletes a measure previously added with the same API key.
77
+ # @param measure_id [Integer] the id of the measure to be deleted (obtained in the response from `add_measure` or via `list_measures`)
78
+ # @return [Response] the result received from the server
79
+ def delete_measure(measure_id)
80
+ raise ArgumentError,"measure_id cannot be nil" unless measure_id
81
+ execute_request_and_parse_response "/measure/delete", {id: measure_id}
82
+ end
83
+
84
+ protected
85
+
86
+ def query_cell_info(path, cell)
87
+ raise ArgumentError, "cell must be a Cell" unless cell.is_a? Cell
88
+ params = cell.to_query_hash
89
+ execute_request_and_parse_response path, params
90
+ end
91
+
92
+ def execute_request_and_parse_response(path, params = {})
93
+ params[:key] = @key if @key
94
+ uri = URI(DEFAULT_URI + path)
95
+ uri.query = URI.encode_www_form params if params.count > 0
96
+ res = Net::HTTP.get_response uri
97
+ if res.is_a? Net::HTTPOK
98
+ Response.from_xml res.body
99
+ else
100
+ response = Response.new false
101
+ response.error = ::Opencellid::Error.new(0, "Http Request failed with code #{res.code}")
102
+ response
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ end
@@ -0,0 +1,63 @@
1
+ require 'rexml/document'
2
+ require_relative 'error'
3
+ require_relative 'utils'
4
+
5
+ module Opencellid
6
+
7
+ # The class modelling all possible answers from the server. Also when the server is not reachable or encounters an
8
+ # error (e.g. a 500 response), the library will return a Response object, so that clients will not need to handle
9
+ # application errors and HTTP errors separately
10
+ class Response
11
+
12
+ attr_accessor :cells, :error, :cell_id, :measure_id, :result, :measures
13
+
14
+
15
+ # @param ok [bool] whether the response is an ok response or a failure one
16
+ def initialize(ok)
17
+ @ok = ok
18
+ end
19
+
20
+ # @return [bool] `true` if the response is a successful response
21
+ def ok?
22
+ @ok
23
+ end
24
+
25
+ # @return [bool] `true` if the response is a failure response
26
+ def failed?
27
+ not @ok
28
+ end
29
+
30
+ # @param string [String] a string containing the XML response received from the server
31
+ # @return [Response] the Response object obtained by parsing the XML file
32
+ def self.from_xml(string)
33
+ doc = REXML::Document.new string
34
+ raise RuntimeError, "Could not parse server response" unless doc and doc.root
35
+ case doc.root.name
36
+ when "rsp"
37
+ response = Response.new(doc.root.attributes['stat'] == "ok")
38
+ response.measures = []
39
+ if response.ok?
40
+ response.cells= doc.root.elements.collect('cell') {|e| Cell.from_element e }
41
+ response.cell_id = ::Opencellid.to_i_or_nil(doc.root.attributes['cellid'])
42
+ response.measure_id = ::Opencellid.to_i_or_nil(doc.root.attributes['id'])
43
+ res = doc.root.elements['res']
44
+ response.result = res.text if res
45
+ else
46
+ response.cells = []
47
+ response.error = Error.from_element doc.root.elements['err']
48
+ end
49
+ when "measures"
50
+ response = Response.new true
51
+ response.measures = doc.root.elements.collect('measure') {|e| Measure.from_element e }
52
+ response.cells = []
53
+ else
54
+ raise RuntimeError, "The server response does not contain a valid response"
55
+ end
56
+ response
57
+ end
58
+
59
+
60
+ end
61
+
62
+
63
+ end