opencellid-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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