pnnl-building_id 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 49ac47cb4cc46314480468bea4e7e9789f870c277573d46d2b95efc79b312dd4
4
+ data.tar.gz: 83ea482877932626e12356a88542d0de86918cf082be193683a2da643bbde54f
5
+ SHA512:
6
+ metadata.gz: c9257253ed4f74811a6f83070999941f9be631104b60d11d0853c9ced6e14797f99eb13b490cafe3df255c42119ad1643fa7be191cda204e24f0250c785dda81
7
+ data.tar.gz: 34408cc0c29ba7feee402e662afe952481074e2cbde709679ff7fae32571c88473a78ff2b3e7f7d07d60edd83d01d21d6198d21187cbf423f9de331b0c7c3ea2
data/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ Copyright (c) 2018, Battelle Memorial Institute
2
+ All rights reserved.
3
+
4
+ 1. Battelle Memorial Institute (hereinafter Battelle) hereby grants permission
5
+ to any person or entity lawfully obtaining a copy of this software and
6
+ associated documentation files (hereinafter "the Software") to redistribute
7
+ and use the Software in source and binary forms, with or without
8
+ modification. Such person or entity may use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and may permit
10
+ others to do so, subject to the following conditions:
11
+
12
+ * Redistributions of source code must retain the above copyright notice, this
13
+ list of conditions and the following disclaimers.
14
+ * Redistributions in binary form must reproduce the above copyright notice,
15
+ this list of conditions and the following disclaimer in the documentation
16
+ and/or other materials provided with the distribution.
17
+ * Other than as used herein, neither the name Battelle Memorial Institute or
18
+ Battelle may be used in any form whatsoever without the express written
19
+ consent of Battelle.
20
+
21
+ 2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL BATTELLE OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
30
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Unique Building Identification (UBID)
2
+
3
+ **Website:** https://buildingid.pnnl.gov/
4
+
5
+ ## Documentation
6
+
7
+ ### Install
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'pnnl-building_id'
13
+ ```
14
+
15
+ And then execute:
16
+ ```bash
17
+ $ bundle
18
+ ```
19
+
20
+ Or install it yourself as:
21
+ ```bash
22
+ $ gem install pnnl-building_id
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ The `pnnl-building_id` package supports one usage:
28
+ * Application programming interface (API)
29
+
30
+ ### The API
31
+
32
+ UBID codecs are encapsulated in separate modules:
33
+ * `PNNL::BuildingId::V3` (format: "C-n-e-s-w")
34
+
35
+ Modules export the same API:
36
+ * `decode(String) ~> PNNL::BuildingId::CodeArea`
37
+ * `encode(Float, Float, Float, Float, Float, Float, Integer) ~> String`
38
+ * `encode_code_area(PNNL::BuildingId::CodeArea) ~> String`
39
+ * `valid?(String) ~> Boolean`
40
+
41
+ In the following example, a UBID code is decoded and then re-encoded:
42
+
43
+ ```ruby
44
+ # Use the "C-n-e-s-w" format for UBID codes.
45
+ require 'pnnl/building_id'
46
+
47
+ # Initialize UBID code.
48
+ code = '849VQJH6+95J-51-58-42-50'
49
+ $stdout.puts(code)
50
+
51
+ # Decode the UBID code.
52
+ code_area = PNNL::BuildingId::V3.decode(code)
53
+ $stdout.puts(code_area)
54
+
55
+ # Resize the resulting UBID code area.
56
+ #
57
+ # The effect of this operation is that the height and width of the UBID code
58
+ # area are reduced by half an OLC code area.
59
+ new_code_area = code_area.resize
60
+ $stdout.puts(new_code_area)
61
+
62
+ # Encode the new UBID code area.
63
+ new_code = PNNL::BuildingId::V3.encode_code_area(new_code_area)
64
+ $stdout.puts(new_code)
65
+
66
+ # Test that the new UBID code matches the original.
67
+ $stdout.puts(code == newCode)
68
+ ```
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause).
73
+
74
+ ## Contributions
75
+
76
+ Contributions are accepted on [GitHub](https://github.com/) via the fork and pull request workflow. See [here](https://help.github.com/articles/using-pull-requests/) for more information.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'PNNL::BuildingId'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ require 'bundler/gem_tasks'
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+ task default: :test
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PNNL
4
+ module BuildingId
5
+ # A UBID code area.
6
+ class CodeArea
7
+ attr_reader :centroid_code_area, :centroid_code_length, :north_latitude, :south_latitude, :east_longitude, :west_longitude
8
+
9
+ # Default constructor.
10
+ #
11
+ # @param centroid_code_area [PlusCodes::CodeArea]
12
+ # @param centroid_code_length [Integer]
13
+ # @param north_latitude [Float]
14
+ # @param south_latitude [Float]
15
+ # @param east_longitude [Float]
16
+ # @param west_longitude [Float]
17
+ def initialize(centroid_code_area, centroid_code_length, north_latitude, south_latitude, east_longitude, west_longitude)
18
+ @centroid_code_area = centroid_code_area
19
+ @centroid_code_length = centroid_code_length
20
+ @north_latitude = north_latitude
21
+ @south_latitude = south_latitude
22
+ @east_longitude = east_longitude
23
+ @west_longitude = west_longitude
24
+ end
25
+
26
+ # Returns a resized version of this UBID code area, where the latitude and
27
+ # longitude of the lower left and upper right corners of the OLC bounding
28
+ # box are moved inwards by dimensions that correspond to half of the height
29
+ # and width of the OLC grid reference cell for the centroid.
30
+ #
31
+ # The purpose of the resizing operation is to ensure that re-encoding a
32
+ # given UBID code area results in the same coordinates.
33
+ #
34
+ # @return [PNNL::BuildingId::CodeArea]
35
+ def resize
36
+ # Calculate the (half-)dimensions of OLC grid reference cell for the
37
+ # centroid.
38
+ half_height = Float(@centroid_code_area.north_latitude - @centroid_code_area.south_latitude) / 2.0
39
+ half_width = Float(@centroid_code_area.east_longitude - @centroid_code_area.west_longitude) / 2.0
40
+
41
+ # Construct and return the new UBID code area.
42
+ self.class.new(
43
+ @centroid_code_area,
44
+ @centroid_code_length,
45
+ @north_latitude - half_height,
46
+ @south_latitude + half_height,
47
+ @east_longitude - half_width,
48
+ @west_longitude + half_width
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "plus_codes"
4
+ require "plus_codes/code_area"
5
+ require "plus_codes/open_location_code"
6
+ require "pnnl/building_id/code_area"
7
+
8
+ module PNNL
9
+ module BuildingId
10
+ module V3
11
+ # The separator for OLC codes in a UBID code.
12
+ SEPARATOR_ = "-"
13
+
14
+ # Format string for UBID codes.
15
+ FORMAT_STRING_ = "%s-%.0f-%.0f-%.0f-%.0f"
16
+
17
+ # Regular expression for UBID codes.
18
+ RE_PATTERN_ = Regexp.new([
19
+ "^",
20
+ "([#{Regexp.quote(PlusCodes::CODE_ALPHABET)}]{4,8}#{Regexp.quote(PlusCodes::SEPARATOR)}[#{Regexp.quote(PlusCodes::CODE_ALPHABET)}]*)",
21
+ "#{Regexp.quote(SEPARATOR_)}",
22
+ "(0|[1-9][0-9]*)",
23
+ "#{Regexp.quote(SEPARATOR_)}",
24
+ "(0|[1-9][0-9]*)",
25
+ "#{Regexp.quote(SEPARATOR_)}",
26
+ "(0|[1-9][0-9]*)",
27
+ "#{Regexp.quote(SEPARATOR_)}",
28
+ "(0|[1-9][0-9]*)",
29
+ "$",
30
+ ].join)
31
+
32
+ # The first group of a UBID code is the OLC for the geometric center of mass
33
+ # (i.e., centroid) of the building footprint.
34
+ RE_GROUP_OPENLOCATIONCODE_ = 1
35
+
36
+ # The second group of the UBID code is the Chebyshev distance in OLC grid units
37
+ # from the OLC for the centroid of the building footprint to the northern extent
38
+ # of the OLC bounding box for the building footprint.
39
+ RE_GROUP_NORTH_ = 2
40
+
41
+ # The third group of the UBID code is the Chebyshev distance in OLC grid units
42
+ # from the OLC for the centroid of the building footprint to the eastern extent
43
+ # of the OLC bounding box for the building footprint.
44
+ RE_GROUP_EAST_ = 3
45
+
46
+ # The fourth group of the UBID code is the Chebyshev distance in OLC grid units
47
+ # from the OLC for the centroid of the building footprint to the southern extent
48
+ # of the OLC bounding box for the building footprint.
49
+ RE_GROUP_SOUTH_ = 4
50
+
51
+ # The fifth group of the UBID code is the Chebyshev distance in OLC grid units
52
+ # from the OLC for the centroid of the building footprint to the western extent
53
+ # of the OLC bounding box for the building footprint.
54
+ RE_GROUP_WEST_ = 5
55
+
56
+ # Returns the UBID code area for the given UBID code.
57
+ #
58
+ # @param code [String] the UBID code
59
+ # @return [PNNL::BuildingId::CodeArea] The UBID code area.
60
+ # @raise [ArgumentError] if the UBID code is invalid or if the OLC for the centroid of the building footprint is invalid
61
+ def self.decode(code)
62
+ if !(md = RE_PATTERN_.match(code)).nil?
63
+ # Extract the OLC for the centroid of the building footprint.
64
+ centroid_openlocationcode = md[RE_GROUP_OPENLOCATIONCODE_]
65
+
66
+ # Decode the OLC for the centroid of the building footprint.
67
+ centroid_openlocationcode_CodeArea = open_location_code.decode(centroid_openlocationcode)
68
+
69
+ # Calculate the size of the OLC for the centroid of the building footprint
70
+ # in decimal degree units.
71
+ height = centroid_openlocationcode_CodeArea.north_latitude - centroid_openlocationcode_CodeArea.south_latitude
72
+ width = centroid_openlocationcode_CodeArea.east_longitude - centroid_openlocationcode_CodeArea.west_longitude
73
+
74
+ # Calculate the size of the OLC bounding box for the building footprint,
75
+ # assuming that the datum are Chebyshev distances.
76
+ north_latitude = centroid_openlocationcode_CodeArea.north_latitude + (Float(md[RE_GROUP_NORTH_]) * height)
77
+ east_longitude = centroid_openlocationcode_CodeArea.east_longitude + (Float(md[RE_GROUP_EAST_]) * width)
78
+ south_latitude = centroid_openlocationcode_CodeArea.south_latitude - (Float(md[RE_GROUP_SOUTH_]) * height)
79
+ west_longitude = centroid_openlocationcode_CodeArea.west_longitude - (Float(md[RE_GROUP_WEST_]) * width)
80
+
81
+ # Construct and return the UBID code area.
82
+ return PNNL::BuildingId::CodeArea.new(
83
+ centroid_openlocationcode_CodeArea,
84
+ centroid_openlocationcode.length - PlusCodes::SEPARATOR.length,
85
+ north_latitude,
86
+ south_latitude,
87
+ east_longitude,
88
+ west_longitude
89
+ )
90
+ else
91
+ raise ArgumentError.new('Invalid UBID')
92
+ end
93
+ end
94
+
95
+ # Returns the UBID code for the given coordinates.
96
+ #
97
+ # @param latitude_lo [Float] the latitude in decimal degrees of the southwest corner of the minimal bounding box for the building footprint
98
+ # @param longitude_lo [Float] the longitude in decimal degrees of the southwest corner of the minimal bounding box for the building footprint
99
+ # @param latitude_hi [Float] the latitude in decimal degrees of the northeast corner of the minimal bounding box for the building footprint
100
+ # @param longitude_hi [Float] the longitude in decimal degrees of the northeast corner of the minimal bounding box for the building footprint
101
+ # @param latitudeCenter [Float] the latitude in decimal degrees of the centroid of the building footprint
102
+ # @param longitudeCenter [Float] the longitude in decimal degrees of the centroid of the building footprint
103
+ # @param options [Hash]
104
+ # @option options [Integer] :codeLength (`PlusCodes::PAIR_CODE_LENGTH`) the OLC code length (not including the separator)
105
+ # @return [String] The UBID code.
106
+ # @raise [ArgumentError] if the OLC for the centroid of the building footprint cannot be encoded (e.g., invalid code length)
107
+ def self.encode(latitude_lo, longitude_lo, latitude_hi, longitude_hi, latitudeCenter, longitudeCenter, options = {})
108
+ codeLength = options[:codeLength] || PlusCodes::PAIR_CODE_LENGTH
109
+
110
+ # Encode the OLCs for the northeast and southwest corners of the minimal
111
+ # bounding box for the building footprint.
112
+ northeast_openlocationcode = open_location_code.encode(latitude_hi, longitude_hi, codeLength)
113
+ southwest_openlocationcode = open_location_code.encode(latitude_lo, longitude_lo, codeLength)
114
+
115
+ # Encode the OLC for the centroid of the building footprint.
116
+ centroid_openlocationcode = open_location_code.encode(latitudeCenter, longitudeCenter, codeLength)
117
+
118
+ # Decode the OLCs for the northeast and southwest corners of the minimal
119
+ # bounding box for the building footprint.
120
+ northeast_openlocationcode_CodeArea = open_location_code.decode(northeast_openlocationcode)
121
+ southwest_openlocationcode_CodeArea = open_location_code.decode(southwest_openlocationcode)
122
+
123
+ # Decode the OLC for the centroid of the building footprint.
124
+ centroid_openlocationcode_CodeArea = open_location_code.decode(centroid_openlocationcode)
125
+
126
+ # Calculate the size of the OLC for the centroid of the building footprint
127
+ # in decimal degree units.
128
+ height = centroid_openlocationcode_CodeArea.north_latitude - centroid_openlocationcode_CodeArea.south_latitude
129
+ width = centroid_openlocationcode_CodeArea.east_longitude - centroid_openlocationcode_CodeArea.west_longitude
130
+
131
+ # Calculate the Chebyshev distances to the northern, eastern, southern and
132
+ # western of the OLC bounding box for the building footprint.
133
+ delta_north = (northeast_openlocationcode_CodeArea.north_latitude - centroid_openlocationcode_CodeArea.north_latitude) / height
134
+ delta_east = (northeast_openlocationcode_CodeArea.east_longitude - centroid_openlocationcode_CodeArea.east_longitude) / width
135
+ delta_south = (centroid_openlocationcode_CodeArea.south_latitude - southwest_openlocationcode_CodeArea.south_latitude) / height
136
+ delta_west = (centroid_openlocationcode_CodeArea.west_longitude - southwest_openlocationcode_CodeArea.west_longitude) / width
137
+
138
+ # Construct and return the UBID code.
139
+ Kernel.sprintf(FORMAT_STRING_, centroid_openlocationcode, delta_north, delta_east, delta_south, delta_west)
140
+ end
141
+
142
+ # Returns the UBID code for the given UBID code area.
143
+ #
144
+ # @param code_area [PNNL::BuildingId::CodeArea]
145
+ # @return [String] The UBID code.
146
+ # @raise [ArgumentError] if the OLC for the centroid of the building footprint cannot be encoded (e.g., invalid code length)
147
+ def self.encode_code_area(code_area)
148
+ raise ArgumentError.new('Invalid PNNL::BuildingId::CodeArea') if code_area.nil?
149
+
150
+ # Delegate.
151
+ encode(code_area.south_latitude, code_area.west_longitude, code_area.north_latitude, code_area.east_longitude, code_area.centroid_code_area.latitude_center, code_area.centroid_code_area.longitude_center, codeLength: code_area.centroid_code_length)
152
+ end
153
+
154
+ # Is the given UBID code valid?
155
+ #
156
+ # @param code [String] the UBID code
157
+ # @return [Boolean] `true` if the UBID code is valid. Otherwise, `false`.
158
+ def self.valid?(code)
159
+ # Undefined UBID codes are invalid.
160
+ return false if code.nil?
161
+
162
+ # Attempt to match the regular expression.
163
+ if !(md = RE_PATTERN_.match(code)).nil?
164
+ open_location_code.valid?(md[RE_GROUP_OPENLOCATIONCODE_])
165
+ else
166
+ # UBID codes that fail to match the regular expression are invalid.
167
+ false
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ # Singleton instance of OLC.
174
+ #
175
+ # @return [PlusCodes::OpenLocationCode]
176
+ def self.open_location_code
177
+ @@open_location_code ||= PlusCodes::OpenLocationCode.new
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PNNL
4
+ module BuildingId
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PNNL
4
+ module BuildingId
5
+ autoload :CodeArea, 'pnnl/building_id/code_area'
6
+ autoload :V3, 'pnnl/building_id/v3'
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pnnl-building_id
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mark Borkum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: plus_codes
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.1
27
+ description: Unique Building Identification (UBID) for Ruby
28
+ email:
29
+ - mark.borkum@pnnl.gov
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/pnnl/building_id.rb
38
+ - lib/pnnl/building_id/code_area.rb
39
+ - lib/pnnl/building_id/v3.rb
40
+ - lib/pnnl/building_id/version.rb
41
+ homepage: https://buildingid.pnnl.gov/
42
+ licenses:
43
+ - BSD-3-Clause
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.7.6
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Unique Building Identification (UBID) for Ruby
65
+ test_files: []