-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpnnl-buildingid.js
300 lines (267 loc) · 11.9 KB
/
pnnl-buildingid.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/**
* Filename: pnnl-buildingid.js
* Author: Mark Borkum <[email protected]>
*
* Copyright (c) 2018, Battelle Memorial Institute
* All rights reserved.
*
* See LICENSE.txt and WARRANTY.txt for details.
*/
(function (root, factory) {
/* global define, module */
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['open-location-code'], function ($) {
return (root.UniqueBuildingIdentification = factory(new ($.OpenLocationCode)()));
});
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(new (require('open-location-code').OpenLocationCode)());
} else {
// Browser globals
root.UniqueBuildingIdentification = factory(root.OpenLocationCode);
}
} (this, function (OpenLocationCode) {
const UniqueBuildingIdentification = {};
/**
* The coordinates of a decoded Unique Building Identifier (UBID).
*
* The coordinates include the latitude and longitude of the lower left and
* upper right corners of the Open Location Code (OLC) bounding box for the
* building footprint that the code represents, along with the latitude and
* longitude of the southwest (SW) and northeast (NE) corners of the OLC grid
* reference cell for the geometric center of mass (i.e., centroid) of the
* building footprint that the code represents.
*
* @param {number} latitudeLo The latitude of the SW corner in degrees.
* @param {number} longitudeLo The longitude of the SW corner in degrees.
* @param {number} latitudeHi The latitude of the NE corner in degrees.
* @param {number} longitudeHi The longitude of the NE corner in degrees.
* @param {OpenLocationCode.CodeArea} centerOfMass The OLC grid reference cell
* for the centroid.
* @param {string} codeVersion The UBID code version.
*
* @constructor
*/
UniqueBuildingIdentification.CodeArea = function (latitudeLo, longitudeLo, latitudeHi, longitudeHi, centerOfMass, codeVersion) {
this.latitudeLo = latitudeLo;
this.longitudeLo = longitudeLo;
this.latitudeHi = latitudeHi;
this.longitudeHi = longitudeHi;
this.centerOfMass = centerOfMass;
this.codeVersion = codeVersion;
};
/**
* Returns a resized version of this UBID code area, where the latitude and
* longitude of the SW and NE corners of the OLC bounding box are moved
* inwards by dimensions that correspond to half of the height and width of
* the OLC grid reference cell for the centroid.
*
* The purpose of the resizing operation is to ensure that re-encoding a given
* UBID code area results in the same coordinates.
*
* @return {UniqueBuildingIdentification.CodeArea}
*/
UniqueBuildingIdentification.CodeArea.prototype.resize = function () {
// Calculate the (half-)dimensions of OLC grid reference cell for the centroid.
const halfHeight = (this.centerOfMass.latitudeHi - this.centerOfMass.latitudeLo) / 2.0;
const halfWidth = (this.centerOfMass.longitudeHi - this.centerOfMass.longitudeLo) / 2.0;
// Construct and return the new UBID code area.
return new UniqueBuildingIdentification.CodeArea(this.latitudeLo + halfHeight, this.longitudeLo + halfWidth, this.latitudeHi - halfHeight, this.longitudeHi - halfWidth, this.centerOfMass, this.codeVersion);
};
UniqueBuildingIdentification.v3 = (function () {
const v3 = {};
/**
* The separator for OLC codes in a UBID code.
*
* @type {string}
*/
v3.SEPARATOR_ = '-';
/**
* Regular expression for UBID codes.
*
* @type {RegExp}
*/
v3.RE_PATTERN_ = (function () {
// https://stackoverflow.com/a/25114677
const escapeRegExp = function (string) {
return string.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
};
return new RegExp([
'^',
'(',
'[', escapeRegExp('23456789CFGHJMPQRVWX'), ']{4,8}',
escapeRegExp('+'),
'[', escapeRegExp('23456789CFGHJMPQRVWX'), ']*',
')',
escapeRegExp(v3.SEPARATOR_),
'(0|[1-9][0-9]*)',
escapeRegExp(v3.SEPARATOR_),
'(0|[1-9][0-9]*)',
escapeRegExp(v3.SEPARATOR_),
'(0|[1-9][0-9]*)',
escapeRegExp(v3.SEPARATOR_),
'(0|[1-9][0-9]*)',
'$',
].join(''));
})();
/**
* The first group of a UBID code is the OLC for the geometric center of
* mass (i.e., centroid) of the building footprint.
*
* @type {number}
*/
v3.RE_GROUP_OPENLOCATIONCODE_ = 1;
/**
* The second group of the UBID code is the Chebyshev distance in OLC grid
* units from the OLC for the centroid of the building footprint to the
* northern extent of the OLC bounding box for the building footprint.
*
* @type {number}
*/
v3.RE_GROUP_NORTH_ = 2;
/**
* The third group of the UBID code is the Chebyshev distance in OLC grid
* units from the OLC for the centroid of the building footprint to the
* eastern extent of the OLC bounding box for the building footprint.
*
* @type {number}
*/
v3.RE_GROUP_EAST_ = 3;
/**
* The fourth group of the UBID code is the Chebyshev distance in OLC grid
* units from the OLC for the centroid of the building footprint to the
* southern extent of the OLC bounding box for the building footprint.
*
* @type {number}
*/
v3.RE_GROUP_SOUTH_ = 4;
/**
* The fifth group of the UBID code is the Chebyshev distance in OLC grid
* units from the OLC for the centroid of the building footprint to the
* western extent of the OLC bounding box for the building footprint.
*
* @type {number}
*/
v3.RE_GROUP_WEST_ = 5;
/**
* Returns the UBID code area for the given UBID code.
*
* @param {string} code The UBID code.
* @return {UniqueBuildingIdentification.CodeArea} The UBID code area.
* @throws {Error} If the UBID code is invalid or if the OLC for the
* centroid of the building footprint is invalid.
*/
v3.decode = function (code) {
if ((code === null) || (code === undefined)) {
throw new Error('Invalid UBID');
}
// Attempt to match the regular expression.
const matchData = String(code).match(v3.RE_PATTERN_);
// If the UBID code does not match the regular expression, raise an error.
if (matchData === null) {
throw new Error('Invalid UBID');
}
// Extract the OLC for the centroid of the building footprint.
const olc = matchData[v3.RE_GROUP_OPENLOCATIONCODE_];
// Decode the OLC for the centroid of the building footprint.
const centerOfMass = OpenLocationCode.decode(olc);
// Calculate the size of the OLC for the centroid of the building
// footprint in decimal degree units.
const height = centerOfMass.latitudeHi - centerOfMass.latitudeLo;
const width = centerOfMass.longitudeHi - centerOfMass.longitudeLo;
// Calculate the size of the OLC bounding box for the building footprint,
// assuming that the datum are Chebyshev distances.
const latitudeHi = centerOfMass.latitudeHi + (parseInt(matchData[v3.RE_GROUP_NORTH_]) * height);
const longitudeHi = centerOfMass.longitudeHi + (parseInt(matchData[v3.RE_GROUP_EAST_]) * width);
const latitudeLo = centerOfMass.latitudeLo - (parseInt(matchData[v3.RE_GROUP_SOUTH_]) * height);
const longitudeLo = centerOfMass.longitudeLo - (parseInt(matchData[v3.RE_GROUP_WEST_]) * width);
// Construct and return the UBID code area.
return new UniqueBuildingIdentification.CodeArea(latitudeLo, longitudeLo, latitudeHi, longitudeHi, centerOfMass, 3);
};
/**
* Returns the UBID code for the given coordinates.
*
* @param {number} latitudeLo The latitude in decimal degrees of the SW
* corner of the minimal bounding box for the building footprint.
* @param {number} longitudeLo The longitude in decimal degrees of the SW
* corner of the minimal bounding box for the building footprint.
* @param {number} latitudeHi The latitude in decimal degrees of the NE
* corner of the minimal bounding box for the building footprint.
* @param {number} longitudeHi The longitude in decimal degrees of the NE
* corner of the minimal bounding box for the building footprint.
* @param {number} latitudeCenter The latitude in decimal degrees of the
* centroid of the building footprint.
* @param {number} longitudeCenter The longitude in decimal degrees of the
* centroid of the building footprint.
* @param {number} codeLength The OLC code length (not including the
* separator).
* @return {string} The UBID code.
* @throws {Error} If the OLC for the centroid of the building footprint
* cannot be encoded (e.g., invalid code length).
*/
v3.encode = function (latitudeLo, longitudeLo, latitudeHi, longitudeHi, latitudeCenter, longitudeCenter, codeLength) {
// Encode the OLCs for the NE and SW corners of the minimal bounding box
// for the building footprint.
const northeast_openlocationcode = OpenLocationCode.encode(latitudeHi, longitudeHi, codeLength);
const southwest_openlocationcode = OpenLocationCode.encode(latitudeLo, longitudeLo, codeLength);
// Encode the OLC for the centroid of the building footprint.
const centroid_openlocationcode = OpenLocationCode.encode(latitudeCenter, longitudeCenter, codeLength);
// Decode the OLCs for the NE and SW corners of the minimal bounding box
// for the building footprint.
const northeast_openlocationcode_CodeArea = OpenLocationCode.decode(northeast_openlocationcode);
const southwest_openlocationcode_CodeArea = OpenLocationCode.decode(southwest_openlocationcode);
// Decode the OLC for the centroid of the building footprint.
const centroid_openlocationcode_CodeArea = OpenLocationCode.decode(centroid_openlocationcode);
// Calculate the size of the OLC for the centroid of the building
// footprint in decimal degree units.
const height = centroid_openlocationcode_CodeArea.latitudeHi - centroid_openlocationcode_CodeArea.latitudeLo;
const width = centroid_openlocationcode_CodeArea.longitudeHi - centroid_openlocationcode_CodeArea.longitudeLo;
// Calculate the Chebyshev distances to the northern, eastern, southern
// and western of the OLC bounding box for the building footprint.
const delta_north = (northeast_openlocationcode_CodeArea.latitudeHi - centroid_openlocationcode_CodeArea.latitudeHi) / height;
const delta_east = (northeast_openlocationcode_CodeArea.longitudeHi - centroid_openlocationcode_CodeArea.longitudeHi) / width;
const delta_south = (centroid_openlocationcode_CodeArea.latitudeLo - southwest_openlocationcode_CodeArea.latitudeLo) / height;
const delta_west = (centroid_openlocationcode_CodeArea.longitudeLo - southwest_openlocationcode_CodeArea.longitudeLo) / width;
// Construct and return the UBID code.
return [
centroid_openlocationcode,
String(Math.round(delta_north)),
String(Math.round(delta_east)),
String(Math.round(delta_south)),
String(Math.round(delta_west)),
].join(v3.SEPARATOR_);
};
/**
* Returns the UBID code for the given UBID code area.
*
* @param {UniqueBuildingIdentification.CodeArea} codeArea The UBID code
* area.
* @return {string} The UBID code.
* @throws {Error} If the OLC for the centroid of the building footprint
* cannot be encoded (e.g., invalid code length).
*/
v3.encodeCodeArea = function (codeArea) {
if (codeArea instanceof UniqueBuildingIdentification.CodeArea) {
return v3.encode(codeArea.latitudeLo, codeArea.longitudeLo, codeArea.latitudeHi, codeArea.longitudeHi, codeArea.centerOfMass.latitudeCenter, codeArea.centerOfMass.longitudeCenter, codeArea.centerOfMass.codeLength);
} else {
throw new Error('Invalid UBID code area');
}
};
/**
* Is the given UBID code valid?
*
* @param {string} code The UBID code.
* @return {boolean} Returns 'true' if the UBID code is valid. Otherwise,
* returns 'false'.
*/
v3.isValid = function (code) {
const matchData = String(code).match(v3.RE_PATTERN_);
return (matchData instanceof Array) && OpenLocationCode.isValid(matchData[v3.RE_GROUP_OPENLOCATIONCODE_]);
};
return v3;
})();
return UniqueBuildingIdentification;
}));