pnnl-building_id 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.
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: []